diff --git a/src/lib/server/modules/auth.ts b/src/lib/server/modules/auth.ts index 32bce3a..8276d6d 100644 --- a/src/lib/server/modules/auth.ts +++ b/src/lib/server/modules/auth.ts @@ -41,7 +41,7 @@ export const authenticate = (cookies: Cookies) => { error(401, "Access token not found"); } - const tokenPayload = verifyToken(accessToken); + const tokenPayload = verifyToken(accessToken.trim()); if (tokenPayload === TokenError.EXPIRED) { error(401, "Access token expired"); } else if (tokenPayload === TokenError.INVALID || tokenPayload.type !== "access") { diff --git a/src/lib/server/services/auth.ts b/src/lib/server/services/auth.ts index e60c4be..ef50978 100644 --- a/src/lib/server/services/auth.ts +++ b/src/lib/server/services/auth.ts @@ -7,6 +7,7 @@ import { getRefreshToken, registerRefreshToken, rotateRefreshToken, + upgradeRefreshToken, revokeRefreshToken, } from "$lib/server/db/token"; import { UserClientState } from "$lib/server/db/schema"; @@ -87,3 +88,27 @@ export const refreshTokens = async (refreshToken: string) => { refreshToken: issueToken({ type: "refresh", jti: newJti }), }; }; + +export const upgradeTokens = async (refreshToken: string, pubKey: string) => { + const { jti: oldJti, userId, clientId } = await verifyRefreshToken(refreshToken); + if (clientId) { + error(403, "Forbidden"); + } + + const client = await getClientByPubKey(pubKey); + const userClient = client ? await getUserClient(userId, client.id) : undefined; + if (!client) { + error(401, "Invalid public key"); + } else if (client && (!userClient || userClient.state === UserClientState.Challenging)) { + error(401, "Unregistered public key"); + } + + const newJti = uuidv4(); + if (!(await upgradeRefreshToken(oldJti, newJti, client.id))) { + error(500, "Refresh token not found"); + } + return { + accessToken: issueAccessToken(userId, client.id), + refreshToken: issueToken({ type: "refresh", jti: newJti }), + }; +}; diff --git a/src/routes/(fullscreen)/key/export/+page.svelte b/src/routes/(fullscreen)/key/export/+page.svelte index 74bc801..762fdc9 100644 --- a/src/routes/(fullscreen)/key/export/+page.svelte +++ b/src/routes/(fullscreen)/key/export/+page.svelte @@ -9,6 +9,7 @@ import { createBlobFromKeyPairBase64, requestPubKeyRegistration, + requestTokenUpgrade, storeKeyPairPersistently, } from "./service"; @@ -40,7 +41,12 @@ if (await requestPubKeyRegistration(data.pubKeyBase64, $keyPairStore.privateKey)) { await storeKeyPairPersistently($keyPairStore); - await goto(data.redirectPath); + + if (await requestTokenUpgrade(data.pubKeyBase64)) { + await goto(data.redirectPath); + } else { + // TODO: Error handling + } } else { // TODO: Error handling } diff --git a/src/routes/(fullscreen)/key/export/service.ts b/src/routes/(fullscreen)/key/export/service.ts index 5ca135e..11b6d53 100644 --- a/src/routes/(fullscreen)/key/export/service.ts +++ b/src/routes/(fullscreen)/key/export/service.ts @@ -38,6 +38,17 @@ export const requestPubKeyRegistration = async (pubKeyBase64: string, privateKey return res.ok; }; +export const requestTokenUpgrade = async (pubKeyBase64: string) => { + const res = await fetch("/api/auth/upgradeToken", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ pubKey: pubKeyBase64 }), + }); + return res.ok; +}; + export const storeKeyPairPersistently = async (keyPair: CryptoKeyPair) => { await storeKeyPairIntoIndexedDB(keyPair.publicKey, keyPair.privateKey); }; diff --git a/src/routes/api/auth/upgradeToken/+server.ts b/src/routes/api/auth/upgradeToken/+server.ts new file mode 100644 index 0000000..20237e4 --- /dev/null +++ b/src/routes/api/auth/upgradeToken/+server.ts @@ -0,0 +1,29 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { upgradeTokens } from "$lib/server/services/auth"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ request, cookies }) => { + const token = cookies.get("refreshToken"); + if (!token) error(401, "Refresh token not found"); + + const zodRes = z + .object({ + pubKey: z.string().base64().nonempty(), + }) + .safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + + const { pubKey } = zodRes.data; + const { accessToken, refreshToken } = await upgradeTokens(token.trim(), pubKey.trim()); + + cookies.set("accessToken", accessToken, { + path: "/", + sameSite: "strict", + }); + cookies.set("refreshToken", refreshToken, { + path: "/api/auth", + sameSite: "strict", + }); + return text("Token upgraded", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/key/register/+server.ts b/src/routes/api/key/register/+server.ts index 766b13f..2be9f3b 100644 --- a/src/routes/api/key/register/+server.ts +++ b/src/routes/api/key/register/+server.ts @@ -17,6 +17,7 @@ export const POST: RequestHandler = async ({ request, cookies, getClientAddress error(403, "Forbidden"); } - const challenge = await registerPubKey(userId, getClientAddress(), zodRes.data.pubKey); + const { pubKey } = zodRes.data; + const challenge = await registerPubKey(userId, getClientAddress(), pubKey.trim()); return json({ challenge }); }; diff --git a/src/routes/api/key/verify/+server.ts b/src/routes/api/key/verify/+server.ts index bc59816..45301e8 100644 --- a/src/routes/api/key/verify/+server.ts +++ b/src/routes/api/key/verify/+server.ts @@ -17,6 +17,7 @@ export const POST: RequestHandler = async ({ request, cookies, getClientAddress error(403, "Forbidden"); } - await verifyPubKey(userId, getClientAddress(), zodRes.data.answer); + const { answer } = zodRes.data; + await verifyPubKey(userId, getClientAddress(), answer.trim()); return text("Key verified", { headers: { "Content-Type": "text/plain" } }); };