From 52a61297c53200b5aea725ccfcc8496ffe5eba35 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 28 Dec 2024 22:15:46 +0900 Subject: [PATCH] =?UTF-8?q?=EC=95=94=ED=98=B8=20=ED=82=A4=20=EB=82=B4?= =?UTF-8?q?=EB=B3=B4=EB=82=B4=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + pnpm-lock.yaml | 14 ++++++ src/lib/components/BottomSheet.svelte | 31 ++++++++++++ src/lib/components/Modal.svelte | 15 ++---- src/lib/components/index.ts | 1 + src/lib/hooks/gotoStateful.ts | 1 + .../(fullscreen)/auth/login/+page.svelte | 3 +- .../(fullscreen)/key/export/+page.svelte | 50 +++++++++++++------ .../export/BeforeContinueBottomSheet.svelte | 35 +++++++++++++ .../key/export/BeforeContinueModal.svelte | 4 +- src/routes/(fullscreen)/key/export/service.ts | 17 +++++++ .../(fullscreen)/key/generate/+page.svelte | 20 +++++++- src/routes/(fullscreen)/key/generate/+page.ts | 6 +++ 13 files changed, 169 insertions(+), 30 deletions(-) create mode 100644 src/lib/components/BottomSheet.svelte create mode 100644 src/routes/(fullscreen)/key/export/BeforeContinueBottomSheet.svelte create mode 100644 src/routes/(fullscreen)/key/generate/+page.ts diff --git a/package.json b/package.json index b57af77..ca5bc28 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0", "@types/better-sqlite3": "^7.6.11", + "@types/file-saver": "^2.0.7", "@types/jsonwebtoken": "^9.0.7", "@types/ms": "^0.7.34", "@types/node-schedule": "^2.1.7", @@ -33,6 +34,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.36.0", "eslint-plugin-tailwindcss": "^3.17.5", + "file-saver": "^2.0.5", "globals": "^15.0.0", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df5d1cf..b791d45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,6 +49,9 @@ devDependencies: '@types/better-sqlite3': specifier: ^7.6.11 version: 7.6.12 + '@types/file-saver': + specifier: ^2.0.7 + version: 2.0.7 '@types/jsonwebtoken': specifier: ^9.0.7 version: 9.0.7 @@ -79,6 +82,9 @@ devDependencies: eslint-plugin-tailwindcss: specifier: ^3.17.5 version: 3.17.5(tailwindcss@3.4.17) + file-saver: + specifier: ^2.0.5 + version: 2.0.5 globals: specifier: ^15.0.0 version: 15.14.0 @@ -1288,6 +1294,10 @@ packages: resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} dev: true + /@types/file-saver@2.0.7: + resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} + dev: true + /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true @@ -2269,6 +2279,10 @@ packages: flat-cache: 4.0.1 dev: true + /file-saver@2.0.5: + resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} + dev: true + /file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} dev: false diff --git a/src/lib/components/BottomSheet.svelte b/src/lib/components/BottomSheet.svelte new file mode 100644 index 0000000..81e24e1 --- /dev/null +++ b/src/lib/components/BottomSheet.svelte @@ -0,0 +1,31 @@ + + +{#if isOpen} + + +
{ + isOpen = false; + }} + class="fixed inset-0 flex items-end justify-center" + > +
+
e.stopPropagation()} + class="z-10 flex max-h-[70vh] min-h-[30vh] w-full items-stretch rounded-t-2xl bg-white p-4" + transition:fly={{ y: 100, duration: 200 }} + > + {@render children?.()} +
+
+{/if} diff --git a/src/lib/components/Modal.svelte b/src/lib/components/Modal.svelte index 36c1171..37d7466 100644 --- a/src/lib/components/Modal.svelte +++ b/src/lib/components/Modal.svelte @@ -8,24 +8,19 @@ } let { children, isOpen = $bindable() }: Props = $props(); - - const closeModal = () => { - isOpen = false; - }; {#if isOpen}
{ + isOpen = false; + }} class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 px-2" + transition:fade={{ duration: 100 }} > -
e.stopPropagation()} - class="max-w-full rounded-2xl bg-white p-4" - transition:fade={{ duration: 100 }} - > +
e.stopPropagation()} class="max-w-full rounded-2xl bg-white p-4"> {@render children?.()}
diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index 5ddd0fe..55979fb 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -1 +1,2 @@ +export { default as BottomSheet } from "./BottomSheet.svelte"; export { default as Modal } from "./Modal.svelte"; diff --git a/src/lib/hooks/gotoStateful.ts b/src/lib/hooks/gotoStateful.ts index 1a05294..064be29 100644 --- a/src/lib/hooks/gotoStateful.ts +++ b/src/lib/hooks/gotoStateful.ts @@ -3,6 +3,7 @@ import { goto } from "$app/navigation"; type Path = "/key/export"; interface KeyExportState { + redirectPath: string; pubKeyBase64: string; privKeyBase64: string; } diff --git a/src/routes/(fullscreen)/auth/login/+page.svelte b/src/routes/(fullscreen)/auth/login/+page.svelte index ffac02d..46c03fc 100644 --- a/src/routes/(fullscreen)/auth/login/+page.svelte +++ b/src/routes/(fullscreen)/auth/login/+page.svelte @@ -1,5 +1,4 @@ @@ -64,7 +82,9 @@
- + diff --git a/src/routes/(fullscreen)/key/export/BeforeContinueBottomSheet.svelte b/src/routes/(fullscreen)/key/export/BeforeContinueBottomSheet.svelte new file mode 100644 index 0000000..58aa9a5 --- /dev/null +++ b/src/routes/(fullscreen)/key/export/BeforeContinueBottomSheet.svelte @@ -0,0 +1,35 @@ + + + +
+
+

