OpenWork Den Admin API 路由深度解析从 requireAdminMiddleware 鉴权到平台级运营报表【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork导读本文以 OpenWork 仓库中ee/apps/den-api/src/routes/admin/README.md为骨架系统讲解 Den API 的 Admin 专属路由面如何通过requireAdminMiddleware白名单中间件完成平台管理员鉴权、GET /v1/admin/overview的响应结构与分页协议、以及scale-performance.ts中分页参数的边界约束。文章还结合ee/apps/den-api/src/routes/admin/index.ts约 2100 行与ee/apps/den-api/src/middleware/admin.ts的源码实现逐一拆解管理员增删、用户删除、推理用量重置、组织套餐/席位/能力开关等管理端点的请求体与错误语义并给出仓库内测试用例路径帮助你掌握这套「白名单鉴权 报表只读 变更审计」的 admin 路由设计范式。目录结构Admin 路由的归属与边界Den API 的 admin 路由被刻意收敛在独立目录中避免与 auth、org 等业务路由混在一起ee/apps/den-api/src/routes/admin/ ├── README.md # 目录说明本文骨架 ├── index.ts # 全部 admin 路由注册与处理逻辑约 2100 行 └── scale-performance.ts # 分页参数归一化与 LIKE 搜索转义工具在ee/apps/den-api/src/app.ts中路由通过registerAdminRoutes(app)挂载到 Hono 应用上见 app.ts。目录说明明确强调三条工程约束见 README.md所有路由必须由requireAdminMiddleware门禁管理报表逻辑留在此处不混入 auth 或 org 路由报表标志如includeBilling优先使用 query 校验器处理。从实际代码看这一设计已经远超 README 描述的最小形态目录内不仅注册了 overview 端点还生长出一整套用户/组织管理端点并配套了scale-performance.ts来统一分页行为。鉴权基石requireAdminMiddleware 与平台管理员白名单中间件实现requireAdminMiddleware定义在 middleware/admin.ts逻辑清晰从c.get(user)取当前会话用户无user.id直接返回401 { error: unauthorized }规范化邮箱trim().toLowerCase()为空返回403 { error: admin_email_required }调用isAdminEmailAllowed(email)查询AdminAllowlistTable白名单不在白名单返回403 { error: forbidden }通过后await next()放行。关键细节鉴权依据是邮箱白名单而非角色字段。isAdminEmailAllowed每次调用前都会执行ensureAdminAllowlistSeeded()把环境变量DEN_BOOTSTRAP_ADMIN_EMAILS中的邮箱幂等写入白名单表见 admin-allowlist.ts写入标记为Seeded bootstrap admin。这意味着首次部署时只要配置引导管理员邮箱该邮箱即可立即具备 admin 权限无需手动初始化数据库。路由访问策略标记体系requireAdminMiddleware同时被注册进 Den API 的「显式访问守卫」集合见 middleware/route-access.ts。Den API 路由是**默认拒绝deny-by-default**的每个app.get/post/...注册都必须携带一个显式访问策略标记requireAdminMiddleware、requireUserMiddleware等共享守卫会执行统一鉴权。adminRoute()工厂函数正是requireAdminMiddleware的别名封装所有 admin 端点都通过adminRoute()挂载。test/route-access-policy.test.ts仓库 evals/specs 中有同名约束会在 CI 中检查路由是否遗漏访问标记从机制上杜绝「忘记加鉴权」的漏洞。GET /v1/admin/overview初始管理视图响应结构overview 是管理后台的首屏数据源响应遵循adminOverviewResponseSchema见 index.tsviewer当前管理员自身信息id、email、nameadmins当前白名单管理员列表含note与createdAtsummary全局汇总指标见下文users/organizations受分页约束的首批用户/组织数据userPage/organizationPage分页元信息total、limit、offset、returned、hasMore、search、durationMsgeneratedAtISO 时间戳。惰性汇总deferred summary设计overview 故意不在首屏加载重指标。loadAdminInitialOverviewPayload只并行执行三项轻查询白名单管理员列表、用户首页includeBillingfalse、组织总数汇总指标由buildDeferredOverviewSummary填充为null占位见 index.ts。真正的分析指标由独立的GET /v1/admin/metrics端点提供loadAdminMetricsSummary覆盖规模totalUsers、verifiedUsers、recentUsers7d/30d、totalOrganizationsWorkertotalWorkers、cloudWorkersdestinationcloud、localWorkers、usersWithWorkers、usersWithoutWorkers活跃度activeUsers1d/7d/30d基于 AuthSession TelemetryEvent 去重realActiveUsers*仅统计带session_id的task.started/completed/failed事件recurringUsers活跃天数 ≥ 2增长漏斗inviters、medianHoursToFirstInvite注册到首次邀请的间隔中位数30 天趋势序列activitySeries每日activeUsers、realActiveUsers、signups三元组。活跃度聚合窗口为 90 天趋势序列回看 30 天telemetry 相关查询都带.catch(() [])说明遥测表被视为可选数据源即使缺失也不影响 overview 可用性。查询参数与分页约束overview 与用户/组织分页端点共用adminPageQuerySchemaincludeBilling、limit、offset、search均为可选字符串。这些参数最终交给normalizeAdminPageRequest见 scale-performance.ts做防御性归一化参数默认值约束limit50钳制在[1, 100]ADMIN_MAX_PAGE_LIMIToffset0上限100000ADMIN_MAX_PAGE_OFFSETsearchtrim后截断至 160 字符非法值非整数、负数自动回退默认因此客户端传limit9999也不会拖垮数据库。buildAdminPageInfo中的hasMore判定为offset 100000 offset returned total保证分页循环有明确的终止条件。搜索语义用户与组织用户搜索userSearchCondition若search命中邮箱正则则精确匹配AuthUserTable.email否则对name、email、id、AuthAccount.providerId以及用户所属组织的名称/角色做LIKE模糊匹配见 index.ts。所有%、_、|都经sanitizeAdminSearchForLike转义|作为转义符见 scale-performance.ts防止用户输入注入通配符导致全表扫描。组织搜索organizationSearchCondition对name、slug、id做同样的转义模糊匹配。平台管理员管理端点POST /v1/admin/admins —— 新增平台管理员请求体经createAdminSchema校验email必须合法且trim().toLowerCase()归一化note可选≤255 字符。成功返回200 { ok, admin }若邮箱已存在检测到ER_DUP_ENTRYerrno 1062返回409 { error: conflict }见 index.ts。ID 由createDenTypeId(adminAllowlist)生成。DELETE /v1/admin/admins/:adminId —— 移除平台管理员删除在数据库事务内加FOR UPDATE行锁执行并内置两条保护规则见 index.ts不能删除自己邮箱与当前 viewer 相同 →400不能删除最后一个管理员白名单只剩 1 条 →400。这两条规则从机制上防止管理员误操作把自己锁在系统外。用户管理端点GET /v1/admin/users/:userId/inference-usage —— 查询推理用量返回目标用户在其所属每个组织内、每个计费窗口window_type的用量明细窗口起止、limitAmount、组织总用量used_amount、用户分摊用量coalesce(sum(bucket_charge.amount), 0)。数据来自InferenceOrgLimitPolicyTable/InferenceOrgUsageBucketTable/InferenceUsageLedgerBucketChargeTable的多表关联见 index.ts按组织名与窗口类型升序排列。POST /v1/admin/users/:userId/inference-usage/reset —— 重置推理用量在事务中按「策略 → 桶与账目」的顺序加锁与 admission/settlement 的锁序一致避免死锁找出该用户在各组织当前窗口内的所有 charge扣减桶的used_amountgreatest(used_amount - amount, 0)并将 charge 的amount置 0 而非删除从而保留记账身份、防止重试「复活」已豁免的用量见 index.ts。响应{ ok, resetAmount }返回释放的总量。DELETE /v1/admin/users/:userId —— 删除用户这是最重的一个端点事务内级联清理OAuth 令牌/同意/客户端、API Key、会话、账号、Desktop 移交授权、外部身份、SCIM 事件、连接账号、推理成员凭据并将成员关系软删除置removedAt、Worker 归属置空、最后删除AuthUserTable记录见 index.ts。事务外还会调用revokeGoogleCredentials撤销 Google 凭据逐一失效会话缓存token 与 session-id 两条缓存条目都要清与 OAuth grant 缓存清除受影响组织的成员缓存并重新同步席位订阅数量syncSeatSubscriptionQuantityAfterMemberChange。保护规则管理员不能删除自己的账号400。组织管理端点PATCH /v1/admin/organizations/:organizationId/plan请求体{ tier: free | team | enterprise, seatLimit: 1..100000 }。写操作通过updateOrganizationMetadata原子合并元数据写入{ tier, source: manual }enterprise 额外记grantedAt并把limits.members更新为seatLimit见 index.ts。响应回显parseOrganizationPlan的结果便于客户端立即确认生效。PATCH /v1/admin/organizations/:organizationId/free-seats请求体{ totalFreeSeats }下限为calculateOrganizationSeatBillingCounts({ memberCount: 0 }).includedFree即默认免费席位上限 100000。实现上把「超出默认免费席位的部分」写入seatsFreeAdditional元数据随后重算getOrganizationSeatBillingCounts并同步 Stripe 订阅数量见 index.ts。响应给出memberCount、freeSeatCount、seatsFreeAdditional、billableSeatCount四个计费口径。PATCH /v1/admin/organizations/:organizationId/dpa记录 DPA数据处理协议签署决策事务内FOR UPDATE读组织元数据readOrganizationMetadata解析失败返回503 managed_models_policy_unavailable成功则更新dpaSigned并写入一条AuditEventTable审计事件记录 actor、前后值与 reason实现「决策 审计」的原子提交见 index.ts。变更经logOrganizationAuditEvent输出。PUT /v1/admin/organizations/:organizationId/openwork-web-access授予/撤销组织的 OpenWork Web 免费访问complimentary access。请求体{ enabled: boolean, reason: 3..500 字符 }。关键约束存在进行中的付费 OpenWork Web 订阅时不能授予免费访问返回409 openwork_web_subscription_exists事务内二次检查订阅状态防竞态操作同样写入审计事件openWorkWebComplimentaryAccessGranted/Revoked见 index.ts。GET/PUT /v1/admin/organizations/:organizationId/capabilities管理组织的功能开关capability overrides当前可见四个维度installLinks、mcpConnections、modelsAnalytics、gatewayDashboard。PUT支持true/false/null三种语义null表示删除覆盖、回归默认见 index.ts。readUnmanagedCapabilityMetadata会过滤已退役的键workflows、codemodeScripts、remoteMcpApps、cloud避免过期覆盖项透传到下游。分页与报表端点GET /v1/admin/users 与 GET /v1/admin/organizations两者共用queryValidator(adminPageQuerySchema)与normalizeAdminPageRequest返回{ users/organizations, page, generatedAt }。用户页在includeBillingtrue时逐页加载计费状态paid/unpaid/unavailable并通过mapWithConcurrency(subscriptionIds, 4, refreshOrgSubscriptionFromStripe)以 4 并发刷新 Stripe 订阅保证页面规模的计费口径新鲜见 index.ts。组织页则计算plan、seatLimit、免费/可计费席位、能力开关与 OpenWork Web 访问状态见 index.ts。GET /v1/admin/metrics即上文「惰性汇总」的完整版返回{ summary, generatedAt }是 overview 首屏之后按需加载的分析接口见 index.ts。GET /v1/admin/overview返回初始视图queryValidator(overviewQuerySchema)校验includeBilling、limit、offset、search见 index.ts。错误语义与 OpenAPI 文档化所有 admin 端点通过hono-openapi的describeRoute声明统一挂在tags: [Admin]下并复用adminRouteErrors常量见 index.ts状态码语义400请求体/参数/ID 非法{ error: invalid_request, message }401未认证{ error: unauthorized }403已认证但非白名单管理员{ error: forbidden }404目标用户/组织不存在{ error: not_found, message }409冲突重复 admin、存在付费订阅等{ error, message }503元数据策略不可读managed_models_policy_unavailable文档化描述同时充当 API 契约请求体字段、成功与失败响应结构都在describeRoute的responses中逐一声明可作为生成 OpenAPI 文档与客户端类型的唯一事实来源。测试佐证仓库内验证路径Admin 面在ee/apps/den-api/test/下有专门测试覆盖可作为理解行为边界的补充材料admin-scale-performance.test.ts验证分页参数归一化limit/offset/search 边界admin-management-inference-usage.test.ts验证推理用量查询与重置的事务语义admin-delete-user-id-validation.test.ts验证删除用户时的 ID 校验与自我保护admin-capabilities.test.ts、admin-organization-capabilities.test.ts验证组织能力开关的读写与null清除语义admin-mcp.test.ts、admin-mcp-routes.test.ts验证 admin 面与 den-admin MCP 的联动。从 README 到实现的演进小结README 描述的是「小而美」的起点——一个 overview 端点、一个独立目录。而当前index.ts已扩展为完整的平台运营面白名单管理员生命周期、用户删除与推理用量管理、组织套餐/席位/DPA/功能开关管理以及带分页与惰性加载的报表体系。不变的是三条骨架约束依旧成立鉴权统一收敛全部端点经adminRoute()即requireAdminMiddleware门禁白名单 deny-by-default 策略标记双保险职责隔离管理逻辑保留在routes/admin/不侵入 auth/org 路由参数防御分页与搜索统一走normalizeAdminPageRequestsanitizeAdminSearchForLike报表标志如includeBilling通过 query 校验器解析。对开发者而言这套设计可以直接借鉴以中间件工厂 显式守卫标记 白名单表构建管理面鉴权以「首屏轻量 重指标惰性加载」平衡管理后台性能以审计事件表为每次管理变更留痕。如需在自部署 Den 环境中启用管理面先在DEN_BOOTSTRAP_ADMIN_EMAILS配置引导管理员邮箱即可。【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
