diff --git a/src/lib/server/middlewares/authenticate.ts b/src/lib/server/middlewares/authenticate.ts index 49f6545..ecd1adf 100644 --- a/src/lib/server/middlewares/authenticate.ts +++ b/src/lib/server/middlewares/authenticate.ts @@ -1,6 +1,5 @@ import { error, redirect, type Handle } from "@sveltejs/kit"; -import env from "$lib/server/loadenv"; -import { authenticate, AuthenticationError } from "$lib/server/modules/auth"; +import { cookieOptions, authenticate, AuthenticationError } from "$lib/server/modules/auth"; export const authenticateMiddleware: Handle = async ({ event, resolve }) => { try { @@ -11,12 +10,7 @@ export const authenticateMiddleware: Handle = async ({ event, resolve }) => { const { ip, userAgent } = event.locals; event.locals.session = await authenticate(sessionIdSigned, ip, userAgent); - event.cookies.set("sessionId", sessionIdSigned, { - path: "/", - maxAge: env.session.exp / 1000, - secure: true, - sameSite: "strict", - }); + event.cookies.set("sessionId", sessionIdSigned, cookieOptions); } catch (e) { if (e instanceof AuthenticationError) { const { pathname, search } = event.url; diff --git a/src/lib/server/modules/auth.ts b/src/lib/server/modules/auth.ts index 6ae3865..b736a49 100644 --- a/src/lib/server/modules/auth.ts +++ b/src/lib/server/modules/auth.ts @@ -1,11 +1,9 @@ import { error } from "@sveltejs/kit"; -import { getUserClient } from "$lib/server/db/client"; -import { IntegrityError } from "$lib/server/db/error"; -import { createSession, refreshSession } from "$lib/server/db/session"; +import { ClientRepo, SessionRepo, IntegrityError } from "$lib/server/db"; import env from "$lib/server/loadenv"; -import { issueSessionId, verifySessionId } from "$lib/server/modules/crypto"; +import { verifySessionId } from "$lib/server/modules/crypto"; -interface Session { +export interface Session { sessionId: string; userId: number; clientId?: number; @@ -42,11 +40,12 @@ export class AuthorizationError extends Error { } } -export const startSession = async (userId: number, ip: string, userAgent: string) => { - const { sessionId, sessionIdSigned } = await issueSessionId(32, env.session.secret); - await createSession(userId, sessionId, ip, userAgent); - return sessionIdSigned; -}; +export const cookieOptions = { + path: "/", + maxAge: env.session.exp / 1000, + secure: true, + sameSite: "strict", +} as const; export const authenticate = async (sessionIdSigned: string, ip: string, userAgent: string) => { const sessionId = verifySessionId(sessionIdSigned, env.session.secret); @@ -55,7 +54,7 @@ export const authenticate = async (sessionIdSigned: string, ip: string, userAgen } try { - const { userId, clientId } = await refreshSession(sessionId, ip, userAgent); + const { userId, clientId } = await SessionRepo.refreshSession(sessionId, ip, userAgent); return { id: sessionId, userId, @@ -96,7 +95,7 @@ export const authorizeInternal = async ( if (!clientId) { throw new AuthorizationError(403, "Forbidden"); } - const userClient = await getUserClient(userId, clientId); + const userClient = await ClientRepo.getUserClient(userId, clientId); if (!userClient) { throw new AuthorizationError(500, "Invalid session id"); } else if (userClient.state !== "pending") { @@ -108,7 +107,7 @@ export const authorizeInternal = async ( if (!clientId) { throw new AuthorizationError(403, "Forbidden"); } - const userClient = await getUserClient(userId, clientId); + const userClient = await ClientRepo.getUserClient(userId, clientId); if (!userClient) { throw new AuthorizationError(500, "Invalid session id"); } else if (userClient.state !== "active") { diff --git a/src/lib/services/auth.ts b/src/lib/services/auth.ts index 5d2d01a..2131943 100644 --- a/src/lib/services/auth.ts +++ b/src/lib/services/auth.ts @@ -12,7 +12,7 @@ export const requestSessionUpgrade = async ( const trpc = useTRPC(); let id, challenge; try { - ({ id, challenge } = await trpc.auth.upgradeSession.mutate({ + ({ id, challenge } = await trpc.auth.upgrade.mutate({ encPubKey: encryptKeyBase64, sigPubKey: verifyKeyBase64, })); @@ -26,7 +26,7 @@ export const requestSessionUpgrade = async ( const answerSig = await signMessageRSA(answer, signKey); try { - await trpc.auth.verifySessionUpgrade.mutate({ + await trpc.auth.verifyUpgrade.mutate({ id, answerSig: encodeToBase64(answerSig), force, diff --git a/src/trpc/routers/auth.ts b/src/trpc/routers/auth.ts index e0f3a7f..e0d3d50 100644 --- a/src/trpc/routers/auth.ts +++ b/src/trpc/routers/auth.ts @@ -3,51 +3,39 @@ import argon2 from "argon2"; import { z } from "zod"; import { ClientRepo, SessionRepo, UserRepo, IntegrityError } from "$lib/server/db"; import env from "$lib/server/loadenv"; -import { startSession } from "$lib/server/modules/auth"; -import { generateChallenge, verifySignature } from "$lib/server/modules/crypto"; -import { publicProcedure, roleProcedure, router } from "../init.server"; - -const hashPassword = async (password: string) => { - return await argon2.hash(password); -}; - -const verifyPassword = async (hash: string, password: string) => { - return await argon2.verify(hash, password); -}; +import { cookieOptions } from "$lib/server/modules/auth"; +import { generateChallenge, verifySignature, issueSessionId } from "$lib/server/modules/crypto"; +import { router, publicProcedure, roleProcedure } from "../init.server"; const authRouter = router({ login: publicProcedure .input( z.object({ email: z.email(), - password: z.string().trim().nonempty(), + password: z.string().nonempty(), }), ) .mutation(async ({ ctx, input }) => { const user = await UserRepo.getUserByEmail(input.email); - if (!user || !(await verifyPassword(user.password, input.password))) { + if (!user || !(await argon2.verify(user.password, input.password))) { throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid email or password" }); } - const sessionIdSigned = await startSession(user.id, ctx.locals.ip, ctx.locals.userAgent); - ctx.cookies.set("sessionId", sessionIdSigned, { - path: "/", - maxAge: env.session.exp / 1000, - secure: true, - sameSite: "strict", - }); + const { sessionId, sessionIdSigned } = await issueSessionId(32, env.session.secret); + await SessionRepo.createSession(user.id, sessionId, ctx.locals.ip, ctx.locals.userAgent); + ctx.cookies.set("sessionId", sessionIdSigned, cookieOptions); }), logout: roleProcedure["any"].mutation(async ({ ctx }) => { await SessionRepo.deleteSession(ctx.session.sessionId); - ctx.cookies.delete("sessionId", { path: "/" }); + ctx.cookies.delete("sessionId", cookieOptions); }), changePassword: roleProcedure["any"] .input( z.object({ - oldPassword: z.string().trim().nonempty(), - newPassword: z.string().trim().nonempty(), + oldPassword: z.string().nonempty(), + newPassword: z.string().nonempty(), }), ) .mutation(async ({ ctx, input }) => { @@ -60,15 +48,15 @@ const authRouter = router({ const user = await UserRepo.getUser(ctx.session.userId); if (!user) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Invalid session id" }); - } else if (!(await verifyPassword(user.password, input.oldPassword))) { + } else if (!(await argon2.verify(user.password, input.oldPassword))) { throw new TRPCError({ code: "FORBIDDEN", message: "Invalid password" }); } - await UserRepo.setUserPassword(ctx.session.userId, await hashPassword(input.newPassword)); + await UserRepo.setUserPassword(ctx.session.userId, await argon2.hash(input.newPassword)); await SessionRepo.deleteAllOtherSessions(ctx.session.userId, ctx.session.sessionId); }), - upgradeSession: roleProcedure["notClient"] + upgrade: roleProcedure["notClient"] .input( z.object({ encPubKey: z.base64().nonempty(), @@ -94,10 +82,11 @@ const authRouter = router({ ctx.locals.ip, new Date(Date.now() + env.challenge.sessionUpgradeExp), ); + return { id, challenge: challenge.toString("base64") }; }), - verifySessionUpgrade: roleProcedure["notClient"] + verifyUpgrade: roleProcedure["notClient"] .input( z.object({ id: z.int().positive(), diff --git a/src/trpc/routers/client.ts b/src/trpc/routers/client.ts index 37c5bc6..db7229a 100644 --- a/src/trpc/routers/client.ts +++ b/src/trpc/routers/client.ts @@ -19,6 +19,7 @@ const createUserClientChallenge = async ( ip, new Date(Date.now() + env.challenge.userClientExp), ); + return { id, challenge: challenge.toString("base64") }; };