this repo has no description
at main 7.7 kB view raw
1use crate::oauth::jwks::{JwkSet, create_jwk_set}; 2use crate::state::AppState; 3use axum::{Json, extract::State}; 4use serde::{Deserialize, Serialize}; 5 6#[derive(Debug, Serialize, Deserialize)] 7pub struct ProtectedResourceMetadata { 8 pub resource: String, 9 pub authorization_servers: Vec<String>, 10 pub bearer_methods_supported: Vec<String>, 11 pub scopes_supported: Vec<String>, 12 #[serde(skip_serializing_if = "Option::is_none")] 13 pub resource_documentation: Option<String>, 14} 15 16#[derive(Debug, Serialize, Deserialize)] 17pub struct AuthorizationServerMetadata { 18 pub issuer: String, 19 pub authorization_endpoint: String, 20 pub token_endpoint: String, 21 pub jwks_uri: String, 22 #[serde(skip_serializing_if = "Option::is_none")] 23 pub registration_endpoint: Option<String>, 24 #[serde(skip_serializing_if = "Option::is_none")] 25 pub scopes_supported: Option<Vec<String>>, 26 pub response_types_supported: Vec<String>, 27 #[serde(skip_serializing_if = "Option::is_none")] 28 pub response_modes_supported: Option<Vec<String>>, 29 #[serde(skip_serializing_if = "Option::is_none")] 30 pub grant_types_supported: Option<Vec<String>>, 31 #[serde(skip_serializing_if = "Option::is_none")] 32 pub token_endpoint_auth_methods_supported: Option<Vec<String>>, 33 #[serde(skip_serializing_if = "Option::is_none")] 34 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>, 35 #[serde(skip_serializing_if = "Option::is_none")] 36 pub code_challenge_methods_supported: Option<Vec<String>>, 37 #[serde(skip_serializing_if = "Option::is_none")] 38 pub pushed_authorization_request_endpoint: Option<String>, 39 #[serde(skip_serializing_if = "Option::is_none")] 40 pub require_pushed_authorization_requests: Option<bool>, 41 #[serde(skip_serializing_if = "Option::is_none")] 42 pub require_request_uri_registration: Option<bool>, 43 #[serde(skip_serializing_if = "Option::is_none")] 44 pub dpop_signing_alg_values_supported: Option<Vec<String>>, 45 #[serde(skip_serializing_if = "Option::is_none")] 46 pub authorization_response_iss_parameter_supported: Option<bool>, 47 #[serde(skip_serializing_if = "Option::is_none")] 48 pub revocation_endpoint: Option<String>, 49 #[serde(skip_serializing_if = "Option::is_none")] 50 pub introspection_endpoint: Option<String>, 51 #[serde(skip_serializing_if = "Option::is_none")] 52 pub client_id_metadata_document_supported: Option<bool>, 53} 54 55pub async fn oauth_protected_resource( 56 State(_state): State<AppState>, 57) -> Json<ProtectedResourceMetadata> { 58 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 59 let public_url = format!("https://{}", pds_hostname); 60 Json(ProtectedResourceMetadata { 61 resource: public_url.clone(), 62 authorization_servers: vec![public_url], 63 bearer_methods_supported: vec!["header".to_string()], 64 scopes_supported: vec![], 65 resource_documentation: Some("https://atproto.com".to_string()), 66 }) 67} 68 69pub async fn oauth_authorization_server( 70 State(_state): State<AppState>, 71) -> Json<AuthorizationServerMetadata> { 72 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 73 let issuer = format!("https://{}", pds_hostname); 74 Json(AuthorizationServerMetadata { 75 issuer: issuer.clone(), 76 authorization_endpoint: format!("{}/oauth/authorize", issuer), 77 token_endpoint: format!("{}/oauth/token", issuer), 78 jwks_uri: format!("{}/oauth/jwks", issuer), 79 registration_endpoint: None, 80 scopes_supported: Some(vec![ 81 "atproto".to_string(), 82 "transition:generic".to_string(), 83 "transition:chat.bsky".to_string(), 84 "transition:email".to_string(), 85 "repo:*".to_string(), 86 "repo:*?action=create".to_string(), 87 "repo:*?action=read".to_string(), 88 "repo:*?action=update".to_string(), 89 "repo:*?action=delete".to_string(), 90 "blob:*/*".to_string(), 91 "rpc:*".to_string(), 92 "account:*".to_string(), 93 "account:*?action=read".to_string(), 94 "account:*?action=write".to_string(), 95 "identity:*".to_string(), 96 ]), 97 response_types_supported: vec!["code".to_string()], 98 response_modes_supported: Some(vec!["query".to_string(), "fragment".to_string()]), 99 grant_types_supported: Some(vec![ 100 "authorization_code".to_string(), 101 "refresh_token".to_string(), 102 ]), 103 token_endpoint_auth_methods_supported: Some(vec![ 104 "none".to_string(), 105 "private_key_jwt".to_string(), 106 ]), 107 token_endpoint_auth_signing_alg_values_supported: Some(vec![ 108 "ES256".to_string(), 109 "ES384".to_string(), 110 "ES512".to_string(), 111 "EdDSA".to_string(), 112 ]), 113 code_challenge_methods_supported: Some(vec!["S256".to_string()]), 114 pushed_authorization_request_endpoint: Some(format!("{}/oauth/par", issuer)), 115 require_pushed_authorization_requests: Some(true), 116 require_request_uri_registration: Some(true), 117 dpop_signing_alg_values_supported: Some(vec![ 118 "ES256".to_string(), 119 "ES384".to_string(), 120 "ES512".to_string(), 121 "EdDSA".to_string(), 122 ]), 123 authorization_response_iss_parameter_supported: Some(true), 124 revocation_endpoint: Some(format!("{}/oauth/revoke", issuer)), 125 introspection_endpoint: Some(format!("{}/oauth/introspect", issuer)), 126 client_id_metadata_document_supported: Some(true), 127 }) 128} 129 130pub async fn oauth_jwks(State(_state): State<AppState>) -> Json<JwkSet> { 131 use crate::config::AuthConfig; 132 use crate::oauth::jwks::Jwk; 133 let config = AuthConfig::get(); 134 let server_key = Jwk { 135 kty: "EC".to_string(), 136 key_use: Some("sig".to_string()), 137 kid: Some(config.signing_key_id.clone()), 138 alg: Some("ES256".to_string()), 139 crv: Some("P-256".to_string()), 140 x: Some(config.signing_key_x.clone()), 141 y: Some(config.signing_key_y.clone()), 142 }; 143 Json(create_jwk_set(vec![server_key])) 144} 145 146#[derive(Debug, Serialize, Deserialize)] 147pub struct FrontendClientMetadata { 148 pub client_id: String, 149 pub client_name: String, 150 pub client_uri: String, 151 pub redirect_uris: Vec<String>, 152 pub grant_types: Vec<String>, 153 pub response_types: Vec<String>, 154 pub scope: String, 155 pub token_endpoint_auth_method: String, 156 pub application_type: String, 157 pub dpop_bound_access_tokens: bool, 158} 159 160pub async fn frontend_client_metadata( 161 State(_state): State<AppState>, 162) -> Json<FrontendClientMetadata> { 163 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 164 let base_url = format!("https://{}", pds_hostname); 165 let client_id = format!("{}/oauth/client-metadata.json", base_url); 166 Json(FrontendClientMetadata { 167 client_id, 168 client_name: "PDS Account Manager".to_string(), 169 client_uri: base_url.clone(), 170 redirect_uris: vec![ 171 format!("{}/app/", base_url), 172 format!("{}/app/migrate", base_url), 173 ], 174 grant_types: vec![ 175 "authorization_code".to_string(), 176 "refresh_token".to_string(), 177 ], 178 response_types: vec!["code".to_string()], 179 scope: "atproto transition:generic repo:* blob:*/* rpc:* rpc:com.atproto.server.createAccount?aud=* account:* identity:*" 180 .to_string(), 181 token_endpoint_auth_method: "none".to_string(), 182 application_type: "web".to_string(), 183 dpop_bound_access_tokens: true, 184 }) 185}