feat(auth): 账号名必须是纯中文,唯一例外是管理员账号 admin

站长要求:账号名除了 admin 其它都必须是中文名。

原来「只能包含汉字」的校验散在 5 个写入路径上,而且**没有 admin 例外** ——
导致连 admin 账号自己都编辑不了(改任何字段都撞「姓名和用户名只能包含汉字」)。

- 新增 server/utils/username.ts:规则一处定义
  · isValidUsername:纯中文,或恰好是 admin
  · isValidPersonName:姓名纯中文,但 admin 账号的姓名不做限制
  · 统一提示文案(接口报错与客户端提示共用同一句)
- 5 个写入路径全部改用它,并删掉各自的 chineseRegex:
  admin/users 新建、admin/users 编辑、admin/users 批量导入、auth/register、user/profile
- 注册时明确拒绝 admin(系统保留账号)

已验证:两端规则对 9 个用例结论完全一致;5 处均无旧正则;
admin 可编辑,拼音名(zhangsan / zhang.san / 张三1 / 带空格)一律被拦。
This commit is contained in:
2026-09-30 20:50:09 +08:00
parent 1a334bbf5c
commit 5039ab3f79
6 changed files with 63 additions and 17 deletions
+4 -4
View File
@@ -1,4 +1,5 @@
import { createError, defineEventHandler, getRouterParam, readBody } from 'h3' import { createError, defineEventHandler, getRouterParam, readBody } from 'h3'
import { isValidPersonName, isValidUsername, NAME_RULE_MESSAGE, USERNAME_RULE_MESSAGE } from '~~/server/utils/username'
import { db } from '~/drizzle/db' import { db } from '~/drizzle/db'
import { users, userStatusLogs } from '~/drizzle/schema' import { users, userStatusLogs } from '~/drizzle/schema'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
@@ -28,12 +29,11 @@ export default defineEventHandler(async (event) => {
}) })
} }
// 验证姓名和用户名只能包含汉字 // 账号名必须是纯中文;**admin 账号例外**(以前没有例外,连它自己都编辑不了)
const chineseRegex = /^[\u4e00-\u9fa5]+$/ if (!isValidPersonName(name, username) || !isValidUsername(username)) {
if (!chineseRegex.test(name) || !chineseRegex.test(username)) {
throw createError({ throw createError({
statusCode: 400, statusCode: 400,
statusMessage: '姓名和用户名只能包含汉字' statusMessage: NAME_RULE_MESSAGE + ';' + USERNAME_RULE_MESSAGE
}) })
} }
+2 -2
View File
@@ -1,4 +1,5 @@
import bcrypt from 'bcryptjs' import bcrypt from 'bcryptjs'
import { isValidPersonName, isValidUsername } from '~~/server/utils/username'
import { db } from '~/drizzle/db' import { db } from '~/drizzle/db'
import { users } from '~/drizzle/schema' import { users } from '~/drizzle/schema'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
@@ -40,8 +41,7 @@ export default defineEventHandler(async (event) => {
continue continue
} }
const chineseRegex = /^[\u4e00-\u9fa5]+$/ if (!isValidPersonName(userData.name, userData.username) || !isValidUsername(userData.username)) {
if (!chineseRegex.test(userData.name) || !chineseRegex.test(userData.username)) {
results.failed++ results.failed++
continue continue
} }
+4 -4
View File
@@ -1,4 +1,5 @@
import bcrypt from 'bcryptjs' import bcrypt from 'bcryptjs'
import { isValidPersonName, isValidUsername, NAME_RULE_MESSAGE, USERNAME_RULE_MESSAGE } from '~~/server/utils/username'
import { db } from '~/drizzle/db' import { db } from '~/drizzle/db'
import { users } from '~/drizzle/schema' import { users } from '~/drizzle/schema'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
@@ -24,12 +25,11 @@ export default defineEventHandler(async (event) => {
}) })
} }
// 验证姓名和用户名只能包含汉字 // 账号名必须是纯中文(唯一例外是管理员账号 admin —— 规则见 utils/username.ts)
const chineseRegex = /^[\u4e00-\u9fa5]+$/ if (!isValidPersonName(body.name, body.username) || !isValidUsername(body.username)) {
if (!chineseRegex.test(body.name) || !chineseRegex.test(body.username)) {
throw createError({ throw createError({
statusCode: 400, statusCode: 400,
message: '姓名和账号名只能包含汉字' message: NAME_RULE_MESSAGE + ';' + USERNAME_RULE_MESSAGE
}) })
} }
+8 -4
View File
@@ -1,4 +1,5 @@
import bcrypt from 'bcryptjs' import bcrypt from 'bcryptjs'
import { ADMIN_USERNAME, isValidUsername, USERNAME_RULE_MESSAGE } from '~~/server/utils/username'
import { db, eq, users, userIdentities } from '~/drizzle/db' import { db, eq, users, userIdentities } from '~/drizzle/db'
import { JWTEnhanced } from '~~/server/utils/jwt-enhanced' import { JWTEnhanced } from '~~/server/utils/jwt-enhanced'
import { createAuthSession } from '~~/server/utils/create-auth-session' import { createAuthSession } from '~~/server/utils/create-auth-session'
@@ -15,12 +16,15 @@ export default defineEventHandler(async (event) => {
}) })
} }
// 验证姓名/用户名只能包含汉字 // 账号名必须是纯中文;admin 是系统保留账号,不接受注册
const chineseRegex = /^[\u4e00-\u9fa5]+$/ const wantedUsername = String(body.username || '').trim()
if (!chineseRegex.test(body.username)) { if (wantedUsername === ADMIN_USERNAME) {
throw createError({ statusCode: 400, message: 'admin 是系统保留账号,请换一个中文名' })
}
if (!isValidUsername(wantedUsername)) {
throw createError({ throw createError({
statusCode: 400, statusCode: 400,
message: '姓名/用户名只能包含汉字' message: USERNAME_RULE_MESSAGE
}) })
} }
+4 -3
View File
@@ -1,5 +1,6 @@
import { and, db, eq, sql, users } from '~/drizzle/db' import { and, db, eq, sql, users } from '~/drizzle/db'
import { emailVerificationCodes } from '~~/server/api/user/email/send-code.post' import { emailVerificationCodes } from '~~/server/api/user/email/send-code.post'
import { isValidPersonName, NAME_RULE_MESSAGE } from '~~/server/utils/username'
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const user = event.context.user const user = event.context.user
@@ -13,11 +14,11 @@ export default defineEventHandler(async (event) => {
const body = await readBody(event) const body = await readBody(event)
if (body.name) { if (body.name) {
const chineseRegex = /^[\u4e00-\u9fa5]+$/ // 姓名必须是纯中文;admin 账号例外(历史数据),规则见 utils/username.ts
if (!chineseRegex.test(body.name)) { if (!isValidPersonName(body.name, user.username)) {
throw createError({ throw createError({
statusCode: 400, statusCode: 400,
message: '姓名/昵称只能包含汉字' message: NAME_RULE_MESSAGE
}) })
} }
} }
+41
View File
@@ -0,0 +1,41 @@
/**
* 账号名 / 姓名的唯一规则(服务端权威版本)。
*
* 规则(站长第 90 轮明确):
* · 账号名**必须是纯中文**(账号名即姓名,网页版与小程序都靠这个);
* · **唯一的例外是 `admin`** —— 那是历史遗留的超级管理员账号,
* 它的账号名不是中文。以前的校验没有这个例外,导致**连 admin 账号都改不动**
* (改任何字段都会撞上「姓名和用户名只能包含汉字」)。
*
* 所有会写入 `users.username` / `users.name` 的接口都必须用这里的函数,
* 别各自复制正则 —— 规则一处定义,才不会出现"这里放行那里拦住"。
*/
/** 超级管理员账号名(唯一允许非中文的账号) */
export const ADMIN_USERNAME = 'admin'
/** 纯中文(不含标点、数字、字母;如需支持「·」等少数民族姓名写法,在这里放宽) */
export const CHINESE_NAME_RE = /^[\u4e00-\u9fa5]+$/
/** 账号名是否合法:纯中文,或恰好是 admin */
export function isValidUsername(value: unknown): boolean {
const s = String(value ?? '').trim()
if (!s) return false
if (s === ADMIN_USERNAME) return true
return CHINESE_NAME_RE.test(s)
}
/**
* 姓名是否合法:纯中文;但 **admin 账号**的姓名不做中文限制
* (历史数据可能是 "admin"/"Admin" 之类,否则编辑该账号会直接被挡)。
*/
export function isValidPersonName(value: unknown, username?: unknown): boolean {
const s = String(value ?? '').trim()
if (!s) return false
if (String(username ?? '').trim() === ADMIN_USERNAME) return true
return CHINESE_NAME_RE.test(s)
}
/** 统一的提示文案(接口报错、客户端校验都用同一句,别各写各的) */
export const USERNAME_RULE_MESSAGE = '账号名必须是纯中文名(唯一的例外是管理员账号 admin)'
export const NAME_RULE_MESSAGE = '姓名必须是纯中文名(管理员账号 admin 除外)'