Add user authentication and role management

This commit is contained in:
巫凤萍
2026-08-07 11:06:20 +08:00
parent 1f910c975d
commit c926a6a874
25 changed files with 2370 additions and 132 deletions

138
app/api/users/route.ts Normal file
View File

@@ -0,0 +1,138 @@
import {
createPasswordRecord,
getRequestPrincipal,
managerForbidden,
normalizeUsername,
type UserRole,
validatePassword,
validateUsername,
} from "../../../lib/user-auth";
import { ensureSchema, getRawDb } from "../../../lib/mvp-db";
export const runtime = "edge";
type UserRow = {
id: string;
username: string;
role: UserRole;
created_at: string;
updated_at: string;
};
async function manager(request: Request) {
const principal = await getRequestPrincipal(request);
if (!principal || principal.kind !== "user") return null;
return ["super_admin", "admin"].includes(principal.user.role)
? principal.user
: null;
}
async function listUsers() {
const result = await getRawDb()
.prepare(
`SELECT id, username, role, created_at, updated_at
FROM users
ORDER BY CASE role WHEN 'super_admin' THEN 1 WHEN 'admin' THEN 2 ELSE 3 END,
created_at ASC`,
)
.all<UserRow>();
return result.results;
}
export async function GET(request: Request) {
await ensureSchema();
if (!(await manager(request))) return managerForbidden();
return Response.json({ users: await listUsers() });
}
export async function POST(request: Request) {
await ensureSchema();
const currentUser = await manager(request);
if (!currentUser) return managerForbidden();
try {
const body = (await request.json()) as {
action?: string;
username?: string;
password?: string;
role?: UserRole;
userId?: string;
};
const password = String(body.password ?? "");
if (!validatePassword(password)) {
return Response.json({ error: "密码需为8—72位" }, { status: 400 });
}
const db = getRawDb();
const passwordRecord = await createPasswordRecord(password);
if (body.action === "create") {
const username = normalizeUsername(body.username);
const role = body.role === "admin" ? "admin" : "user";
if (!validateUsername(username)) {
return Response.json(
{ error: "账号需为2—32位中文、字母、数字或 _ . @ + -" },
{ status: 400 },
);
}
if (currentUser.role !== "super_admin" && role !== "user") {
return managerForbidden();
}
await db
.prepare(
`INSERT INTO users
(id, username, password_hash, password_salt, password_iterations, role)
VALUES (?, ?, ?, ?, ?, ?)`,
)
.bind(
crypto.randomUUID(),
username,
passwordRecord.passwordHash,
passwordRecord.passwordSalt,
passwordRecord.passwordIterations,
role,
)
.run();
} else if (body.action === "reset_password") {
const target = await db
.prepare("SELECT id, role FROM users WHERE id = ?")
.bind(String(body.userId ?? ""))
.first<{ id: string; role: UserRole }>();
if (!target) {
return Response.json({ error: "没有找到这个账号" }, { status: 404 });
}
if (
target.role === "super_admin" &&
!(currentUser.role === "super_admin" && target.id === currentUser.id)
) {
return Response.json({ error: "不能修改其他超级管理员账号" }, { status: 403 });
}
if (currentUser.role !== "super_admin" && target.role !== "user") {
return managerForbidden();
}
await db.batch([
db
.prepare(
`UPDATE users SET
password_hash = ?, password_salt = ?, password_iterations = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
)
.bind(
passwordRecord.passwordHash,
passwordRecord.passwordSalt,
passwordRecord.passwordIterations,
target.id,
),
db.prepare("DELETE FROM auth_sessions WHERE user_id = ?").bind(target.id),
]);
} else {
return Response.json({ error: "不支持的操作" }, { status: 400 });
}
return Response.json({ users: await listUsers() });
} catch (error) {
const message = error instanceof Error ? error.message : "操作失败";
return Response.json(
{ error: message.includes("UNIQUE") ? "这个登录账号已存在" : message },
{ status: message.includes("UNIQUE") ? 409 : 500 },
);
}
}