프론트엔드에서 세션 ID 기반 인증 대응 및 DB 마이그레이션 스크립트 재생성

This commit is contained in:
static
2025-01-12 08:31:11 +09:00
parent be8587694e
commit 85ebb529ba
14 changed files with 141 additions and 155 deletions

View File

@@ -1,35 +1,11 @@
export const refreshToken = async (fetchInternal = fetch) => {
return await fetchInternal("/api/auth/refreshToken", { method: "POST" });
export const callGetApi = async (input: RequestInfo, fetchInternal = fetch) => {
return await fetchInternal(input);
};
const callApi = async (input: RequestInfo, init?: RequestInit, fetchInternal = fetch) => {
let res = await fetchInternal(input, init);
if (res.status === 401) {
res = await refreshToken();
if (!res.ok) {
return res;
}
res = await fetchInternal(input, init);
}
return res;
};
export const callGetApi = async (input: RequestInfo, fetchInternal?: typeof fetch) => {
return await callApi(input, undefined, fetchInternal);
};
export const callPostApi = async <T>(
input: RequestInfo,
payload?: T,
fetchInternal?: typeof fetch,
) => {
return await callApi(
input,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: payload ? JSON.stringify(payload) : undefined,
},
fetchInternal,
);
export const callPostApi = async <T>(input: RequestInfo, payload?: T, fetchInternal = fetch) => {
return await fetchInternal(input, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: payload ? JSON.stringify(payload) : undefined,
});
};

View File

