feat: 新增用户管理
ocs-nuxt / deploy (push) Successful in 1m46s

This commit is contained in:
2026-05-24 23:26:43 +08:00
parent 3c467df01d
commit 90412a3688
22 changed files with 1620 additions and 114 deletions
+126
View File
@@ -0,0 +1,126 @@
// server/api/admin/users.post.ts - 超级管理员创建用户
//
// 流程:鉴权 → 二次校验 superadmin → JSON 对象和字段白名单校验 → Better Auth Admin createUser
// 安全边界:不使用公开注册接口,不返回密码、apiToken 或 Better Auth 原始错误。
import { readBody, setResponseStatus } from "h3";
import {
ADMIN_USER_SELECT,
getUnknownKeys,
isUserRole,
readTrimmedString,
requireSuperAdmin,
toAdminUser,
validateEmail,
validatePassword
} from "~~/server/utils/adminUsers";
import { auth, getAuthHeaderSnapshot } from "~~/server/utils/auth";
import { prisma } from "~~/server/utils/db";
import { createApiLogger, toSafeLogError } from "~~/server/utils/logging";
import { apiErr, apiOk } from "~~/server/utils/response";
import type {
IAdminUserCreateRequest,
IAdminUserResponse
} from "~~/shared/types/adminUsers";
const ALLOWED_KEYS = new Set(["name", "email", "password", "role"]);
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
const adminUserErr = (code: number, msg: string) =>
apiErr(code, msg) as unknown as IAdminUserResponse;
export default defineEventHandler(
async (event): Promise<IAdminUserResponse> => {
const forbidden = requireSuperAdmin(event);
if (forbidden) return forbidden as unknown as IAdminUserResponse;
const logger = createApiLogger(event, "admin.users.create");
const body = await readBody<unknown>(event).catch(() => null);
if (!isRecord(body)) {
setResponseStatus(event, 400);
return adminUserErr(400, "请求体必须是 JSON 对象");
}
const unknownKeys = getUnknownKeys(body, ALLOWED_KEYS);
if (unknownKeys.length > 0) {
setResponseStatus(event, 400);
return adminUserErr(400, "请求体包含不支持的字段");
}
const req: Partial<IAdminUserCreateRequest> = {
name: readTrimmedString(body.name),
email: readTrimmedString(body.email)?.toLowerCase(),
password: typeof body.password === "string" ? body.password : undefined,
role: isUserRole(body.role) ? body.role : undefined
};
if (!req.name) {
setResponseStatus(event, 400);
return adminUserErr(400, "昵称不能为空");
}
if (req.name.length > 20) {
setResponseStatus(event, 400);
return adminUserErr(400, "昵称不能超过 20 个字符");
}
if (!req.email || !validateEmail(req.email)) {
setResponseStatus(event, 400);
return adminUserErr(400, "请输入有效的邮箱地址");
}
if (!req.password) {
setResponseStatus(event, 400);
return adminUserErr(400, "密码不能为空");
}
const passwordError = validatePassword(req.password);
if (passwordError) {
setResponseStatus(event, 400);
return adminUserErr(400, passwordError);
}
if (!req.role) {
setResponseStatus(event, 400);
return adminUserErr(400, "用户角色无效");
}
try {
const existing = await prisma.user.findUnique({
where: { email: req.email },
select: { id: true }
});
if (existing) {
setResponseStatus(event, 400);
return adminUserErr(400, "邮箱已被使用");
}
await auth.api.createUser({
body: {
name: req.name,
email: req.email,
password: req.password,
role: req.role
},
headers: getAuthHeaderSnapshot(event)
});
const created = await prisma.user.findUnique({
where: { email: req.email },
select: ADMIN_USER_SELECT
});
if (!created) {
setResponseStatus(event, 500);
return adminUserErr(500, "服务器内部错误");
}
logger.info("finish_success", {
targetUserId: created.id,
role: created.role
});
return apiOk(toAdminUser(created));
} catch (error) {
logger.error("create_failed", { error: toSafeLogError(error) });
setResponseStatus(event, 500);
return adminUserErr(500, "服务器内部错误");
}
}
);