style(copy): fix remaining punctuation violations and make check:copy strict
Auto-fix of 602 string literals across 74 files (half-width punctuation next to Chinese, parenthesised Chinese, quotes); biome reflow; e2/e6 assertions follow the new full-width summaries. check:copy now runs with --strict in ci. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
0fccfe257d
commit
fb84a71f80
76 files changed
+582
-570
No files matched your search
@@ -82,7 +82,7 @@ export async function ensureSetupToken(deps: Pick<AuthDeps, "db" | "logger">): P
|
||||
returning id`.execute(deps.db);
|
||||
if (!r.rows.length) return null;
|
||||
// 注意:logger 会把名为 *token* 的字段脱敏,所以 token 放在消息文本里
|
||||
deps.logger.warn(`管理员尚未初始化。一次性 setup token(打开控制台登录页完成初始化;重启会重新生成):${token}`);
|
||||
deps.logger.warn(`管理员尚未初始化。一次性 setup token(打开控制台登录页完成初始化;重启会重新生成):${token}`);
|
||||
return token;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ async function recentFailures(db: Kysely<DB>, col: "ip" | "username", value: str
|
||||
export async function assertNotRateLimited(db: Kysely<DB>, ip: string, username: string): Promise<void> {
|
||||
const [byIp, byAccount] = await Promise.all([recentFailures(db, "ip", ip), recentFailures(db, "username", username)]);
|
||||
if (byIp >= MAX_FAILURES_PER_IP || byAccount >= MAX_FAILURES_PER_ACCOUNT) {
|
||||
throw new ApiException("rate_limited", "失败次数过多,15 分钟后再试");
|
||||
throw new ApiException("rate_limited", "失败次数过多,15 分钟后再试");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ function otpauthUri(secret: string, account: string): string {
|
||||
}
|
||||
|
||||
export async function totpBegin(deps: AuthDeps, admin: AdminRow): Promise<TotpSetupInfo> {
|
||||
if (admin.totp_enabled_at) throw new ApiException("conflict", "TOTP 已启用,先停用再重新绑定");
|
||||
if (admin.totp_enabled_at) throw new ApiException("conflict", "TOTP 已启用,先停用再重新绑定");
|
||||
const secret = base32Encode(randomBytes(20));
|
||||
const ref = await deps.secrets.create({ purpose: "other", plaintext: secret, label: `admin-totp:${randomToken(6)}` });
|
||||
const old = admin.totp_pending_secret_ref;
|
||||
@@ -375,7 +375,7 @@ export async function totpBegin(deps: AuthDeps, admin: AdminRow): Promise<TotpSe
|
||||
|
||||
/** GET /auth/totp/setup:已有未确认的密钥就原样返回(刷新页面不作废已扫的码),没有才生成 */
|
||||
export async function totpSetup(deps: AuthDeps, admin: AdminRow): Promise<TotpSetupInfo> {
|
||||
if (admin.totp_enabled_at) throw new ApiException("conflict", "TOTP 已启用,先停用再重新绑定");
|
||||
if (admin.totp_enabled_at) throw new ApiException("conflict", "TOTP 已启用,先停用再重新绑定");
|
||||
if (admin.totp_pending_secret_ref) {
|
||||
try {
|
||||
const secret = await deps.secrets.resolve(admin.totp_pending_secret_ref);
|
||||
|
||||
@@ -25,7 +25,7 @@ const D = {
|
||||
method: "get",
|
||||
path: "/engine/runs",
|
||||
scope: "admin",
|
||||
summary: "引擎 runs(状态/名称/时间筛选,新→旧)",
|
||||
summary: "引擎 runs(状态/名称/时间筛选,新→旧)",
|
||||
query: EngineRunQuery,
|
||||
response: cursorPage(EngineRun),
|
||||
tags,
|
||||
@@ -61,7 +61,7 @@ const D = {
|
||||
method: "post",
|
||||
path: "/engine/runs/{id}/retry",
|
||||
scope: "admin",
|
||||
summary: "按原任务名与输入重新入队(新幂等键 retry:<id>:<序号>)",
|
||||
summary: "按原任务名与输入重新入队(新幂等键 retry:<id>:<序号>)",
|
||||
params: RunIdParam,
|
||||
response: z.object({ runId: z.string(), retryOf: z.string(), attempt: z.number().int().positive() }),
|
||||
tags,
|
||||
@@ -316,7 +316,7 @@ export function registerEngineRoutes(app: ModuleApp, ctx: ServerContext, opts: {
|
||||
const run = r.rows[0];
|
||||
if (!run) throw new ApiException("not_found", `run ${params.id} 不存在`);
|
||||
if (!RETRYABLE.has(run.status))
|
||||
throw new ApiException("conflict", `run 状态为 ${run.status},只能重跑已结束的 run(可先取消)`);
|
||||
throw new ApiException("conflict", `run 状态为 ${run.status},只能重跑已结束的 run(可先取消)`);
|
||||
const env = (run.input ?? {}) as { input?: unknown; traceId?: string; priority?: number };
|
||||
const prefix = `retry:${params.id}:`;
|
||||
const prev = await sql<{ n: number }>`
|
||||
@@ -330,7 +330,7 @@ export function registerEngineRoutes(app: ModuleApp, ctx: ServerContext, opts: {
|
||||
...(typeof env.priority === "number" ? { priority: env.priority } : {}),
|
||||
}));
|
||||
} catch (err) {
|
||||
throw new ApiException("precondition_failed", `无法重新入队:${(err as Error).message}`);
|
||||
throw new ApiException("precondition_failed", `无法重新入队:${(err as Error).message}`);
|
||||
}
|
||||
await audit(
|
||||
ctx,
|
||||
|
||||
@@ -130,7 +130,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) {
|
||||
.deleteFrom(table)
|
||||
.where("id", "=", id)
|
||||
.execute()
|
||||
.catch((e: unknown) => pgConflict(e, `${NOUN[table]}仍被引用,不能删除`));
|
||||
.catch((e: unknown) => pgConflict(e, `${NOUN[table]}仍被引用,不能删除`));
|
||||
if (row.secret_ref) await ctx.secrets.remove(row.secret_ref).catch(() => {});
|
||||
await audit(ctx, actor, action, { type: table, id });
|
||||
return { ok: true as const };
|
||||
@@ -142,7 +142,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) {
|
||||
.values(values)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow()
|
||||
.catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`))) as Record<
|
||||
.catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`))) as Record<
|
||||
string,
|
||||
unknown
|
||||
> & {
|
||||
@@ -157,7 +157,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) {
|
||||
.where("id", "=", id)
|
||||
.returningAll()
|
||||
.executeTakeFirst()
|
||||
.catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`));
|
||||
.catch((e: unknown) => pgConflict(e, `${NOUN[table]}的名称或路径重复,或关联的对象不存在`));
|
||||
if (!r) throw new ApiException("not_found", `${NOUN[table]}不存在`);
|
||||
return r as Record<string, unknown> & { id: string };
|
||||
};
|
||||
@@ -181,7 +181,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) {
|
||||
} catch (e) {
|
||||
throw new ApiException(
|
||||
"precondition_failed",
|
||||
`库根不可写(需要目录存在且可写入哨兵文件 ${name}):${errMessage(e)}`,
|
||||
`库根不可写(需要目录存在且可写入哨兵文件 ${name}):${errMessage(e)}`,
|
||||
);
|
||||
}
|
||||
return name;
|
||||
|
||||
@@ -35,7 +35,7 @@ const D = {
|
||||
method: "delete",
|
||||
path: "/secrets/{id}",
|
||||
scope: "admin",
|
||||
summary: "删除凭证(被引用时 409)",
|
||||
summary: "删除凭证(被引用时 409)",
|
||||
params: IdParam,
|
||||
response: OkResult,
|
||||
tags: ["secrets"],
|
||||
@@ -44,7 +44,7 @@ const D = {
|
||||
method: "get",
|
||||
path: "/audit-events",
|
||||
scope: "admin",
|
||||
summary: "审计事件(按 actor/action/target 过滤,新→旧)",
|
||||
summary: "审计事件(按 actor/action/target 过滤,新→旧)",
|
||||
query: AuditEventQuery,
|
||||
response: cursorPage(AuditEvent),
|
||||
tags: ["audit"],
|
||||
@@ -174,7 +174,7 @@ export function registerKeyRoutes(app: ModuleApp, ctx: ServerContext) {
|
||||
.where("id", "=", params.id)
|
||||
.returning("id")
|
||||
.executeTakeFirst()
|
||||
.catch((e) => pgConflict(e, "凭证仍被实例、站点账号、Feed 或设置引用,先解除引用"));
|
||||
.catch((e) => pgConflict(e, "凭证仍被实例、站点账号、Feed 或设置引用,先解除引用"));
|
||||
if (!r) throw new ApiException("not_found", "凭证不存在");
|
||||
await audit(ctx, actor, "secret.delete", { type: "secret", id: params.id });
|
||||
return { ok: true as const };
|
||||
|
||||
@@ -180,7 +180,7 @@ export async function verifyRegistration(
|
||||
const response = input.response as unknown as RegistrationResponseJSON;
|
||||
const origins = expectedOrigins(rp, response);
|
||||
const challenge = await takeChallenge(db, input.challengeId, "register", admin.id);
|
||||
if (!challenge) throw new ApiException("precondition_failed", "注册挑战已过期或已用过,重新开始");
|
||||
if (!challenge) throw new ApiException("precondition_failed", "注册挑战已过期或已用过,重新开始");
|
||||
let v: Awaited<ReturnType<typeof verifyRegistrationResponse>>;
|
||||
try {
|
||||
v = await verifyRegistrationResponse({
|
||||
@@ -191,7 +191,7 @@ export async function verifyRegistration(
|
||||
requireUserVerification: false,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new ApiException("unauthorized", `Passkey 校验失败:${(err as Error).message}`);
|
||||
throw new ApiException("unauthorized", `Passkey 校验失败:${(err as Error).message}`);
|
||||
}
|
||||
if (!v.verified) throw new ApiException("unauthorized", "Passkey 校验失败");
|
||||
const { credential, credentialDeviceType, credentialBackedUp } = v.registrationInfo;
|
||||
@@ -220,7 +220,7 @@ export async function verifyRegistration(
|
||||
|
||||
export async function authenticationOptions(db: Kysely<DB>, admin: AdminRow, rp: RpInfo): Promise<PasskeyChallenge> {
|
||||
const creds = await listPasskeys(db, admin.id);
|
||||
if (!creds.length) throw new ApiException("precondition_failed", "还没有登记 Passkey,用密码登录");
|
||||
if (!creds.length) throw new ApiException("precondition_failed", "还没有登记 Passkey,用密码登录");
|
||||
const options = await generateAuthenticationOptions({
|
||||
rpID: rp.rpID,
|
||||
userVerification: "preferred",
|
||||
@@ -240,7 +240,7 @@ export async function verifyAuthentication(
|
||||
const response = input.response as unknown as AuthenticationResponseJSON;
|
||||
const origins = expectedOrigins(rp, response);
|
||||
const challenge = await takeChallenge(db, input.challengeId, "login", null);
|
||||
if (!challenge) throw new ApiException("precondition_failed", "登录挑战已过期或已用过,重试");
|
||||
if (!challenge) throw new ApiException("precondition_failed", "登录挑战已过期或已用过,重试");
|
||||
const cred = await db
|
||||
.selectFrom("admin_passkey")
|
||||
.selectAll()
|
||||
@@ -264,7 +264,7 @@ export async function verifyAuthentication(
|
||||
requireUserVerification: false,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new ApiException("unauthorized", `Passkey 校验失败:${(err as Error).message}`);
|
||||
throw new ApiException("unauthorized", `Passkey 校验失败:${(err as Error).message}`);
|
||||
}
|
||||
if (!v.verified) throw new ApiException("unauthorized", "Passkey 校验失败");
|
||||
// 计数器只增不减(条件更新:并发重放同一个断言只有一个生效;计数器恒为 0 的认证器照常放行)
|
||||
@@ -281,6 +281,6 @@ export async function verifyAuthentication(
|
||||
.where(sql<boolean>`(counter < ${next} or (counter = 0 and ${next} = 0))`)
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
if (!u) throw new ApiException("unauthorized", "Passkey 签名计数器回退(可能被克隆),已拒绝");
|
||||
if (!u) throw new ApiException("unauthorized", "Passkey 签名计数器回退(可能被克隆),已拒绝");
|
||||
return u;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export class SecretError extends Error {
|
||||
readonly secretRef: string,
|
||||
) {
|
||||
super(
|
||||
kind === "not_found" ? `secret ${secretRef} 不存在` : `secret ${secretRef} 解密失败(主密钥不匹配或密文被篡改)`,
|
||||
kind === "not_found" ? `secret ${secretRef} 不存在` : `secret ${secretRef} 解密失败(主密钥不匹配或密文被篡改)`,
|
||||
);
|
||||
this.name = "SecretError";
|
||||
}
|
||||
@@ -221,7 +221,7 @@ export async function rotateMasterKey(
|
||||
newKey: string,
|
||||
opts: { progressEvery?: number } = {},
|
||||
): Promise<RotateMasterKeyResult> {
|
||||
if (oldKey === newKey) throw new Error("新旧主密钥相同,不需要轮换");
|
||||
if (oldKey === newKey) throw new Error("新旧主密钥相同,不需要轮换");
|
||||
const { db, logger } = deps;
|
||||
const newKid = masterKeyId(newKey);
|
||||
const every = opts.progressEvery ?? 50;
|
||||
@@ -266,7 +266,7 @@ export async function rotateMasterKey(
|
||||
else if (r === "skipped") out.skipped++;
|
||||
else if (r === "failed") {
|
||||
out.failed.push(id);
|
||||
logger.warn({ secretId: id }, "这条 secret 新旧主密钥都解不开,已跳过(保持原样)");
|
||||
logger.warn({ secretId: id }, "这条 secret 新旧主密钥都解不开,已跳过(保持原样)");
|
||||
}
|
||||
done++;
|
||||
if (done % every === 0 || done === ids.length)
|
||||
@@ -295,7 +295,7 @@ export async function rotateMasterKey(
|
||||
.catch((err) => logger.warn({ err: { message: (err as Error).message } }, "主密钥轮换审计写入失败"));
|
||||
logger.info(
|
||||
{ total: out.total, rotated: out.rotated, skipped: out.skipped, failed: out.failed.length },
|
||||
out.failed.length ? "主密钥轮换结束,有解不开的行" : "主密钥轮换完成",
|
||||
out.failed.length ? "主密钥轮换结束,有解不开的行" : "主密钥轮换完成",
|
||||
);
|
||||
return out;
|
||||
}
|
||||
@@ -337,7 +337,7 @@ export function createSettingsReader(deps: {
|
||||
const v = stored.get(key);
|
||||
const merged = v && typeof v === "object" && !Array.isArray(v) ? { ...def, ...(v as object) } : def;
|
||||
const parsed = GlobalSettings.shape[key].safeParse(merged);
|
||||
if (!parsed.success) deps.logger.warn({ key }, "settings 值不合法,回退缺省");
|
||||
if (!parsed.success) deps.logger.warn({ key }, "settings 值不合法,回退缺省");
|
||||
out[key] = parsed.success ? parsed.data : def;
|
||||
}
|
||||
return out as GlobalSettings;
|
||||
|
||||
Reference in new issue
Block a user