diff --git a/src/lib/server/db/client.ts b/src/lib/server/db/client.ts index a4e2cdb..c3c64ad 100644 --- a/src/lib/server/db/client.ts +++ b/src/lib/server/db/client.ts @@ -2,6 +2,14 @@ import { and, eq } from "drizzle-orm"; import db from "./drizzle"; import { client, userClient } from "./schema"; +export const createClient = async (pubKey: string, userId: number) => { + await db.transaction(async (tx) => { + const insertRes = await tx.insert(client).values({ pubKey }).returning({ id: client.id }); + const { id: clientId } = insertRes[0]!; + await tx.insert(userClient).values({ userId, clientId }); + }); +}; + export const getClientByPubKey = async (pubKey: string) => { const clients = await db.select().from(client).where(eq(client.pubKey, pubKey)).execute(); return clients[0] ?? null; diff --git a/src/lib/server/modules/auth.ts b/src/lib/server/modules/auth.ts index 9ab7420..4d456b1 100644 --- a/src/lib/server/modules/auth.ts +++ b/src/lib/server/modules/auth.ts @@ -1,3 +1,4 @@ +import { error } from "@sveltejs/kit"; import jwt from "jsonwebtoken"; import env from "$lib/server/loadenv"; @@ -36,3 +37,22 @@ export const verifyToken = (token: string) => { return TokenError.INVALID; } }; + +export const authenticate = (request: Request) => { + const accessToken = request.headers.get("Authorization"); + if (!accessToken?.startsWith("Bearer ")) { + error(401, "Token required"); + } + + const tokenData = verifyToken(accessToken.slice(7)); + if (tokenData === TokenError.EXPIRED) { + error(401, "Token expired"); + } else if (tokenData === TokenError.INVALID || tokenData.type !== "access") { + error(401, "Invalid token"); + } + + return { + userId: tokenData.userId, + clientId: tokenData.clientId, + }; +}; diff --git a/src/lib/server/services/key.ts b/src/lib/server/services/key.ts new file mode 100644 index 0000000..4d57e08 --- /dev/null +++ b/src/lib/server/services/key.ts @@ -0,0 +1,10 @@ +import { error } from "@sveltejs/kit"; +import { createClient, getClientByPubKey } from "$lib/server/db/client"; + +export const registerPubKey = async (userId: number, pubKey: string) => { + if (await getClientByPubKey(pubKey)) { + error(409, "Public key already registered"); + } + + await createClient(pubKey, userId); +}; diff --git a/src/routes/(fullscreen)/auth/login/service.ts b/src/routes/(fullscreen)/auth/login/service.ts index f88dbc8..47f8f5b 100644 --- a/src/routes/(fullscreen)/auth/login/service.ts +++ b/src/routes/(fullscreen)/auth/login/service.ts @@ -1,8 +1,7 @@ -import { callAPI } from "$lib/hooks"; import { accessTokenStore } from "$lib/stores"; export const requestLogin = async (email: string, password: string) => { - const res = await callAPI("/api/auth/login", { + const res = await fetch("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json", diff --git a/src/routes/(fullscreen)/key/export/+page.svelte b/src/routes/(fullscreen)/key/export/+page.svelte index 14d2656..73fa06a 100644 --- a/src/routes/(fullscreen)/key/export/+page.svelte +++ b/src/routes/(fullscreen)/key/export/+page.svelte @@ -2,6 +2,7 @@ import { Button, TextButton } from "$lib/components/buttons"; import { BottomDiv } from "$lib/components/divs"; import BeforeContinueModal from "./BeforeContinueModal.svelte"; + import { requestPubKeyRegistration } from "./service"; import IconKey from "~icons/material-symbols/key"; @@ -15,9 +16,15 @@ console.log(data.privKeyBase64); }; - const continueWithoutExport = () => { + const continueWithoutExport = async () => { isBeforeContinueModalOpen = false; + const ok = await requestPubKeyRegistration(data.pubKeyBase64); + if (!ok) { + // TODO + return; + } + // TODO }; diff --git a/src/routes/(fullscreen)/key/export/service.ts b/src/routes/(fullscreen)/key/export/service.ts new file mode 100644 index 0000000..3cef2bc --- /dev/null +++ b/src/routes/(fullscreen)/key/export/service.ts @@ -0,0 +1,12 @@ +import { callAPI } from "$lib/hooks"; + +export const requestPubKeyRegistration = async (pubKeyBase64: string) => { + const res = await callAPI("/api/key/register", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ pubKey: pubKeyBase64 }), + }); + return res.ok; +}; diff --git a/src/routes/api/auth/login/+server.ts b/src/routes/api/auth/login/+server.ts index 65f7623..7a4351b 100644 --- a/src/routes/api/auth/login/+server.ts +++ b/src/routes/api/auth/login/+server.ts @@ -13,7 +13,7 @@ export const POST: RequestHandler = async ({ request, cookies }) => { pubKey: z.string().nonempty().optional(), }) .safeParse(await request.json()); - if (!zodRes.success) error(400, zodRes.error.message); + if (!zodRes.success) error(400, "Invalid request body"); const { email, password, pubKey } = zodRes.data; const { accessToken, refreshToken } = await login(email.trim(), password.trim(), pubKey?.trim()); diff --git a/src/routes/api/key/register/+server.ts b/src/routes/api/key/register/+server.ts new file mode 100644 index 0000000..1c98bb4 --- /dev/null +++ b/src/routes/api/key/register/+server.ts @@ -0,0 +1,22 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authenticate } from "$lib/server/modules/auth"; +import { registerPubKey } from "$lib/server/services/key"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ request }) => { + const zodRes = z + .object({ + pubKey: z.string().base64().nonempty(), + }) + .safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + + const { userId, clientId } = authenticate(request); + if (clientId) { + error(403, "Forbidden"); + } + + await registerPubKey(userId, zodRes.data.pubKey); + return text("Public key registered"); +};