1use crate::errors::{AuthError, Result};
7use crate::oauth2_server::OAuth2Server; use serde::{Deserialize, Serialize};
11#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AuthorizationServerMetadata {
16 pub issuer: String,
18
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub authorization_endpoint: Option<String>,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub token_endpoint: Option<String>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub jwks_uri: Option<String>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub registration_endpoint: Option<String>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub scopes_supported: Option<Vec<String>>,
38
39 pub response_types_supported: Vec<String>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub response_modes_supported: Option<Vec<String>>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub grant_types_supported: Option<Vec<String>>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub token_endpoint_auth_methods_supported: Option<Vec<String>>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub revocation_endpoint_auth_methods_supported: Option<Vec<String>>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub introspection_endpoint_auth_methods_supported: Option<Vec<String>>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub code_challenge_methods_supported: Option<Vec<String>>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub revocation_endpoint: Option<String>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub introspection_endpoint: Option<String>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub authorization_response_iss_parameter_supported: Option<bool>,
77
78 #[serde(skip_serializing_if = "Option::is_none")]
81 pub pushed_authorization_request_endpoint: Option<String>,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub require_pushed_authorization_requests: Option<bool>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
90 pub device_authorization_endpoint: Option<String>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
95 pub dpop_signing_alg_values_supported: Option<Vec<String>>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
100 pub mtls_endpoint_aliases: Option<MtlsEndpointAliases>,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub tls_client_certificate_bound_access_tokens: Option<bool>,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
109 pub userinfo_endpoint: Option<String>,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub subject_types_supported: Option<Vec<String>>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub id_token_signing_alg_values_supported: Option<Vec<String>>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
126
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub claims_supported: Option<Vec<String>>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub claims_parameter_supported: Option<bool>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub request_parameter_supported: Option<bool>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub request_uri_parameter_supported: Option<bool>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct MtlsEndpointAliases {
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub token_endpoint: Option<String>,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub revocation_endpoint: Option<String>,
154
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub introspection_endpoint: Option<String>,
158}
159
160pub struct MetadataBuilder {
162 metadata: AuthorizationServerMetadata,
163}
164
165impl MetadataBuilder {
166 pub fn new(issuer: String) -> Self {
168 Self {
169 metadata: AuthorizationServerMetadata {
170 issuer,
171 authorization_endpoint: None,
172 token_endpoint: None,
173 jwks_uri: None,
174 registration_endpoint: None,
175 scopes_supported: None,
176 response_types_supported: vec!["code".to_string()],
177 response_modes_supported: None,
178 grant_types_supported: None,
179 token_endpoint_auth_methods_supported: None,
180 revocation_endpoint_auth_methods_supported: None,
181 introspection_endpoint_auth_methods_supported: None,
182 code_challenge_methods_supported: None,
183 revocation_endpoint: None,
184 introspection_endpoint: None,
185 authorization_response_iss_parameter_supported: None,
186 pushed_authorization_request_endpoint: None,
187 require_pushed_authorization_requests: None,
188 device_authorization_endpoint: None,
189 dpop_signing_alg_values_supported: None,
190 mtls_endpoint_aliases: None,
191 tls_client_certificate_bound_access_tokens: None,
192 userinfo_endpoint: None,
193 subject_types_supported: None,
194 id_token_signing_alg_values_supported: None,
195 id_token_encryption_alg_values_supported: None,
196 id_token_encryption_enc_values_supported: None,
197 claims_supported: None,
198 claims_parameter_supported: None,
199 request_parameter_supported: None,
200 request_uri_parameter_supported: None,
201 },
202 }
203 }
204
205 pub fn authorization_endpoint(mut self, endpoint: String) -> Self {
207 self.metadata.authorization_endpoint = Some(endpoint);
208 self
209 }
210
211 pub fn token_endpoint(mut self, endpoint: String) -> Self {
213 self.metadata.token_endpoint = Some(endpoint);
214 self
215 }
216
217 pub fn jwks_uri(mut self, uri: String) -> Self {
219 self.metadata.jwks_uri = Some(uri);
220 self
221 }
222
223 pub fn scopes_supported(mut self, scopes: Vec<String>) -> Self {
225 self.metadata.scopes_supported = Some(scopes);
226 self
227 }
228
229 pub fn response_types_supported(mut self, response_types: Vec<String>) -> Self {
231 self.metadata.response_types_supported = response_types;
232 self
233 }
234
235 pub fn grant_types_supported(mut self, grant_types: Vec<String>) -> Self {
237 self.metadata.grant_types_supported = Some(grant_types);
238 self
239 }
240
241 pub fn token_endpoint_auth_methods_supported(mut self, methods: Vec<String>) -> Self {
243 self.metadata.token_endpoint_auth_methods_supported = Some(methods);
244 self
245 }
246
247 pub fn code_challenge_methods_supported(mut self, methods: Vec<String>) -> Self {
249 self.metadata.code_challenge_methods_supported = Some(methods);
250 self
251 }
252
253 pub fn revocation_endpoint(mut self, endpoint: String) -> Self {
255 self.metadata.revocation_endpoint = Some(endpoint);
256 self
257 }
258
259 pub fn introspection_endpoint(mut self, endpoint: String) -> Self {
261 self.metadata.introspection_endpoint = Some(endpoint);
262 self
263 }
264
265 pub fn enable_par(mut self, endpoint: String, required: bool) -> Self {
267 self.metadata.pushed_authorization_request_endpoint = Some(endpoint);
268 self.metadata.require_pushed_authorization_requests = Some(required);
269 self
270 }
271
272 pub fn device_authorization_endpoint(mut self, endpoint: String) -> Self {
274 self.metadata.device_authorization_endpoint = Some(endpoint);
275 self
276 }
277
278 pub fn enable_dpop(mut self, signing_algorithms: Vec<String>) -> Self {
280 self.metadata.dpop_signing_alg_values_supported = Some(signing_algorithms);
281 self
282 }
283
284 pub fn enable_mtls(
286 mut self,
287 mtls_endpoints: MtlsEndpointAliases,
288 certificate_bound_tokens: bool,
289 ) -> Self {
290 self.metadata.mtls_endpoint_aliases = Some(mtls_endpoints);
291 self.metadata.tls_client_certificate_bound_access_tokens = Some(certificate_bound_tokens);
292 self
293 }
294
295 pub fn enable_openid_connect(
297 mut self,
298 userinfo_endpoint: String,
299 subject_types: Vec<String>,
300 id_token_signing_algs: Vec<String>,
301 ) -> Self {
302 self.metadata.userinfo_endpoint = Some(userinfo_endpoint);
303 self.metadata.subject_types_supported = Some(subject_types);
304 self.metadata.id_token_signing_alg_values_supported = Some(id_token_signing_algs);
305 self
306 }
307
308 pub fn build(self) -> AuthorizationServerMetadata {
310 self.metadata
311 }
312}
313
314pub struct MetadataProvider {
316 metadata: AuthorizationServerMetadata,
317}
318
319impl MetadataProvider {
320 pub fn new(metadata: AuthorizationServerMetadata) -> Self {
322 Self { metadata }
323 }
324
325 pub fn from_oauth2_server(_server: &OAuth2Server, base_url: &str) -> Result<Self> {
327 let mut builder = MetadataBuilder::new(base_url.to_string())
328 .authorization_endpoint(format!("{}/oauth2/authorize", base_url))
329 .token_endpoint(format!("{}/oauth2/token", base_url))
330 .jwks_uri(format!("{}/.well-known/jwks.json", base_url))
331 .response_types_supported(vec!["code".to_string()])
332 .grant_types_supported(vec![
333 "authorization_code".to_string(),
334 "client_credentials".to_string(),
335 "refresh_token".to_string(),
336 ])
337 .token_endpoint_auth_methods_supported(vec![
338 "client_secret_basic".to_string(),
339 "client_secret_post".to_string(),
340 ])
341 .code_challenge_methods_supported(vec!["S256".to_string(), "plain".to_string()])
342 .revocation_endpoint(format!("{}/oauth2/revoke", base_url))
343 .introspection_endpoint(format!("{}/oauth2/introspect", base_url))
344 .scopes_supported(vec![
345 "openid".to_string(),
346 "profile".to_string(),
347 "email".to_string(),
348 "address".to_string(),
349 "phone".to_string(),
350 ]);
351
352 builder = builder
354 .device_authorization_endpoint(format!("{}/oauth2/device_authorization", base_url));
355
356 builder = builder.enable_par(format!("{}/oauth2/par", base_url), false);
358
359 builder = builder.enable_dpop(vec![
361 "ES256".to_string(),
362 "ES384".to_string(),
363 "ES512".to_string(),
364 "RS256".to_string(),
365 ]);
366
367 let mtls_endpoints = MtlsEndpointAliases {
369 token_endpoint: Some(format!("{}/oauth2/token", base_url)),
370 revocation_endpoint: Some(format!("{}/oauth2/revoke", base_url)),
371 introspection_endpoint: Some(format!("{}/oauth2/introspect", base_url)),
372 };
373 builder = builder.enable_mtls(mtls_endpoints, true);
374
375 Ok(Self::new(builder.build()))
376 }
377
378 pub fn from_oauth21_server(_server: &OAuth2Server, base_url: &str) -> Result<Self> {
380 let mut builder = MetadataBuilder::new(base_url.to_string())
382 .authorization_endpoint(format!("{}/oauth2/authorize", base_url))
383 .token_endpoint(format!("{}/oauth2/token", base_url))
384 .jwks_uri(format!("{}/.well-known/jwks.json", base_url))
385 .response_types_supported(vec!["code".to_string()]) .grant_types_supported(vec![
387 "authorization_code".to_string(),
388 "client_credentials".to_string(),
389 "refresh_token".to_string(),
390 ])
391 .token_endpoint_auth_methods_supported(vec![
392 "client_secret_basic".to_string(),
393 "client_secret_post".to_string(),
394 "tls_client_auth".to_string(),
395 "self_signed_tls_client_auth".to_string(),
396 ])
397 .code_challenge_methods_supported(vec!["S256".to_string()]) .revocation_endpoint(format!("{}/oauth2/revoke", base_url))
399 .introspection_endpoint(format!("{}/oauth2/introspect", base_url))
400 .scopes_supported(vec![
401 "openid".to_string(),
402 "profile".to_string(),
403 "email".to_string(),
404 ]);
405
406 builder = builder.enable_par(format!("{}/oauth2/par", base_url), true);
408
409 builder = builder.enable_dpop(vec![
411 "ES256".to_string(),
412 "ES384".to_string(),
413 "ES512".to_string(),
414 ]);
415
416 let mtls_endpoints = MtlsEndpointAliases {
418 token_endpoint: Some(format!("{}/oauth2/token", base_url)),
419 revocation_endpoint: Some(format!("{}/oauth2/revoke", base_url)),
420 introspection_endpoint: Some(format!("{}/oauth2/introspect", base_url)),
421 };
422 builder = builder.enable_mtls(mtls_endpoints, true);
423
424 Ok(Self::new(builder.build()))
425 }
426
427 pub fn get_metadata(&self) -> &AuthorizationServerMetadata {
429 &self.metadata
430 }
431
432 pub fn get_metadata_json(&self) -> Result<String> {
434 serde_json::to_string_pretty(&self.metadata).map_err(|e| {
435 AuthError::auth_method("metadata", format!("Failed to serialize metadata: {}", e))
436 })
437 }
438
439 pub fn validate(&self) -> Result<()> {
441 let mut errors = Vec::new();
442
443 if self.metadata.issuer.is_empty() {
445 errors.push("Issuer is required");
446 }
447
448 if self.metadata.response_types_supported.is_empty() {
449 errors.push("At least one response type must be supported");
450 }
451
452 let endpoints = [
454 &self.metadata.authorization_endpoint,
455 &self.metadata.token_endpoint,
456 &self.metadata.jwks_uri,
457 &self.metadata.revocation_endpoint,
458 &self.metadata.introspection_endpoint,
459 ];
460
461 for endpoint in endpoints.iter().filter_map(|ep| ep.as_ref()) {
462 if url::Url::parse(endpoint).is_err() {
463 errors.push("Invalid endpoint URL format");
464 }
465 }
466
467 if self
469 .metadata
470 .code_challenge_methods_supported
471 .as_ref()
472 .is_some_and(|methods| methods.len() == 1 && methods[0] == "S256")
473 {
474 if self.metadata.response_types_supported.len() != 1
476 || self.metadata.response_types_supported[0] != "code"
477 {
478 errors.push("OAuth 2.1 must only support 'code' response type");
479 }
480 }
481
482 if !errors.is_empty() {
483 return Err(AuthError::auth_method("metadata", errors.join(", ")));
484 }
485
486 Ok(())
487 }
488
489 pub fn update_metadata<F>(&mut self, updater: F) -> Result<()>
491 where
492 F: FnOnce(&mut AuthorizationServerMetadata),
493 {
494 updater(&mut self.metadata);
495 self.validate()
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn test_metadata_builder() {
505 let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
506 .authorization_endpoint("https://auth.example.com/oauth2/authorize".to_string())
507 .token_endpoint("https://auth.example.com/oauth2/token".to_string())
508 .grant_types_supported(vec!["authorization_code".to_string()])
509 .code_challenge_methods_supported(vec!["S256".to_string()])
510 .build();
511
512 assert_eq!(metadata.issuer, "https://auth.example.com");
513 assert_eq!(
514 metadata.authorization_endpoint,
515 Some("https://auth.example.com/oauth2/authorize".to_string())
516 );
517 assert_eq!(
518 metadata.grant_types_supported,
519 Some(vec!["authorization_code".to_string()])
520 );
521 }
522
523 #[test]
524 fn test_metadata_provider() {
525 let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
526 .authorization_endpoint("https://auth.example.com/oauth2/authorize".to_string())
527 .token_endpoint("https://auth.example.com/oauth2/token".to_string())
528 .build();
529
530 let provider = MetadataProvider::new(metadata);
531 let json = provider.get_metadata_json().unwrap();
532
533 assert!(json.contains("https://auth.example.com"));
534 assert!(json.contains("authorization_endpoint"));
535 }
536
537 #[test]
538 fn test_metadata_validation() {
539 let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
540 .authorization_endpoint("https://auth.example.com/oauth2/authorize".to_string())
541 .token_endpoint("https://auth.example.com/oauth2/token".to_string())
542 .build();
543
544 let provider = MetadataProvider::new(metadata);
545 provider.validate().unwrap();
546 }
547
548 #[test]
549 fn test_oauth21_specific_metadata() {
550 let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
551 .response_types_supported(vec!["code".to_string()])
552 .code_challenge_methods_supported(vec!["S256".to_string()])
553 .enable_par("https://auth.example.com/oauth2/par".to_string(), true)
554 .enable_dpop(vec!["ES256".to_string()])
555 .build();
556
557 let provider = MetadataProvider::new(metadata);
558 provider.validate().unwrap();
559
560 let metadata = provider.get_metadata();
561 assert_eq!(metadata.require_pushed_authorization_requests, Some(true));
562 assert!(metadata.dpop_signing_alg_values_supported.is_some());
563 }
564
565 #[test]
566 fn test_mtls_metadata() {
567 let mtls_endpoints = MtlsEndpointAliases {
568 token_endpoint: Some("https://mtls.auth.example.com/oauth2/token".to_string()),
569 revocation_endpoint: Some("https://mtls.auth.example.com/oauth2/revoke".to_string()),
570 introspection_endpoint: Some(
571 "https://mtls.auth.example.com/oauth2/introspect".to_string(),
572 ),
573 };
574
575 let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
576 .enable_mtls(mtls_endpoints, true)
577 .build();
578
579 assert!(metadata.mtls_endpoint_aliases.is_some());
580 assert_eq!(
581 metadata.tls_client_certificate_bound_access_tokens,
582 Some(true)
583 );
584 }
585}
586
587