Activepieces 企业版认证与授权指南:SAML SSO、联邦 OAuth、OTP 与项目级 RBAC 全解析
【免费下载链接】activepiecesAI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces
导读
本文以 EE Authentication (SSO/RBAC) 为核心骨架,系统梳理 Activepieces 企业版(EE)认证层如何在社区版(CE)之上叠加能力:SAML 2.0 SSO、Google/GitHub 联邦 OAuth、OTP 邮件验证流、按项目的 RBAC 授权,以及为嵌入场景设计的 Managed Auth JWT 交换。读完本文,你将掌握各认证路径的完整调用链(从 HTTP 入口到federatedAuthn汇聚点)、OTP 原语的安全细节(过期、重发、尝试计数)、RBAC 的权限判定模型,以及企业部署中最容易踩中的版本边界与"坑"。
所有 SSO 路径最终都汇聚到authenticationService.federatedAuthn():它负责创建或关联用户身份,并签发标准的 Activepieces JWT。
一、EE 认证层总览:模块与存储
1.1 实体与服务划分
EE 认证层由以下模块组成,全部位于 packages/server/api/src/app/ee/authentication/:
| 模块 | 职责 |
|---|---|
saml-authn/ | SAML 2.0 服务提供方(SP)实现:登录入口、ACS 回调、域名发现与验证 |
federated-authn/ | Google 联邦 OAuth:登录重定向与 code 交换 |
otp/ | OTP 原语(现位于 packages/server/api/src/app/authentication/otp/,已迁出ee/,见后文) |
enterprise-local-authn/ | 企业本地认证:邮箱验证、密码重置 |
project-role/ | RBAC 服务与中间件:rbac-service.ts、rbac-middleware.ts |
ee-authorization.ts | 计划(plan)与平台归属(ownership)相关的 preHandler 授权钩子 |
managed-authn/ | Managed Auth:面向 Embedding SDK 的 JWT 交换(位于 packages/server/api/src/app/ee/managed-authn/) |
1.2 配置存储:platform.federatedAuthProviders
SSO 与联邦认证的配置统一存放在平台行(platform row)的federatedAuthProviders字段中,典型结构如下:
{ "saml": { "entityId": "https://idp.example.com/metadata", "ssoUrl": "https://idp.example.com/sso", "certificate": "-----BEGIN CERTIFICATE-----..." }, "google": { "clientId": "...apps.googleusercontent.com", "clientSecret": "..." } }从源码看,SAML 侧的服务在 authn-sso-saml-service.ts 中通过getSamlConfigOrThrow读取该字段,且要求平台 ID 非空、SAML 配置存在,否则分别抛出Platform ID is required for SAML authentication与SAML IDP metadata is not configured for this platform错误;Google 侧的 clientId/clientSecret 则来自系统环境变量GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET(见 federated-authn-service.ts 的getClientIdAndSecret)。
二、SAML 2.0 SSO
2.1 完整流程
SAML SSO 由 authn-sso-saml-controller.ts 暴露以下端点:
- 发起登录:浏览器访问
POST /v1/authn/saml/login,服务端读取平台配置的 SAML provider,构建 SP 客户端后返回 IdP 重定向地址(302 redirect到 IdP)。 - IdP 回传断言:用户在 IdP 完成认证后,IdP 以 POST 形式把 SAML 断言提交到 ACS(Assertion Consumer Service)端点
POST /v1/authn/saml/acs。 - 解析与汇聚:服务端调用
samlClient.parseAndValidateLoginResponse解析断言,取出email / firstName / lastName,然后调用authenticationService.federatedAuthn({ provider: UserIdentityProvider.SAML, predefinedPlatformId: platformId, ... })——创建/关联用户并签发 JWT。 - 落地:控制器把认证响应序列化后重定向到
/authenticate前端路由(即 packages/web/src/app/routes/authenticate/ 的 SAML ACS 回调落地页),同时通过applicationEvents发送USER_SIGNED_UP事件(source 标记为sso)。
该能力由platform.plan.ssoEnabled门控——前端 SSO 设置页也由LockedFeatureGuard(键为ssoEnabled)包裹,未开启该计划的平台连设置入口都看不到。
2.2 ACS 地址的多环境处理(源码细节)
getAcsUrl的实现值得注意(authn-sso-saml-service.ts):
- 非 Cloud 环境:直接返回
domainHelper.getPublicApiUrl({ path: '/v1/authn/saml/acs' })。 - Cloud 环境:为兼容仍在使用自定义域名的企业客户,优先用
platformUtils.getLegacyHostByPlatformId返回旧主机名拼接 ACS 地址(https://<legacyHost>/api/v1/authn/saml/acs);没有 legacy host 时则返回{baseUrl}?platformId=<platformId>,把平台 ID 放进 query 参数供 ACS 回调识别。
对应地,ACS 端点在解析platformId时依次尝试:query 参数中的platformId→ legacy host 反查 → 请求上下文推断(platformUtils.getPlatformIdForRequest)。
2.3 域名发现(Discover)与域名验证
为支持"按邮箱域名自动跳转对应平台的 SSO",服务还提供:
POST /v1/authn/saml/discover:入参为域名,服务端先按ssoDomain查平台,再依次校验ssoDomainVerification.status === VERIFIED、plan.ssoEnabled、hasSamlConfigured,全部通过才返回{ platformId },否则返回{ platformId: null }(见 authn-sso-saml-service.ts)。POST /v1/authn/saml/sso-domain与POST /v1/authn/saml/sso-domain/verify:前者设置 SSO 域名,后者触发验证(例如生成 TXT 记录供域名持有者配置)。
三、联邦 OAuth(Google / GitHub)
联邦 OAuth 提供两条端点(federated-authn-service.ts):
/v1/authn/federated/login:返回 Google 的授权重定向 URL(FederatedAuthnLoginResponse.loginUrl)。/v1/authn/federated/claim:携带授权码code(可附带platformId)发起交换;服务端用 clientId/clientSecret 换取 idToken,解析出email / firstName / lastName / imageUrl后同样调用federatedAuthn({ provider: UserIdentityProvider.GOOGLE, ... })签发 JWT。
需要特别注意的是:联邦 OAuth 的重定向地址固定使用FRONTEND_URL,不支持自定义域名(这是文档明确标注的边界)。在 federated-authn-service.ts 中可以看到getThirdPartyRedirectUrl走的是domainHelper.getInternalUrl({ path: '/redirect' })。
前端方面,Google 登录已从早期的独立 OAuth 对话框简化为 SSO 页面上的googleAuthEnabled开关(原sso/oauth2-dialog.tsx已删除),第三方登录按钮与认证相关 hooks 位于 packages/web/src/features/authentication/。
四、OTP 邮件验证流:验证、重置与无密码登录
OTP(One-Time Password)是 EE 认证层最精细的部分,也是历次决策记录反复打磨的对象。它由otpService.createAndSend/otpService.confirm两个核心方法驱动(otp-service.ts)。
4.1 类型与过期时间
OtpType枚举定义在 packages/core/shared/src/lib/ee/otp/otp-type.ts,共三种:
export enum OtpType { EMAIL_VERIFICATION = 'EMAIL_VERIFICATION', PASSWORD_RESET = 'PASSWORD_RESET', EMAIL_LOGIN = 'EMAIL_LOGIN', }每种类型在OTP_EXPIRATION_MS(otp-service.ts)中有独立的过期时长:
| 类型 | 过期时间 | 凭据形态 |
|---|---|---|
EMAIL_VERIFICATION | 24 小时 | randomUUID()链接 |
PASSWORD_RESET | 10 分钟 | randomUUID()链接 |
EMAIL_LOGIN | 10 分钟 | 6 位数字码(用户手动输入) |
状态机为PENDING / CONFIRMED,模型 schema 见 packages/core/shared/src/lib/ee/otp/otp-model.ts;数据库层对(identityId, type)有唯一约束,即每个身份每种类型同时最多一行。
4.2 重发语义:复用旧值,绝不延长寿命
重发(resend)是此处最微妙的设计:createAndSend发现已有PENDING且未过期的 OTP 时,直接重新投递同一个既有值,不触碰数据行。这意味着:
- 过期时间始终锚定在值的创建时刻,重发无法延长一个(可能已泄露的)OTP 的生命周期;
- 只有当旧值过期或被消费后,才会生成新值。
这正是 GIT-1733 修复的问题:旧实现的早退(early-return)让重发变成了静默的 204 空操作。设计考量记录于 000027 决策:之所以不在重发时铸造新码,是因为"用户往往正读着第一封邮件里的码",重新签发会让一半用户手里的码失效。
4.3 尝试计数:五猜即废,原始 SQL 计数
EMAIL_LOGIN的 6 位码只有 10^6 种组合,暴力枚举的预算必须被显式封顶。OTP 行带有attempts计数器,第 5 次错误猜测后凭据作废。计数用裸 SQL 完成(UPDATE "otp" SET "attempts" = "attempts" + 1 WHERE "id" = $1,见 otp-service.ts),原因有二:
- TypeORM 的
update会触碰updated列——而这正是过期与重发抑制检查读取的列,一旦被刷新,每次错误猜测反而会为攻击者多买 10 分钟窗口; - 读-改-写式的自增在并发验证下可能写回相同值,并行攻击下"五猜预算"将形同虚设;原子自增保证了计数正确性。
此外,源码中还有一层按身份的预算:MAX_ATTEMPTS_PER_IDENTITY = 10,配合IDENTITY_BUDGET_WINDOW_SECONDS = 3600(1 小时窗口),超出后confirm直接拒绝([otpService#confirm] identity guess budget exhausted)。
4.4 验证成功的清理:删除行而非标记 CONFIRMED
confirm成功后现在删除整行而非标记CONFIRMED。这同时是旧版 bug 的修复:updated是updateDate列,旧实现标记 CONFIRMED 会刷新它,导致该身份在成功验证后 10 分钟内无法再次申请新码。OtpState.CONFIRMED目前没有任何写入方,仅作为otpIsPending读取逻辑对历史遗留行的兼容判断保留。
4.5 版本边界与两个关键"坑"
坑 1:CE 曾经"有表无实"。在无密码登录工作之前,OTP 实体虽为所有版本注册,但otpModule只在app.ts的 CLOUD 与 ENTERPRISE 分支注册,且emailService.sendOtp在非付费版本上直接早退——CE 上表存在、迁移照跑,却永远发不出任何邮件。EMAIL_LOGIN改变了这一点:otpModule现在对 COMMUNITY 也注册,EMAIL_LOGIN是唯一从付费版发送门禁中豁免的类型,OTP 原语由此覆盖所有版本。但其上的登录流程并未跟随——000032 决策 把/otp/request、/otp/verify两条路由放进了仅在ApEdition.CLOUD且配置了 Turnstile 人机验证时注册的passwordlessAuthModule,因此该豁免目前不可达,CE 依然发不出任何码。两类链接型 OTP(验证/重置)仍是付费版专属。RBAC 基础类型属于 CE;SSO、Managed Auth、联邦 OAuth 为 EE/Cloud 专属。
坑 2:公共POST /v1/otp路由故意不能铸造登录码。该路由无认证、无rateLimit配置、也不套用任何注册防护,因此其CreateOtpRequestBody把type收窄为EMAIL_VERIFICATION | PASSWORD_RESET;EMAIL_LOGIN只能通过有速率限制且被门控的POST /v1/authentication/otp/request签发。若把枚举放宽回整个OtpType,等于向任何人开放一个无节流的"向任意地址投递可用登录码"原语。
坑 3:验证时必须重断言平台认证策略。在 Cloud 上,platformUtils.getPlatformIdForRequest对一切未认证请求返回 null,因此请求作用域分支在 Cloud 从不执行,平台只能在身份解析之后才可知。verifyCode因此会在解析出的平台上再次调用assertEmailAuthIsEnabled+assertDomainIsAllowed这一对断言;否则一个邮箱码就能把成员签入一个故意禁用了邮箱认证或移除了其域名的平台。之所以不在请求期断言,是因为按地址回报这些错误会把请求端点变成"存在性探测预言机"(existence oracle)。
坑 4:一个常量身兼两职。TEN_MINUTES既充当confirm的新鲜度检查阈值,又充当createAndSend中"已有 OTP 则早退"的重发抑制——在本次工作之前,重发在凭据过期前根本不可能发生,而请求端点仍返回 204。现在的重发会重新投递既有值且不触碰updated。
坑 5:email-service.ts对OtpType不穷举。frontendPath是一个只含两个成员的字面量对象,却用整个联合类型索引——新增OtpType成员会直接编译失败;其姊妹映射otpToTemplate类型为Record<string, EmailTemplateData>,能通过类型检查却在运行时把undefined交给发送器。
4.6 设计原则:为何用"打字的码"而非"魔法链接"
000027 决策 记录了关键背景:Microsoft Safe Links、Mimecast、Proofpoint 等邮件安全网关会以 GET 预取邮件中的 URL,且该行为与人工点击无法区分——一次性登录链接会被预取直接消费,用户自己点击时凭据已过期。打字输入的 6 位码从根上绕开了这一类问题。
五、企业本地认证:邮箱验证与密码重置
enterprise-local-authn/(enterprise-local-authn/)在 OTP 之上提供两个动作:
verifyEmail:确认 OTP 后把身份标记为已验证;resetPassword:确认 OTP 后更新密码哈希。
两者都属于 OTP 链接型(EMAIL_VERIFICATION/PASSWORD_RESET),并全程写入审计日志。对应的 DTO 与 ACL 类型定义在 packages/core/shared/src/lib/ee/authn/。
六、项目级 RBAC:权限模型与请求授权接线
6.1 核心判定函数
RBAC 的两个核心函数位于 rbac-service.ts:
assertPrinicpalAccessToProject({ principal, permission, projectId })(注意:源码中确实拼错了Prinicpal,文档已注明):按principal.type分派——UNKNOWN / WORKER / ONBOARDING:一律拒绝;USER:取该用户在项目中的角色,按角色 ID 与路由所需permission比对,无权限则抛AUTHORIZATION;ENGINE:仅允许 principal 的projectId与目标一致;SERVICE:要求项目所属平台与 principal 的平台一致。
assertUserHasPermissionToFlow({ principal, operationType, projectId }):把FlowOperationType映射为Permission,例如LOCK_AND_PUBLISH/CHANGE_STATUS映射为UPDATE_FLOW_STATUS,再委托给上面的项目级断言;同时该方法仅对CLOUD/ENTERPRISE版本生效,CE 上直接放行。
6.2 请求授权接线
RBAC 被接入统一请求授权层:assertPrinicpalAccessToProject在 packages/server/api/src/app/core/security/v2/authz/authorize.ts 中被调用,覆盖每一个项目作用域的请求(project-scoped request)。同目录下的 authorization-middleware.ts 负责从请求解析项目 ID 并挂载中间件。
6.3 三个授权钩子(preHandler)
ee-authorization.ts 导出三个 Fastify 钩子工厂:
| 钩子 | 行为 |
|---|---|
platformMustHaveFeatureEnabled(handler) | 读取请求 principal 的平台,调用handler(platform)判定;未开启时抛402FEATURE_DISABLED |
projectMustBeTeamType | 仅对 USER / SERVICE principal 生效,要求项目为团队类型(Team),否则拒绝 |
platformMustBeOwnedByCurrentUser | 校验user.platformRole === PlatformRole.ADMIN且user.platformId === platformId;API Key(SERVICE 类型)自动放行 |
七、Managed Auth:面向 Embedding 的 JWT 交换
Managed Auth 是给 Embedding 场景使用的托管登录:由宿主应用(而非 Activepieces 前端)完成身份认证后,通过 managed-authn-controller.ts 与 managed-authn-service.ts 交换得到 Activepieces JWT。它由embeddingEnabled(签名密钥)独立门控,与 SSO 的ssoEnabled互不干扰;lib/external-token-extractor.ts负责从外部 token 中提取身份。前端对应实现位于 packages/web/src/features/authentication/ 的 managed auth client。
八、认证速率限制:默认保护"零",逐路由显式加入
packages/server/api/src/app/core/security/rate-limit.ts 是本文档特别强调的一个安全要点:
rateLimitModule注册@fastify/rate-limit时使用global: false——默认不保护任何路由;- 因此,每个会发送邮件或做认证工作的公共端点都必须通过
config.rateLimit逐路由显式接入,典型模式即authentication.controller.ts/otp-controller.ts中的API_RATE_LIMIT_AUTHN_*系列; - 速率限制插件仅在
API_RATE_LIMIT_AUTHN_ENABLED为真时注册,key 使用真实客户端 IP(支持CLIENT_REAL_IP_HEADER),计数存储于 Redis; - 预置的限流参数:
authnRateLimit(API_RATE_LIMIT_AUTHN_MAX与API_RATE_LIMIT_AUTHN_WINDOW)与emailCodeRateLimit(API_RATE_LIMIT_EMAIL_CODE_MAX),后者专门用于邮箱码端点。
九、版本能力矩阵速查
| 能力 | CE(Community) | EE / Cloud |
|---|---|---|
RBAC 基础类型(assertPrinicpalAccessToProject等) | ✅ | ✅ |
| OTP 原语(模块注册、发送管线) | ✅(自无密码工作后) | ✅ |
| 邮箱验证 / 密码重置(链接型 OTP) | ❌ | ✅ |
无密码邮箱登录码(EMAIL_LOGIN) | ❌(原语豁免但登录路由仅 Cloud+Turnstile) | Cloud 限定 |
| SAML SSO、联邦 OAuth | ❌ | ✅ |
| Managed Auth | ❌ | ✅(受embeddingEnabled门控) |
十、关键文件索引
- EE 认证模块根:packages/server/api/src/app/ee/authentication/——
saml-authn/、federated-authn/、otp/(现移至 packages/server/api/src/app/authentication/otp/)、enterprise-local-authn/、project-role/、ee-authorization.ts - RBAC 请求授权接线:packages/server/api/src/app/core/security/v2/authz/
- Managed Auth:packages/server/api/src/app/ee/managed-authn/
- 共享企业认证导出 / DTO:packages/core/shared/src/lib/ee/authn/
- OTP 模型与枚举:packages/core/shared/src/lib/ee/otp/
- 前端认证 UI:packages/web/src/features/authentication/
- SSO 设置页 / SAML 对话框:packages/web/src/app/routes/platform/security/sso/
- SAML ACS 落地页:packages/web/src/app/routes/authenticate/
- 认证速率限制:packages/server/api/src/app/core/security/rate-limit.ts
- 相关决策记录:000027 邮箱登录码设计、000032 邮箱登录码仅限 Cloud 且需人机验证
结语
Activepieces 的 EE 认证层不是一个独立的"第二套系统",而是在 CE 认证原语上按版本叠加的模块化组合:SAML 与联邦 OAuth 收敛到federatedAuthn单一入口,OTP 用"类型化码 + 过期锚定 + 尝试预算"的精细化设计兼顾可用性与防爆破,RBAC 通过authorize.ts统一接入每个项目作用域请求。部署企业版时,请务必核对ssoEnabled/embeddingEnabled计划开关、SSO 域名验证状态、联邦 OAuth 的FRONTEND_URL约束,以及每个公共认证端点的逐路由速率限制配置——这些正是该模块多年迭代沉淀下来的全部边界条件。
【免费下载链接】activepiecesAI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考