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 coderecoverysourcecause 是 machine contract。
  • ResourceSnapshot 可以保留原始 error object,供调用方诊断和重试控制使用。
  • lifecycle events 与 tracing 只投影 secret-safe ErrorSummaryerrorName、可选 errorKinderrorCoderecovery,不复制 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 所需的 kindsourceretryable 应从原始 ClientError 读取。

TS SDK 的 ClientErrorKind 固定为 authorizationtransportserverprotocolstorageconfigurationunauthenticatedunauthorizedcancelledtimeoutinternal。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,包括 AuthFlowDiagnosisDiagnosedResult、共享 AuthFlowOperation 名称(例如 projection.config_fetchoidc.callbackoidc.token_refreshpropagation.forwardforward_auth.check),以及较新的 server operation,如 dashboard_auth.checkcreds_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 规则

  • 不要从 Displaysource.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:用户无法自行解决。

它优于 retryablereauth_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.rspackages/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 应断言 kindcoderecovery;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.