diff --git a/src/lib/server/modules/mek.ts b/src/lib/server/modules/mek.ts deleted file mode 100644 index 23623f8..0000000 --- a/src/lib/server/modules/mek.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { error } from "@sveltejs/kit"; -import { getUserClientWithDetails } from "$lib/server/db/client"; -import { verifySignature } from "$lib/server/modules/crypto"; - -export const verifyClientEncMekSig = async ( - userId: number, - clientId: number, - version: number, - encMek: string, - encMekSig: string, -) => { - const userClient = await getUserClientWithDetails(userId, clientId); - if (!userClient) { - error(500, "Invalid session id"); - } - - const data = JSON.stringify({ version, key: encMek }); - return verifySignature(Buffer.from(data), encMekSig, userClient.sigPubKey); -}; diff --git a/src/lib/server/schemas/hsk.ts b/src/lib/server/schemas/hsk.ts deleted file mode 100644 index 6f6b428..0000000 --- a/src/lib/server/schemas/hsk.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { z } from "zod"; - -export const hmacSecretListResponse = z.object({ - hsks: z.array( - z.object({ - version: z.number().int().positive(), - state: z.enum(["active"]), - mekVersion: z.number().int().positive(), - hsk: z.string().base64().nonempty(), - }), - ), -}); -export type HmacSecretListResponse = z.output; - -export const initialHmacSecretRegisterRequest = z.object({ - mekVersion: z.number().int().positive(), - hsk: z.string().base64().nonempty(), -}); -export type InitialHmacSecretRegisterRequest = z.input; diff --git a/src/lib/server/schemas/index.ts b/src/lib/server/schemas/index.ts index b2d4fa5..d9ddce7 100644 --- a/src/lib/server/schemas/index.ts +++ b/src/lib/server/schemas/index.ts @@ -2,6 +2,3 @@ export * from "./auth"; export * from "./category"; export * from "./directory"; export * from "./file"; -export * from "./hsk"; -export * from "./mek"; -export * from "./user"; diff --git a/src/lib/server/schemas/mek.ts b/src/lib/server/schemas/mek.ts deleted file mode 100644 index 3d6f468..0000000 --- a/src/lib/server/schemas/mek.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { z } from "zod"; - -export const masterKeyListResponse = z.object({ - meks: z.array( - z.object({ - version: z.number().int().positive(), - state: z.enum(["active", "retired"]), - mek: z.string().base64().nonempty(), - mekSig: z.string().base64().nonempty(), - }), - ), -}); -export type MasterKeyListResponse = z.output; - -export const initialMasterKeyRegisterRequest = z.object({ - mek: z.string().base64().nonempty(), - mekSig: z.string().base64().nonempty(), -}); -export type InitialMasterKeyRegisterRequest = z.input; diff --git a/src/lib/server/schemas/user.ts b/src/lib/server/schemas/user.ts deleted file mode 100644 index 9aec819..0000000 --- a/src/lib/server/schemas/user.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { z } from "zod"; - -export const userInfoResponse = z.object({ - email: z.string().email(), - nickname: z.string().nonempty(), -}); -export type UserInfoResponse = z.output; - -export const nicknameChangeRequest = z.object({ - newNickname: z.string().trim().min(2).max(8), -}); -export type NicknameChangeRequest = z.input; diff --git a/src/lib/server/services/hsk.ts b/src/lib/server/services/hsk.ts deleted file mode 100644 index c381c51..0000000 --- a/src/lib/server/services/hsk.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { error } from "@sveltejs/kit"; -import { IntegrityError } from "$lib/server/db/error"; -import { registerInitialHsk, getAllValidHsks } from "$lib/server/db/hsk"; - -export const getHskList = async (userId: number) => { - const hsks = await getAllValidHsks(userId); - return { - encHsks: hsks.map(({ version, state, mekVersion, encHsk }) => ({ - version, - state, - mekVersion, - encHsk, - })), - }; -}; - -export const registerInitialActiveHsk = async ( - userId: number, - createdBy: number, - mekVersion: number, - encHsk: string, -) => { - try { - await registerInitialHsk(userId, createdBy, mekVersion, encHsk); - } catch (e) { - if (e instanceof IntegrityError && e.message === "HSK already registered") { - error(409, "Initial HSK already registered"); - } - throw e; - } -}; diff --git a/src/lib/server/services/mek.ts b/src/lib/server/services/mek.ts deleted file mode 100644 index 097906a..0000000 --- a/src/lib/server/services/mek.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { error } from "@sveltejs/kit"; -import { setUserClientStateToActive } from "$lib/server/db/client"; -import { IntegrityError } from "$lib/server/db/error"; -import { registerInitialMek, getAllValidClientMeks } from "$lib/server/db/mek"; -import { verifyClientEncMekSig } from "$lib/server/modules/mek"; - -export const getClientMekList = async (userId: number, clientId: number) => { - const clientMeks = await getAllValidClientMeks(userId, clientId); - return { - encMeks: clientMeks.map(({ version, state, encMek, encMekSig }) => ({ - version, - state, - encMek, - encMekSig, - })), - }; -}; - -export const registerInitialActiveMek = async ( - userId: number, - createdBy: number, - encMek: string, - encMekSig: string, -) => { - if (!(await verifyClientEncMekSig(userId, createdBy, 1, encMek, encMekSig))) { - error(400, "Invalid signature"); - } - - try { - await registerInitialMek(userId, createdBy, encMek, encMekSig); - await setUserClientStateToActive(userId, createdBy); - } catch (e) { - if (e instanceof IntegrityError && e.message === "MEK already registered") { - error(409, "Initial MEK already registered"); - } - throw e; - } -}; diff --git a/src/lib/server/services/user.ts b/src/lib/server/services/user.ts deleted file mode 100644 index 5c06458..0000000 --- a/src/lib/server/services/user.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { error } from "@sveltejs/kit"; -import { getUser, setUserNickname } from "$lib/server/db/user"; - -export const getUserInformation = async (userId: number) => { - const user = await getUser(userId); - if (!user) { - error(500, "Invalid session id"); - } - - return { email: user.email, nickname: user.nickname }; -}; - -export const changeNickname = async (userId: number, nickname: string) => { - await setUserNickname(userId, nickname); -}; diff --git a/src/lib/services/key.ts b/src/lib/services/key.ts index 349197b..6149829 100644 --- a/src/lib/services/key.ts +++ b/src/lib/services/key.ts @@ -1,4 +1,4 @@ -import { callGetApi, callPostApi } from "$lib/hooks"; +import { TRPCClientError } from "@trpc/client"; import { storeMasterKeys } from "$lib/indexedDB"; import { encodeToBase64, @@ -9,11 +9,6 @@ import { signMasterKeyWrapped, verifyMasterKeyWrapped, } from "$lib/modules/crypto"; -import type { - InitialHmacSecretRegisterRequest, - MasterKeyListResponse, - InitialMasterKeyRegisterRequest, -} from "$lib/server/schemas"; import { requestSessionUpgrade } from "$lib/services/auth"; import { masterKeyStore, type ClientKeys } from "$lib/stores"; import { useTRPC } from "$trpc/client"; @@ -74,10 +69,16 @@ export const requestClientRegistrationAndSessionUpgrade = async ( }; export const requestMasterKeyDownload = async (decryptKey: CryptoKey, verifyKey: CryptoKey) => { - const res = await callGetApi("/api/mek/list"); - if (!res.ok) return false; + const trpc = useTRPC(); + + let masterKeysWrapped; + try { + masterKeysWrapped = await trpc.mek.list.query(); + } catch { + // TODO: Error Handling + return false; + } - const { meks: masterKeysWrapped }: MasterKeyListResponse = await res.json(); const masterKeys = await Promise.all( masterKeysWrapped.map( async ({ version, state, mek: masterKeyWrapped, mekSig: masterKeyWrappedSig }) => { @@ -109,17 +110,32 @@ export const requestInitialMasterKeyAndHmacSecretRegistration = async ( hmacSecretWrapped: string, signKey: CryptoKey, ) => { - let res = await callPostApi("/api/mek/register/initial", { - mek: masterKeyWrapped, - mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey), - }); - if (!res.ok) { - return res.status === 403 || res.status === 409; + const trpc = useTRPC(); + + try { + await trpc.mek.registerInitial.mutate({ + mek: masterKeyWrapped, + mekSig: await signMasterKeyWrapped(masterKeyWrapped, 1, signKey), + }); + } catch (e) { + if ( + e instanceof TRPCClientError && + (e.data?.code === "FORBIDDEN" || e.data?.code === "CONFLICT") + ) { + return true; + } + // TODO: Error Handling + return false; } - res = await callPostApi("/api/hsk/register/initial", { - mekVersion: 1, - hsk: hmacSecretWrapped, - }); - return res.ok; + try { + await trpc.hsk.registerInitial.mutate({ + mekVersion: 1, + hsk: hmacSecretWrapped, + }); + return true; + } catch { + // TODO: Error Handling + return false; + } }; diff --git a/src/routes/(main)/directory/[[id]]/service.svelte.ts b/src/routes/(main)/directory/[[id]]/service.svelte.ts index ba5fc4a..40394f7 100644 --- a/src/routes/(main)/directory/[[id]]/service.svelte.ts +++ b/src/routes/(main)/directory/[[id]]/service.svelte.ts @@ -1,5 +1,5 @@ import { getContext, setContext } from "svelte"; -import { callGetApi, callPostApi } from "$lib/hooks"; +import { callPostApi } from "$lib/hooks"; import { storeHmacSecrets } from "$lib/indexedDB"; import { generateDataKey, wrapDataKey, unwrapHmacSecret, encryptString } from "$lib/modules/crypto"; import { @@ -13,10 +13,10 @@ import type { DirectoryRenameRequest, DirectoryCreateRequest, FileRenameRequest, - HmacSecretListResponse, DirectoryDeleteResponse, } from "$lib/server/schemas"; import { hmacSecretStore, type MasterKey, type HmacSecret } from "$lib/stores"; +import { useTRPC } from "$trpc/client"; export interface SelectedEntry { type: "directory" | "file"; @@ -40,10 +40,16 @@ export const useContext = () => { export const requestHmacSecretDownload = async (masterKey: CryptoKey) => { // TODO: MEK rotation - const res = await callGetApi("/api/hsk/list"); - if (!res.ok) return false; + const trpc = useTRPC(); + + let hmacSecretsWrapped; + try { + hmacSecretsWrapped = await trpc.hsk.list.query(); + } catch { + // TODO: Error Handling + return false; + } - const { hsks: hmacSecretsWrapped }: HmacSecretListResponse = await res.json(); const hmacSecrets = await Promise.all( hmacSecretsWrapped.map(async ({ version, state, hsk: hmacSecretWrapped }) => { const { hmacSecret } = await unwrapHmacSecret(hmacSecretWrapped, masterKey); diff --git a/src/routes/(main)/menu/+page.ts b/src/routes/(main)/menu/+page.ts index 30a265a..b1582e7 100644 --- a/src/routes/(main)/menu/+page.ts +++ b/src/routes/(main)/menu/+page.ts @@ -1,14 +1,14 @@ import { error } from "@sveltejs/kit"; -import { callGetApi } from "$lib/hooks"; -import type { UserInfoResponse } from "$lib/server/schemas"; +import { useTRPC } from "$trpc/client"; import type { PageLoad } from "./$types"; export const load: PageLoad = async ({ fetch }) => { - const res = await callGetApi("/api/user", fetch); - if (!res.ok) { + const trpc = useTRPC(fetch); + + try { + const { nickname } = await trpc.user.info.query(); + return { nickname }; + } catch { error(500, "Internal server error"); } - - const { nickname }: UserInfoResponse = await res.json(); - return { nickname }; }; diff --git a/src/routes/api/hsk/list/+server.ts b/src/routes/api/hsk/list/+server.ts deleted file mode 100644 index 50957a3..0000000 --- a/src/routes/api/hsk/list/+server.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { json } from "@sveltejs/kit"; -import { authorize } from "$lib/server/modules/auth"; -import { hmacSecretListResponse, type HmacSecretListResponse } from "$lib/server/schemas"; -import { getHskList } from "$lib/server/services/hsk"; -import type { RequestHandler } from "./$types"; - -export const GET: RequestHandler = async ({ locals }) => { - const { userId } = await authorize(locals, "activeClient"); - const { encHsks } = await getHskList(userId); - return json( - hmacSecretListResponse.parse({ - hsks: encHsks.map(({ version, state, mekVersion, encHsk }) => ({ - version, - state, - mekVersion, - hsk: encHsk, - })), - } satisfies HmacSecretListResponse), - ); -}; diff --git a/src/routes/api/hsk/register/initial/+server.ts b/src/routes/api/hsk/register/initial/+server.ts deleted file mode 100644 index 8b32952..0000000 --- a/src/routes/api/hsk/register/initial/+server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { error, text } from "@sveltejs/kit"; -import { authorize } from "$lib/server/modules/auth"; -import { initialHmacSecretRegisterRequest } from "$lib/server/schemas"; -import { registerInitialActiveHsk } from "$lib/server/services/hsk"; -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ locals, request }) => { - const { userId, clientId } = await authorize(locals, "activeClient"); - - const zodRes = initialHmacSecretRegisterRequest.safeParse(await request.json()); - if (!zodRes.success) error(400, "Invalid request body"); - const { mekVersion, hsk } = zodRes.data; - - await registerInitialActiveHsk(userId, clientId, mekVersion, hsk); - return text("HSK registered", { headers: { "Content-Type": "text/plain" } }); -}; diff --git a/src/routes/api/mek/list/+server.ts b/src/routes/api/mek/list/+server.ts deleted file mode 100644 index b3df9fe..0000000 --- a/src/routes/api/mek/list/+server.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { json } from "@sveltejs/kit"; -import { authorize } from "$lib/server/modules/auth"; -import { masterKeyListResponse, type MasterKeyListResponse } from "$lib/server/schemas"; -import { getClientMekList } from "$lib/server/services/mek"; -import type { RequestHandler } from "./$types"; - -export const GET: RequestHandler = async ({ locals }) => { - const { userId, clientId } = await authorize(locals, "activeClient"); - const { encMeks } = await getClientMekList(userId, clientId); - return json( - masterKeyListResponse.parse({ - meks: encMeks.map(({ version, state, encMek, encMekSig }) => ({ - version, - state, - mek: encMek, - mekSig: encMekSig, - })), - } satisfies MasterKeyListResponse), - ); -}; diff --git a/src/routes/api/mek/register/initial/+server.ts b/src/routes/api/mek/register/initial/+server.ts deleted file mode 100644 index bb761e2..0000000 --- a/src/routes/api/mek/register/initial/+server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { error, text } from "@sveltejs/kit"; -import { authorize } from "$lib/server/modules/auth"; -import { initialMasterKeyRegisterRequest } from "$lib/server/schemas"; -import { registerInitialActiveMek } from "$lib/server/services/mek"; -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ locals, request }) => { - const { userId, clientId } = await authorize(locals, "pendingClient"); - - const zodRes = initialMasterKeyRegisterRequest.safeParse(await request.json()); - if (!zodRes.success) error(400, "Invalid request body"); - const { mek, mekSig } = zodRes.data; - - await registerInitialActiveMek(userId, clientId, mek, mekSig); - return text("MEK registered", { headers: { "Content-Type": "text/plain" } }); -}; diff --git a/src/routes/api/user/+server.ts b/src/routes/api/user/+server.ts deleted file mode 100644 index b10b13e..0000000 --- a/src/routes/api/user/+server.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { json } from "@sveltejs/kit"; -import { authorize } from "$lib/server/modules/auth"; -import { userInfoResponse, type UserInfoResponse } from "$lib/server/schemas"; -import { getUserInformation } from "$lib/server/services/user"; -import type { RequestHandler } from "./$types"; - -export const GET: RequestHandler = async ({ locals }) => { - const { userId } = await authorize(locals, "any"); - const { email, nickname } = await getUserInformation(userId); - return json(userInfoResponse.parse({ email, nickname } satisfies UserInfoResponse)); -}; diff --git a/src/routes/api/user/changeNickname/+server.ts b/src/routes/api/user/changeNickname/+server.ts deleted file mode 100644 index ad651ac..0000000 --- a/src/routes/api/user/changeNickname/+server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { error, text } from "@sveltejs/kit"; -import { authorize } from "$lib/server/modules/auth"; -import { nicknameChangeRequest } from "$lib/server/schemas"; -import { changeNickname } from "$lib/server/services/user"; -import type { RequestHandler } from "./$types"; - -export const POST: RequestHandler = async ({ locals, request }) => { - const { userId } = await authorize(locals, "any"); - - const zodRes = nicknameChangeRequest.safeParse(await request.json()); - if (!zodRes.success) error(400, "Invalid request body"); - const { newNickname } = zodRes.data; - - await changeNickname(userId, newNickname); - return text("Nickname changed", { headers: { "Content-Type": "text/plain" } }); -}; diff --git a/src/trpc/router.server.ts b/src/trpc/router.server.ts index fdaee15..3c44bea 100644 --- a/src/trpc/router.server.ts +++ b/src/trpc/router.server.ts @@ -1,10 +1,13 @@ import type { RequestEvent } from "@sveltejs/kit"; import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"; import { createContext, router } from "./init.server"; -import { clientRouter } from "./routers"; +import { clientRouter, hskRouter, mekRouter, userRouter } from "./routers"; export const appRouter = router({ client: clientRouter, + hsk: hskRouter, + mek: mekRouter, + user: userRouter, }); export const createCaller = (event: RequestEvent) => appRouter.createCaller(createContext(event)); diff --git a/src/trpc/routers/hsk.ts b/src/trpc/routers/hsk.ts new file mode 100644 index 0000000..eed9d25 --- /dev/null +++ b/src/trpc/routers/hsk.ts @@ -0,0 +1,41 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { HskRepo, IntegrityError } from "$lib/server/db"; +import { router, roleProcedure } from "../init.server"; + +const hskRouter = router({ + list: roleProcedure["activeClient"].query(async ({ ctx }) => { + const hsks = await HskRepo.getAllValidHsks(ctx.session.userId); + return hsks.map(({ version, state, mekVersion, encHsk }) => ({ + version, + state, + mekVersion, + hsk: encHsk, + })); + }), + + registerInitial: roleProcedure["activeClient"] + .input( + z.object({ + mekVersion: z.number().int().positive(), + hsk: z.string().base64().nonempty(), + }), + ) + .mutation(async ({ ctx, input }) => { + try { + await HskRepo.registerInitialHsk( + ctx.session.userId, + ctx.session.clientId, + input.mekVersion, + input.hsk, + ); + } catch (e) { + if (e instanceof IntegrityError && e.message === "HSK already registered") { + throw new TRPCError({ code: "CONFLICT", message: "Initial HSK already registered" }); + } + throw e; + } + }), +}); + +export default hskRouter; diff --git a/src/trpc/routers/index.ts b/src/trpc/routers/index.ts index 6f13a73..26ac7b2 100644 --- a/src/trpc/routers/index.ts +++ b/src/trpc/routers/index.ts @@ -1 +1,4 @@ export { default as clientRouter } from "./client"; +export { default as hskRouter } from "./hsk"; +export { default as mekRouter } from "./mek"; +export { default as userRouter } from "./user"; diff --git a/src/trpc/routers/mek.ts b/src/trpc/routers/mek.ts new file mode 100644 index 0000000..fa264a5 --- /dev/null +++ b/src/trpc/routers/mek.ts @@ -0,0 +1,63 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { ClientRepo, MekRepo, IntegrityError } from "$lib/server/db"; +import { verifySignature } from "$lib/server/modules/crypto"; +import { router, roleProcedure } from "../init.server"; + +const verifyClientEncMekSig = async ( + userId: number, + clientId: number, + version: number, + encMek: string, + encMekSig: string, +) => { + const userClient = await ClientRepo.getUserClientWithDetails(userId, clientId); + if (!userClient) { + throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Invalid session id" }); + } + + const data = JSON.stringify({ version, key: encMek }); + return verifySignature(Buffer.from(data), encMekSig, userClient.sigPubKey); +}; + +const mekRouter = router({ + list: roleProcedure["activeClient"].query(async ({ ctx }) => { + const clientMeks = await MekRepo.getAllValidClientMeks( + ctx.session.userId, + ctx.session.clientId, + ); + return clientMeks.map(({ version, state, encMek, encMekSig }) => ({ + version, + state, + mek: encMek, + mekSig: encMekSig, + })); + }), + + registerInitial: roleProcedure["pendingClient"] + .input( + z.object({ + mek: z.string().base64().nonempty(), + mekSig: z.string().base64().nonempty(), + }), + ) + .mutation(async ({ ctx, input }) => { + const { userId, clientId } = ctx.session; + const { mek, mekSig } = input; + if (!(await verifyClientEncMekSig(userId, clientId, 1, mek, mekSig))) { + throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid signature" }); + } + + try { + await MekRepo.registerInitialMek(userId, clientId, mek, mekSig); + await ClientRepo.setUserClientStateToActive(userId, clientId); + } catch (e) { + if (e instanceof IntegrityError && e.message === "MEK already registered") { + throw new TRPCError({ code: "CONFLICT", message: "Initial MEK already registered" }); + } + throw e; + } + }), +}); + +export default mekRouter; diff --git a/src/trpc/routers/user.ts b/src/trpc/routers/user.ts new file mode 100644 index 0000000..37b2460 --- /dev/null +++ b/src/trpc/routers/user.ts @@ -0,0 +1,27 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { UserRepo } from "$lib/server/db"; +import { router, roleProcedure } from "../init.server"; + +const userRouter = router({ + info: roleProcedure.any.query(async ({ ctx }) => { + const user = await UserRepo.getUser(ctx.session.userId); + if (!user) { + throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Invalid session id" }); + } + + return { email: user.email, nickname: user.nickname }; + }), + + changeNickname: roleProcedure.any + .input( + z.object({ + newNickname: z.string().trim().min(2).max(8), + }), + ) + .mutation(async ({ ctx, input }) => { + await UserRepo.setUserNickname(ctx.session.userId, input.newNickname); + }), +}); + +export default userRouter;