From 04780d249386216654a2f0f3995922f1772b08bf Mon Sep 17 00:00:00 2001 From: static Date: Mon, 30 Dec 2024 00:37:53 +0900 Subject: [PATCH] =?UTF-8?q?/api/mek/register/initial=20Endpoint=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/client.ts | 23 ++++++---- src/lib/server/db/mek.ts | 44 +++++++++++++++++-- src/lib/server/modules/auth.ts | 35 +++++++++++++++ src/lib/server/modules/mek.ts | 6 +++ src/lib/server/services/client.ts | 9 +--- src/lib/server/services/mek.ts | 22 ++++++++-- src/routes/api/mek/list/+server.ts | 8 +--- src/routes/api/mek/register/+server.ts | 9 ++-- .../api/mek/register/initial/+server.ts | 24 ++++++++++ 9 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 src/lib/server/modules/mek.ts create mode 100644 src/routes/api/mek/register/initial/+server.ts diff --git a/src/lib/server/db/client.ts b/src/lib/server/db/client.ts index bb86671..53218c9 100644 --- a/src/lib/server/db/client.ts +++ b/src/lib/server/db/client.ts @@ -25,15 +25,6 @@ export const getAllUserClients = async (userId: number) => { return await db.select().from(userClient).where(eq(userClient.userId, userId)).execute(); }; -export const countActiveUserClients = async (userId: number) => { - const userClients = await db - .select({ count: count() }) - .from(userClient) - .where(and(eq(userClient.userId, userId), eq(userClient.state, "active"))) - .execute(); - return userClients[0]?.count ?? null; -}; - export const getUserClient = async (userId: number, clientId: number) => { const userClients = await db .select() @@ -57,6 +48,20 @@ export const setUserClientStateToPending = async (userId: number, clientId: numb .execute(); }; +export const setUserClientStateToActive = async (userId: number, clientId: number) => { + await db + .update(userClient) + .set({ state: "active" }) + .where( + and( + eq(userClient.userId, userId), + eq(userClient.clientId, clientId), + eq(userClient.state, "pending"), + ), + ) + .execute(); +}; + export const createUserClientChallenge = async ( userId: number, clientId: number, diff --git a/src/lib/server/db/mek.ts b/src/lib/server/db/mek.ts index 54ca67f..cfe2a52 100644 --- a/src/lib/server/db/mek.ts +++ b/src/lib/server/db/mek.ts @@ -1,4 +1,4 @@ -import { and, or, eq, lt } from "drizzle-orm"; +import { and, or, eq, lt, desc } from "drizzle-orm"; import db from "./drizzle"; import { mek, clientMek, userClient } from "./schema"; @@ -7,6 +7,30 @@ export interface ClientMek { encMek: string; } +export const registerInitialMek = async (userId: number, createdBy: number, encMek: string) => { + await db.transaction(async (tx) => { + await tx + .insert(mek) + .values({ + userId, + version: 1, + createdBy, + createdAt: new Date(), + state: "active", + }) + .execute(); + await tx + .insert(clientMek) + .values({ + userId, + clientId: createdBy, + mekVersion: 1, + encMek, + }) + .execute(); + }); +}; + export const registerActiveMek = async ( userId: number, version: number, @@ -63,15 +87,29 @@ export const registerActiveMek = async ( }); }; -export const getActiveMek = async (userId: number) => { +export const getInitialMek = async (userId: number) => { const meks = await db .select() .from(mek) - .where(and(eq(mek.userId, userId), eq(mek.state, "active"))) + .where(and(eq(mek.userId, userId), eq(mek.version, 1))) .execute(); return meks[0] ?? null; }; +export const getNextActiveMekVersion = async (userId: number) => { + const meks = await db + .select({ version: mek.version }) + .from(mek) + .where(eq(mek.userId, userId)) + .orderBy(desc(mek.version)) + .limit(1) + .execute(); + if (!meks[0]) { + throw new Error("No MEK found"); + } + return meks[0].version + 1; +}; + export const getAllValidClientMeks = async (userId: number, clientId: number) => { return await db .select() diff --git a/src/lib/server/modules/auth.ts b/src/lib/server/modules/auth.ts index 8276d6d..f68df5c 100644 --- a/src/lib/server/modules/auth.ts +++ b/src/lib/server/modules/auth.ts @@ -1,5 +1,6 @@ import { error, type Cookies } from "@sveltejs/kit"; import jwt from "jsonwebtoken"; +import { getUserClient } from "$lib/server/db/client"; import env from "$lib/server/loadenv"; type TokenPayload = @@ -18,6 +19,8 @@ export enum TokenError { INVALID, } +type Permission = "pendingClient" | "activeClient"; + export const issueToken = (payload: TokenPayload) => { return jwt.sign(payload, env.jwt.secret, { expiresIn: payload.type === "access" ? env.jwt.accessExp : env.jwt.refreshExp, @@ -53,3 +56,35 @@ export const authenticate = (cookies: Cookies) => { clientId: tokenPayload.clientId, }; }; + +export async function authorize( + cookies: Cookies, + requiredPermission: "pendingClient", +): Promise<{ userId: number; clientId: number }>; + +export async function authorize( + cookies: Cookies, + requiredPermission: "activeClient", +): Promise<{ userId: number; clientId: number }>; + +export async function authorize( + cookies: Cookies, + requiredPermission: Permission, +): Promise<{ userId: number; clientId?: number }> { + const tokenPayload = authenticate(cookies); + const { userId, clientId } = tokenPayload; + const userClient = clientId ? await getUserClient(userId, clientId) : undefined; + + switch (requiredPermission) { + case "pendingClient": + if (!userClient || userClient.state !== "pending") { + error(403, "Forbidden"); + } + return tokenPayload; + case "activeClient": + if (!userClient || userClient.state !== "active") { + error(403, "Forbidden"); + } + return tokenPayload; + } +} diff --git a/src/lib/server/modules/mek.ts b/src/lib/server/modules/mek.ts new file mode 100644 index 0000000..940774a --- /dev/null +++ b/src/lib/server/modules/mek.ts @@ -0,0 +1,6 @@ +import { getInitialMek } from "$lib/server/db/mek"; + +export const isInitialMekNeeded = async (userId: number) => { + const initialMek = await getInitialMek(userId); + return !initialMek; +}; diff --git a/src/lib/server/services/client.ts b/src/lib/server/services/client.ts index 8f59529..a3b9605 100644 --- a/src/lib/server/services/client.ts +++ b/src/lib/server/services/client.ts @@ -7,13 +7,12 @@ import { getClientByPubKey, createUserClient, getAllUserClients, - countActiveUserClients, getUserClient, createUserClientChallenge, getUserClientChallenge, setUserClientStateToPending, } from "$lib/server/db/client"; -import { getActiveMek } from "$lib/server/db/mek"; +import { isInitialMekNeeded } from "$lib/server/modules/mek"; import env from "$lib/server/loadenv"; export const getUserClientList = async (userId: number) => { @@ -73,13 +72,9 @@ export const getUserClientStatus = async (userId: number, clientId: number) => { error(500, "Invalid access token"); } - const activeMek = await getActiveMek(userId); - const activeUserClientCount = await countActiveUserClients(userId); - const isInitialMekNeeded = !activeMek && activeUserClientCount === 0; - return { state: userClient.state, - isInitialMekNeeded, + isInitialMekNeeded: await isInitialMekNeeded(userId), }; }; diff --git a/src/lib/server/services/mek.ts b/src/lib/server/services/mek.ts index 71a5b33..5ed39ed 100644 --- a/src/lib/server/services/mek.ts +++ b/src/lib/server/services/mek.ts @@ -1,11 +1,13 @@ import { error } from "@sveltejs/kit"; -import { getAllUserClients } from "$lib/server/db/client"; +import { getAllUserClients, setUserClientStateToActive } from "$lib/server/db/client"; import { getAllValidClientMeks, - getActiveMek, + registerInitialMek, registerActiveMek, + getNextActiveMekVersion, type ClientMek, } from "$lib/server/db/mek"; +import { isInitialMekNeeded } from "$lib/server/modules/mek"; export const getClientMekList = async (userId: number, clientId: number) => { const clientMeks = await getAllValidClientMeks(userId, clientId); @@ -18,6 +20,19 @@ export const getClientMekList = async (userId: number, clientId: number) => { }; }; +export const registerInitialActiveMek = async ( + userId: number, + createdBy: number, + encMek: string, +) => { + if (!(await isInitialMekNeeded(userId))) { + error(403, "Forbidden"); + } + + await registerInitialMek(userId, createdBy, encMek); + await setUserClientStateToActive(userId, createdBy); +}; + export const registerNewActiveMek = async ( userId: number, createdBy: number, @@ -34,7 +49,6 @@ export const registerNewActiveMek = async ( error(400, "Invalid key list"); } - const oldActiveMek = await getActiveMek(userId); - const newMekVersion = (oldActiveMek?.version ?? 0) + 1; + const newMekVersion = await getNextActiveMekVersion(userId); await registerActiveMek(userId, newMekVersion, createdBy, clientMeks); }; diff --git a/src/routes/api/mek/list/+server.ts b/src/routes/api/mek/list/+server.ts index 35184e9..ed0f9bf 100644 --- a/src/routes/api/mek/list/+server.ts +++ b/src/routes/api/mek/list/+server.ts @@ -1,14 +1,10 @@ import { error, json } from "@sveltejs/kit"; -import { authenticate } from "$lib/server/modules/auth"; +import { authorize } from "$lib/server/modules/auth"; import { getClientMekList } from "$lib/server/services/mek"; import type { RequestHandler } from "@sveltejs/kit"; export const GET: RequestHandler = async ({ cookies }) => { - const { userId, clientId } = authenticate(cookies); - if (!clientId) { - error(403, "Forbidden"); - } - + const { userId, clientId } = await authorize(cookies, "activeClient"); const { meks } = await getClientMekList(userId, clientId); return json({ meks }); }; diff --git a/src/routes/api/mek/register/+server.ts b/src/routes/api/mek/register/+server.ts index 21ed7b2..0fe22e2 100644 --- a/src/routes/api/mek/register/+server.ts +++ b/src/routes/api/mek/register/+server.ts @@ -1,6 +1,6 @@ import { error, text } from "@sveltejs/kit"; import { z } from "zod"; -import { authenticate } from "$lib/server/modules/auth"; +import { authorize } from "$lib/server/modules/auth"; import { registerNewActiveMek } from "$lib/server/services/mek"; import type { RequestHandler } from "@sveltejs/kit"; @@ -17,11 +17,7 @@ export const POST: RequestHandler = async ({ request, cookies }) => { .safeParse(await request.json()); if (!zodRes.success) error(400, "Invalid request body"); - const { userId, clientId } = authenticate(cookies); - if (!clientId) { - error(403, "Forbidden"); - } - + const { userId, clientId } = await authorize(cookies, "activeClient"); const { meks } = zodRes.data; await registerNewActiveMek( userId, @@ -31,5 +27,6 @@ export const POST: RequestHandler = async ({ request, cookies }) => { encMek: mek.trim(), })), ); + return text("MEK registered", { headers: { "Content-Type": "text/plain" } }); }; diff --git a/src/routes/api/mek/register/initial/+server.ts b/src/routes/api/mek/register/initial/+server.ts new file mode 100644 index 0000000..9c44227 --- /dev/null +++ b/src/routes/api/mek/register/initial/+server.ts @@ -0,0 +1,24 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authenticate } from "$lib/server/modules/auth"; +import { registerInitialActiveMek } from "$lib/server/services/mek"; +import type { RequestHandler } from "@sveltejs/kit"; + +export const POST: RequestHandler = async ({ request, cookies }) => { + const zodRes = z + .object({ + mek: z.string().base64().nonempty(), + }) + .safeParse(await request.json()); + if (!zodRes.success) error(400, "Invalid request body"); + + const { userId, clientId } = authenticate(cookies); + if (!clientId) { + error(403, "Forbidden"); + } + + const { mek } = zodRes.data; + await registerInitialActiveMek(userId, clientId, mek); + + return text("MEK registered", { headers: { "Content-Type": "text/plain" } }); +};