diff --git a/package-lock.json b/package-lock.json index c79bf87..cc1f611 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@amap/amap-jsapi-loader": "^1.0.1", "@vercel/speed-insights": "^2.0.0", + "better-auth": "^1.7.6", "naive-ui": "^2.44.1", "pinia": "^3.0.4", "vue": "^3.5.34", @@ -81,6 +82,135 @@ "node": ">=6.9.0" } }, + "node_modules/@better-auth/core": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@better-auth/core/-/core-1.7.6.tgz", + "integrity": "sha512-yBDDO0J4VCHT1qEZtzj6aMexX4etyLesitoxehcWHzPrzYqrFHwe/hgskye+imlGhQVOysuljMg8M8BA6GrtIg==", + "license": "MIT", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.41.1", + "@standard-schema/spec": "^1.1.0", + "zod": "^4.5.4" + }, + "peerDependencies": { + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.2", + "@opentelemetry/api": "^1.9.0", + "better-call": "1.4.0", + "jose": "^6.1.0", + "kysely": "^0.28.5 || ^0.29.0", + "nanostores": "^1.0.1" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@better-auth/drizzle-adapter": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@better-auth/drizzle-adapter/-/drizzle-adapter-1.7.6.tgz", + "integrity": "sha512-ENlhwC7kkTjquuiel3Efggik0hC4vtD7ppJ8FhsiHhRJA8K6RT4hbhzkfm6keNvjdnAeWwCejm7FmMO4GoSGpA==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.6", + "@better-auth/utils": "0.4.2", + "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" + }, + "peerDependenciesMeta": { + "drizzle-orm": { + "optional": true + } + } + }, + "node_modules/@better-auth/kysely-adapter": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@better-auth/kysely-adapter/-/kysely-adapter-1.7.6.tgz", + "integrity": "sha512-BV+DX2/z/6ozNC9DYSge78wjLerq6Bxd/lSPjzzuq9eEutkQDAJTuiaETzgGYqNV5LlEWi8TZFAQTEq/wavOEQ==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.6", + "@better-auth/utils": "0.4.2", + "kysely": "^0.28.17 || ^0.29.0" + }, + "peerDependenciesMeta": { + "kysely": { + "optional": true + } + } + }, + "node_modules/@better-auth/memory-adapter": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@better-auth/memory-adapter/-/memory-adapter-1.7.6.tgz", + "integrity": "sha512-ng1xv8k2JsEIjnqzJzdoVOQrnoCLRuk4EiQL9yYAThyg6zYzV00LynlhH+O9OMUZ+yGVpGqGZmr5xcTiUSV2uA==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.6", + "@better-auth/utils": "0.4.2" + } + }, + "node_modules/@better-auth/mongo-adapter": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@better-auth/mongo-adapter/-/mongo-adapter-1.7.6.tgz", + "integrity": "sha512-GvBwZli8i4XINMAj9tlW7eV9E8Dr+JWqavXjUs2o81GZt/Vh8wiX+31KK1jQ4iOuejfjpLoXcoFtlIPgf/pVog==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.6", + "@better-auth/utils": "0.4.2", + "mongodb": "^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "mongodb": { + "optional": true + } + } + }, + "node_modules/@better-auth/prisma-adapter": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@better-auth/prisma-adapter/-/prisma-adapter-1.7.6.tgz", + "integrity": "sha512-31rz2OXIx2Q1L4Jhd5F71dafIWqder/Bo6vJ+eCgO5iZ0gvnv7fUmM95DvbVUoPC69XYpcIM/6PJmQ3M6RydWg==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.6", + "@better-auth/utils": "0.4.2", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@prisma/client": { + "optional": true + }, + "prisma": { + "optional": true + } + } + }, + "node_modules/@better-auth/telemetry": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@better-auth/telemetry/-/telemetry-1.7.6.tgz", + "integrity": "sha512-xgRwPja2wTJtcu+OQZR2IiNpAmviddnW3n5Do5umXRugJFW3v9KlSmYjYm580mVsPnU4upvZkvSRl9v8DgSDtA==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.6", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.2" + } + }, + "node_modules/@better-auth/utils": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/@better-auth/utils/-/utils-0.4.2.tgz", + "integrity": "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1" + } + }, + "node_modules/@better-fetch/fetch": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/@better-fetch/fetch/-/fetch-1.3.2.tgz", + "integrity": "sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow==", + "license": "MIT" + }, "node_modules/@css-render/plugin-bem": { "version": "0.15.14", "resolved": "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz", @@ -213,6 +343,39 @@ "@emnapi/runtime": "^1.7.1" } }, + "node_modules/@noble/ciphers": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/@noble/ciphers/-/ciphers-2.4.0.tgz", + "integrity": "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmmirror.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@oxc-project/types": { "version": "0.133.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", @@ -505,6 +668,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", @@ -1189,6 +1358,136 @@ "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", "license": "MIT" }, + "node_modules/better-auth": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/better-auth/-/better-auth-1.7.6.tgz", + "integrity": "sha512-WTMqOpmTTj+oBoX7zzPOzL85342Upyf+/v0IkH1kvGRA85lV4JGRFIYMIRKqvjZ6ShsuL5VX/c9C13jtoZXcKA==", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.7.6", + "@better-auth/drizzle-adapter": "1.7.6", + "@better-auth/kysely-adapter": "1.7.6", + "@better-auth/memory-adapter": "1.7.6", + "@better-auth/mongo-adapter": "1.7.6", + "@better-auth/prisma-adapter": "1.7.6", + "@better-auth/telemetry": "1.7.6", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.2", + "@noble/ciphers": "^2.2.0", + "@noble/hashes": "^2.2.0", + "better-call": "1.4.0", + "defu": "^6.1.4", + "jose": "^6.2.3", + "kysely": "^0.28.17 || ^0.29.0", + "nanostores": "^1.3.0", + "zod": "^4.5.4" + }, + "peerDependencies": { + "@lynx-js/react": "*", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "@sveltejs/kit": "^2.0.0", + "@tanstack/react-start": "^1.0.0", + "@tanstack/solid-start": "^1.0.0", + "drizzle-kit": ">=0.31.4 || >=1.0.0-beta.1", + "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0", + "mongodb": "^6.0.0 || ^7.0.0", + "mysql2": "^3.0.0", + "next": "^14.0.0 || ^15.0.0 || ^16.0.0", + "pg": "^8.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "solid-js": "^1.0.0", + "svelte": "^4.0.0 || ^5.0.0", + "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@lynx-js/react": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "@tanstack/react-start": { + "optional": true + }, + "@tanstack/solid-start": { + "optional": true + }, + "drizzle-kit": { + "optional": true + }, + "drizzle-orm": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "next": { + "optional": true + }, + "pg": { + "optional": true + }, + "prisma": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vitest": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/better-call": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/better-call/-/better-call-1.4.0.tgz", + "integrity": "sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA==", + "license": "MIT", + "dependencies": { + "@better-auth/utils": "^0.5.0", + "@better-fetch/fetch": "^1.3.1", + "rou3": "^0.9.1", + "set-cookie-parser": "^3.1.2" + }, + "peerDependencies": { + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/better-call/node_modules/@better-auth/utils": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/@better-auth/utils/-/utils-0.5.0.tgz", + "integrity": "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1" + } + }, "node_modules/birpc": { "version": "2.9.0", "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", @@ -1254,6 +1553,12 @@ "date-fns": "^3.0.0 || ^4.0.0" } }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1379,6 +1684,24 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmmirror.com/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/kysely": { + "version": "0.29.6", + "resolved": "https://registry.npmmirror.com/kysely/-/kysely-0.29.6.tgz", + "integrity": "sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg==", + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz", @@ -1736,6 +2059,21 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nanostores": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/nanostores/-/nanostores-1.5.4.tgz", + "integrity": "sha512-4UXY7OAJEK0Y6V3SudBU7cXjAzTDc189R78dDOh0Xbm19gpKYto6WizSg58LJf12lTsRxKSLGyE117OiLpM4DQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", @@ -1857,12 +2195,24 @@ "@rolldown/binding-win32-x64-msvc": "1.0.3" } }, + "node_modules/rou3": { + "version": "0.9.2", + "resolved": "https://registry.npmmirror.com/rou3/-/rou3-0.9.2.tgz", + "integrity": "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==", + "license": "MIT" + }, "node_modules/seemly": { "version": "0.3.10", "resolved": "https://registry.npmmirror.com/seemly/-/seemly-0.3.10.tgz", "integrity": "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==", "license": "MIT" }, + "node_modules/set-cookie-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", + "license": "MIT" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2151,6 +2501,15 @@ "peerDependencies": { "vue": "^3.0.11" } + }, + "node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 963028e..b4a3250 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@amap/amap-jsapi-loader": "^1.0.1", "@vercel/speed-insights": "^2.0.0", + "better-auth": "^1.7.6", "naive-ui": "^2.44.1", "pinia": "^3.0.4", "vue": "^3.5.34", diff --git a/src/components/layout/AppHeader.vue b/src/components/layout/AppHeader.vue index 673bc55..79c9778 100644 --- a/src/components/layout/AppHeader.vue +++ b/src/components/layout/AppHeader.vue @@ -1,6 +1,6 @@ diff --git a/src/lib/api.ts b/src/lib/api.ts index 091f6e9..ebf0999 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -27,9 +27,6 @@ export class ApiError extends Error { interface ApiRequestOptions extends Omit { body?: BodyInit | Record | null auth?: boolean - // Kept for callers that explicitly disable retries. Cookie sessions do not - // use a refresh-and-retry flow, so this option is intentionally ignored. - retry?: boolean } function isJsonBody(body: ApiRequestOptions['body']): body is Record { @@ -105,7 +102,7 @@ async function toApiError(response: Response) { } async function request(path: string, options: ApiRequestOptions = {}) { - const { body, auth = true, retry: _retry, ...requestOptions } = options + const { body, auth = true, ...requestOptions } = options const headers = new Headers(requestOptions.headers) let requestBody: BodyInit | null | undefined = body as BodyInit | null | undefined diff --git a/src/lib/authClient.ts b/src/lib/authClient.ts new file mode 100644 index 0000000..394a953 --- /dev/null +++ b/src/lib/authClient.ts @@ -0,0 +1,33 @@ +import { createAuthClient } from 'better-auth/client' +import { API_URL } from '@/lib/api' + +export const authClient = createAuthClient({ + baseURL: `${API_URL}/auth`, + fetchOptions: { credentials: 'include' }, +}) + +interface AuthClientFailure { + code?: string + message?: string + status?: number +} + +export class AuthClientError extends Error { + code?: string + status?: number + + constructor(failure: AuthClientFailure, fallback: string) { + super(failure.code === 'EMAIL_NOT_VERIFIED' + ? '邮箱尚未验证,请先查收验证邮件。' + : failure.status === 429 + ? '操作过于频繁,请稍后重试。' + : fallback) + this.name = 'AuthClientError' + this.code = failure.code + this.status = failure.status + } +} + +export function assertAuthResult(result: { error?: AuthClientFailure | null }, fallback: string) { + if (result.error) throw new AuthClientError(result.error, fallback) +} diff --git a/src/stores/auth.ts b/src/stores/auth.ts index df4def1..44d8476 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -1,7 +1,8 @@ import { computed, ref } from 'vue' import { defineStore } from 'pinia' import { AUTH_EXPIRED_EVENT, apiRequest, toAuthUser } from '@/lib/api' -import type { ActionResponse, BackendUserProfile, LoginResponse, RegisterResponse } from '@/types/api' +import { assertAuthResult, authClient } from '@/lib/authClient' +import type { BackendUserProfile } from '@/types/api' import type { Profile } from '@/types/database' export const useAuthStore = defineStore('auth', () => { @@ -37,62 +38,45 @@ export const useAuthStore = defineStore('auth', () => { } async function login(email: string, password: string) { - await apiRequest('/auth/login', { - method: 'POST', - auth: false, - body: { email, password }, - }) + assertAuthResult(await authClient.signIn.email({ email, password }), '登录失败,请检查邮箱和密码。') + const session = await authClient.getSession() + assertAuthResult(session, '获取登录会话失败,请稍后重试。') + if (!session.data) throw new Error('未能建立登录会话,请稍后重试。') await fetchMe() } async function register(name: string, email: string, password: string) { - return await apiRequest('/auth/register', { - method: 'POST', - auth: false, - body: { name, email, password }, - }) + assertAuthResult(await authClient.signUp.email({ name, email, password }), '注册失败,请稍后重试。') } async function resendConfirmation(email: string) { - return await apiRequest('/auth/resend-confirmation', { - method: 'POST', - auth: false, - body: { email }, - }) + assertAuthResult(await authClient.sendVerificationEmail({ email }), '验证邮件发送失败,请稍后重试。') } async function confirmEmail(token: string) { - return await apiRequest('/auth/confirm-email', { - method: 'POST', - auth: false, - body: { token }, - }) + assertAuthResult(await authClient.$fetch('/verify-email', { + method: 'GET', + query: { token }, + }), '确认链接无效或已过期。') } async function sendPasswordReset(email: string) { - return await apiRequest('/auth/forgot-password', { - method: 'POST', - auth: false, - body: { email }, - }) + assertAuthResult(await authClient.requestPasswordReset({ email }), '重置邮件发送失败,请稍后重试。') } - async function resetPassword(email: string, token: string, newPassword: string) { - const response = await apiRequest('/auth/reset-password', { - method: 'POST', - auth: false, - body: { email, token, new_password: newPassword }, - }) + async function resetPassword(token: string, newPassword: string) { + assertAuthResult(await authClient.resetPassword({ token, newPassword }), '密码重置失败,请重新申请重置邮件。') clearAuth() - return response } async function logout() { - try { - await apiRequest('/auth/logout', { method: 'POST', retry: false }) - } finally { + const result = await authClient.signOut() + if (result.error?.status === 401) { clearAuth() + return } + assertAuthResult(result, '退出登录失败,请稍后重试。') + clearAuth() } async function updateUsername(username: string) { @@ -104,14 +88,11 @@ export const useAuthStore = defineStore('auth', () => { } async function updatePassword(currentPassword: string, newPassword: string) { - await apiRequest('/auth/password', { - method: 'PATCH', - body: { - current_password: currentPassword, - new_password: newPassword, - }, - }) - clearAuth() + assertAuthResult(await authClient.changePassword({ + currentPassword, + newPassword, + revokeOtherSessions: true, + }), '密码更新失败,请检查当前密码。') } async function initialize() { @@ -121,7 +102,10 @@ export const useAuthStore = defineStore('auth', () => { } try { - await fetchMe() + const session = await authClient.getSession() + assertAuthResult(session, '获取登录会话失败,请稍后重试。') + if (session.data) await fetchMe() + else clearAuth() } catch { clearAuth() } finally { diff --git a/src/types/api.ts b/src/types/api.ts index 34478b0..5015fda 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -48,14 +48,6 @@ export interface ActionResponse { message: string } -export interface RegisterResponse extends ActionResponse { - email_sent: boolean -} - -export interface LoginResponse { - access_token: string -} - export interface CloudCreateResponse extends ActionResponse { cloud_id: string } diff --git a/src/views/admin/AdminView.vue b/src/views/admin/AdminView.vue index 848b0e6..0c04365 100644 --- a/src/views/admin/AdminView.vue +++ b/src/views/admin/AdminView.vue @@ -646,8 +646,9 @@ async function handleLogout() { accountMenuOpen.value = false try { await authStore.logout() - } finally { await router.push('/') + } catch (error) { + message.error(error instanceof Error ? error.message : '退出登录失败,请稍后重试。') } } diff --git a/src/views/auth/ForgotPasswordView.vue b/src/views/auth/ForgotPasswordView.vue index 0092adb..a1e3965 100644 --- a/src/views/auth/ForgotPasswordView.vue +++ b/src/views/auth/ForgotPasswordView.vue @@ -70,7 +70,7 @@ onUnmounted(() => { if (timer) clearInterval(timer) })
Password Recovery

忘记密码

-

输入注册邮箱,我们会发送有效期为 30 分钟的重置链接。

+

输入注册邮箱,我们会发送有效期为 1 小时的重置链接。

diff --git a/src/views/auth/LoginView.vue b/src/views/auth/LoginView.vue index 8b93c7b..2621f42 100644 --- a/src/views/auth/LoginView.vue +++ b/src/views/auth/LoginView.vue @@ -1,7 +1,8 @@