From d9eaee3939bf2e93e5d775d32bce77842201faba Mon Sep 17 00:00:00 2001 From: Josh Morel Date: Fri, 31 Aug 2018 03:18:19 -0400 Subject: add user account email verificiation (#977) * add user account email verificiation includes server and client code to: * enable verificationRequired via custom config * send verification email with registration * ask for verification email * verify via email * prevent login if not verified and required * conditional client links to ask for new verification email * allow login for verified=null these are users created when verification not required should still be able to login when verification is enabled * refactor email verifcation pr * change naming from verified to emailVerified * change naming from askVerifyEmail to askSendVerifyEmail * undo unrelated automatic prettier formatting on api/config * use redirectService for home * remove redundant success notification on email verified * revert test.yaml smpt host --- server/controllers/api/config.ts | 16 +++++++++--- server/controllers/api/users/index.ts | 47 +++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) (limited to 'server/controllers') diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 25ddd1fa6..6edbe4820 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts @@ -60,7 +60,8 @@ async function getConfig (req: express.Request, res: express.Response, next: exp serverVersion: packageJSON.version, signup: { allowed, - allowedForCurrentIP + allowedForCurrentIP, + requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION }, transcoding: { enabledResolutions @@ -159,12 +160,20 @@ async function updateCustomConfig (req: express.Request, res: express.Response, toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10) // camelCase to snake_case key - const toUpdateJSON = omit(toUpdate, 'user.videoQuota', 'instance.defaultClientRoute', 'instance.shortDescription', 'cache.videoCaptions') + const toUpdateJSON = omit( + toUpdate, + 'user.videoQuota', + 'instance.defaultClientRoute', + 'instance.shortDescription', + 'cache.videoCaptions', + 'signup.requiresEmailVerification' + ) toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota toUpdateJSON.user['video_quota_daily'] = toUpdate.user.videoQuotaDaily toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription toUpdateJSON.instance['default_nsfw_policy'] = toUpdate.instance.defaultNSFWPolicy + toUpdateJSON.signup['requires_email_verification'] = toUpdate.signup.requiresEmailVerification await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 }) @@ -220,7 +229,8 @@ function customConfig (): CustomConfig { }, signup: { enabled: CONFIG.SIGNUP.ENABLED, - limit: CONFIG.SIGNUP.LIMIT + limit: CONFIG.SIGNUP.LIMIT, + requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION }, admin: { email: CONFIG.ADMIN.EMAIL diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 25d51ae5e..008c34ca4 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts @@ -25,7 +25,10 @@ import { usersSortValidator, usersUpdateValidator } from '../../../middlewares' -import { usersAskResetPasswordValidator, usersBlockingValidator, usersResetPasswordValidator } from '../../../middlewares/validators' +import { + usersAskResetPasswordValidator, usersBlockingValidator, usersResetPasswordValidator, + usersAskSendVerifyEmailValidator, usersVerifyEmailValidator +} from '../../../middlewares/validators' import { UserModel } from '../../../models/account/user' import { OAuthTokenModel } from '../../../models/oauth/oauth-token' import { auditLoggerFactory, UserAuditView } from '../../../helpers/audit-logger' @@ -110,6 +113,17 @@ usersRouter.post('/:id/reset-password', asyncMiddleware(resetUserPassword) ) +usersRouter.post('/ask-send-verify-email', + loginRateLimiter, + asyncMiddleware(usersAskSendVerifyEmailValidator), + asyncMiddleware(askSendVerifyUserEmail) +) + +usersRouter.post('/:id/verify-email', + asyncMiddleware(usersVerifyEmailValidator), + asyncMiddleware(verifyUserEmail) +) + usersRouter.post('/token', loginRateLimiter, token, @@ -165,7 +179,8 @@ async function registerUser (req: express.Request, res: express.Response) { autoPlayVideo: true, role: UserRole.USER, videoQuota: CONFIG.USER.VIDEO_QUOTA, - videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY + videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY, + emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null }) const { user } = await createUserAccountAndChannel(userToCreate) @@ -173,6 +188,10 @@ async function registerUser (req: express.Request, res: express.Response) { auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON())) logger.info('User %s with its channel and account registered.', body.username) + if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { + await sendVerifyUserEmail(user) + } + return res.type('json').status(204).end() } @@ -261,6 +280,30 @@ async function resetUserPassword (req: express.Request, res: express.Response, n return res.status(204).end() } +async function sendVerifyUserEmail (user: UserModel) { + const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) + const url = CONFIG.WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString + await Emailer.Instance.addVerifyEmailJob(user.email, url) + return +} + +async function askSendVerifyUserEmail (req: express.Request, res: express.Response, next: express.NextFunction) { + const user = res.locals.user as UserModel + + await sendVerifyUserEmail(user) + + return res.status(204).end() +} + +async function verifyUserEmail (req: express.Request, res: express.Response, next: express.NextFunction) { + const user = res.locals.user as UserModel + user.emailVerified = true + + await user.save() + + return res.status(204).end() +} + function success (req: express.Request, res: express.Response, next: express.NextFunction) { res.end() } -- cgit v1.2.3