암호 키 파일을 저장하셨나요?

+

+ 보안상의 이유로 지금 시점 이후로는 암호 키를 파일로 내보낼 수 없어요. 파일이 저장되었는지 + 다시 확인해 주세요. +

+
+ +
+
+ +
+
+ +
+
+
+
+
diff --git a/src/routes/(fullscreen)/key/export/BeforeContinueModal.svelte b/src/routes/(fullscreen)/key/export/BeforeContinueModal.svelte index 984f553..3255026 100644 --- a/src/routes/(fullscreen)/key/export/BeforeContinueModal.svelte +++ b/src/routes/(fullscreen)/key/export/BeforeContinueModal.svelte @@ -23,8 +23,10 @@ color="gray" onclick={() => { isOpen = false; - }}>아니요 + 아니요 + diff --git a/src/routes/(fullscreen)/key/export/service.ts b/src/routes/(fullscreen)/key/export/service.ts index 3cef2bc..c45ff50 100644 --- a/src/routes/(fullscreen)/key/export/service.ts +++ b/src/routes/(fullscreen)/key/export/service.ts @@ -1,4 +1,17 @@ import { callAPI } from "$lib/hooks"; +import { storeKeyPairIntoIndexedDB } from "$lib/indexedDB"; + +export const createBlobFromKeyPairBase64 = (pubKeyBase64: string, privKeyBase64: string) => { + const pubKeyFormatted = pubKeyBase64.match(/.{1,64}/g)?.join("\n"); + const privKeyFormatted = privKeyBase64.match(/.{1,64}/g)?.join("\n"); + if (!pubKeyFormatted || !privKeyFormatted) { + throw new Error("Failed to format key pair"); + } + + const pubKeyPem = `-----BEGIN PUBLIC KEY-----\n${pubKeyFormatted}\n-----END PUBLIC KEY-----`; + const privKeyPem = `-----BEGIN PRIVATE KEY-----\n${privKeyFormatted}\n-----END PRIVATE KEY-----`; + return new Blob([`${pubKeyPem}\n${privKeyPem}\n`], { type: "text/plain" }); +}; export const requestPubKeyRegistration = async (pubKeyBase64: string) => { const res = await callAPI("/api/key/register", { @@ -10,3 +23,7 @@ export const requestPubKeyRegistration = async (pubKeyBase64: string) => { }); return res.ok; }; + +export const storeKeyPairPersistently = async (keyPair: CryptoKeyPair) => { + await storeKeyPairIntoIndexedDB(keyPair.publicKey, keyPair.privateKey); +}; diff --git a/src/routes/(fullscreen)/key/generate/+page.svelte b/src/routes/(fullscreen)/key/generate/+page.svelte index af84aa8..3a751b6 100644 --- a/src/routes/(fullscreen)/key/generate/+page.svelte +++ b/src/routes/(fullscreen)/key/generate/+page.svelte @@ -1,12 +1,16 @@ diff --git a/src/routes/(fullscreen)/key/generate/+page.ts b/src/routes/(fullscreen)/key/generate/+page.ts new file mode 100644 index 0000000..626d2e0 --- /dev/null +++ b/src/routes/(fullscreen)/key/generate/+page.ts @@ -0,0 +1,6 @@ +import type { PageLoad } from "./$types"; + +export const load: PageLoad = async ({ url }) => { + const redirectPath = url.searchParams.get("redirect") || "/"; + return { redirectPath }; +};