mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-12 21:08:46 +00:00
/api/mek/list, /api/mek/register Endpoint 구현
This commit is contained in:
@@ -21,6 +21,14 @@ export const createUserClient = async (userId: number, clientId: number) => {
|
||||
await db.insert(userClient).values({ userId, clientId }).execute();
|
||||
};
|
||||
|
||||
export const getAllValidUserClients = async (userId: number) => {
|
||||
return await db
|
||||
.select()
|
||||
.from(userClient)
|
||||
.where(and(eq(userClient.userId, userId), eq(userClient.state, "active")))
|
||||
.execute();
|
||||
};
|
||||
|
||||
export const getUserClient = async (userId: number, clientId: number) => {
|
||||
const userClients = await db
|
||||
.select()
|
||||
|
||||
88
src/lib/server/db/mek.ts
Normal file
88
src/lib/server/db/mek.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { and, or, eq, lt } from "drizzle-orm";
|
||||
import db from "./drizzle";
|
||||
import { mek, clientMek, userClient } from "./schema";
|
||||
|
||||
export interface ClientMek {
|
||||
clientId: number;
|
||||
encMek: string;
|
||||
}
|
||||
|
||||
export const registerActiveMek = async (
|
||||
userId: number,
|
||||
version: number,
|
||||
createdBy: number,
|
||||
clientMeks: ClientMek[],
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
// 1. Check if the clientMeks are valid
|
||||
const userClients = await tx
|
||||
.select()
|
||||
.from(userClient)
|
||||
.where(and(eq(userClient.userId, userId), eq(userClient.state, "active")));
|
||||
if (
|
||||
clientMeks.length !== userClients.length ||
|
||||
!clientMeks.every((clientMek) =>
|
||||
userClients.some((userClient) => userClient.clientId === clientMek.clientId),
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid key list");
|
||||
}
|
||||
|
||||
// 2. Retire the old active MEK and insert the new one
|
||||
await tx
|
||||
.update(mek)
|
||||
.set({
|
||||
state: "retired",
|
||||
retiredAt: new Date(),
|
||||
})
|
||||
.where(and(eq(mek.userId, userId), lt(mek.version, version), eq(mek.state, "active")))
|
||||
.execute();
|
||||
await tx
|
||||
.insert(mek)
|
||||
.values({
|
||||
userId,
|
||||
version,
|
||||
createdBy,
|
||||
createdAt: new Date(),
|
||||
state: "active",
|
||||
})
|
||||
.execute();
|
||||
|
||||
// 3. Insert the new client MEKs
|
||||
await tx
|
||||
.insert(clientMek)
|
||||
.values(
|
||||
clientMeks.map(({ clientId, encMek }) => ({
|
||||
userId,
|
||||
clientId,
|
||||
mekVersion: version,
|
||||
encMek,
|
||||
})),
|
||||
)
|
||||
.execute();
|
||||
});
|
||||
};
|
||||
|
||||
export const getActiveMek = async (userId: number) => {
|
||||
const meks = await db
|
||||
.select()
|
||||
.from(mek)
|
||||
.where(and(eq(mek.userId, userId), eq(mek.state, "active")))
|
||||
.execute();
|
||||
return meks[0] ?? null;
|
||||
};
|
||||
|
||||
export const getAllValidClientMeks = async (userId: number, clientId: number) => {
|
||||
return await db
|
||||
.select()
|
||||
.from(clientMek)
|
||||
.innerJoin(mek, and(eq(clientMek.userId, mek.userId), eq(clientMek.mekVersion, mek.version)))
|
||||
.where(
|
||||
and(
|
||||
eq(clientMek.userId, userId),
|
||||
eq(clientMek.clientId, clientId),
|
||||
or(eq(mek.state, "active"), eq(mek.state, "retired")),
|
||||
),
|
||||
)
|
||||
.execute();
|
||||
};
|
||||
@@ -13,9 +13,8 @@ export const mek = sqliteTable(
|
||||
.notNull()
|
||||
.references(() => client.id),
|
||||
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
||||
state: text("state", { enum: ["pending", "active", "retired", "dead"] })
|
||||
.notNull()
|
||||
.default("pending"),
|
||||
state: text("state", { enum: ["active", "retired", "dead"] }).notNull(),
|
||||
retiredAt: integer("retired_at", { mode: "timestamp_ms" }),
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey({ columns: [t.userId, t.version] }),
|
||||
@@ -42,24 +41,3 @@ export const clientMek = sqliteTable(
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const mekChallenge = sqliteTable(
|
||||
"master_encryption_key_challenge",
|
||||
{
|
||||
userId: integer("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id),
|
||||
mekVersion: integer("master_encryption_key_version").notNull(),
|
||||
answer: text("answer").notNull().unique(), // Base64
|
||||
challenge: text("challenge").unique(), // Base64
|
||||
allowedIp: text("allowed_ip").notNull(),
|
||||
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey({ columns: [t.userId, t.mekVersion] }),
|
||||
ref: foreignKey({
|
||||
columns: [t.userId, t.mekVersion],
|
||||
foreignColumns: [mek.userId, mek.version],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
39
src/lib/server/services/mek.ts
Normal file
39
src/lib/server/services/mek.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { error } from "@sveltejs/kit";
|
||||
import { getAllValidUserClients } from "$lib/server/db/client";
|
||||
import {
|
||||
getAllValidClientMeks,
|
||||
getActiveMek,
|
||||
registerActiveMek,
|
||||
type ClientMek,
|
||||
} from "$lib/server/db/mek";
|
||||
|
||||
export const getClientMekList = async (userId: number, clientId: number) => {
|
||||
const clientMeks = await getAllValidClientMeks(userId, clientId);
|
||||
return {
|
||||
meks: clientMeks.map((clientMek) => ({
|
||||
version: clientMek.master_encryption_key.version,
|
||||
state: clientMek.master_encryption_key.state,
|
||||
mek: clientMek.client_master_encryption_key.encMek,
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
export const registerNewActiveMek = async (
|
||||
userId: number,
|
||||
createdBy: number,
|
||||
clientMeks: ClientMek[],
|
||||
) => {
|
||||
const userClients = await getAllValidUserClients(userId);
|
||||
if (
|
||||
clientMeks.length !== userClients.length ||
|
||||
!clientMeks.every((clientMek) =>
|
||||
userClients.some((userClient) => userClient.clientId === clientMek.clientId),
|
||||
)
|
||||
) {
|
||||
error(400, "Invalid key list");
|
||||
}
|
||||
|
||||
const oldActiveMek = await getActiveMek(userId);
|
||||
const newMekVersion = (oldActiveMek?.version ?? 0) + 1;
|
||||
await registerActiveMek(userId, newMekVersion, createdBy, clientMeks);
|
||||
};
|
||||
14
src/routes/api/mek/list/+server.ts
Normal file
14
src/routes/api/mek/list/+server.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { error, json } from "@sveltejs/kit";
|
||||
import { authenticate } 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 { meks } = await getClientMekList(userId, clientId);
|
||||
return json({ meks });
|
||||
};
|
||||
35
src/routes/api/mek/register/+server.ts
Normal file
35
src/routes/api/mek/register/+server.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { error, text } from "@sveltejs/kit";
|
||||
import { z } from "zod";
|
||||
import { authenticate } from "$lib/server/modules/auth";
|
||||
import { registerNewActiveMek } from "$lib/server/services/mek";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
|
||||
export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
const zodRes = z
|
||||
.object({
|
||||
meks: z.array(
|
||||
z.object({
|
||||
clientId: z.number(),
|
||||
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 { meks } = zodRes.data;
|
||||
await registerNewActiveMek(
|
||||
userId,
|
||||
clientId,
|
||||
meks.map(({ clientId, mek }) => ({
|
||||
clientId,
|
||||
encMek: mek.trim(),
|
||||
})),
|
||||
);
|
||||
return text("MEK registered", { headers: { "Content-Type": "text/plain" } });
|
||||
};
|
||||
Reference in New Issue
Block a user