fix(auth): migrate frontend to Better Auth
This commit is contained in:
Generated
+359
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||||
"@vercel/speed-insights": "^2.0.0",
|
"@vercel/speed-insights": "^2.0.0",
|
||||||
|
"better-auth": "^1.7.6",
|
||||||
"naive-ui": "^2.44.1",
|
"naive-ui": "^2.44.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.34",
|
"vue": "^3.5.34",
|
||||||
@@ -81,6 +82,135 @@
|
|||||||
"node": ">=6.9.0"
|
"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": {
|
"node_modules/@css-render/plugin-bem": {
|
||||||
"version": "0.15.14",
|
"version": "0.15.14",
|
||||||
"resolved": "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz",
|
"resolved": "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz",
|
||||||
@@ -213,6 +343,39 @@
|
|||||||
"@emnapi/runtime": "^1.7.1"
|
"@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": {
|
"node_modules/@oxc-project/types": {
|
||||||
"version": "0.133.0",
|
"version": "0.133.0",
|
||||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
||||||
@@ -505,6 +668,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
|
||||||
@@ -1189,6 +1358,136 @@
|
|||||||
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
|
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/birpc": {
|
||||||
"version": "2.9.0",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
|
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
|
||||||
@@ -1254,6 +1553,12 @@
|
|||||||
"date-fns": "^3.0.0 || ^4.0.0"
|
"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": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
@@ -1379,6 +1684,24 @@
|
|||||||
"jiti": "lib/jiti-cli.mjs"
|
"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": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.32.0",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz",
|
"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": "^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": {
|
"node_modules/path-browserify": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
|
"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"
|
"@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": {
|
"node_modules/seemly": {
|
||||||
"version": "0.3.10",
|
"version": "0.3.10",
|
||||||
"resolved": "https://registry.npmmirror.com/seemly/-/seemly-0.3.10.tgz",
|
"resolved": "https://registry.npmmirror.com/seemly/-/seemly-0.3.10.tgz",
|
||||||
"integrity": "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==",
|
"integrity": "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -2151,6 +2501,15 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.0.11"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||||
"@vercel/speed-insights": "^2.0.0",
|
"@vercel/speed-insights": "^2.0.0",
|
||||||
|
"better-auth": "^1.7.6",
|
||||||
"naive-ui": "^2.44.1",
|
"naive-ui": "^2.44.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.34",
|
"vue": "^3.5.34",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import { NIcon, NSpace } from 'naive-ui'
|
import { NIcon, NSpace, useMessage } from 'naive-ui'
|
||||||
import { ChevronDown, Logout, Settings, Shield, User } from '@vicons/tabler'
|
import { ChevronDown, Logout, Settings, Shield, User } from '@vicons/tabler'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
||||||
@@ -8,6 +8,7 @@ import { RouterLink, useRoute, useRouter } from 'vue-router'
|
|||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const message = useMessage()
|
||||||
const isMapRoute = computed(() => route.name === 'map')
|
const isMapRoute = computed(() => route.name === 'map')
|
||||||
|
|
||||||
const headerHidden = ref(false)
|
const headerHidden = ref(false)
|
||||||
@@ -119,10 +120,14 @@ function closeAccountCard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
await authStore.logout()
|
try {
|
||||||
closeAccountCard()
|
await authStore.logout()
|
||||||
if (route.meta.requiresAuth) {
|
closeAccountCard()
|
||||||
router.push({ name: 'map' })
|
if (route.meta.requiresAuth) {
|
||||||
|
void router.push({ name: 'map' })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error instanceof Error ? error.message : '退出登录失败,请稍后重试。')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+1
-4
@@ -27,9 +27,6 @@ export class ApiError extends Error {
|
|||||||
interface ApiRequestOptions extends Omit<RequestInit, 'body'> {
|
interface ApiRequestOptions extends Omit<RequestInit, 'body'> {
|
||||||
body?: BodyInit | Record<string, unknown> | null
|
body?: BodyInit | Record<string, unknown> | null
|
||||||
auth?: boolean
|
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<string, unknown> {
|
function isJsonBody(body: ApiRequestOptions['body']): body is Record<string, unknown> {
|
||||||
@@ -105,7 +102,7 @@ async function toApiError(response: Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function request<T>(path: string, options: ApiRequestOptions = {}) {
|
async function request<T>(path: string, options: ApiRequestOptions = {}) {
|
||||||
const { body, auth = true, retry: _retry, ...requestOptions } = options
|
const { body, auth = true, ...requestOptions } = options
|
||||||
const headers = new Headers(requestOptions.headers)
|
const headers = new Headers(requestOptions.headers)
|
||||||
let requestBody: BodyInit | null | undefined = body as BodyInit | null | undefined
|
let requestBody: BodyInit | null | undefined = body as BodyInit | null | undefined
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
+29
-45
@@ -1,7 +1,8 @@
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { AUTH_EXPIRED_EVENT, apiRequest, toAuthUser } from '@/lib/api'
|
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'
|
import type { Profile } from '@/types/database'
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
@@ -37,62 +38,45 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function login(email: string, password: string) {
|
async function login(email: string, password: string) {
|
||||||
await apiRequest<LoginResponse>('/auth/login', {
|
assertAuthResult(await authClient.signIn.email({ email, password }), '登录失败,请检查邮箱和密码。')
|
||||||
method: 'POST',
|
const session = await authClient.getSession()
|
||||||
auth: false,
|
assertAuthResult(session, '获取登录会话失败,请稍后重试。')
|
||||||
body: { email, password },
|
if (!session.data) throw new Error('未能建立登录会话,请稍后重试。')
|
||||||
})
|
|
||||||
await fetchMe()
|
await fetchMe()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function register(name: string, email: string, password: string) {
|
async function register(name: string, email: string, password: string) {
|
||||||
return await apiRequest<RegisterResponse>('/auth/register', {
|
assertAuthResult(await authClient.signUp.email({ name, email, password }), '注册失败,请稍后重试。')
|
||||||
method: 'POST',
|
|
||||||
auth: false,
|
|
||||||
body: { name, email, password },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resendConfirmation(email: string) {
|
async function resendConfirmation(email: string) {
|
||||||
return await apiRequest<ActionResponse>('/auth/resend-confirmation', {
|
assertAuthResult(await authClient.sendVerificationEmail({ email }), '验证邮件发送失败,请稍后重试。')
|
||||||
method: 'POST',
|
|
||||||
auth: false,
|
|
||||||
body: { email },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmEmail(token: string) {
|
async function confirmEmail(token: string) {
|
||||||
return await apiRequest<ActionResponse>('/auth/confirm-email', {
|
assertAuthResult(await authClient.$fetch('/verify-email', {
|
||||||
method: 'POST',
|
method: 'GET',
|
||||||
auth: false,
|
query: { token },
|
||||||
body: { token },
|
}), '确认链接无效或已过期。')
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendPasswordReset(email: string) {
|
async function sendPasswordReset(email: string) {
|
||||||
return await apiRequest<ActionResponse>('/auth/forgot-password', {
|
assertAuthResult(await authClient.requestPasswordReset({ email }), '重置邮件发送失败,请稍后重试。')
|
||||||
method: 'POST',
|
|
||||||
auth: false,
|
|
||||||
body: { email },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetPassword(email: string, token: string, newPassword: string) {
|
async function resetPassword(token: string, newPassword: string) {
|
||||||
const response = await apiRequest<ActionResponse>('/auth/reset-password', {
|
assertAuthResult(await authClient.resetPassword({ token, newPassword }), '密码重置失败,请重新申请重置邮件。')
|
||||||
method: 'POST',
|
|
||||||
auth: false,
|
|
||||||
body: { email, token, new_password: newPassword },
|
|
||||||
})
|
|
||||||
clearAuth()
|
clearAuth()
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
try {
|
const result = await authClient.signOut()
|
||||||
await apiRequest('/auth/logout', { method: 'POST', retry: false })
|
if (result.error?.status === 401) {
|
||||||
} finally {
|
|
||||||
clearAuth()
|
clearAuth()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
assertAuthResult(result, '退出登录失败,请稍后重试。')
|
||||||
|
clearAuth()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateUsername(username: string) {
|
async function updateUsername(username: string) {
|
||||||
@@ -104,14 +88,11 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updatePassword(currentPassword: string, newPassword: string) {
|
async function updatePassword(currentPassword: string, newPassword: string) {
|
||||||
await apiRequest('/auth/password', {
|
assertAuthResult(await authClient.changePassword({
|
||||||
method: 'PATCH',
|
currentPassword,
|
||||||
body: {
|
newPassword,
|
||||||
current_password: currentPassword,
|
revokeOtherSessions: true,
|
||||||
new_password: newPassword,
|
}), '密码更新失败,请检查当前密码。')
|
||||||
},
|
|
||||||
})
|
|
||||||
clearAuth()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
@@ -121,7 +102,10 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetchMe()
|
const session = await authClient.getSession()
|
||||||
|
assertAuthResult(session, '获取登录会话失败,请稍后重试。')
|
||||||
|
if (session.data) await fetchMe()
|
||||||
|
else clearAuth()
|
||||||
} catch {
|
} catch {
|
||||||
clearAuth()
|
clearAuth()
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -48,14 +48,6 @@ export interface ActionResponse {
|
|||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterResponse extends ActionResponse {
|
|
||||||
email_sent: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginResponse {
|
|
||||||
access_token: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CloudCreateResponse extends ActionResponse {
|
export interface CloudCreateResponse extends ActionResponse {
|
||||||
cloud_id: string
|
cloud_id: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -646,8 +646,9 @@ async function handleLogout() {
|
|||||||
accountMenuOpen.value = false
|
accountMenuOpen.value = false
|
||||||
try {
|
try {
|
||||||
await authStore.logout()
|
await authStore.logout()
|
||||||
} finally {
|
|
||||||
await router.push('/')
|
await router.push('/')
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error instanceof Error ? error.message : '退出登录失败,请稍后重试。')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ onUnmounted(() => { if (timer) clearInterval(timer) })
|
|||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div class="text-sm uppercase tracking-[0.22em] text-slate-500">Password Recovery</div>
|
<div class="text-sm uppercase tracking-[0.22em] text-slate-500">Password Recovery</div>
|
||||||
<h1 class="mt-3 text-3xl font-bold text-slate-900">忘记密码</h1>
|
<h1 class="mt-3 text-3xl font-bold text-slate-900">忘记密码</h1>
|
||||||
<p class="mt-2 text-sm text-slate-500">输入注册邮箱,我们会发送有效期为 30 分钟的重置链接。</p>
|
<p class="mt-2 text-sm text-slate-500">输入注册邮箱,我们会发送有效期为 1 小时的重置链接。</p>
|
||||||
</div>
|
</div>
|
||||||
<NForm @submit.prevent="submit">
|
<NForm @submit.prevent="submit">
|
||||||
<NFormItem label="邮箱">
|
<NFormItem label="邮箱">
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { NAlert, NButton, NCard, NForm, NFormItem, NInput } from 'naive-ui'
|
import { NAlert, NButton, NCard, NForm, NFormItem, NInput } from 'naive-ui'
|
||||||
|
import { AuthClientError } from '@/lib/authClient'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@@ -12,7 +13,7 @@ const email = ref(typeof route.query.email === 'string' ? route.query.email : ''
|
|||||||
const password = ref('')
|
const password = ref('')
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const needsEmailConfirmation = computed(() => error.value.includes('邮箱尚未验证'))
|
const needsEmailConfirmation = ref(false)
|
||||||
|
|
||||||
function getSafeRedirect(value: unknown) {
|
function getSafeRedirect(value: unknown) {
|
||||||
if (typeof value !== 'string' || !value.startsWith('/')) return '/'
|
if (typeof value !== 'string' || !value.startsWith('/')) return '/'
|
||||||
@@ -29,11 +30,13 @@ function getSafeRedirect(value: unknown) {
|
|||||||
|
|
||||||
async function handleLogin() {
|
async function handleLogin() {
|
||||||
error.value = ''
|
error.value = ''
|
||||||
|
needsEmailConfirmation.value = false
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
await authStore.login(email.value, password.value)
|
await authStore.login(email.value, password.value)
|
||||||
void router.push(getSafeRedirect(route.query.redirect))
|
void router.push(getSafeRedirect(route.query.redirect))
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
needsEmailConfirmation.value = e instanceof AuthClientError && e.code === 'EMAIL_NOT_VERIFIED'
|
||||||
error.value = e instanceof Error ? e.message : '登录失败,请稍后重试'
|
error.value = e instanceof Error ? e.message : '登录失败,请稍后重试'
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { NAlert, NButton, NCard, NForm, NFormItem, NInput, NResult } from 'naive-ui'
|
import { NAlert, NButton, NCard, NForm, NFormItem, NInput, NResult } from 'naive-ui'
|
||||||
import { ApiError } from '@/lib/api'
|
import { AuthClientError } from '@/lib/authClient'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@@ -15,9 +15,7 @@ const confirmPassword = ref('')
|
|||||||
const error = ref('')
|
const error = ref('')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const registered = ref(false)
|
const registered = ref(false)
|
||||||
const emailDelivered = ref(true)
|
|
||||||
const registerConflict = ref(false)
|
const registerConflict = ref(false)
|
||||||
const canResend = computed(() => registerConflict.value)
|
|
||||||
|
|
||||||
async function handleRegister() {
|
async function handleRegister() {
|
||||||
error.value = ''
|
error.value = ''
|
||||||
@@ -48,12 +46,11 @@ async function handleRegister() {
|
|||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await authStore.register(trimmedName, trimmedEmail, password.value)
|
await authStore.register(trimmedName, trimmedEmail, password.value)
|
||||||
email.value = trimmedEmail
|
email.value = trimmedEmail
|
||||||
emailDelivered.value = response.email_sent
|
|
||||||
registered.value = true
|
registered.value = true
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (e instanceof ApiError && e.status === 409) {
|
if (e instanceof AuthClientError && (e.status === 409 || e.code === 'USER_ALREADY_EXISTS')) {
|
||||||
registerConflict.value = true
|
registerConflict.value = true
|
||||||
error.value = '邮箱或用户名已被占用;若邮箱尚未验证,可以尝试重发验证邮件。'
|
error.value = '邮箱或用户名已被占用;若邮箱尚未验证,可以尝试重发验证邮件。'
|
||||||
} else {
|
} else {
|
||||||
@@ -93,11 +90,9 @@ async function handleRegister() {
|
|||||||
<NCard class="oc-panel-card-xl min-w-0 h-full">
|
<NCard class="oc-panel-card-xl min-w-0 h-full">
|
||||||
<NResult
|
<NResult
|
||||||
v-if="registered"
|
v-if="registered"
|
||||||
:status="emailDelivered ? 'success' : 'warning'"
|
status="success"
|
||||||
title="确认你的邮箱"
|
title="确认你的邮箱"
|
||||||
:description="emailDelivered
|
description="如该邮箱可以注册,请查收验证邮件并点击链接。验证后请重新登录。"
|
||||||
? '确认邮件已经发送,请查收并点击链接完成注册。'
|
|
||||||
: '账号已创建,但验证邮件暂未送达。请使用重发验证功能,不要重复注册。'"
|
|
||||||
>
|
>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -137,7 +132,7 @@ async function handleRegister() {
|
|||||||
|
|
||||||
<NAlert v-if="error" type="error" class="mb-4">
|
<NAlert v-if="error" type="error" class="mb-4">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
<div v-if="canResend" class="mt-2">
|
<div v-if="registerConflict" class="mt-2">
|
||||||
<RouterLink :to="{ path: '/resend-confirmation', query: { email } }" class="font-semibold text-rose-700 underline underline-offset-2">重发验证邮件</RouterLink>
|
<RouterLink :to="{ path: '/resend-confirmation', query: { email } }" class="font-semibold text-rose-700 underline underline-offset-2">重发验证邮件</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</NAlert>
|
</NAlert>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { computed, onUnmounted, ref } from 'vue'
|
import { computed, onUnmounted, ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { NAlert, NButton, NCard, NForm, NFormItem, NInput, NResult } from 'naive-ui'
|
import { NAlert, NButton, NCard, NForm, NFormItem, NInput, NResult } from 'naive-ui'
|
||||||
|
import { AuthClientError } from '@/lib/authClient'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@@ -11,8 +12,8 @@ const email = typeof route.query.email === 'string' ? route.query.email : ''
|
|||||||
const token = typeof route.query.token === 'string' ? route.query.token : ''
|
const token = typeof route.query.token === 'string' ? route.query.token : ''
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
const confirmPassword = ref('')
|
const confirmPassword = ref('')
|
||||||
const error = ref(!email || !token ? '密码重置链接缺少必要信息,请重新发送邮件。' : '')
|
const error = ref(!token ? '密码重置链接缺少必要信息,请重新发送邮件。' : '')
|
||||||
const invalid = ref(!email || !token)
|
const invalid = ref(!token)
|
||||||
const success = ref(false)
|
const success = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const countdown = ref(5)
|
const countdown = ref(5)
|
||||||
@@ -46,12 +47,12 @@ async function submit() {
|
|||||||
}
|
}
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
await authStore.resetPassword(email, token, password.value)
|
await authStore.resetPassword(token, password.value)
|
||||||
success.value = true
|
success.value = true
|
||||||
startCountdown()
|
startCountdown()
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
error.value = e instanceof Error ? e.message : '密码重置失败,请稍后重试'
|
error.value = e instanceof Error ? e.message : '密码重置失败,请稍后重试'
|
||||||
if (error.value.includes('无效') || error.value.includes('过期')) invalid.value = true
|
if (e instanceof AuthClientError && (e.status === 400 || e.code === 'INVALID_TOKEN')) invalid.value = true
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { RouterLink, useRouter } from 'vue-router'
|
import { RouterLink } from 'vue-router'
|
||||||
import { NAlert, NButton, NCard, NForm, NInput, useMessage } from 'naive-ui'
|
import { NAlert, NButton, NCard, NForm, NInput, useMessage } from 'naive-ui'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const router = useRouter()
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const usernameDraft = ref('')
|
const usernameDraft = ref('')
|
||||||
@@ -75,8 +74,8 @@ async function savePassword() {
|
|||||||
currentPassword.value = ''
|
currentPassword.value = ''
|
||||||
newPassword.value = ''
|
newPassword.value = ''
|
||||||
confirmNewPassword.value = ''
|
confirmNewPassword.value = ''
|
||||||
message.success('密码已更新,请重新登录')
|
success.value = '密码已更新。其他设备上的会话已退出。'
|
||||||
await router.push('/login')
|
message.success('密码已更新')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = e instanceof Error ? e.message : '密码更新失败'
|
error.value = e instanceof Error ? e.message : '密码更新失败'
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user