十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenWork Den API 组织路由(Org Routes)架构解析:活动组织模型、成员/邀请/角色/SCIM 全链路实现

OpenWork Den API 组织路由(Org Routes)架构解析:活动组织模型、成员/邀请/角色/SCIM 全链路实现 OpenWork Den API 组织路由Org Routes架构解析活动组织模型、成员/邀请/角色/SCIM 全链路实现【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork导读本文基于 ee/apps/den-api/src/routes/org/README.md 展开深入剖析 OpenWork 企业版 Den API 中面向组织Organization / Workspace的全部服务端路由从“活动组织active organization”会话模型、Hono 中间件组合、Zod 校验规范到邀请、成员、动态角色、SCIM 连接器的逐条实现。读完本文你将掌握这套路由层的文件组织原则、路由权限矩阵owner / super-admin / admin / member的真实落地方式以及/v1/org与/v1/orgs/**两类路径的设计差异并可直接对照源码验证每一个端点。一、路由层概览这个目录归谁管ee/apps/den-api/src/routes/org/是 Den API 中“面向组织organization-facing”的专属路由目录。与普通用户级路由不同这里的每个端点都依赖当前会话解析出的活动组织上下文即“当前登录用户当前正在操作的工作区”。README 中声明的职责分工目录清单如下文件职责index.ts注册所有组织路由分组并承载旧版/v1/orgs/:orgId/*兼容代理core.ts组织创建、邀请预览/接受、组织上下文GET/POST/PATCH/v1/orginvitations.ts邀请的创建与取消members.ts成员角色更新、成员移除、所有权转移roles.ts动态自定义角色的 CRUDscim.ts当前组织 SCIM 连接器元数据与令牌轮换shared.ts路由级共享辅助函数、参数 Schema 与权限守卫需要说明的是随着仓库持续演进实际目录中还出现了teams.ts、sso.ts、api-keys.ts、llm-providers.ts、mcp-connections.ts、plugin-system/、delete-organization.ts等更多分组见 routes/org 目录README 描述的是最初拆分时的最小核心集合两者共同构成了当前的组织路由全景。二、活动组织模型Active Organization Model一切路由的前提这是整个组织路由层最核心的设计决策README 用四条规则把它讲清楚了切换活动组织的唯一入口是 Better Auth 端点POST /api/auth/organization/set-active是唯一被允许显式切换用户活动组织的 Better Auth 端点。新会话的初始值来自会话创建钩子新会话应通过src/auth.ts中的 Better Auth 会话创建钩子获得初始activeOrganizationId。GET /v1/org返回活动组织从当前会话读取包含嵌套的organization.owner对象以及当前成员current member和团队上下文member teams。活动组织作用域的资源走顶层路由如/v1/teams、/v1/roles、/v1/api-keys、/v1/llm-providers、/v1/scim以及插件系统的/v1/...路由路径中不应出现:orgId或:orgSlug。这条“路径中不带 orgId”的设计与POST /v1/org创建组织后“把会话切到新组织”的行为是配套的客户端只要先调用 set-active 完成工作区切换随后所有/v1/...资源路由自然操作的就是新工作区。README 特别强调了两类路径的分工/v1/orgs/**保留给跨组织cross-org流程即尚未绑定到活动工作区的操作例如邀请预览/v1/orgs/invitations/preview与邀请接受/v1/orgs/invitations/accept切换工作区的正确姿势先调用 Better Auth set-active再调用活动组织作用域的/v1/...资源路由。源码印证创建组织后自动切换活动组织在 core.ts 的POST /v1/org处理器 中可以看到这套模型的完整实现const organizationId await createOrganizationForUser({ userId: normalizeDenTypeId(user, user.id), name: input.name, }) await setRequestActiveOrganization(c, organizationId)setRequestActiveOrganization优先调用auth.api.setActiveOrganization(...)即 README 指定的唯一切换端点失败时才回退到setSessionActiveOrganization直接写库core.ts。同时GET /v1/org的响应里会附加capabilities如gatewayDashboard、mcpConnections、openworkWeb、cloud、authMethodssso/scim、plan与entitlements供客户端做能力探测core.ts L636-L743。源码注释还特别说明orgManagedDashboards: true是显式的协议信号用于较新版本 Den 在分批上线期间的 fail-closed 判断。三、路由注册中枢与旧版兼容代理index.ts是整个组织路由面的装配点。registerOrgRoutes依次调用各分组的registerXxxRoutes(app)把近三十个路由组挂到同一个 Hono 应用上index.ts L61-L89。值得单独说明的是 README 未展开、但源码中真实存在的旧版路径兼容代理index.ts L91-L107app.all(/v1/orgs/:orgId/*, delegatedRoute, async (c) { const target extractLegacyOrgProxyTarget(url.pathname) ... headers.set(LEGACY_ORG_PROXY_HEADER, target.organizationId) return app.fetch(proxiedRequest) })其逻辑是当请求路径形如/v1/orgs/org_xxx/...且orgId以org_前缀开头时把路径改写为/v1/...并在请求头注入LEGACY_ORG_PROXY_HEADER来源为 middleware/user-organizations.ts再交给 Hono 内部重新分发。这保证了旧客户端“路径携带 orgId”的调用方式在新架构下依然可用同时新代码统一走活动组织模型。四、核心组织路由core.ts 逐条拆解core.ts是组织生命周期最核心的端点集合全部使用 Hono Zod 校验 OpenAPI 描述hono-openapi的describeRoute。以下按端点逐一说明1.POST /v1/org— 创建组织请求体{ name: string }要求trim().min(2).max(120)且.strict()拒绝多余字段createOrganizationSchema中间件链userSessionRoute()jsonValidator(...)单组织部署env.orgMode single_org直接返回409 single_org_mode成功返回201响应体为{ organization }core.ts L288-L332。2.GET /v1/org— 获取活动组织上下文中间件链orgMemberRoute()即resolveOrganizationContextMiddlewarequeryValidatorresolveMemberTeamsMiddleware支持查询参数refreshRolestrue由 owner/admin 触发默认角色播种seedDefaultOrganizationRoles后重新加载上下文响应包含organization.owner从成员列表中找到isOwner的那位、currentMember、currentMemberTeams、deploymentCapabilities、plan、entitlements、capabilities、authMethodscore.ts L636-L743。3.PATCH /v1/org— 更新组织设置权限门槛最高orgRoleRoute([super-admin])ensureOrganizationSuperAdmin且要求近 15 分钟内登录过见第五节“特权会话”说明可更新字段name、allowedEmailDomains最多 100 个非法域返回invalid_email_domain、allowedDesktopVersions最多 200 个、requireSso、brandAppName、brandLogoUrl、brandIconUrl、brandAccentColor启用requireSso/ 桌面版本锁定时会校验 Enterprise 计划权益checkEntitlement(payload.organization.metadata, orgControls)启用品牌字段时校验desktopPolicies权益品牌图标 URL 还会经过validateBrandIconUrl验证core.ts L450-L534。4. 跨组织流程邀请预览与接受这两个端点挂在/v1/orgs/**下正是 README 所说的“跨组织、未绑定活动工作区”的典型GET /v1/orgs/invitations/preview?id...publicRoute无需登录即可查看邀请详情组织名、slug、允许邮箱域、品牌信息、邀请状态与过期时间便于用户在决定加入前先确认core.ts L334-L359POST /v1/orgs/invitations/acceptuserSessionRoute要求已登录且邮箱已验证requireEmailVerification开启时处理邮箱域限制account_email_domain_not_allowed→ 409、SCIM 已撤销scim_deprovisioned→ 409、曾加入后被移除membership_removed→ 410等边界接受成功后同样调用setRequestActiveOrganization把会话切到新工作区core.ts L361-L448。5. 登录路由辅助端点GET /v1/orgs/sso/singleton单组织部署下返回“唯一组织”的 SSO 配置状态与signInUrlGET /v1/orgs/sso/resolve?email...按邮箱解析登录方式SSO 或 google/password/signup。这是一个安全敏感端点源码中专门写了安全说明按已验证域名而非成员关系解析 SSO要求 Vercel BotID 校验并对 IP / 邮箱 / 域名三层做限流——SSO_RESOLVE_IDENTITY_RATE_LIMIT_MAX 20/分钟域级窗口放宽到 10 分钟 120 次、未命中miss桶 10 分钟 30 次core.ts L92-L102、L560-L634。五、共享守卫与校验基建shared.tsREADME 提到的“shared route-local helpers, param schemas, and guard helpers”在 shared.ts 中有非常完整的实现是理解整个组织路由权限体系的关键。权限守卫一览每个守卫都返回{ ok }或{ response }守卫权限要求用途ensureOrganizationAdminRoleowner / admin无需近期登录常规管理工作ensureOrganizationSuperAdminowner / super-admin 特权会话敏感/破坏性操作ensureOrganizationAdminadmin 特权会话安全敏感操作ensureInviteManagerowner / admin 特权会话邀请管理ensureMemberRemoverowner / admin 特权会话移除成员ensureTeamManagerowner / admin 特权会话团队管理ensureOwner仅 owner 特权会话所有权转移ensureApiKeyReader/ensureApiKeyManageradmin / super-adminAPI 密钥读写ensureScimReader/ensureScimManageradmin / super-adminSCIM 读写ensureSsoReader/ensureSsoManageradmin / super-adminSSO 读写特权会话privileged session窗口shared.ts定义了三档会话新鲜度窗口shared.ts L26-L30PRIVILEGED_SESSION_MAX_AGE_MS 15 * 60 * 100015 分钟访问控制、凭据管理、发布与破坏性变更CONTENT_EDIT_SESSION_MAX_AGE_MS 60 * 60 * 10001 小时内容编辑类操作复用确认CONNECTIONS_READ_SESSION_MAX_AGE_MS 24 * 60 * 60 * 100024 小时连接只读操作。守卫通过hasFreshPrivilegedSession比较session.createdAt与当前时间的差值过期则返回{ error: reauth, reason: fresh_auth_required, message: For security, confirm its you before changing workspace settings. }。API 密钥调用方c.get(apiKey)则直接放行。另外replaceRoleValue、splitRoles、normalizeRoleName等辅助函数封装了“成员角色以逗号分隔存储”的细节供角色更新与重命名传播使用。六、邀请流程invitations.ts邀请模块只有两个端点但把“创建/刷新”和“取消”的边界情况处理得相当完整。POST /v1/invitations— 创建或刷新邀请请求体{ email: string, role: string }其中role为trim().min(1).max(64)中间件orgRoleRoute([admin])ensureInviteManager校验链invitations.ts L116-L143邮箱域必须落在allowedEmailDomains白名单内否则409 invite_email_domain_not_allowed通过listAssignableRoles拿到可分配角色集validateInvitationRoleAssignment校验目标角色是否存在、当前用户是否有权授予普通 admin 只能邀请 member事务内逻辑invitations.ts L146-L275组织行与既有邀请行均加FOR UPDATE锁防止并发重复邀请邮箱已是活跃成员 →409 member_exists存在 pending 邀请 → 刷新角色、邀请人、令牌与过期时间默认 7 天now 7 * 24h否则创建新邀请 预建一条userId null的成员占位行并检查 seat 计费额度getOrganizationSeatAddEligibility不足时402 seat_subscription_required写入组织审计事件recordOrganizationAuditEvent邮件发送失败DenEmailSendError时返回502 invitation_email_failed并携带reasonemail_not_configured/resend_rejected/resend_network/nodemailer_rejected与invitationId——邀请行已持久化客户端可据此提示重试邀请链接由buildInvitationLink(inviteToken)生成指向{origin}/join-org?invite{token}shared.ts L132-L140。POST /v1/invitations/:invitationId/cancel— 取消邀请参数invitationId必须是合法的invitation类型 Den IDdenTypeIdSchema非法 ID 直接404仅 pending 状态可取消否则409 invitation_not_pending取消的同时会清理占位成员行removeOrganizationMember并记录审计事件invitations.ts L378-L495。七、成员管理members.tsPOST /v1/members/:memberId/role— 更新成员角色权限orgRoleRoute([super-admin])ensureOrganizationSuperAdmin普通 admin 无权改角色角色必须是组织内已存在的角色listAssignableRoles否则400 invalid_role角色变更时记录memberRoleUpdated审计事件含previousRole/nextRolemembers.ts L21-L89。POST /v1/members/:memberId/transfer-ownership— 转移所有权权限orgRoleRoute([owner])ensureOwner仅 owner 本人可发起且要求 15 分钟内的特权会话目标必须是活跃的 super-admin 成员转移后原 owner 降为member审计事件记录了新旧 owner 的完整前后角色members.ts L91-L153。DELETE /v1/members/:memberId— 移除成员权限orgRoleRoute([admin])ensureMemberRemoverowner 角色受保护removeOrganizationMember内部拒绝删除 owner成功返回204members.ts L155-L212。八、动态角色 CRUDroles.ts组织支持自定义角色权限以“命名权限映射”存储permission: Recordstring, string[]。创建POST /v1/rolesroleName要求 2–64 字符角色名不能是内置受保护角色isProtectedOrganizationRoleName同名角色返回409 role_exists权限必须通过validateAssignableOrganizationPermissionRecord不能授予超过创建者自身权限的权限集roles.ts L34-L103更新PATCH /v1/roles/:roleId允许改名与改权限。改名会级联传播——遍历所有未移除成员与所有 pending 邀请用replaceRoleValue把旧角色名替换为新角色名roles.ts L185-L220权限一旦变化会调用revokeCredentialsForOrganizationRoleMembers撤销该角色成员的既有凭据roles.ts L222-L227删除DELETE /v1/roles/:roleId先确认没有活跃成员或 pending 邀请仍在引用该角色role_in_use内置角色不可删成功返回204roles.ts L246-L328。九、SCIM 连接器scim.tsSCIM 端点全部挂在活动组织顶层路径/v1/scim下是“active-org 资源走顶层路由”的典型例子端点说明GET /v1/scim读取连接器元数据baseUrl、ssoReady、connection含groupMappingModemetadata_only/create_teams与health状态POST /v1/scim/token创建/轮换 SCIM bearer token必须先启用 SSO 连接否则409 sso_required成功返回201并附新令牌PATCH /v1/scim更新组映射模式metadata_only→ 仅元数据create_teams→ 由 SCIM Group 创建并管理团队POST /v1/scim/reconcile执行漂移修复检查 SCIM 托管身份的成员关系/Provider 账户状态不一致返回{ checked, repaired, failures }DELETE /v1/scim删除连接并使当前 token 失效返回204读操作要求 adminensureScimReader写操作要求 super-admin 特权会话ensureScimManager。health字段unresolvedFailureCount、lastFailureAt、nextRetryAt、lastSuccessfulSyncAt等把同步故障状态暴露给管理端便于运维排查scim.ts。十、中间件期望与校验规范README 对路由作者提出两条硬性规范源码也一一对应中间件从src/middleware/index.ts统一导入对应文件 ee/apps/den-api/src/middleware/index.tsrequireUserMiddlewarecurrent-user.ts要求已登录用户resolveOrganizationContextMiddlewareorganization-context.ts解析当前组织与成员上下文填充c.get(organizationContext)resolveMemberTeamsMiddlewaremember-teams.ts加载当前组织成员所属团队填充c.get(memberTeams)。除此之外route-access.ts 还提供组合型入口userSessionRoute()、orgMemberRoute()、orgRoleRoute(roles)、publicRoute、delegatedRoute等并通过hasExplicitAuthGuardHandler实现deny-by-default——test/route-access-policy.test.ts会在 CI 中拦截任何未挂显式访问策略标记的路由注册。角色判定由verifyOrgRole完成roles含member直接放行否则用organizationRoleValueSatisfies按角色层级匹配。校验规范对应 validation.ts 与shared.ts的idParamSchemaquery / JSON body / params 一律使用 Hono Zod 校验器jsonValidator/queryValidator/paramValidator处理器内通过c.req.valid(query | json | param)读取已校验数据禁止在处理器内直接c.req.param()、c.req.query()或手动safeParse()。idParamSchema会把路径参数如memberId绑定为特定 Den Type ID 类型denTypeIdSchema(member)等从源头拦截非法 ID 注入。十一、为什么这样拆分README 结尾给出了拆分的直接理由org 路由面是当前迁移规模最大的区域。如果所有端点挤在一个巨型 router 文件里每次改动都需要通读全局按关注点concern拆分为core/invitations/members/roles/scim等小组后单次编辑范围被压缩到一个文件内代码评审更聚焦AgentAI 协作开发可以只读invitations.ts就完成邀请相关的修改无需扫描无关代码每个分组拥有独立的registerXxxRoutes函数天然适配index.ts的集中装配。十二、延伸阅读角色层级与访问矩阵的官方定义见 docs/cloud-organization-role-access.md这是理解super-admin/admin/member以及orgRoleRoute参数含义的权威文档中间件的完整上下文说明c.get(user)、c.get(organizationContext)、c.get(memberTeams)等见 ee/apps/den-api/src/middleware/README.md组织上下文的核心业务逻辑createOrganizationForUser、acceptInvitationForUser、getOrganizationContextForUser、removeOrganizationMember、transferOrganizationOwnership等集中在 ee/apps/den-api/src/orgs.ts路由访问策略的 deny-by-default 保障来自 ee/apps/den-api/src/middleware/route-access.ts 及配套的test/route-access-policy.test.ts测试。【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表