mirror of
https://github.com/34892002/edgeKey.git
synced 2026-05-07 23:57:02 +08:00
fix: 升级哈希密码
This commit is contained in:
14
README-en.md
14
README-en.md
@@ -141,6 +141,20 @@ The project uses admin username/password login. Before using in production:
|
||||
- Production command: `wrangler secret put AUTH_SECRET`
|
||||
- Default admin credentials: `admin / admin123456` — **change your password immediately after first login.**
|
||||
|
||||
### Forgot Your Password?
|
||||
|
||||
Reset your password to `admin123456` via the D1 Console in Cloudflare Dashboard:
|
||||
|
||||
1. Go to [dash.cloudflare.com](https://dash.cloudflare.com) → **Storage & Databases** → **D1** → click `edgekey-db`
|
||||
2. Open the **Console** tab
|
||||
3. Run the following SQL:
|
||||
|
||||
```sql
|
||||
UPDATE Admin SET passwordHash = '$2b$10$viMe8RgcpM30gmmF9OpOcuA/QgleSIUk5VRtqjOulfSIbgK5jQCI6' WHERE username = 'admin';
|
||||
```
|
||||
|
||||
4. Log in and change your password immediately.
|
||||
|
||||
## Local Development
|
||||
|
||||
Bun is recommended (npm/pnpm/yarn also work).
|
||||
|
||||
14
README.md
14
README.md
@@ -138,6 +138,20 @@ sed -i 's/"database_name": "edgekey-db"/"database_name": "edgekey-db", "database
|
||||
- 生产环境配置AUTH_SECRET命令 `wrangler secret put AUTH_SECRET`
|
||||
- 默认管理员账号为 `admin / admin123456`,首次登录后请立即修改密码
|
||||
|
||||
### 忘记密码?
|
||||
|
||||
在 Cloudflare Dashboard 中通过 D1 Console 将密码重置为 `admin123456`:
|
||||
|
||||
1. 进入 [dash.cloudflare.com](https://dash.cloudflare.com) → **Storage & Databases** → **D1** → 点击 `edgekey-db`
|
||||
2. 顶部 tab → **Console**
|
||||
3. 执行以下 SQL:
|
||||
|
||||
```sql
|
||||
UPDATE Admin SET passwordHash = '$2b$10$viMe8RgcpM30gmmF9OpOcuA/QgleSIUk5VRtqjOulfSIbgK5jQCI6' WHERE username = 'admin';
|
||||
```
|
||||
|
||||
4. 登录后台后立即修改密码
|
||||
|
||||
## 本地开发
|
||||
|
||||
推荐使用 Bun(也可替换为 npm/pnpm/yarn)。
|
||||
|
||||
3
bun.lock
3
bun.lock
@@ -17,6 +17,7 @@
|
||||
"@tiptap/starter-kit": "^3.22.3",
|
||||
"@tiptap/vue-3": "^3.22.3",
|
||||
"@universal-middleware/core": "^0.4.17",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"hono": "^4.12.8",
|
||||
"pinyin-pro": "^3.28.1",
|
||||
"telefunc": "^0.2.19",
|
||||
@@ -614,6 +615,8 @@
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.13", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw=="],
|
||||
|
||||
"bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="],
|
||||
|
||||
"better-result": ["better-result@2.7.0", "", { "dependencies": { "@clack/prompts": "^0.11.0" }, "bin": { "better-result": "bin/cli.mjs" } }, "sha512-7zrmXjAK8u8Z6SOe4R65XObOR5X+Y2I/VVku3t5cPOGQ8/WsBcfFmfnIPiEl5EBMDOzPHRwbiPbMtQBKYdw7RA=="],
|
||||
|
||||
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
export function hashAdminPassword(password: string) {
|
||||
return createHash("sha256").update(password).digest("hex");
|
||||
export async function hashAdminPassword(password: string) {
|
||||
return bcrypt.hash(password, 10);
|
||||
}
|
||||
|
||||
export async function verifyAdminPassword(password: string, hash: string) {
|
||||
// 兼容旧 SHA-256 哈希(不以 $2b$ 开头)
|
||||
if (!hash.startsWith("$2b$") && !hash.startsWith("$2a$")) {
|
||||
const sha256 = createHash("sha256").update(password).digest("hex");
|
||||
return sha256 === hash;
|
||||
}
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getContext } from "telefunc";
|
||||
import type { PrismaClient } from "../../generated/prisma/client";
|
||||
import { badRequestError, notFoundError, unauthorizedError } from "../../lib/app-error";
|
||||
import { hashAdminPassword } from "./crypto";
|
||||
import { hashAdminPassword, verifyAdminPassword } from "./crypto";
|
||||
|
||||
export function assertAdminAccess() {
|
||||
const context = getContext<{ session?: { user?: { role?: string } } }>();
|
||||
@@ -80,13 +80,14 @@ export async function updateAdminProfile(input: {
|
||||
throw badRequestError("新密码长度不能少于 8 位", "PASSWORD_TOO_SHORT");
|
||||
}
|
||||
|
||||
const currentHash = hashAdminPassword(currentPassword);
|
||||
if (currentHash !== admin.passwordHash) {
|
||||
const currentValid = await verifyAdminPassword(currentPassword, admin.passwordHash);
|
||||
if (!currentValid) {
|
||||
throw badRequestError("当前密码不正确", "CURRENT_PASSWORD_INVALID");
|
||||
}
|
||||
|
||||
const newHash = hashAdminPassword(newPassword);
|
||||
if (newHash === admin.passwordHash) {
|
||||
const newHash = await hashAdminPassword(newPassword);
|
||||
const newSameAsOld = await verifyAdminPassword(newPassword, admin.passwordHash);
|
||||
if (newSameAsOld) {
|
||||
throw badRequestError("新密码不能与当前密码相同", "PASSWORD_UNCHANGED");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.1",
|
||||
"scripts": {
|
||||
"dev": "vike dev",
|
||||
"build": "bun run db:generate && vike build",
|
||||
@@ -31,6 +31,7 @@
|
||||
"@tiptap/starter-kit": "^3.22.3",
|
||||
"@tiptap/vue-3": "^3.22.3",
|
||||
"@universal-middleware/core": "^0.4.17",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"hono": "^4.12.8",
|
||||
"pinyin-pro": "^3.28.1",
|
||||
"telefunc": "^0.2.19",
|
||||
|
||||
@@ -40,12 +40,15 @@ const csrfToken = ref("");
|
||||
const loading = ref(true);
|
||||
const errorMsg = ref("");
|
||||
|
||||
const ERROR_MAP: Record<string, string> = {CredentialsSignin: "用户名或密码错误",
|
||||
const ERROR_MAP: Record<string, string> = {
|
||||
CredentialsSignin: "用户名或密码错误",
|
||||
password_upgrade_failed: "登录成功但密码升级失败,请重置密码后重试",
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const error = new URLSearchParams(location.search).get("error");
|
||||
if (error) errorMsg.value = ERROR_MAP[error] ?? "登录失败,请重试";
|
||||
const params = new URLSearchParams(location.search);
|
||||
const code = params.get("code") ?? params.get("error");
|
||||
if (code) errorMsg.value = ERROR_MAP[code] ?? "登录失败,请重试";
|
||||
try {
|
||||
const response = await fetch("/api/auth/csrf", {
|
||||
credentials: "same-origin",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
-- 管理员账号
|
||||
INSERT INTO "Admin" ("username", "passwordHash", "nickname", "status", "updatedAt")
|
||||
VALUES ('admin', 'ac0e7d037817094e9e0b4441f9bae3209d67b02fa484917065f71b16109a1a78', '管理员', 'ACTIVE', CURRENT_TIMESTAMP)
|
||||
VALUES ('admin', '$2b$10$viMe8RgcpM30gmmF9OpOcuA/QgleSIUk5VRtqjOulfSIbgK5jQCI6', '管理员', 'ACTIVE', CURRENT_TIMESTAMP)
|
||||
ON CONFLICT("username") DO NOTHING;
|
||||
|
||||
-- 站点设置
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Auth, type AuthConfig, createActionURL, setEnvDefaults } from "@auth/core";
|
||||
import { CredentialsSignin } from "@auth/core/errors";
|
||||
import CredentialsProvider from "@auth/core/providers/credentials";
|
||||
import type { Session } from "@auth/core/types";
|
||||
import { enhance, type UniversalHandler, type UniversalMiddleware } from "@universal-middleware/core";
|
||||
import { PrismaClient } from "../generated/prisma/client";
|
||||
import { internalServerError, rateLimitError } from "../lib/app-error";
|
||||
import { logger } from "../lib/logger";
|
||||
import { hashAdminPassword } from "../modules/auth/crypto";
|
||||
import { verifyAdminPassword, hashAdminPassword } from "../modules/auth/crypto";
|
||||
|
||||
const ADMIN_ROLE = "admin" as const;
|
||||
const loginAttemptStore = new Map<string, { count: number; expiresAt: number }>();
|
||||
@@ -75,11 +76,24 @@ async function findAdminByCredentials(prisma: PrismaClient, username: string, pa
|
||||
return null;
|
||||
}
|
||||
|
||||
const passwordHash = hashAdminPassword(password);
|
||||
if (admin.passwordHash !== passwordHash) {
|
||||
const valid = await verifyAdminPassword(password, admin.passwordHash);
|
||||
if (!valid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 旧 SHA-256 哈希自动升级为 bcrypt
|
||||
if (!admin.passwordHash.startsWith("$2b$") && !admin.passwordHash.startsWith("$2a$")) {
|
||||
try {
|
||||
const newHash = await hashAdminPassword(password);
|
||||
await prisma.admin.update({ where: { username }, data: { passwordHash: newHash } });
|
||||
} catch (e) {
|
||||
logger.error("auth.password_upgrade.failed", { error: e });
|
||||
const err = new CredentialsSignin("哈希字符串升级失败,请参考官网文档重置管理员密码");
|
||||
err.code = "password_upgrade_failed";
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: String(admin.id),
|
||||
name: admin.nickname || admin.username,
|
||||
|
||||
Reference in New Issue
Block a user