diff --git a/server/auth/index.ts b/server/auth/index.ts index a97d64ba..fbf04632 100644 --- a/server/auth/index.ts +++ b/server/auth/index.ts @@ -1,3 +1,6 @@ +export * from "./unauthorizedResponse"; +export * from "./verifySession"; + import { Lucia, TimeSpan } from "lucia"; import { DrizzleSQLiteAdapter } from "@lucia-auth/adapter-drizzle"; import db from "@server/db"; diff --git a/server/auth/unauthorizedResponse.ts b/server/auth/unauthorizedResponse.ts new file mode 100644 index 00000000..c0c42252 --- /dev/null +++ b/server/auth/unauthorizedResponse.ts @@ -0,0 +1,6 @@ +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; + +export function unauthorized(msg?: string) { + return createHttpError(HttpCode.UNAUTHORIZED, msg || "Unauthorized"); +} diff --git a/server/auth/verifySession.ts b/server/auth/verifySession.ts new file mode 100644 index 00000000..a371c336 --- /dev/null +++ b/server/auth/verifySession.ts @@ -0,0 +1,9 @@ +import { Request } from "express"; +import { lucia } from "@server/auth"; + +export async function verifySession(req: Request) { + const res = await lucia.validateSession( + req.cookies[lucia.sessionCookieName], + ); + return res; +} diff --git a/server/routers/auth/login.ts b/server/routers/auth/login.ts index 861e60a9..1c1f0ecb 100644 --- a/server/routers/auth/login.ts +++ b/server/routers/auth/login.ts @@ -7,10 +7,10 @@ import response from "@server/utils/response"; import { eq } from "drizzle-orm"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; -import { z } from "zod"; -import { fromError } from "zod-validation-error"; import { decodeHex } from "oslo/encoding"; import { TOTPController } from "oslo/otp"; +import { z } from "zod"; +import { fromError } from "zod-validation-error"; export const loginBodySchema = z.object({ email: z.string().email(), @@ -45,15 +45,13 @@ export async function login( const sessionId = req.cookies[lucia.sessionCookieName]; const { session: existingSession } = await lucia.validateSession(sessionId); if (existingSession) { - return res.status(HttpCode.OK).send( - response({ - data: null, - success: true, - error: false, - message: "Already logged in", - status: HttpCode.OK, - }), - ); + return response(res, { + data: null, + success: true, + error: false, + message: "Already logged in", + status: HttpCode.OK, + }); } const existingUserRes = await db @@ -89,15 +87,13 @@ export async function login( if (existingUser.twoFactorEnabled) { if (!code) { - return res.status(HttpCode.ACCEPTED).send( - response<{ codeRequested: boolean }>({ - data: { codeRequested: true }, - success: true, - error: false, - message: "Two-factor authentication required", - status: HttpCode.ACCEPTED, - }), - ); + return response<{ codeRequested: boolean }>(res, { + data: { codeRequested: true }, + success: true, + error: false, + message: "Two-factor authentication required", + status: HttpCode.ACCEPTED, + }); } if (!existingUser.twoFactorSecret) { @@ -131,13 +127,11 @@ export async function login( lucia.createSessionCookie(session.id).serialize(), ); - return res.status(HttpCode.OK).send( - response({ - data: null, - success: true, - error: false, - message: "Logged in successfully", - status: HttpCode.OK, - }), - ); + return response(res, { + data: null, + success: true, + error: false, + message: "Logged in successfully", + status: HttpCode.OK, + }); } diff --git a/server/routers/auth/logout.ts b/server/routers/auth/logout.ts index b3806ae5..e71be664 100644 --- a/server/routers/auth/logout.ts +++ b/server/routers/auth/logout.ts @@ -23,13 +23,11 @@ export async function logout( await lucia.invalidateSession(sessionId); res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize()); - return res.status(HttpCode.OK).send( - response({ - data: null, - success: true, - error: false, - message: "Logged out successfully", - status: HttpCode.OK, - }), - ); + return response(res, { + data: null, + success: true, + error: false, + message: "Logged out successfully", + status: HttpCode.OK, + }); } diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index 78ed7b35..eb0ebca6 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -28,7 +28,11 @@ export const signupBodySchema = z.object({ export type SignUpBody = z.infer; -export async function signup(req: Request, res: Response, next: NextFunction): Promise { +export async function signup( + req: Request, + res: Response, + next: NextFunction, +): Promise { const parsedBody = signupBodySchema.safeParse(req.body); if (!parsedBody.success) { @@ -64,15 +68,13 @@ export async function signup(req: Request, res: Response, next: NextFunction): P lucia.createSessionCookie(session.id).serialize(), ); - return res.status(HttpCode.OK).send( - response({ - data: null, - success: true, - error: false, - message: "User created successfully", - status: HttpCode.OK, - }), - ); + return response(res, { + data: null, + success: true, + error: false, + message: "User created successfully", + status: HttpCode.OK, + }); } catch (e) { if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { return next( diff --git a/server/routers/auth/verifyTotp.ts b/server/routers/auth/verifyTotp.ts new file mode 100644 index 00000000..e338f929 --- /dev/null +++ b/server/routers/auth/verifyTotp.ts @@ -0,0 +1,42 @@ +import { Request, Response, NextFunction } from "express"; +import createHttpError from "http-errors"; +import { z } from "zod"; +import { fromError } from "zod-validation-error"; +import { decodeHex } from "oslo/encoding"; +import { TOTPController } from "oslo/otp"; +import HttpCode from "@server/types/HttpCode"; +import { verifySession, lucia, unauthorized } from "@server/auth"; + +export const verifyTotpBody = z.object({ + code: z.string(), +}); + +export type VerifyTotpBody = z.infer; + +export type VerifyTotpResponse = { + valid: boolean; +}; + +export async function verifyTotp( + req: Request, + res: Response, + next: NextFunction, +): Promise { + const parsedBody = verifyTotpBody.safeParse(req.body); + + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString(), + ), + ); + } + + const { code } = parsedBody.data; + + const { session, user } = await verifySession(req); + if (!session) { + return unauthorized(); + } +} diff --git a/server/routers/external.ts b/server/routers/external.ts index 952b9570..d116e87f 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -47,3 +47,4 @@ unauthenticated.use("/auth", authRouter); authRouter.put("/signup", auth.signup); authRouter.post("/login", auth.login); authRouter.post("/logout", auth.logout); +authRouter.post("/verify-totp", auth.logout); diff --git a/server/routers/site/createSite.ts b/server/routers/site/createSite.ts index e0bd06dd..781e91fc 100644 --- a/server/routers/site/createSite.ts +++ b/server/routers/site/createSite.ts @@ -1,17 +1,19 @@ -import { Request, Response, NextFunction } from 'express'; +import { Request, Response, NextFunction } from "express"; import response from "@server/utils/response"; -import HttpCode from '@server/types/HttpCode'; +import HttpCode from "@server/types/HttpCode"; // define zod type here -export async function createSite(req: Request, res: Response, next: NextFunction): Promise { - return res.status(HttpCode.OK).send( - response({ - data: null, - success: true, - error: false, - message: "Logged in successfully", - status: HttpCode.OK, - }), - ); +export async function createSite( + req: Request, + res: Response, + next: NextFunction, +): Promise { + return response(res, { + data: null, + success: true, + error: false, + message: "Logged in successfully", + status: HttpCode.OK, + }); } diff --git a/server/utils/index.ts b/server/utils/index.ts new file mode 100644 index 00000000..9d2cfb1f --- /dev/null +++ b/server/utils/index.ts @@ -0,0 +1 @@ +export * from "./response"; diff --git a/server/utils/response.ts b/server/utils/response.ts index 4c07cd71..ae8461ba 100644 --- a/server/utils/response.ts +++ b/server/utils/response.ts @@ -1,19 +1,17 @@ import { ResponseT } from "@server/types/Response"; +import { Response } from "express"; -export const response = ({ - data, - success, - error, - message, - status, -}: ResponseT) => { - return { +export const response = ( + res: Response, + { data, success, error, message, status }: ResponseT, +) => { + return res.status(status).send({ data, success, error, message, status, - }; + }); }; export default response;