docs(plan): v0.2 adds Pi subagents and scheduled-jobs board
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
b839ed4056
commit
16ab2a6c84
97 files changed
+592
-648
No files matched your search
@@ -136,7 +136,7 @@ export function registerAuthRoutes(app: ModuleApp, ctx: ServerContext) {
|
||||
|
||||
endpoint(app, D.password, async ({ body, c, actor }) => {
|
||||
const session = c.get("session");
|
||||
if (!session) throw new ApiException("forbidden", "该操作只能在控制台登录后进行");
|
||||
if (!session) throw new ApiException("forbidden", "只能在控制台登录后操作");
|
||||
const revoked = await changePassword(deps(), body, {
|
||||
adminUserId: session.adminUserId,
|
||||
sessionId: session.id,
|
||||
|
||||
@@ -103,7 +103,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 分钟后再试");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ export async function setupAdmin(
|
||||
const admin = await loadAdmin(deps.db);
|
||||
if (admin?.password_hash) throw new ApiException("conflict", "管理员已初始化");
|
||||
await recordAttempt(deps.db, { ip, username: "(setup)", success: false, reason: "bad_setup_token" });
|
||||
throw new ApiException("unauthorized", "setup token 无效(以服务端日志最新打印的为准)");
|
||||
throw new ApiException("unauthorized", "setup token 无效。使用服务端日志里最新打印的那个");
|
||||
}
|
||||
const admin = await loadAdmin(deps.db);
|
||||
if (!admin) throw new Error("admin_user 行丢失");
|
||||
@@ -206,7 +206,7 @@ export async function verifyLogin(
|
||||
): Promise<LoginOutcome> {
|
||||
await assertNotRateLimited(deps.db, ip, input.username);
|
||||
const admin = await loadAdmin(deps.db);
|
||||
if (!admin?.password_hash) throw new ApiException("precondition_failed", "管理员尚未初始化,请先完成初始化");
|
||||
if (!admin?.password_hash) throw new ApiException("precondition_failed", "管理员尚未初始化");
|
||||
const userOk = admin.username === input.username;
|
||||
// 用户名不对也跑一次 argon2,避免用时差探测账号
|
||||
let pwOk = false;
|
||||
@@ -284,7 +284,7 @@ export async function totpBegin(deps: AuthDeps, admin: AdminRow): Promise<{ uri:
|
||||
}
|
||||
|
||||
export async function totpEnable(deps: AuthDeps, admin: AdminRow, code: string): Promise<void> {
|
||||
if (!admin.totp_pending_secret_ref) throw new ApiException("precondition_failed", "请先生成 TOTP 密钥");
|
||||
if (!admin.totp_pending_secret_ref) throw new ApiException("precondition_failed", "先生成 TOTP 密钥");
|
||||
const secret = await deps.secrets.resolve(admin.totp_pending_secret_ref);
|
||||
const counter = verifyTotp(secret, code, null);
|
||||
if (counter === null) throw new ApiException("unauthorized", "验证码错误");
|
||||
|
||||
@@ -68,7 +68,7 @@ const JSON_COLS = ["settings", "version_info", "health_detail", "verify_detail"]
|
||||
|
||||
const errMessage = (e: unknown) => redactString(String((e as Error)?.message ?? e)).slice(0, 500);
|
||||
|
||||
const LR = crudDefs("/library-roots", "媒体库根", {
|
||||
const LR = crudDefs("/library-roots", "库根", {
|
||||
row: LibraryRoot,
|
||||
create: LibraryRootCreate,
|
||||
update: LibraryRootUpdate,
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in new issue
Block a user