//! `client-metadata.json` + `jwks.json` handlers. //! //! `client-metadata.json` advertises starhaven as a confidential OAuth client; //! `jwks.json` publishes the public half of its signing keys so PDSes can //! verify `private_key_jwt` client assertions. use atproto_identity::key::to_public; use axum::extract::State; use axum::http::{header, HeaderValue}; use axum::response::{IntoResponse, Response}; use axum::Json; use serde_json::json; use crate::state::AppState; /// `GET /client-metadata.json` -- the OAuth client document. pub async fn client_metadata(State(state): State) -> Response { let config = &state.config; let doc = json!({ "client_id": config.oauth_client_id(), "client_name": "starhaven", "client_uri": config.external_base(), "redirect_uris": [config.oauth_redirect_uri()], "jwks_uri": config.jwks_uri(), "scope": crate::config::OAUTH_SCOPE.as_str(), "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "application_type": "web", "token_endpoint_auth_method": "private_key_jwt", "token_endpoint_auth_signing_alg": "ES256", "dpop_bound_access_tokens": true, "subject_type": "public", }); json_cors(Json(doc)) } /// `GET /jwks.json` -- public keys for client-assertion verification. pub async fn jwks(State(state): State) -> Response { // `jwk::generate` embeds key material verbatim, so never hand it the // private key -- that would publish the private scalar (`d`). let keys: Vec<_> = to_public(&state.secrets.oauth_private_key) .ok() .and_then(|key| atproto_oauth::jwk::generate(&key).ok()) .into_iter() .collect(); json_cors(Json(json!({ "keys": keys }))) } /// Attach `Access-Control-Allow-Origin: *`, since PDS/AS software fetches /// these two endpoints cross-origin. fn json_cors(body: Json) -> Response { ( [( header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"), )], body, ) .into_response() }