metadata.rs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. //! Authorization-server metadata (RFC 8414).
  2. //!
  3. //! `GET /.well-known/oauth-authorization-server` returns the AS document. The
  4. //! only server-state-dependent member is `scopes_supported`: it is read from
  5. //! the enabled `scopes` table (the single source of truth, populated at runtime
  6. //! via `config sync` / `scope create`) on every request. The catalog is mutated
  7. //! out of process by the CLI, so an in-process cache could not be invalidated
  8. //! across replicas; a per-request read makes a mutation visible immediately with
  9. //! no restart. When Postgres is unavailable the endpoint fails closed with `503
  10. //! temporarily_unavailable` rather than advertising an unknown scope list.
  11. //!
  12. //! No `jwks_uri`: the AS signs nothing (there is no AS JWT-signing key), and
  13. //! `code_challenge_methods_supported` advertises `S256` only — `plain` is
  14. //! never offered.
  15. use axum::{Json, extract::State};
  16. use serde::Serialize;
  17. use crate::{db::scope_repo, error::AppError, state::AppState};
  18. /// The five grant types this server supports (RFC 6749, RFC 8628, RFC 8693).
  19. pub const GRANT_TYPES_SUPPORTED: [&str; 5] = [
  20. "authorization_code",
  21. "refresh_token",
  22. "client_credentials",
  23. "urn:ietf:params:oauth:grant-type:device_code",
  24. "urn:ietf:params:oauth:grant-type:token-exchange",
  25. ];
  26. /// Client-authentication methods accepted at the token endpoint.
  27. ///
  28. /// `none` covers public clients; `tls_client_auth` and
  29. /// `self_signed_tls_client_auth` are the RFC 8705 mTLS methods.
  30. pub const TOKEN_AUTH_METHODS: [&str; 6] = [
  31. "client_secret_basic",
  32. "client_secret_post",
  33. "none",
  34. "private_key_jwt",
  35. "tls_client_auth",
  36. "self_signed_tls_client_auth",
  37. ];
  38. /// Methods accepted at the revocation endpoint (RFC 7009). Public clients may
  39. /// revoke their own tokens, so `none` is included.
  40. pub const REVOCATION_AUTH_METHODS: [&str; 6] = TOKEN_AUTH_METHODS;
  41. /// Methods accepted at the introspection endpoint (RFC 7662). Introspection
  42. /// requires an authenticated caller, so `none` is not offered.
  43. pub const INTROSPECTION_AUTH_METHODS: [&str; 5] = [
  44. "client_secret_basic",
  45. "client_secret_post",
  46. "private_key_jwt",
  47. "tls_client_auth",
  48. "self_signed_tls_client_auth",
  49. ];
  50. /// JWS algorithms accepted for `private_key_jwt` client assertions
  51. /// (`token_endpoint_auth_signing_alg_values_supported`); kept in sync with
  52. /// `protocol::client_auth::assertion::ALLOWED_ALGS`.
  53. pub const CLIENT_ASSERTION_SIGNING_ALG_VALUES: [&str; 4] = ["ES256", "EdDSA", "RS256", "PS256"];
  54. /// JWS algorithms accepted for DPoP proofs (`dpop_signing_alg_values_supported`).
  55. pub const DPOP_SIGNING_ALG_VALUES: [&str; 3] = ["ES256", "EdDSA", "RS256"];
  56. /// RFC 8414 authorization-server metadata document.
  57. #[derive(Debug, Serialize)]
  58. pub struct AuthorizationServerMetadata {
  59. /// Public issuer identifier.
  60. pub issuer: String,
  61. /// Authorization endpoint.
  62. pub authorization_endpoint: String,
  63. /// Token endpoint.
  64. pub token_endpoint: String,
  65. /// Dynamic client-registration endpoint.
  66. pub registration_endpoint: String,
  67. /// Token-introspection endpoint.
  68. pub introspection_endpoint: String,
  69. /// Token-revocation endpoint.
  70. pub revocation_endpoint: String,
  71. /// Pushed authorization request endpoint.
  72. pub pushed_authorization_request_endpoint: String,
  73. /// Device authorization endpoint.
  74. pub device_authorization_endpoint: String,
  75. /// Response types supported (code only).
  76. pub response_types_supported: [&'static str; 1],
  77. /// Grant types supported.
  78. pub grant_types_supported: [&'static str; 5],
  79. /// Token-endpoint client-authentication methods.
  80. pub token_endpoint_auth_methods_supported: [&'static str; 6],
  81. /// Signing algorithms for `private_key_jwt` client assertions.
  82. pub token_endpoint_auth_signing_alg_values_supported: [&'static str; 4],
  83. /// Introspection-endpoint client-authentication methods.
  84. pub introspection_endpoint_auth_methods_supported: [&'static str; 5],
  85. /// Revocation-endpoint client-authentication methods.
  86. pub revocation_endpoint_auth_methods_supported: [&'static str; 6],
  87. /// PKCE code-challenge methods (`S256` only; `plain` is never offered).
  88. pub code_challenge_methods_supported: [&'static str; 1],
  89. /// DPoP proof signing algorithms.
  90. pub dpop_signing_alg_values_supported: [&'static str; 3],
  91. /// Whether mTLS-bound access tokens are supported (RFC 8705).
  92. pub tls_client_certificate_bound_access_tokens: bool,
  93. /// Whether the `iss` authorization-response parameter is supported (RFC 9207).
  94. pub authorization_response_iss_parameter_supported: bool,
  95. /// Enabled scope catalog (single source of truth: the `scopes` table).
  96. pub scopes_supported: Vec<String>,
  97. }
  98. impl AuthorizationServerMetadata {
  99. /// Build the document for `issuer` with the given enabled scope catalog.
  100. ///
  101. /// `issuer` is validated elsewhere to be absolute `https` with no trailing
  102. /// slash, so endpoint paths append directly.
  103. #[must_use]
  104. pub fn for_issuer(issuer: &str, scopes_supported: Vec<String>) -> Self {
  105. Self {
  106. issuer: issuer.to_owned(),
  107. authorization_endpoint: endpoint(issuer, "/authorize"),
  108. token_endpoint: endpoint(issuer, "/token"),
  109. registration_endpoint: endpoint(issuer, "/register"),
  110. introspection_endpoint: endpoint(issuer, "/introspect"),
  111. revocation_endpoint: endpoint(issuer, "/revoke"),
  112. pushed_authorization_request_endpoint: endpoint(issuer, "/par"),
  113. device_authorization_endpoint: endpoint(issuer, "/device_authorization"),
  114. response_types_supported: ["code"],
  115. grant_types_supported: GRANT_TYPES_SUPPORTED,
  116. token_endpoint_auth_methods_supported: TOKEN_AUTH_METHODS,
  117. token_endpoint_auth_signing_alg_values_supported: CLIENT_ASSERTION_SIGNING_ALG_VALUES,
  118. introspection_endpoint_auth_methods_supported: INTROSPECTION_AUTH_METHODS,
  119. revocation_endpoint_auth_methods_supported: REVOCATION_AUTH_METHODS,
  120. code_challenge_methods_supported: ["S256"],
  121. dpop_signing_alg_values_supported: DPOP_SIGNING_ALG_VALUES,
  122. tls_client_certificate_bound_access_tokens: true,
  123. authorization_response_iss_parameter_supported: true,
  124. scopes_supported,
  125. }
  126. }
  127. }
  128. /// Append an endpoint path to the (trailing-slash-free) issuer.
  129. fn endpoint(issuer: &str, path: &str) -> String {
  130. format!("{issuer}{path}")
  131. }
  132. /// `GET /.well-known/oauth-authorization-server`.
  133. ///
  134. /// # Errors
  135. /// Returns `503 temporarily_unavailable` when Postgres is unreachable and the
  136. /// enabled scope list cannot be read.
  137. pub async fn metadata(
  138. State(state): State<AppState>,
  139. ) -> Result<Json<AuthorizationServerMetadata>, AppError> {
  140. let rows = scope_repo::list_enabled(&state.pool).await?;
  141. let mut scopes: Vec<String> = rows.into_iter().map(|row| row.name).collect();
  142. scopes.sort();
  143. Ok(Json(AuthorizationServerMetadata::for_issuer(
  144. &state.config.issuer,
  145. scopes,
  146. )))
  147. }
  148. #[cfg(test)]
  149. mod tests;