move sendActivationEmail mutation from user to admin resolver

This commit is contained in:
Moriz Wahl 2022-03-24 19:14:59 +01:00
parent 1e3300ad5a
commit 9da2047e8c
2 changed files with 57 additions and 85 deletions

View File

@ -34,6 +34,8 @@ import { Decay } from '@model/Decay'
import Paginated from '@arg/Paginated' import Paginated from '@arg/Paginated'
import { Order } from '@enum/Order' import { Order } from '@enum/Order'
import { communityUser } from '@/util/communityUser' import { communityUser } from '@/util/communityUser'
import { checkExistingOptInCode, activationLink } from './UserResolver'
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
// const EMAIL_OPT_IN_REGISTER = 1 // const EMAIL_OPT_IN_REGISTER = 1
// const EMAIL_OPT_UNKNOWN = 3 // elopage? // const EMAIL_OPT_UNKNOWN = 3 // elopage?
@ -369,6 +371,40 @@ export class AdminResolver {
const user = await dbUser.findOneOrFail({ id: userId }) const user = await dbUser.findOneOrFail({ id: userId })
return userTransactions.map((t) => new Transaction(t, new User(user), communityUser)) return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
} }
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
@Mutation(() => Boolean)
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
email = email.trim().toLowerCase()
const user = await dbUser.findOneOrFail({ email: email })
// can be both types: REGISTER and RESET_PASSWORD
let optInCode = await LoginEmailOptIn.findOne({
userId: user.id,
})
optInCode = checkExistingOptInCode(optInCode, user.id)
// keep the optin type (when newly created is is REGISTER)
await LoginEmailOptIn.save(optInCode)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emailSent = await sendAccountActivationEmail({
link: activationLink(optInCode),
firstName: user.firstName,
lastName: user.lastName,
email,
})
/* uncomment this, when you need the activation link on the console
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
// eslint-disable-next-line no-console
console.log(`Account confirmation link: ${activationLink}`)
}
*/
return true
}
} }
interface CreationMap { interface CreationMap {

View File

@ -3,7 +3,7 @@
import fs from 'fs' import fs from 'fs'
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql' import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
import { getConnection, getCustomRepository, QueryRunner } from '@dbTools/typeorm' import { getConnection, getCustomRepository } from '@dbTools/typeorm'
import CONFIG from '@/config' import CONFIG from '@/config'
import { User } from '@model/User' import { User } from '@model/User'
import { User as DbUser } from '@entity/User' import { User as DbUser } from '@entity/User'
@ -155,35 +155,29 @@ const newEmailOptIn = (userId: number): LoginEmailOptIn => {
return emailOptIn return emailOptIn
} }
const createEmailOptIn = async ( // needed by AdminResolver
// checks if given code exists and can be resent
// if optIn does not exits, it is created
export const checkExistingOptInCode = (
optInCode: LoginEmailOptIn | undefined,
userId: number, userId: number,
queryRunner: QueryRunner, ): LoginEmailOptIn => {
): Promise<LoginEmailOptIn> => { if (optInCode) {
let emailOptIn = await LoginEmailOptIn.findOne({ if (!canResendOptIn(optInCode)) {
userId,
emailOptInTypeId: OptInType.EMAIL_OPT_IN_REGISTER,
})
if (emailOptIn) {
if (isOptInValid(emailOptIn)) {
throw new Error( throw new Error(
`email already sent less than ${printTimeDuration(CONFIG.EMAIL_CODE_REQUEST_TIME)} ago`, `email already sent less than $(printTimeDuration(CONFIG.EMAIL_CODE_REQUEST_TIME)} minutes ago`,
) )
} }
emailOptIn.updatedAt = new Date() optInCode.updatedAt = new Date()
emailOptIn.resendCount++ optInCode.resendCount++
} else { } else {
emailOptIn = new LoginEmailOptIn() optInCode = newEmailOptIn(userId)
emailOptIn.verificationCode = random(64)
emailOptIn.userId = userId
emailOptIn.emailOptInTypeId = OptInType.EMAIL_OPT_IN_REGISTER
} }
await queryRunner.manager.save(emailOptIn).catch((error) => { return optInCode
// eslint-disable-next-line no-console }
console.log('Error while saving emailOptIn', error)
throw new Error('error saving email opt in') export const activationLink = (optInCode: LoginEmailOptIn): string => {
}) return CONFIG.EMAIL_LINK_SETPASSWORD.replace(/{optin}/g, optInCode.verificationCode.toString())
return emailOptIn
} }
@Resolver() @Resolver()
@ -388,50 +382,6 @@ export class UserResolver {
return new User(dbUser) return new User(dbUser)
} }
// This is used by the admin only - should we move it to the admin resolver?
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
@Mutation(() => Boolean)
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
email = email.trim().toLowerCase()
const user = await DbUser.findOneOrFail({ email: email })
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('READ UNCOMMITTED')
try {
const emailOptIn = await createEmailOptIn(user.id, queryRunner)
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
/{optin}/g,
emailOptIn.verificationCode.toString(),
)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emailSent = await sendAccountActivationEmail({
link: activationLink,
firstName: user.firstName,
lastName: user.lastName,
email,
})
/* uncomment this, when you need the activation link on the console
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
// eslint-disable-next-line no-console
console.log(`Account confirmation link: ${activationLink}`)
}
*/
await queryRunner.commitTransaction()
} catch (e) {
await queryRunner.rollbackTransaction()
throw e
} finally {
await queryRunner.release()
}
return true
}
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL]) @Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
@Query(() => Boolean) @Query(() => Boolean)
async sendResetPasswordEmail(@Arg('email') email: string): Promise<boolean> { async sendResetPasswordEmail(@Arg('email') email: string): Promise<boolean> {
@ -443,29 +393,14 @@ export class UserResolver {
userId: user.id, userId: user.id,
}) })
if (optInCode) { optInCode = checkExistingOptInCode(optInCode, user.id)
if (!canResendOptIn(optInCode)) {
throw new Error(
`email already sent less than $(printTimeDuration(CONFIG.EMAIL_CODE_REQUEST_TIME)} minutes ago`,
)
}
optInCode.updatedAt = new Date()
optInCode.resendCount++
} else {
optInCode = newEmailOptIn(user.id)
}
// now it is RESET_PASSWORD // now it is RESET_PASSWORD
optInCode.emailOptInTypeId = OptInType.EMAIL_OPT_IN_RESET_PASSWORD optInCode.emailOptInTypeId = OptInType.EMAIL_OPT_IN_RESET_PASSWORD
await LoginEmailOptIn.save(optInCode) await LoginEmailOptIn.save(optInCode)
const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace(
/{optin}/g,
optInCode.verificationCode.toString(),
)
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const emailSent = await sendResetPasswordEmail({ const emailSent = await sendResetPasswordEmail({
link, link: activationLink(optInCode),
firstName: user.firstName, firstName: user.firstName,
lastName: user.lastName, lastName: user.lastName,
email, email,
@ -547,6 +482,7 @@ export class UserResolver {
throw new Error('error saving user: ' + error) throw new Error('error saving user: ' + error)
}) })
// why do we delete the code?
// Delete Code // Delete Code
await queryRunner.manager.remove(optInCode).catch((error) => { await queryRunner.manager.remove(optInCode).catch((error) => {
throw new Error('error deleting code: ' + error) throw new Error('error deleting code: ' + error)