forwarding even with account activation email

This commit is contained in:
einhornimmond 2025-02-24 13:20:30 +01:00
parent 1fdbee906c
commit ca628319f9
10 changed files with 65 additions and 35 deletions

View File

@ -67,28 +67,25 @@ export class HumHubClient {
public async createAutoLoginUrl(username: string, project?: string | null) {
const secret = new TextEncoder().encode(CONFIG.HUMHUB_JWT_KEY)
logger.info(`user ${username} as username for humhub auto-login`)
let projectBrandingPromise: Promise<ProjectBranding | null> | undefined
let redirectLink: string | undefined
if (project) {
projectBrandingPromise = ProjectBranding.findOne({
const projectBranding = await ProjectBranding.findOne({
where: { alias: project },
select: { spaceUrl: true },
})
if (projectBranding?.spaceUrl) {
redirectLink = projectBranding.spaceUrl
}
}
const token = await new SignJWT({ username })
const token = await new SignJWT({ username, redirectLink })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime(CONFIG.JWT_EXPIRES_IN)
.sign(secret)
let loginUrl = `${CONFIG.HUMHUB_API_URL}user/auth/external?authclient=jwt&jwt=${token}`
if (projectBrandingPromise) {
const projectBranding = await projectBrandingPromise
if (projectBranding?.spaceUrl) {
loginUrl += `&redirect=${projectBranding.spaceUrl as string}`
}
}
return loginUrl
return `${CONFIG.HUMHUB_API_URL}${
CONFIG.HUMHUB_API_URL.endsWith('/') ? '' : '/'
}user/auth/external?authclient=jwt&jwt=${token}`
}
/**

View File

@ -81,8 +81,8 @@ const loginServer = {
}
const email = {
EMAIL: process.env.EMAIL === 'true' ?? false,
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' ?? false,
EMAIL: process.env.EMAIL === 'true' || false,
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || false,
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER ?? 'stage1@gradido.net',
EMAIL_USERNAME: process.env.EMAIL_USERNAME ?? '',
EMAIL_SENDER: process.env.EMAIL_SENDER ?? 'info@gradido.net',
@ -123,7 +123,7 @@ const federation = {
process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER ?? 60000,
),
FEDERATION_XCOM_SENDCOINS_ENABLED:
process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' ?? false,
process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' || false,
// default value for community-uuid is equal uuid of stage-3
FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID:
process.env.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID ?? '56a55482-909e-46a4-bfa2-cd025e894ebc',

View File

@ -40,6 +40,7 @@ export const sendAccountActivationEmail = (data: {
language: string
activationLink: string
timeDurationObject: Record<string, unknown>
logoUrl?: string | null
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
@ -50,6 +51,7 @@ export const sendAccountActivationEmail = (data: {
locale: data.language,
activationLink: data.activationLink,
timeDurationObject: data.timeDurationObject,
logoUrl: data.logoUrl,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,

View File

@ -1,6 +1,8 @@
extend ../layout.pug
block content
if logoUrl
img(src=logoUrl, alt="Banner", style="max-width: 680px; max-height: 250px;")
h2= t('emails.accountActivation.title')
.text-block
include ../includes/salutation.pug

View File

@ -14,4 +14,8 @@ export class UnsecureLoginArgs {
@Field(() => Int, { nullable: true })
@IsInt()
publisherId?: number | null
@Field(() => String, { nullable: true })
@IsString()
project?: string | null
}

View File

@ -146,10 +146,10 @@ export class UserResolver {
@Authorized([RIGHTS.LOGIN])
@Mutation(() => User)
async login(
@Args() { email, password, publisherId }: UnsecureLoginArgs,
@Args() { email, password, publisherId, project }: UnsecureLoginArgs,
@Ctx() context: Context,
): Promise<User> {
logger.info(`login with ${email}, ***, ${publisherId} ...`)
logger.info(`login with ${email}, ***, ${publisherId}, project=${project} ...`)
email = email.trim().toLowerCase()
let dbUser: DbUser
@ -178,6 +178,7 @@ export class UserResolver {
// request to humhub and klicktipp run in parallel
let humhubUserPromise: Promise<IRestResponse<GetUser>> | undefined
let projectBrandingPromise: Promise<ProjectBranding | null> | undefined
const klicktippStatePromise = getKlicktippState(dbUser.emailContact.email)
if (CONFIG.HUMHUB_ACTIVE && dbUser.humhubAllowed) {
const getHumhubUser = new PostUser(dbUser)
@ -185,6 +186,12 @@ export class UserResolver {
getHumhubUser.account.username,
)
}
if (project) {
projectBrandingPromise = ProjectBranding.findOne({
where: { alias: project },
select: { spaceId: true },
})
}
if (dbUser.passwordEncryptionType !== PasswordEncryptionType.GRADIDO_ID) {
dbUser.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
@ -214,11 +221,20 @@ export class UserResolver {
})
await EVENT_USER_LOGIN(dbUser)
const projectBranding = await projectBrandingPromise
logger.debug('project branding: ', projectBranding)
// load humhub state
if (humhubUserPromise) {
try {
const result = await humhubUserPromise
user.humhubAllowed = result?.result?.account.status === 1
if (user.humhubAllowed) {
let spaceId = null
if (projectBranding) {
spaceId = projectBranding.spaceId
}
void syncHumhub(null, dbUser, spaceId)
}
} catch (e) {
logger.error("couldn't reach out to humhub, disable for now", e)
user.humhubAllowed = false
@ -255,7 +271,7 @@ export class UserResolver {
): Promise<User> {
logger.addContext('user', 'unknown')
logger.info(
`createUser(email=${email}, firstName=${firstName}, lastName=${lastName}, language=${language}, publisherId=${publisherId}, redeemCode =${redeemCode})`,
`createUser(email=${email}, firstName=${firstName}, lastName=${lastName}, language=${language}, publisherId=${publisherId}, redeemCode=${redeemCode}, project=${project})`,
)
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
// default int publisher_id = 0;
@ -310,7 +326,13 @@ export class UserResolver {
return user
}
}
let projectBrandingPromise: Promise<ProjectBranding | null> | undefined
if (project) {
projectBrandingPromise = ProjectBranding.findOne({
where: { alias: project },
select: { logoUrl: true, spaceId: true },
})
}
const gradidoID = await newGradidoID()
const eventRegisterRedeem = Event(
@ -358,6 +380,7 @@ export class UserResolver {
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
let projectBranding: ProjectBranding | null | undefined
try {
dbUser = await queryRunner.manager.save(dbUser).catch((error) => {
throw new LogError('Error while saving dbUser', error)
@ -375,8 +398,11 @@ export class UserResolver {
const activationLink = `${
CONFIG.EMAIL_LINK_VERIFICATION
}${emailContact.emailVerificationCode.toString()}${redeemCode ? `/${redeemCode}` : ''}`
}${emailContact.emailVerificationCode.toString()}${redeemCode ? `/${redeemCode}` : ''}${
project ? `?project=` + project : ''
}`
projectBranding = projectBrandingPromise ? await projectBrandingPromise : undefined
void sendAccountActivationEmail({
firstName,
lastName,
@ -384,6 +410,7 @@ export class UserResolver {
language,
activationLink,
timeDurationObject: getTimeDurationObject(CONFIG.EMAIL_CODE_VALID_TIME),
logoUrl: projectBranding?.logoUrl,
})
logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`)
@ -400,14 +427,8 @@ export class UserResolver {
logger.info('createUser() successful...')
if (CONFIG.HUMHUB_ACTIVE) {
let spaceId: number | null = null
if (project) {
const projectBranding = await ProjectBranding.findOne({
where: { alias: project, newUserToSpace: true },
select: { spaceId: true },
})
if (projectBranding) {
spaceId = projectBranding.spaceId
}
if (projectBranding) {
spaceId = projectBranding.spaceId
}
void syncHumhub(null, dbUser, spaceId)
}

View File

@ -55,8 +55,8 @@ export async function syncHumhub(
result: ExecutedHumhubAction[result as ExecutedHumhubAction],
})
if (spaceId && humhubUser) {
logger.debug(`add user to space ${spaceId}`)
await humhubClient.addUserToSpace(humhubUser.id, spaceId)
logger.debug(`user added to space ${spaceId}`)
}
return user.id
}

View File

@ -68,6 +68,7 @@ export const createUser = gql`
$language: String!
$publisherId: Int
$redeemCode: String
$project: String
) {
createUser(
email: $email
@ -76,6 +77,7 @@ export const createUser = gql`
language: $language
publisherId: $publisherId
redeemCode: $redeemCode
project: $project
) {
id
}
@ -166,8 +168,8 @@ export const createContributionMessage = gql`
`
export const login = gql`
mutation ($email: String!, $password: String!, $publisherId: Int) {
login(email: $email, password: $password, publisherId: $publisherId) {
mutation ($email: String!, $password: String!, $publisherId: Int, $project: String) {
login(email: $email, password: $password, publisherId: $publisherId, project: $project) {
gradidoID
alias
firstName

View File

@ -97,11 +97,10 @@
<script setup>
import { onMounted, computed } from 'vue'
import { useQuery } from '@vue/apollo-composable'
import { useLazyQuery } from '@vue/apollo-composable'
import AuthNavbar from '@/components/Auth/AuthNavbar'
import AuthNavbarSmall from '@/components/Auth/AuthNavbarSmall'
import AuthCarousel from '@/components/Auth/AuthCarousel'
import LanguageSwitch2 from '@/components/LanguageSwitch2'
import AuthFooter from '@/components/Auth/AuthFooter'
import CONFIG from '@/config'
import { useStore } from 'vuex'
@ -119,8 +118,8 @@ const project = computed(() => store.state.project)
const {
result: projectBannerResult,
loading: projectBannerLoading,
refetch,
} = useQuery(
load,
} = useLazyQuery(
gql`
query ($project: String!) {
projectBrandingBanner(alias: $project)
@ -132,6 +131,8 @@ onMounted(async () => {
const urlParams = new URLSearchParams(window.location.search)
const projectValue = urlParams.get('project')
if (projectValue) {
console.log('project value: ', projectValue)
load()
store.commit('project', projectValue)
} else {
store.commit('project', '')

View File

@ -93,6 +93,7 @@ const onSubmit = handleSubmit(async (values) => {
email: values.email,
password: values.password,
publisherId: store.state.publisherId,
project: store.state.project,
})
const { login: loginResponse } = result.data
await store.dispatch('login', loginResponse)