A library for ATProtocol identities.
at main 3.4 kB view raw
1//! OAuth client metadata endpoint handler. 2//! 3//! RFC 7591 compliant client metadata for dynamic registration 4//! and authorization server discovery. 5 6use atproto_identity::key::to_public; 7use axum::{Json, response::IntoResponse}; 8use serde::Serialize; 9 10use crate::state::OAuthClientConfig; 11use atproto_oauth::jwk::{WrappedJsonWebKeySet, generate}; 12 13// See also: https://atproto.com/specs/oauth#client-id-metadata-document 14#[derive(Serialize, Default)] 15struct AuthMetadata<'a> { 16 client_id: String, 17 dpop_bound_access_tokens: bool, 18 application_type: &'a str, 19 redirect_uris: Vec<String>, 20 grant_types: Vec<&'a str>, 21 response_types: Vec<&'a str>, 22 scope: String, 23 token_endpoint_auth_method: &'a str, 24 subject_type: &'a str, 25 token_endpoint_auth_signing_alg: &'a str, 26 27 #[serde(skip_serializing_if = "Option::is_none")] 28 #[serde(default)] 29 jwks_uri: Option<String>, 30 31 #[serde(skip_serializing_if = "Option::is_none")] 32 #[serde(default)] 33 jwks: Option<WrappedJsonWebKeySet>, 34 35 #[serde(skip_serializing_if = "Option::is_none")] 36 #[serde(default)] 37 client_name: Option<String>, 38 39 #[serde(skip_serializing_if = "Option::is_none")] 40 #[serde(default)] 41 client_uri: Option<String>, 42 43 #[serde(skip_serializing_if = "Option::is_none")] 44 #[serde(default)] 45 logo_uri: Option<String>, 46 47 #[serde(skip_serializing_if = "Option::is_none")] 48 #[serde(default)] 49 tos_uri: Option<String>, 50 51 #[serde(skip_serializing_if = "Option::is_none")] 52 #[serde(default)] 53 policy_uri: Option<String>, 54} 55 56/// Handles requests for OAuth client metadata. 57/// 58/// Returns RFC 7591 compliant client metadata for dynamic client registration. 59pub async fn handle_oauth_metadata(oauth_client_config: OAuthClientConfig) -> impl IntoResponse { 60 // Determine jwks_uri and jwks based on configuration 61 let (jwks_uri, jwks) = if let Some(ref uri) = oauth_client_config.jwks_uri { 62 // If jwks_uri is provided, use it and don't include inline jwks 63 (Some(uri.clone()), None) 64 } else { 65 // If no jwks_uri, build inline jwks from signing keys 66 let mut jwks_keys = Vec::new(); 67 for key_data in &oauth_client_config.signing_keys { 68 if let Ok(public_key_data) = to_public(key_data) 69 && let Ok(jwk) = generate(&public_key_data) 70 { 71 jwks_keys.push(jwk); 72 } 73 } 74 (None, Some(WrappedJsonWebKeySet { keys: jwks_keys })) 75 }; 76 77 let resp = AuthMetadata { 78 application_type: "web", 79 client_id: oauth_client_config.client_id.clone(), 80 dpop_bound_access_tokens: true, 81 grant_types: vec!["authorization_code", "refresh_token"], 82 redirect_uris: vec![oauth_client_config.redirect_uris.clone()], 83 response_types: vec!["code"], 84 scope: oauth_client_config.scope().to_string(), 85 token_endpoint_auth_method: "private_key_jwt", 86 token_endpoint_auth_signing_alg: "ES256", 87 subject_type: "public", 88 jwks_uri, 89 jwks, 90 client_name: oauth_client_config.client_name.clone(), 91 client_uri: oauth_client_config.client_uri.clone(), 92 logo_uri: oauth_client_config.logo_uri.clone(), 93 tos_uri: oauth_client_config.tos_uri.clone(), 94 policy_uri: oauth_client_config.policy_uri.clone(), 95 }; 96 Json(resp) 97}