import { error } from "@sveltejs/kit"; import argon2 from "argon2"; import { getClient, getClientByPubKeys, getUserClient } from "$lib/server/db/client"; import { IntegrityError } from "$lib/server/db/error"; import { upgradeSession, deleteSession, deleteAllOtherSessions, registerSessionUpgradeChallenge, consumeSessionUpgradeChallenge, } from "$lib/server/db/session"; import { getUser, getUserByEmail, setUserPassword } from "$lib/server/db/user"; import env from "$lib/server/loadenv"; import { startSession } from "$lib/server/modules/auth"; import { verifySignature, generateChallenge } from "$lib/server/modules/crypto"; const hashPassword = async (password: string) => { return await argon2.hash(password); }; const verifyPassword = async (hash: string, password: string) => { return await argon2.verify(hash, password); }; export const changePassword = async ( userId: number, sessionId: string, oldPassword: string, newPassword: string, ) => { if (oldPassword === newPassword) { error(400, "Same passwords"); } else if (newPassword.length < 8) { error(400, "Too short password"); } const user = await getUser(userId); if (!user) { error(500, "Invalid session id"); } else if (!(await verifyPassword(user.password, oldPassword))) { error(403, "Invalid password"); } await setUserPassword(userId, await hashPassword(newPassword)); await deleteAllOtherSessions(userId, sessionId); }; export const login = async (email: string, password: string, ip: string, userAgent: string) => { const user = await getUserByEmail(email); if (!user || !(await verifyPassword(user.password, password))) { error(401, "Invalid email or password"); } return { sessionIdSigned: await startSession(user.id, ip, userAgent) }; }; export const logout = async (sessionId: string) => { await deleteSession(sessionId); }; export const createSessionUpgradeChallenge = async ( sessionId: string, userId: number, ip: string, encPubKey: string, sigPubKey: string, ) => { const client = await getClientByPubKeys(encPubKey, sigPubKey); const userClient = client ? await getUserClient(userId, client.id) : undefined; if (!client) { error(401, "Invalid public key(s)"); } else if (!userClient || userClient.state === "challenging") { error(403, "Unregistered client"); } const { answer, challenge } = await generateChallenge(32, encPubKey); const { id } = await registerSessionUpgradeChallenge( sessionId, client.id, answer.toString("base64"), ip, new Date(Date.now() + env.challenge.sessionUpgradeExp), ); return { id, challenge: challenge.toString("base64") }; }; export const verifySessionUpgradeChallenge = async ( sessionId: string, userId: number, ip: string, challengeId: number, answerSig: string, force: boolean, ) => { const challenge = await consumeSessionUpgradeChallenge(challengeId, sessionId, ip); if (!challenge) { error(403, "Invalid challenge answer"); } const client = await getClient(challenge.clientId); if (!client) { error(500, "Invalid challenge answer"); } else if ( !verifySignature(Buffer.from(challenge.answer, "base64"), answerSig, client.sigPubKey) ) { error(403, "Invalid challenge answer signature"); } try { await upgradeSession(userId, sessionId, client.id, force); } catch (e) { if (e instanceof IntegrityError) { if (e.message === "Session not found") { error(500, "Invalid challenge answer"); } else if (!force && e.message === "Session already exists") { error(409, "Already logged in"); } } throw e; } };