From 29b848fd5dc0522eb80fb9914df9ff975bda0b55 Mon Sep 17 00:00:00 2001 From: Milo Schwartz Date: Fri, 25 Oct 2024 21:39:18 -0400 Subject: [PATCH] ability to disable email verification requirement --- server/config.ts | 4 ++ server/middlewares/verifyUser.ts | 12 ++-- server/routers/auth/login.ts | 32 ++++++----- .../auth/requestEmailVerificationCode.ts | 20 +++++-- server/routers/auth/signup.ts | 56 +++++++++++++------ server/routers/auth/verifyEmail.ts | 26 ++++++--- src/app/auth/verify-email/page.tsx | 10 ++-- 7 files changed, 107 insertions(+), 53 deletions(-) diff --git a/server/config.ts b/server/config.ts index 0642110c..fdeaf90a 100644 --- a/server/config.ts +++ b/server/config.ts @@ -118,5 +118,9 @@ process.env.NEXT_PUBLIC_INTERNAL_API_BASE_URL = new URL( `http://${parsedConfig.data.server.internal_hostname}:${parsedConfig.data.server.external_port}` ).href; process.env.NEXT_PUBLIC_APP_NAME = parsedConfig.data.app.name; +process.env.NEXT_PUBLIC_FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data + .flags?.require_email_verification + ? "true" + : "false"; export default parsedConfig.data; diff --git a/server/middlewares/verifyUser.ts b/server/middlewares/verifyUser.ts index 3b2b8963..8657b779 100644 --- a/server/middlewares/verifyUser.ts +++ b/server/middlewares/verifyUser.ts @@ -6,11 +6,12 @@ import { users } from "@server/db/schema"; import { eq } from "drizzle-orm"; import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; +import config from "@server/config"; export const verifySessionUserMiddleware = async ( req: any, res: Response, - next: NextFunction, + next: NextFunction ) => { const { session, user } = await verifySession(req); if (!session || !user) { @@ -24,16 +25,19 @@ export const verifySessionUserMiddleware = async ( if (!existingUser || !existingUser[0]) { return next( - createHttpError(HttpCode.BAD_REQUEST, "User does not exist"), + createHttpError(HttpCode.BAD_REQUEST, "User does not exist") ); } req.user = existingUser[0]; req.session = session; - if (!existingUser[0].emailVerified) { + if ( + !existingUser[0].emailVerified && + config.flags?.require_email_verification + ) { return next( - createHttpError(HttpCode.BAD_REQUEST, "Email is not verified"), // Might need to change the response type? + createHttpError(HttpCode.BAD_REQUEST, "Email is not verified") // Might need to change the response type? ); } diff --git a/server/routers/auth/login.ts b/server/routers/auth/login.ts index a6fb1937..b1793b18 100644 --- a/server/routers/auth/login.ts +++ b/server/routers/auth/login.ts @@ -15,6 +15,7 @@ import createHttpError from "http-errors"; import { z } from "zod"; import { fromError } from "zod-validation-error"; import { verifyTotpCode } from "@server/auth/2fa"; +import config from "@server/config"; export const loginBodySchema = z.object({ email: z.string().email(), @@ -32,7 +33,7 @@ export type LoginResponse = { export async function login( req: Request, res: Response, - next: NextFunction, + next: NextFunction ): Promise { const parsedBody = loginBodySchema.safeParse(req.body); @@ -40,8 +41,8 @@ export async function login( return next( createHttpError( HttpCode.BAD_REQUEST, - fromError(parsedBody.error).toString(), - ), + fromError(parsedBody.error).toString() + ) ); } @@ -67,8 +68,8 @@ export async function login( return next( createHttpError( HttpCode.BAD_REQUEST, - "Username or password is incorrect", - ), + "Username or password is incorrect" + ) ); } @@ -82,14 +83,14 @@ export async function login( timeCost: 2, outputLen: 32, parallelism: 1, - }, + } ); if (!validPassword) { return next( createHttpError( HttpCode.BAD_REQUEST, - "Username or password is incorrect", - ), + "Username or password is incorrect" + ) ); } @@ -107,15 +108,15 @@ export async function login( const validOTP = await verifyTotpCode( code, existingUser.twoFactorSecret!, - existingUser.userId, + existingUser.userId ); if (!validOTP) { return next( createHttpError( HttpCode.BAD_REQUEST, - "The two-factor code you entered is incorrect", - ), + "The two-factor code you entered is incorrect" + ) ); } } @@ -126,7 +127,10 @@ export async function login( res.appendHeader("Set-Cookie", cookie); - if (!existingUser.emailVerified) { + if ( + !existingUser.emailVerified && + config.flags?.require_email_verification + ) { return response(res, { data: { emailVerificationRequired: true }, success: true, @@ -147,8 +151,8 @@ export async function login( return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - "Failed to authenticate user", - ), + "Failed to authenticate user" + ) ); } } diff --git a/server/routers/auth/requestEmailVerificationCode.ts b/server/routers/auth/requestEmailVerificationCode.ts index 00329b04..7aab3c72 100644 --- a/server/routers/auth/requestEmailVerificationCode.ts +++ b/server/routers/auth/requestEmailVerificationCode.ts @@ -4,6 +4,7 @@ import HttpCode from "@server/types/HttpCode"; import { response } from "@server/utils"; import { User } from "@server/db/schema"; import { sendEmailVerificationCode } from "./sendEmailVerificationCode"; +import config from "@server/config"; export type RequestEmailVerificationCodeResponse = { codeSent: boolean; @@ -12,8 +13,17 @@ export type RequestEmailVerificationCodeResponse = { export async function requestEmailVerificationCode( req: Request, res: Response, - next: NextFunction, + next: NextFunction ): Promise { + if (!config.flags?.require_email_verification) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Email verification is not enabled" + ) + ); + } + try { const user = req.user as User; @@ -21,8 +31,8 @@ export async function requestEmailVerificationCode( return next( createHttpError( HttpCode.BAD_REQUEST, - "Email is already verified", - ), + "Email is already verified" + ) ); } @@ -41,8 +51,8 @@ export async function requestEmailVerificationCode( return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - "Failed to send email verification code", - ), + "Failed to send email verification code" + ) ); } } diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index c0639c05..536d9c17 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -19,6 +19,7 @@ import { serializeSessionCookie, } from "@server/auth"; import { ActionsEnum } from "@server/auth/actions"; +import config from "@server/config"; export const signupBodySchema = z.object({ email: z.string().email(), @@ -28,13 +29,13 @@ export const signupBodySchema = z.object({ export type SignUpBody = z.infer; export type SignUpResponse = { - emailVerificationRequired: boolean; + emailVerificationRequired?: boolean; }; export async function signup( req: Request, res: Response, - next: NextFunction, + next: NextFunction ): Promise { const parsedBody = signupBodySchema.safeParse(req.body); @@ -42,8 +43,8 @@ export async function signup( return next( createHttpError( HttpCode.BAD_REQUEST, - fromError(parsedBody.error).toString(), - ), + fromError(parsedBody.error).toString() + ) ); } @@ -64,6 +65,15 @@ export async function signup( .where(eq(users.email, email)); if (existing && existing.length > 0) { + if (!config.flags?.require_email_verification) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "A user with that email address already exists" + ) + ); + } + const user = existing[0]; // If the user is already verified, we don't want to create a new user @@ -71,8 +81,8 @@ export async function signup( return next( createHttpError( HttpCode.BAD_REQUEST, - "A user with that email address already exists", - ), + "A user with that email address already exists" + ) ); } @@ -85,8 +95,8 @@ export async function signup( return next( createHttpError( HttpCode.BAD_REQUEST, - "A verification email was already sent to this email address. Please check your email for the verification code.", - ), + "A verification email was already sent to this email address. Please check your email for the verification code." + ) ); } else { // If the user was created more than 2 hours ago, we want to delete the old user and create a new one @@ -101,7 +111,7 @@ export async function signup( dateCreated: moment().toISOString(), }); - // give the user their default permissions: + // give the user their default permissions: // await db.insert(userActions).values({ // userId: userId, // actionId: ActionsEnum.createOrg, @@ -113,15 +123,25 @@ export async function signup( const cookie = serializeSessionCookie(token); res.appendHeader("Set-Cookie", cookie); - sendEmailVerificationCode(email, userId); + if (config.flags?.require_email_verification) { + sendEmailVerificationCode(email, userId); + + return response(res, { + data: { + emailVerificationRequired: true, + }, + success: true, + error: false, + message: `User created successfully. We sent an email to ${email} with a verification code.`, + status: HttpCode.OK, + }); + } return response(res, { - data: { - emailVerificationRequired: true, - }, + data: {}, success: true, error: false, - message: `User created successfully. We sent an email to ${email} with a verification code.`, + message: "User created successfully", status: HttpCode.OK, }); } catch (e) { @@ -129,15 +149,15 @@ export async function signup( return next( createHttpError( HttpCode.BAD_REQUEST, - "A user with that email address already exists", - ), + "A user with that email address already exists" + ) ); } else { return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - "Failed to create user", - ), + "Failed to create user" + ) ); } } diff --git a/server/routers/auth/verifyEmail.ts b/server/routers/auth/verifyEmail.ts index 38ba13e4..6df3d489 100644 --- a/server/routers/auth/verifyEmail.ts +++ b/server/routers/auth/verifyEmail.ts @@ -8,6 +8,7 @@ import { db } from "@server/db"; import { User, emailVerificationCodes, users } from "@server/db/schema"; import { eq } from "drizzle-orm"; import { isWithinExpirationDate } from "oslo"; +import config from "@server/config"; export const verifyEmailBody = z.object({ code: z.string(), @@ -22,16 +23,25 @@ export type VerifyEmailResponse = { export async function verifyEmail( req: Request, res: Response, - next: NextFunction, + next: NextFunction ): Promise { + if (!config.flags?.require_email_verification) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Email verification is not enabled" + ) + ); + } + const parsedBody = verifyEmailBody.safeParse(req.body); if (!parsedBody.success) { return next( createHttpError( HttpCode.BAD_REQUEST, - fromError(parsedBody.error).toString(), - ), + fromError(parsedBody.error).toString() + ) ); } @@ -41,7 +51,7 @@ export async function verifyEmail( if (user.emailVerified) { return next( - createHttpError(HttpCode.BAD_REQUEST, "Email is already verified"), + createHttpError(HttpCode.BAD_REQUEST, "Email is already verified") ); } @@ -63,8 +73,8 @@ export async function verifyEmail( return next( createHttpError( HttpCode.BAD_REQUEST, - "Invalid verification code", - ), + "Invalid verification code" + ) ); } @@ -81,8 +91,8 @@ export async function verifyEmail( return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - "Failed to verify email", - ), + "Failed to verify email" + ) ); } } diff --git a/src/app/auth/verify-email/page.tsx b/src/app/auth/verify-email/page.tsx index 3bc52980..3db73215 100644 --- a/src/app/auth/verify-email/page.tsx +++ b/src/app/auth/verify-email/page.tsx @@ -2,11 +2,13 @@ import VerifyEmailForm from "@app/app/auth/verify-email/VerifyEmailForm"; import { verifySession } from "@app/lib/auth/verifySession"; import { redirect } from "next/navigation"; -export default async function Page( - props: { - searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +export default async function Page(props: { + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +}) { + if (!process.env.NEXT_PUBLIC_FLAGS_EMAIL_VERIFICATION_REQUIRED) { + redirect("/"); } -) { + const searchParams = await props.searchParams; const user = await verifySession();