@@ -1,11 +1,9 @@
import { error, redirect, type Handle } from "@sveltejs/kit";
import { authenticate, AuthenticationError } from "$lib/server/modules/auth";
const whitelist = ["/auth/login", "/api/auth/login"];
export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
const { pathname, search } = event.url;
if (whitelist.some((path) => pathname.startsWith(path))) {
if (pathname === "/api/auth/login") {
return await resolve(event);
}
@@ -19,7 +17,9 @@ export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
event.locals.session = await authenticate(sessionIdSigned, ip, userAgent);
} catch (e) {
if (e instanceof AuthenticationError) {
if (pathname.startsWith("/api")) {
if (pathname === "/auth/login") {
return await resolve(event);
} else if (pathname.startsWith("/api")) {
error(e.status, e.message);
} else {
redirect(302, "/auth/login?redirect=" + encodeURIComponent(pathname + search));

View File

@@ -17,7 +17,7 @@ export const verifyClientEncMekSig = async (
) => {
const userClient = await getUserClientWithDetails(userId, clientId);
if (!userClient) {
error(500, "Invalid access token");
error(500, "Invalid session id");
}
const data = JSON.stringify({ version, key: encMek });

View File

@@ -98,7 +98,7 @@ export const verifyUserClient = async (
export const getUserClientStatus = async (userId: number, clientId: number) => {
const userClient = await getUserClient(userId, clientId);
if (!userClient) {
error(500, "Invalid access token");
error(500, "Invalid session id");
}
return {

View File

@@ -1,37 +1,30 @@
import { callPostApi } from "$lib/hooks";
import { encodeToBase64, decryptChallenge, signMessage } from "$lib/modules/crypto";
import type {
TokenUpgradeRequest,
TokenUpgradeResponse,
TokenUpgradeVerifyRequest,
SessionUpgradeRequest,
SessionUpgradeResponse,
SessionUpgradeVerifyRequest,
} from "$lib/server/schemas";
export const requestTokenUpgrade = async (
export const requestSessionUpgrade = async (
encryptKeyBase64: string,
decryptKey: CryptoKey,
verifyKeyBase64: string,
signKey: CryptoKey,
) => {
let res = await fetch("/api/auth/upgradeToken", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
encPubKey: encryptKeyBase64,
sigPubKey: verifyKeyBase64,
} satisfies TokenUpgradeRequest),
let res = await callPostApi<SessionUpgradeRequest>("/api/auth/upgradeSession", {
encPubKey: encryptKeyBase64,
sigPubKey: verifyKeyBase64,
});
if (!res.ok) return false;
const { challenge }: TokenUpgradeResponse = await res.json();
const { challenge }: SessionUpgradeResponse = await res.json();
const answer = await decryptChallenge(challenge, decryptKey);
const answerSig = await signMessage(answer, signKey);
res = await fetch("/api/auth/upgradeToken/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
answer: encodeToBase64(answer),
answerSig: encodeToBase64(answerSig),
} satisfies TokenUpgradeVerifyRequest),
res = await callPostApi<SessionUpgradeVerifyRequest>("/api/auth/upgradeSession/verify", {
answer: encodeToBase64(answer),
answerSig: encodeToBase64(answerSig),
});
return res.ok;
};

View File

@@ -1,11 +1,10 @@
import { redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ url, cookies }) => {
export const load: PageServerLoad = async ({ locals, url }) => {
const redirectPath = url.searchParams.get("redirect") || "/home";
const accessToken = cookies.get("accessToken");
if (accessToken) {
if (locals.session) {
redirect(302, redirectPath);
}

View File

@@ -1,12 +1,10 @@
<script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import { Button, TextButton } from "$lib/components/buttons";
import { TitleDiv, BottomDiv } from "$lib/components/divs";
import { TextInput } from "$lib/components/inputs";
import { refreshToken } from "$lib/hooks";
import { clientKeyStore, masterKeyStore } from "$lib/stores";
import { requestLogin, requestTokenUpgrade, requestMasterKeyDownload } from "./service";
import { requestLogin, requestSessionUpgrade, requestMasterKeyDownload } from "./service";
let { data } = $props();
@@ -25,7 +23,8 @@
if (!$clientKeyStore) return await redirect("/key/generate");
if (!(await requestTokenUpgrade($clientKeyStore))) throw new Error("Failed to upgrade token");
if (!(await requestSessionUpgrade($clientKeyStore)))
throw new Error("Failed to upgrade session");
// TODO: Multi-user support
@@ -42,13 +41,6 @@
throw e;
}
};
onMount(async () => {
const res = await refreshToken();
if (res.ok) {
await goto(data.redirectPath, { replaceState: true });
}
});
</script>
<svelte:head>

View File

@@ -1,21 +1,18 @@
import { callPostApi } from "$lib/hooks";
import { exportRSAKeyToBase64 } from "$lib/modules/crypto";
import type { LoginRequest } from "$lib/server/schemas";
import { requestTokenUpgrade as requestTokenUpgradeInternal } from "$lib/services/auth";
import { requestSessionUpgrade as requestSessionUpgradeInternal } from "$lib/services/auth";
import { requestClientRegistration } from "$lib/services/key";
import type { ClientKeys } from "$lib/stores";
export { requestMasterKeyDownload } from "$lib/services/key";
export const requestLogin = async (email: string, password: string) => {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password } satisfies LoginRequest),
});
const res = await callPostApi<LoginRequest>("/api/auth/login", { email, password });
return res.ok;
};
export const requestTokenUpgrade = async ({
export const requestSessionUpgrade = async ({
encryptKey,
decryptKey,
signKey,
@@ -23,12 +20,12 @@ export const requestTokenUpgrade = async ({
}: ClientKeys) => {
const encryptKeyBase64 = await exportRSAKeyToBase64(encryptKey);
const verifyKeyBase64 = await exportRSAKeyToBase64(verifyKey);
if (await requestTokenUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) {
if (await requestSessionUpgradeInternal(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) {
return true;
}
if (await requestClientRegistration(encryptKeyBase64, decryptKey, verifyKeyBase64, signKey)) {
return await requestTokenUpgradeInternal(
return await requestSessionUpgradeInternal(
encryptKeyBase64,
decryptKey,
verifyKeyBase64,

View File

@@ -10,7 +10,7 @@
serializeClientKeys,
requestClientRegistration,
storeClientKeys,
requestTokenUpgrade,
requestSessionUpgrade,
requestInitialMasterKeyRegistration,
} from "./service";
@@ -59,14 +59,14 @@
await storeClientKeys($clientKeyStore);
if (
!(await requestTokenUpgrade(
!(await requestSessionUpgrade(
data.encryptKeyBase64,
$clientKeyStore.decryptKey,
data.verifyKeyBase64,
$clientKeyStore.signKey,
))
)
throw new Error("Failed to upgrade token");
throw new Error("Failed to upgrade session");
if (
!(await requestInitialMasterKeyRegistration(data.masterKeyWrapped, $clientKeyStore.signKey))

View File

@@ -4,7 +4,7 @@ import { signMasterKeyWrapped } from "$lib/modules/crypto";
import type { InitialMasterKeyRegisterRequest } from "$lib/server/schemas";
import type { ClientKeys } from "$lib/stores";
export { requestTokenUpgrade } from "$lib/services/auth";
export { requestSessionUpgrade } from "$lib/services/auth";
export { requestClientRegistration } from "$lib/services/key";
type SerializedKeyPairs = {