Skip to content

错误系统设计 ​

SecurityDept 将内部诊断与公开错误展示分离。目标是在保留可运维日志的同时,避免向 browser 和 API consumer 泄露 provider、token、storage 或 configuration 细节。

Structured diagnosis 是与 public error presentation 分离的另一层契约。面向 operator 的、secret-safe machine-readable auth-flow diagnosis 属于 securitydept-utils::observability,应独立保留,而不是被折叠成 adopter-facing error copy。

当前模型 ​

当前模型有三层彼此独立的语义:

  • 内部错误枚举,通常由 snafu 实现。
  • 通过 ToHttpStatus 或 route/service status helpers 映射 HTTP status。
  • 通过 ToErrorPresentation 生成安全的 public presentation。

TS SDK 对应采用四层边界:

  • ClientError 是公开异步流程的 canonical runtime exception;kind、namespaced code、recovery、source 与 cause 是 machine contract。
  • client Resource 的 failure snapshot、lifecycle failure event 与 registry errors 保留原始 ClientError,供进程内诊断与恢复使用。不得直接序列化完整 error 或其 cause。
  • logging/tracing 通过 describeError() 派生 secret-safe ErrorSummary:errorName、可选 errorKind、errorCode 与 recovery,不复制 runtime message。
  • readErrorPresentationDescriptor() 只读取显式安全 presentation、domain code mapping 或 foundation generic kind copy,绝不把 Error.message 当成用户文案。

在 TypeScript API 中,ServerErrorPresentation 表示服务端提供的安全载荷,ErrorCodePresentation 配置稳定 error code 对应的 host 文案,ErrorPresentationDescriptor 是最终 UI 投影。最终投影只包含 code、展示文案、recovery、tone 与可选 recovery action;machine policy 所需的 kind、source、retryable 应从原始 ClientError 读取。

TS SDK 的 ClientErrorKind 固定为 authorization、transport、server、protocol、storage、configuration、unauthenticated、unauthorized、cancelled、timeout 与 internal。Schema/options 输入错误属于 configuration;remote 或 persisted payload 错误属于 protocol;未知错误在公开 client operation boundary 被包装为 internal 并保留 cause。

共享类型位于 securitydept-utils:

  • ErrorPresentation
  • UserRecovery
  • ServerErrorKind
  • ServerErrorDescriptor
  • ServerErrorEnvelope
  • ToErrorPresentation

相关 diagnosis vocabulary 独立位于 securitydept-utils::observability,包括 AuthFlowDiagnosis、DiagnosedResult、共享 AuthFlowOperation 名称(例如 projection.config_fetch、oidc.callback、oidc.token_refresh、propagation.forward、forward_auth.check),以及 server operation,如 dashboard_auth.check 与 creds_manage.group.* / creds_manage.entry.*。

通过 shared envelope 渲染的 server response 使用以下形状:

json
{
  "success": false,
  "status": 401,
  "error": {
    "kind": "unauthenticated",
    "code": "backend_oidc_mode.bearer_token_required",
    "message": "A bearer access token is required for this endpoint.",
    "recovery": "reauthenticate",
    "presentation": {
      "code": "backend_oidc_mode.bearer_token_required",
      "message": "A bearer access token is required for this endpoint.",
      "recovery": "reauthenticate"
    }
  }
}

顶层 code / message / recovery 是稳定 consumer convenience fields。嵌套的 presentation 保留原始 presentation object,供已经消费它的客户端使用。

Public Response 规则 ​

  • 不要从 Display、source.to_string()、provider response body、transport error 或任意 lower-layer text 派生 public message。
  • 如果 lower-layer condition 对用户有意义,先提升成 typed variant 或 typed reason enum,再公开。
  • 只有当 condition 安全且可行动时,public message 才能具体。
  • 内部日志应包含完整 error chain;public response 只包含 sanitized code、message 与 recovery。
  • Structured diagnosis fields 应保持 machine-readable 且 secret-safe。它们用于日志与 operator tooling,不应被当作生成 public browser/API copy 的捷径。

Recovery Vocabulary ​

UserRecovery 是公开 action hint:

  • none:不暗示用户恢复动作。
  • retry:可以合理重试同一操作。
  • restart_flow:重新开始当前 auth flow。
  • reauthenticate:重新登录或重新获取凭证。
  • contact_support:用户无法自行解决。

它优于 retryable、reauth_required 等多个布尔字段,因为枚举能避免含义模糊的组合。

Disclosure Policy ​

分类Public message 风格示例
Safe and specific解释可恢复问题invalid redirect URL、expired login request、duplicate callback state
Safe but generic保持上下文宽泛session expired、authentication required、access denied
Internal only永不公开 raw detailmetadata fetch errors、introspection transport failures、storage errors、crypto / sealing failures

Route Response Exceptions ​

大多数普通 server failures 使用 ServerErrorEnvelope,但部分 route families 有意不使用:

  • Basic Auth challenge routes 必须保留 WWW-Authenticate。
  • Basic Auth 不存在 credential-revocation route;本地投影清理不得伪装成 server protocol response。
  • ForwardAuth challenge routes 必须保留 proxy protocol semantics。
  • Backend-mode metadata/redeem not-found 是业务 404,不是 shared server failure envelope。
  • Propagation forwarding 保留 underlying upstream status / presentation,而不是改写成 route-local generic error。
  • Static web UI fallback errors 来自底层 static-file service。

apps/server/src/routes/policy.rs 中的 mounted-route policy table 记录这些边界,防止新 route 静默回退 response shape。

Implementation Owners ​

  • packages/utils/src/error.rs:shared presentation 与 envelope types。
  • packages/oidc-client/src/error.rs:OIDC public presentation。
  • packages/oauth-resource-server/src/error.rs:resource-server presentation。
  • packages/creds/src/error.rs:credential verification presentation。
  • packages/creds-manage/src/error.rs:credential-management presentation。
  • packages/session-context/src/lib.rs 与 packages/session-context/src/service.rs:session presentation。
  • packages/basic-auth-context/src/service.rs:Basic Auth context presentation。
  • packages/token-set-context/src/**/error.rs 与 service modules:token-set、substrate、propagation、forwarder presentation。
  • apps/server/src/error.rs:shared server envelope rendering。
  • apps/server/src/routes/policy.rs:mounted-route response-shape policy。
  • apps/webui:host-side presentation components 与 recovery links。

维护规则 ​

  • 新 public route 必须有明确 response-shape classification。
  • 能到达 adopter-facing response 的新 error variant 必须实现 safe presentation。
  • 如果会破坏 browser 或 proxy semantics,protocol-specific routes 不得被“统一”进 shared JSON envelope。
  • Tests 应断言 kind、code 与 recovery;message text 可以验证,但不能成为唯一 machine contract。
  • TS SDK public operation 不得让未知裸 Error 逃逸;使用 ClientError.fromUnknown(...) 建立稳定 boundary,已有 ClientError 不重复包装。
  • Error code 必须由 domain owner 以 const + type 集中定义,并使用 domain namespace。
  • Presentation mapping 由 domain owner 提供;foundation 不包含 popup、OIDC 等应用层文案。

English | 中文

MIT License.