ability to disable email verification requirement

This commit is contained in:
Milo Schwartz 2024-10-25 21:39:18 -04:00
parent 50e1a7abe1
commit 29b848fd5d
No known key found for this signature in database
7 changed files with 107 additions and 53 deletions

View file

@ -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;

View file

@ -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<ErrorResponse>,
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?
);
}

View file

@ -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<any> {
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<LoginResponse>(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"
)
);
}
}

View file

@ -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<any> {
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"
)
);
}
}

View file

@ -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<typeof signupBodySchema>;
export type SignUpResponse = {
emailVerificationRequired: boolean;
emailVerificationRequired?: boolean;
};
export async function signup(
req: Request,
res: Response,
next: NextFunction,
next: NextFunction
): Promise<any> {
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
@ -113,6 +123,7 @@ export async function signup(
const cookie = serializeSessionCookie(token);
res.appendHeader("Set-Cookie", cookie);
if (config.flags?.require_email_verification) {
sendEmailVerificationCode(email, userId);
return response<SignUpResponse>(res, {
@ -124,20 +135,29 @@ export async function signup(
message: `User created successfully. We sent an email to ${email} with a verification code.`,
status: HttpCode.OK,
});
}
return response<SignUpResponse>(res, {
data: {},
success: true,
error: false,
message: "User created successfully",
status: HttpCode.OK,
});
} catch (e) {
if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") {
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"
)
);
}
}

View file

@ -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<any> {
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"
)
);
}
}

View file

@ -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: {
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();