사소한 리팩토링

This commit is contained in:
static
2025-12-26 15:07:59 +09:00
parent 3fc29cf8db
commit d94d14cf83
5 changed files with 33 additions and 50 deletions

View File

@@ -1,6 +1,5 @@
import { error, redirect, type Handle } from "@sveltejs/kit"; import { error, redirect, type Handle } from "@sveltejs/kit";
import env from "$lib/server/loadenv"; import { cookieOptions, authenticate, AuthenticationError } from "$lib/server/modules/auth";
import { authenticate, AuthenticationError } from "$lib/server/modules/auth";
export const authenticateMiddleware: Handle = async ({ event, resolve }) => { export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
try { try {
@@ -11,12 +10,7 @@ export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
const { ip, userAgent } = event.locals; const { ip, userAgent } = event.locals;
event.locals.session = await authenticate(sessionIdSigned, ip, userAgent); event.locals.session = await authenticate(sessionIdSigned, ip, userAgent);
event.cookies.set("sessionId", sessionIdSigned, { event.cookies.set("sessionId", sessionIdSigned, cookieOptions);
path: "/",
maxAge: env.session.exp / 1000,
secure: true,
sameSite: "strict",
});
} catch (e) { } catch (e) {
if (e instanceof AuthenticationError) { if (e instanceof AuthenticationError) {
const { pathname, search } = event.url; const { pathname, search } = event.url;

View File

@@ -1,11 +1,9 @@
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import { getUserClient } from "$lib/server/db/client"; import { ClientRepo, SessionRepo, IntegrityError } from "$lib/server/db";
import { IntegrityError } from "$lib/server/db/error";
import { createSession, refreshSession } from "$lib/server/db/session";
import env from "$lib/server/loadenv"; 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; sessionId: string;
userId: number; userId: number;
clientId?: number; clientId?: number;
@@ -42,11 +40,12 @@ export class AuthorizationError extends Error {
} }
} }
export const startSession = async (userId: number, ip: string, userAgent: string) => { export const cookieOptions = {
const { sessionId, sessionIdSigned } = await issueSessionId(32, env.session.secret); path: "/",
await createSession(userId, sessionId, ip, userAgent); maxAge: env.session.exp / 1000,
return sessionIdSigned; secure: true,
}; sameSite: "strict",
} as const;
export const authenticate = async (sessionIdSigned: string, ip: string, userAgent: string) => { export const authenticate = async (sessionIdSigned: string, ip: string, userAgent: string) => {
const sessionId = verifySessionId(sessionIdSigned, env.session.secret); const sessionId = verifySessionId(sessionIdSigned, env.session.secret);
@@ -55,7 +54,7 @@ export const authenticate = async (sessionIdSigned: string, ip: string, userAgen
} }
try { try {
const { userId, clientId } = await refreshSession(sessionId, ip, userAgent); const { userId, clientId } = await SessionRepo.refreshSession(sessionId, ip, userAgent);
return { return {
id: sessionId, id: sessionId,
userId, userId,
@@ -96,7 +95,7 @@ export const authorizeInternal = async (
if (!clientId) { if (!clientId) {
throw new AuthorizationError(403, "Forbidden"); throw new AuthorizationError(403, "Forbidden");
} }
const userClient = await getUserClient(userId, clientId); const userClient = await ClientRepo.getUserClient(userId, clientId);
if (!userClient) { if (!userClient) {
throw new AuthorizationError(500, "Invalid session id"); throw new AuthorizationError(500, "Invalid session id");
} else if (userClient.state !== "pending") { } else if (userClient.state !== "pending") {
@@ -108,7 +107,7 @@ export const authorizeInternal = async (
if (!clientId) { if (!clientId) {
throw new AuthorizationError(403, "Forbidden"); throw new AuthorizationError(403, "Forbidden");
} }
const userClient = await getUserClient(userId, clientId); const userClient = await ClientRepo.getUserClient(userId, clientId);
if (!userClient) { if (!userClient) {
throw new AuthorizationError(500, "Invalid session id"); throw new AuthorizationError(500, "Invalid session id");
} else if (userClient.state !== "active") { } else if (userClient.state !== "active") {

View File

@@ -12,7 +12,7 @@ export const requestSessionUpgrade = async (
const trpc = useTRPC(); const trpc = useTRPC();
let id, challenge; let id, challenge;
try { try {
({ id, challenge } = await trpc.auth.upgradeSession.mutate({ ({ id, challenge } = await trpc.auth.upgrade.mutate({
encPubKey: encryptKeyBase64, encPubKey: encryptKeyBase64,
sigPubKey: verifyKeyBase64, sigPubKey: verifyKeyBase64,
})); }));
@@ -26,7 +26,7 @@ export const requestSessionUpgrade = async (
const answerSig = await signMessageRSA(answer, signKey); const answerSig = await signMessageRSA(answer, signKey);
try { try {
await trpc.auth.verifySessionUpgrade.mutate({ await trpc.auth.verifyUpgrade.mutate({
id, id,
answerSig: encodeToBase64(answerSig), answerSig: encodeToBase64(answerSig),
force, force,

View File

@@ -3,51 +3,39 @@ import argon2 from "argon2";
import { z } from "zod"; import { z } from "zod";
import { ClientRepo, SessionRepo, UserRepo, IntegrityError } from "$lib/server/db"; import { ClientRepo, SessionRepo, UserRepo, IntegrityError } from "$lib/server/db";
import env from "$lib/server/loadenv"; import env from "$lib/server/loadenv";
import { startSession } from "$lib/server/modules/auth"; import { cookieOptions } from "$lib/server/modules/auth";
import { generateChallenge, verifySignature } from "$lib/server/modules/crypto"; import { generateChallenge, verifySignature, issueSessionId } from "$lib/server/modules/crypto";
import { publicProcedure, roleProcedure, router } from "../init.server"; import { router, publicProcedure, roleProcedure } 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);
};
const authRouter = router({ const authRouter = router({
login: publicProcedure login: publicProcedure
.input( .input(
z.object({ z.object({
email: z.email(), email: z.email(),
password: z.string().trim().nonempty(), password: z.string().nonempty(),
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const user = await UserRepo.getUserByEmail(input.email); 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" }); throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid email or password" });
} }
const sessionIdSigned = await startSession(user.id, ctx.locals.ip, ctx.locals.userAgent); const { sessionId, sessionIdSigned } = await issueSessionId(32, env.session.secret);
ctx.cookies.set("sessionId", sessionIdSigned, { await SessionRepo.createSession(user.id, sessionId, ctx.locals.ip, ctx.locals.userAgent);
path: "/", ctx.cookies.set("sessionId", sessionIdSigned, cookieOptions);
maxAge: env.session.exp / 1000,
secure: true,
sameSite: "strict",
});
}), }),
logout: roleProcedure["any"].mutation(async ({ ctx }) => { logout: roleProcedure["any"].mutation(async ({ ctx }) => {
await SessionRepo.deleteSession(ctx.session.sessionId); await SessionRepo.deleteSession(ctx.session.sessionId);
ctx.cookies.delete("sessionId", { path: "/" }); ctx.cookies.delete("sessionId", cookieOptions);
}), }),
changePassword: roleProcedure["any"] changePassword: roleProcedure["any"]
.input( .input(
z.object({ z.object({
oldPassword: z.string().trim().nonempty(), oldPassword: z.string().nonempty(),
newPassword: z.string().trim().nonempty(), newPassword: z.string().nonempty(),
}), }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
@@ -60,15 +48,15 @@ const authRouter = router({
const user = await UserRepo.getUser(ctx.session.userId); const user = await UserRepo.getUser(ctx.session.userId);
if (!user) { if (!user) {
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Invalid session id" }); 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" }); 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); await SessionRepo.deleteAllOtherSessions(ctx.session.userId, ctx.session.sessionId);
}), }),
upgradeSession: roleProcedure["notClient"] upgrade: roleProcedure["notClient"]
.input( .input(
z.object({ z.object({
encPubKey: z.base64().nonempty(), encPubKey: z.base64().nonempty(),
@@ -94,10 +82,11 @@ const authRouter = router({
ctx.locals.ip, ctx.locals.ip,
new Date(Date.now() + env.challenge.sessionUpgradeExp), new Date(Date.now() + env.challenge.sessionUpgradeExp),
); );
return { id, challenge: challenge.toString("base64") }; return { id, challenge: challenge.toString("base64") };
}), }),
verifySessionUpgrade: roleProcedure["notClient"] verifyUpgrade: roleProcedure["notClient"]
.input( .input(
z.object({ z.object({
id: z.int().positive(), id: z.int().positive(),

View File

@@ -19,6 +19,7 @@ const createUserClientChallenge = async (
ip, ip,
new Date(Date.now() + env.challenge.userClientExp), new Date(Date.now() + env.challenge.userClientExp),
); );
return { id, challenge: challenge.toString("base64") }; return { id, challenge: challenge.toString("base64") };
}; };