From b3ec8d2894bf93ab74639840084065ebc499482e Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:10:11 +0800 Subject: [PATCH] [+] Accounts and login --- src/components/status/Dialog.svelte | 7 ++-- src/components/status/ErrorDialog.svelte | 2 +- src/lib/client.ts | 7 +++- src/lib/server/user.ts | 30 ++++++++++++++ src/routes/+page.svelte | 3 +- src/routes/api/auth/login/+server.ts | 21 ++++++++++ src/routes/api/user/sync-code/+server.ts | 11 ++++++ src/routes/user/+page.svelte | 50 ++++++++++++++++++------ 8 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 src/routes/api/auth/login/+server.ts create mode 100644 src/routes/api/user/sync-code/+server.ts diff --git a/src/components/status/Dialog.svelte b/src/components/status/Dialog.svelte index 5e81c6b..8024eba 100644 --- a/src/components/status/Dialog.svelte +++ b/src/components/status/Dialog.svelte @@ -9,12 +9,13 @@ text: string, onclick: () => void }[] - open: boolean + open: boolean, + noClose?: boolean } = $props() - let buttons = $derived(p.buttons ?? [{ + let buttons = $derived([...(p.buttons ?? []), ...(p.noClose ? [] : [{ text: '关闭', onclick: () => open = false - }]) + }])]) {#if open} diff --git a/src/components/status/ErrorDialog.svelte b/src/components/status/ErrorDialog.svelte index 122ccc5..8d8af08 100644 --- a/src/components/status/ErrorDialog.svelte +++ b/src/components/status/ErrorDialog.svelte @@ -11,7 +11,7 @@ location.reload() -}]}> +}]} noClose>
{p.error}
diff --git a/src/lib/client.ts b/src/lib/client.ts index d9319f1..a3e4c54 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -9,7 +9,7 @@ export async function post(endpoint: string, data: any) { 'Content-Type': 'application/json' } }).then(async res => { - if (res.status >= 400) throw new Error(`错误 ${res.status}:${await res.text()}`) + if (res.status >= 400) throw new Error(`${res.status}: ${await res.json().then(json => json['message'])}`) return res.json() }).catch(e => { console.error(e) @@ -25,5 +25,10 @@ export const API = { netease: { startImport: async (link: string) => await post('/api/import/netease/start', { link }), checkProgress: async (id: string) => await post('/api/import/netease/progress', { id }) + }, + + user: { + createSyncCode: async () => await post('/api/user/sync-code', {}), + loginWithSyncCode: async (code: string) => await post('/api/auth/login', { code }) } } diff --git a/src/lib/server/user.ts b/src/lib/server/user.ts index 02aa1df..eac6a45 100644 --- a/src/lib/server/user.ts +++ b/src/lib/server/user.ts @@ -52,4 +52,34 @@ export async function createSyncCode(session: string): Promise { export async function updateUserData(user: UserDocument, data: Partial): Promise { const newData = { ...(user.data || {}), ...data } await users.updateOne({ _id: user._id }, { $set: { data: newData } }) +} + +/** + * Login with sync code. + * @param code Sync code + * @param newUA User Agent of the new device + * @returns New session token + */ +export async function loginWithSyncCode(code: string, newUA: string): Promise { + const user = await users.findOne({ syncCode: code }) + if (!user) throw error(401, 'Invalid sync code') + + // Check expiration (7 days) + if (user.syncCodeCreated && (Date.now() - user.syncCodeCreated.getTime() > 7 * 24 * 60 * 60 * 1000)) { + await users.updateOne({ _id: user._id }, { $unset: { syncCode: "", syncCodeCreated: "" } }) + throw error(401, 'Sync code expired') + } + + const ses = `${crypto.randomUUID()}-${Date.now().toString(36)}` + + // Add new session and clear sync code (one-time use) + await users.updateOne( + { _id: user._id }, + { + $push: { sessions: ses }, + $unset: { syncCode: "", syncCodeCreated: "" } + } + ) + + return ses } \ No newline at end of file diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 3235dfe..87f4fd9 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -5,6 +5,7 @@ import type { PageProps } from "./$types"; import Button from "../components/Button.svelte"; import { Layer } from "m3-svelte"; + import { goto } from "$app/navigation"; let { data }: PageProps = $props() @@ -15,7 +16,7 @@ - alert('Account clicked')} right={[ + goto('/user')} right={[ {icon: "i-material-symbols:settings-rounded", onclick: () => alert('Settings clicked')} ]} /> diff --git a/src/routes/api/auth/login/+server.ts b/src/routes/api/auth/login/+server.ts new file mode 100644 index 0000000..50402b7 --- /dev/null +++ b/src/routes/api/auth/login/+server.ts @@ -0,0 +1,21 @@ +import { error, json } from '@sveltejs/kit' +import { loginWithSyncCode } from '$lib/server/user' +import type { RequestHandler } from './$types' + +export const POST: RequestHandler = async ({ request, cookies }) => { + const { code } = await request.json() + if (!code) throw error(400, 'Missing sync code') + + const ua = request.headers.get('user-agent') || 'unknown' + const session = await loginWithSyncCode(code, ua) + + // Set session cookie + cookies.set('session', session, { + path: '/', + httpOnly: true, + sameSite: 'strict', + maxAge: 60 * 60 * 24 * 365 // 1 year + }) + + return json({ success: true }) +} diff --git a/src/routes/api/user/sync-code/+server.ts b/src/routes/api/user/sync-code/+server.ts new file mode 100644 index 0000000..b95de96 --- /dev/null +++ b/src/routes/api/user/sync-code/+server.ts @@ -0,0 +1,11 @@ +import { error, json } from '@sveltejs/kit' +import { createSyncCode } from '$lib/server/user' +import type { RequestHandler } from './$types' + +export const POST: RequestHandler = async ({ cookies }) => { + const session = cookies.get('session') + if (!session) throw error(401, 'Unauthorized') + + const code = await createSyncCode(session) + return json({ code }) +} diff --git a/src/routes/user/+page.svelte b/src/routes/user/+page.svelte index 93a3f1d..416b878 100644 --- a/src/routes/user/+page.svelte +++ b/src/routes/user/+page.svelte @@ -1,17 +1,43 @@ -引继码生成成功!生成的引继码是:01234-56789-ABCDE-F0123 + + + location.href = '/' +}]}> + 登录成功! + + + navigator.clipboard.writeText(generatedCode) +}]}> + 引继码生成成功!生成的引继码是:{generatedCode}

-这个引继码将会在使用之后、或者未使用的 7 天后会失效
+ 这个引继码将会在使用之后、或者未使用的 7 天后会失效 +
@@ -30,15 +56,13 @@ {/if} -
- -
+
{#if !loginMode} - - + + {:else} - + {/if}