1pub use authentication::{OAuthAuthenticatedClient, OAuthClientAuthenticationOptions};
3mod access_validation;
4mod authentication;
5pub use client_secret::{OAuthClientSecretCipher, OAuthClientSecretHasher};
6mod assertion;
7mod authorization;
8mod claims_request;
9mod client_jwks;
10mod client_secret;
11mod clients;
12mod metadata_document;
13pub use metadata_document::{
14 OAuthClientMetadata, OAuthClientRegistrationResult, RegisterClientMetadataDocumentInput,
15};
16mod consent;
17mod continuation;
18mod credentials;
19mod discovery;
20mod dpop;
21mod extension_authentication;
22mod interaction;
23pub use extension_authentication::{
24 OAuthClientAssertionInput, OAuthClientAuthenticationStrategy,
25 OAuthExtensionClientAuthentication, OAuthVerifiedClientAssertion,
26};
27mod extension_claims;
28pub use extension_claims::{
29 OAuthExtensionClaims, OAuthTokenClaimsContributor, OAuthTokenClaimsInput,
30 OAuthUserInfoClaimsContributor, OAuthUserInfoClaimsInput,
31};
32mod extensions;
33pub use extensions::{
34 OAuthExtensionGrantHandler, OAuthExtensionGrantInput, OAuthMetadataContributor,
35 OAuthMetadataExtensionInput, OAuthMetadataType, OAuthProviderApi, OAuthProviderExtension,
36};
37mod id_token;
38mod issuance;
39pub use issuance::{OAuthTokenGenerator, OAuthTokenIssueParams};
40mod initial_access_token;
41mod introspection;
42pub use initial_access_token::{
43 OAuthInitialAccessTokenAuthorization, OAuthInitialAccessTokenError,
44 OAuthInitialAccessTokenInput, OAuthInitialAccessTokenValidator,
45};
46mod logout;
47mod machine;
48mod post_login;
49mod rate_limit;
50mod refresh;
51pub use rate_limit::{OAuthEndpointRateLimit, OAuthProviderRateLimits};
52mod refresh_format;
53pub use refresh_format::{
54 OAuthDecodedRefreshToken, OAuthRefreshTokenEncryption, OAuthRefreshTokenFormatter,
55};
56mod resource_cache;
57mod resource_policy;
58mod resource_seed;
59pub use resource_seed::{OAuthResourceIdentifierValidator, OAuthResourceSeedMode};
60mod resources;
61mod revocation;
62mod select_account;
63mod session_retirement;
64mod signup;
65pub use post_login::{
66 OAuthConsentReference, OAuthConsentReferenceContext, OAuthInteractionContext,
67 OAuthPostLoginConfig, OAuthPostLoginContext, OAuthPostLoginPolicy,
68};
69pub use select_account::{
70 OAuthSelectAccountConfig, OAuthSelectAccountContext, OAuthSelectAccountPolicy,
71};
72pub use signup::{OAuthSignupConfig, OAuthSignupContext, OAuthSignupPolicy, OAuthSignupRedirect};
73mod subject;
74mod token;
75mod token_storage;
76pub use token_storage::OAuthTokenHasher;
77mod userinfo;
78use alibi_core::endpoint::{
79 EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
80};
81use alibi_core::{
82 AuthContext, AuthError, AuthPlugin, AuthRequest, AuthResponse, AuthResult, AuthRoute,
83 AuthSchema, HttpMethod,
84};
85pub use assertion::consume_client_assertion;
86use async_trait::async_trait;
87pub use client_jwks::{
88 OAuthClientMetadataAbortSignal, OAuthClientMetadataFetchOptions,
89 OAuthClientMetadataFetchResponse, OAuthClientMetadataResourceFetch,
90};
91pub use clients::{
92 CreateOAuthClientInput, OAuthClientRegistrationApplicationType, RegisterOAuthClientInput,
93};
94pub use discovery::OAuthClientDiscovery;
95use serde_json::{Value, json};
96
97#[derive(Debug, Clone)]
100pub struct OAuthClientPrivilegeContext {
101 pub action: &'static str,
102 pub headers: alibi_core::Headers,
103 pub user: Value,
104 pub session: Value,
105 pub client_id: Option<String>,
109}
110#[async_trait]
111pub trait OAuthClientPrivileges: Send + Sync + std::fmt::Debug {
112 async fn allowed(&self, context: &OAuthClientPrivilegeContext) -> AuthResult<bool>;
113}
114#[derive(Debug, Clone)]
116pub struct OAuthResourcePrivilegeContext {
117 pub action: &'static str,
118 pub headers: alibi_core::Headers,
119 pub user: Value,
120 pub session: Value,
121 pub resource_id: Option<String>,
123}
124#[async_trait]
125pub trait OAuthResourcePrivileges: Send + Sync + std::fmt::Debug {
126 async fn allowed(&self, context: &OAuthResourcePrivilegeContext) -> AuthResult<bool>;
127}
128pub trait OAuthClientIdGenerator: Send + Sync + std::fmt::Debug {
130 fn generate(&self) -> AuthResult<String>;
131}
132pub trait OAuthClientSecretGenerator: Send + Sync + std::fmt::Debug {
134 fn generate(&self) -> AuthResult<String>;
135}
136#[async_trait]
142pub trait OAuthRedirectUriValidator: Send + Sync + std::fmt::Debug {
143 async fn validate(
144 &self,
145 redirect_uri: &str,
146 registered_uris: &[String],
147 default_result: bool,
148 ) -> AuthResult<bool>;
149}
150pub struct OAuthRequestUriInput<'a> {
152 pub request_uri: &'a str,
153 pub client_id: &'a str,
154 pub context: &'a alibi_core::CallbackContext,
156}
157pub type OAuthAuthorizationQuery = indexmap::IndexMap<String, alibi_core::utils::json::JsValue>;
159#[async_trait]
165pub trait OAuthRequestUriResolver: Send + Sync + std::fmt::Debug {
166 async fn resolve(
167 &self,
168 input: OAuthRequestUriInput<'_>,
169 ) -> AuthResult<Option<OAuthAuthorizationQuery>>;
170}
171#[derive(Clone, Debug)]
173pub struct OAuthProviderConfig {
174 pub rate_limit: OAuthProviderRateLimits,
176 pub scopes: Vec<String>,
177 pub extensions: Vec<std::sync::Arc<OAuthProviderExtension>>,
178 pub custom_user_info_claims: Option<std::sync::Arc<dyn OAuthUserInfoClaimsContributor>>,
181 pub custom_id_token_claims: Option<std::sync::Arc<dyn OAuthTokenClaimsContributor>>,
184 pub custom_access_token_claims: Option<std::sync::Arc<dyn OAuthTokenClaimsContributor>>,
187 pub custom_token_response_fields: Option<std::sync::Arc<dyn OAuthTokenClaimsContributor>>,
190 pub client_discoveries: Vec<std::sync::Arc<dyn OAuthClientDiscovery>>,
191 pub cached_trusted_clients: std::collections::HashSet<String>,
193 pub client_privileges: Option<std::sync::Arc<dyn OAuthClientPrivileges>>,
197 pub client_credentials_scope_privileges: Option<std::sync::Arc<dyn OAuthClientPrivileges>>,
201 pub client_id_generator: Option<std::sync::Arc<dyn OAuthClientIdGenerator>>,
202 pub client_secret_generator: Option<std::sync::Arc<dyn OAuthClientSecretGenerator>>,
203 pub validate_redirect_uri: Option<std::sync::Arc<dyn OAuthRedirectUriValidator>>,
205 pub request_uri_resolver: Option<std::sync::Arc<dyn OAuthRequestUriResolver>>,
207 pub client_secret_hasher: Option<std::sync::Arc<dyn OAuthClientSecretHasher>>,
208 pub token_hasher: Option<std::sync::Arc<dyn OAuthTokenHasher>>,
210 pub opaque_access_token_generator: Option<std::sync::Arc<dyn OAuthTokenGenerator>>,
211 pub refresh_token_generator: Option<std::sync::Arc<dyn OAuthTokenGenerator>>,
212 pub opaque_access_token_prefix: Option<String>,
214 pub refresh_token_prefix: Option<String>,
216 pub refresh_token_formatter: Option<std::sync::Arc<dyn OAuthRefreshTokenFormatter>>,
218 pub client_secret_cipher: Option<std::sync::Arc<dyn OAuthClientSecretCipher>>,
219 pub client_secret_prefix: Option<String>,
221 pub login_page: String,
222 pub consent_page: String,
223 pub select_account_page: Option<String>,
224 pub select_account: Option<OAuthSelectAccountConfig>,
225 pub signup: Option<OAuthSignupConfig>,
226 pub post_login: Option<OAuthPostLoginConfig>,
227 pub code_expires_in: f64,
228 pub access_token_expires_in: f64,
230 pub m2m_access_token_expires_in: f64,
232 pub id_token_expires_in: f64,
233 pub refresh_token_expires_in: f64,
234 pub scope_expirations: std::collections::HashMap<String, crate::jwt::JwtExpiration>,
237 pub assertion_max_lifetime: f64,
239 pub refresh_token_reuse_interval: f64,
240 pub allow_public_client_prelogin: bool,
241 pub enforce_per_client_resources: bool,
242 pub cached_resources: std::collections::HashSet<String>,
246 pub resources: Vec<Value>,
248 pub resource_seed_mode: OAuthResourceSeedMode,
249 pub identifier_validator: Option<std::sync::Arc<dyn OAuthResourceIdentifierValidator>>,
250 pub resource_privileges: Option<std::sync::Arc<dyn OAuthResourcePrivileges>>,
253 pub advertised_scopes: Option<Vec<String>>,
254 pub advertised_claims: Option<Vec<String>>,
255 pub grant_types: Vec<String>,
256 pub allow_dynamic_client_registration: bool,
257 pub client_registration_require_pkce: Option<bool>,
260 pub allow_client_key_rotation: bool,
265 pub client_registration_default_application_type: OAuthClientRegistrationApplicationType,
268 pub client_registration_default_scopes: Option<Vec<String>>,
271 pub client_registration_allowed_scopes: Option<Vec<String>>,
274 pub client_registration_default_resources: Option<Vec<String>>,
277 pub client_registration_allowed_resources: Option<Vec<String>>,
280 pub initial_access_token_validator:
282 Option<std::sync::Arc<dyn OAuthInitialAccessTokenValidator>>,
283 pub allow_unauthenticated_client_registration: bool,
284 pub disable_jwt_plugin: bool,
285 pub pairwise_secret: Option<String>,
286 pub dpop_signing_algorithms: Option<Vec<String>>,
287 pub dpop_proof_max_age_seconds: Option<f64>,
288}
289impl Default for OAuthProviderConfig {
290 fn default() -> Self {
291 Self {
292 rate_limit: OAuthProviderRateLimits::default(),
293 extensions: Vec::new(),
294 custom_user_info_claims: None,
295 custom_id_token_claims: None,
296 custom_access_token_claims: None,
297 custom_token_response_fields: None,
298 client_discoveries: Vec::new(),
299 cached_trusted_clients: std::collections::HashSet::new(),
300 client_privileges: None,
301 client_credentials_scope_privileges: None,
302 client_id_generator: None,
303 client_secret_generator: None,
304 validate_redirect_uri: None,
305 request_uri_resolver: None,
306 client_secret_hasher: None,
307 client_secret_cipher: None,
308 token_hasher: None,
309 opaque_access_token_generator: None,
310 refresh_token_generator: None,
311 opaque_access_token_prefix: None,
312 refresh_token_prefix: None,
313 refresh_token_formatter: None,
314 client_secret_prefix: None,
315 login_page: "/login".into(),
316 consent_page: "/consent".into(),
317 select_account_page: None,
318 select_account: None,
319 signup: None,
320 post_login: None,
321 code_expires_in: 600.0,
322 access_token_expires_in: 3600.0,
323 m2m_access_token_expires_in: 3600.0,
324 id_token_expires_in: 36_000.0,
325 refresh_token_expires_in: 2_592_000.0,
326 scope_expirations: std::collections::HashMap::new(),
327 assertion_max_lifetime: 300.0,
328 refresh_token_reuse_interval: 0.0,
329 allow_public_client_prelogin: false,
330 enforce_per_client_resources: true,
331 cached_resources: std::collections::HashSet::new(),
332 resources: Vec::new(),
333 resource_seed_mode: OAuthResourceSeedMode::default(),
334 identifier_validator: None,
335 resource_privileges: None,
336 scopes: ["openid", "profile", "email", "offline_access"]
337 .map(str::to_owned)
338 .into(),
339 advertised_scopes: None,
340 advertised_claims: None,
341 grant_types: ["authorization_code", "client_credentials", "refresh_token"]
342 .map(str::to_owned)
343 .into(),
344 allow_dynamic_client_registration: false,
345 client_registration_require_pkce: None,
346 allow_client_key_rotation: false,
347 client_registration_default_application_type:
348 OAuthClientRegistrationApplicationType::default(),
349 client_registration_default_scopes: None,
350 client_registration_allowed_scopes: None,
351 client_registration_default_resources: None,
352 client_registration_allowed_resources: None,
353 initial_access_token_validator: None,
354 allow_unauthenticated_client_registration: false,
355 disable_jwt_plugin: false,
356 pairwise_secret: None,
357 dpop_signing_algorithms: None,
358 dpop_proof_max_age_seconds: None,
359 }
360 }
361}
362#[derive(Clone, Debug, Default)]
363pub struct OAuthProviderPlugin {
364 config: OAuthProviderConfig,
365 client_jwks: std::sync::Arc<client_jwks::Cache>,
366 trusted_clients: std::sync::Arc<discovery::TrustedCache>,
367}
368impl OAuthProviderPlugin {
369 #[must_use]
370 pub fn new() -> Self {
371 Self::default()
372 }
373 #[must_use]
374 pub fn with_config(mut config: OAuthProviderConfig) -> Self {
375 config.scopes = unique(
376 config
377 .scopes
378 .into_iter()
379 .filter(|scope| !scope.is_empty())
380 .collect(),
381 );
382 Self {
383 config,
384 ..Self::default()
385 }
386 }
387 #[must_use]
389 pub fn get_oauth_server_config_endpoint() -> ServerEndpoint<Value> {
390 ServerEndpoint::new("oauth-provider", "getOAuthServerConfig")
391 }
392 #[must_use]
394 pub fn get_open_id_config_endpoint() -> ServerEndpoint<Value> {
395 ServerEndpoint::new("oauth-provider", "getOpenIdConfig")
396 }
397 fn metadata<S: AuthSchema>(&self, ctx: &AuthContext<S>) -> AuthResult<Value> {
398 let jwt = if self.config.disable_jwt_plugin {
399 None
400 } else {
401 Some(
402 ctx.extensions
403 .get::<super::jwt::JwtDiscoveryOptions>()
404 .ok_or_else(|| AuthError::config("jwt_config"))?,
405 )
406 };
407 let base = format!(
408 "{}{}",
409 ctx.config.base_url.trim_end_matches('/'),
410 ctx.config.base_path
411 );
412 let issuer = jwt
413 .as_ref()
414 .and_then(|jwt| jwt.issuer.as_deref())
415 .unwrap_or(&base);
416 let issuer = normalize_issuer(issuer);
417 let grants = self.supported_grant_types();
418 let endpoint_auth = self.supported_auth_methods(false);
419 let mut token_auth = endpoint_auth.clone();
420 if self.config.allow_unauthenticated_client_registration
421 || !self.config.client_discoveries.is_empty()
422 {
423 token_auth.insert(0, "none".into());
424 }
425 let signing = [
426 "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512",
427 "EdDSA",
428 ];
429 let mut output: serde_json::Map<String, Value> = serde_json::from_value(json!({
430 "scopes_supported":self.config.advertised_scopes.as_ref().unwrap_or(&self.config.scopes),
431 "issuer":issuer,"authorization_endpoint":format!("{base}/oauth2/authorize"),"token_endpoint":format!("{base}/oauth2/token"),
432 "introspection_endpoint":format!("{base}/oauth2/introspect"),"revocation_endpoint":format!("{base}/oauth2/revoke"),
433 "response_types_supported":if grants.iter().any(|grant|grant=="authorization_code"){vec!["code"]}else{vec![]},
434 "response_modes_supported":["query"],"grant_types_supported":grants,
435 "token_endpoint_auth_methods_supported":token_auth,"token_endpoint_auth_signing_alg_values_supported":signing,
436 "introspection_endpoint_auth_methods_supported":endpoint_auth,
437 "introspection_endpoint_auth_signing_alg_values_supported":signing,
438 "revocation_endpoint_auth_methods_supported":endpoint_auth,
439 "revocation_endpoint_auth_signing_alg_values_supported":signing,
440 "code_challenge_methods_supported":["S256"],"authorization_response_iss_parameter_supported":true,
441 "dpop_signing_alg_values_supported":self.config.dpop_signing_algorithms.clone().unwrap_or_else(||["EdDSA","ES256","ES512","PS256","RS256"].map(str::to_owned).into()),
442 "backchannel_logout_supported":!self.config.disable_jwt_plugin,"backchannel_logout_session_supported":!self.config.disable_jwt_plugin,
443 }))?;
444 if let Some(jwt) = jwt.as_ref() {
445 _ = output.insert(
446 "jwks_uri".into(),
447 json!(
448 jwt.remote_url
449 .clone()
450 .unwrap_or_else(|| format!("{base}{}", jwt.jwks_path))
451 ),
452 );
453 }
454 if self.config.allow_dynamic_client_registration {
455 _ = output.insert(
456 "registration_endpoint".into(),
457 json!(format!("{base}/oauth2/register")),
458 );
459 }
460 if self.config.scopes.iter().any(|scope| scope == "openid") {
461 _ = output.insert("claims_supported".into(), json!(self.supported_claims()));
462 _ = output.insert("claims_parameter_supported".into(), json!(true));
463 _ = output.insert(
464 "userinfo_endpoint".into(),
465 json!(format!("{base}/oauth2/userinfo")),
466 );
467 _ = output.insert(
468 "subject_types_supported".into(),
469 json!(if self
470 .config
471 .pairwise_secret
472 .as_ref()
473 .is_some_and(|secret| !secret.is_empty())
474 {
475 vec!["public", "pairwise"]
476 } else {
477 vec!["public"]
478 }),
479 );
480 _ = output.insert("acr_values_supported".into(), json!(["0"]));
481 _ = output.insert(
482 "id_token_signing_alg_values_supported".into(),
483 json!(jwt.as_ref().map_or_else(
484 || vec!["HS256".to_owned()],
485 |jwt| jwt.signing_algorithms.clone()
486 )),
487 );
488 _ = output.insert(
489 "end_session_endpoint".into(),
490 json!(format!("{base}/oauth2/end-session")),
491 );
492 _ = output.insert("request_parameter_supported".into(), json!(false));
493 _ = output.insert("request_uri_parameter_supported".into(), json!(false));
494 _ = output.insert(
495 "prompt_values_supported".into(),
496 json!(["login", "consent", "create", "select_account", "none"]),
497 );
498 }
499 let mut contributed = serde_json::Map::new();
500 for discovery in &self.config.client_discoveries {
501 contributed.extend(discovery.discovery_metadata());
502 }
503 contributed.extend(output);
504 self.apply_metadata_extensions(ctx, Value::Object(contributed))
505 }
506}
507fn unique(values: Vec<String>) -> Vec<String> {
508 let mut result = Vec::new();
509 for value in values {
510 if !result.contains(&value) {
511 result.push(value);
512 }
513 }
514 result
515}
516fn normalize_issuer(value: &str) -> String {
517 let Ok(mut url) = url::Url::parse(value) else {
518 return value.into();
519 };
520 let loopback = url.host_str().is_some_and(|host| {
521 host == "localhost" || host == "127.0.0.1" || host == "[::1]" || host == "::1"
522 });
523 if url.scheme() != "https" && !loopback {
524 _ = url.set_scheme("https");
525 }
526 url.set_query(None);
527 url.set_fragment(None);
528 url.to_string().trim_end_matches('/').to_owned()
529}
530#[async_trait]
531impl<S: AuthSchema> AuthPlugin<S> for OAuthProviderPlugin {
532 fn name(&self) -> &'static str {
533 "oauth-provider"
534 }
535 fn rate_limits(&self) -> Vec<alibi_core::PluginRateLimit> {
536 self.config.rate_limit.rules()
537 }
538 fn endpoint_hooks(&self) -> Vec<&dyn alibi_core::endpoint::EndpointHook<S>> {
539 vec![self]
540 }
541 async fn before_request(
542 &self,
543 req: &AuthRequest,
544 ctx: &AuthContext<S>,
545 ) -> AuthResult<Option<alibi_core::BeforeRequestAction>> {
546 let body = req.body_as_json::<alibi_core::utils::json::JsValue>().ok();
547 interaction::admit(
548 body.as_ref().and_then(|body| body.get("oauth_query")),
549 ctx.config.current_secret(),
550 req.extensions(),
551 )?
552 .map(|response| {
553 response
554 .to_response()
555 .map(alibi_core::BeforeRequestAction::Respond)
556 })
557 .transpose()
558 }
559 async fn after_request(
560 &self,
561 req: &AuthRequest,
562 ctx: &AuthContext<S>,
563 response: AuthResponse,
564 ) -> AuthResult<AuthResponse> {
565 let Some(mut resumed) = interaction::resume(self, req, &response.headers, ctx).await?
566 else {
567 return Ok(response);
568 };
569 resumed.merge_headers(response.headers);
570 resumed.to_response()
571 }
572 fn allowed_media_types(&self, route: &AuthRoute) -> Vec<&'static str> {
573 match route.path.as_str() {
574 "/oauth2/authorize"
575 | "/oauth2/end-session/confirm"
576 | "/oauth2/token"
577 | "/oauth2/introspect"
578 | "/oauth2/revoke"
579 | "/oauth2/userinfo" => vec!["application/x-www-form-urlencoded"],
580 "/oauth2/end-session" => vec!["application/json", "application/x-www-form-urlencoded"],
581 _ => vec!["application/json"],
582 }
583 }
584 fn routes(&self) -> Vec<AuthRoute> {
585 vec![
586 AuthRoute::post(
587 "/oauth2/end-session/confirm",
588 "oauth2EndSessionConfirmation",
589 ),
590 AuthRoute::get("/oauth2/end-session", "oauth2EndSession"),
591 AuthRoute::post("/oauth2/end-session", "oauth2EndSession"),
592 AuthRoute::get("/oauth2/userinfo", "oauth2UserInfo"),
593 AuthRoute::post("/oauth2/userinfo", "oauth2UserInfo"),
594 AuthRoute::post("/oauth2/revoke", "oauth2Revoke"),
595 AuthRoute::post("/oauth2/introspect", "oauth2Introspect"),
596 AuthRoute::post("/oauth2/token", "oauth2Token"),
597 AuthRoute::post("/oauth2/create-client", "createOAuthClient"),
598 AuthRoute::get("/oauth2/get-client", "getOAuthClient"),
599 AuthRoute::get("/oauth2/public-client", "getOAuthClientPublic"),
600 AuthRoute::post(
601 "/oauth2/public-client-prelogin",
602 "getOAuthClientPublicPrelogin",
603 ),
604 AuthRoute::get("/oauth2/get-clients", "getOAuthClients"),
605 AuthRoute::post("/oauth2/delete-client", "deleteOAuthClient"),
606 AuthRoute::post("/oauth2/update-client", "updateOAuthClient"),
607 AuthRoute::post("/oauth2/register", "registerOAuthClient"),
608 AuthRoute::post("/oauth2/consent", "oauth2Consent"),
609 AuthRoute::post("/oauth2/continue", "oauth2Continue"),
610 AuthRoute::get("/oauth2/get-consents", "getOAuthConsents"),
611 AuthRoute::get("/oauth2/get-consent", "getOAuthConsent"),
612 AuthRoute::post("/oauth2/update-consent", "updateOAuthConsent"),
613 AuthRoute::post("/oauth2/delete-consent", "deleteOAuthConsent"),
614 AuthRoute::get("/oauth2/authorize", "oauth2Authorize"),
615 AuthRoute::post("/oauth2/authorize", "oauth2Authorize"),
616 AuthRoute::post("/oauth2/client/rotate-secret", "rotateClientSecret"),
617 AuthRoute::post("/oauth2/client/rotate-keys", "rotateClientKeys"),
618 ]
619 }
620 fn server_endpoints(&self) -> Vec<EndpointDefinition> {
621 vec![
622 crate::endpoint::definition(
623 "oauth2EndSessionConfirmation",
624 "oauth2EndSessionConfirmation",
625 Some("/oauth2/end-session/confirm"),
626 HttpMethod::Post,
627 ),
628 crate::endpoint::definition(
629 "oauth2EndSession",
630 "oauth2EndSession",
631 Some("/oauth2/end-session"),
632 HttpMethod::Get,
633 ),
634 crate::endpoint::definition(
635 "oauth2UserInfo",
636 "oauth2UserInfo",
637 Some("/oauth2/userinfo"),
638 HttpMethod::Get,
639 ),
640 crate::endpoint::definition(
641 "oauth2Revoke",
642 "oauth2Revoke",
643 Some("/oauth2/revoke"),
644 HttpMethod::Post,
645 ),
646 crate::endpoint::definition(
647 "oauth2Introspect",
648 "oauth2Introspect",
649 Some("/oauth2/introspect"),
650 HttpMethod::Post,
651 ),
652 crate::endpoint::definition(
653 "oauth2Token",
654 "oauth2Token",
655 Some("/oauth2/token"),
656 HttpMethod::Post,
657 ),
658 crate::endpoint::definition(
659 "oauth2Continue",
660 "oauth2Continue",
661 Some("/oauth2/continue"),
662 HttpMethod::Post,
663 ),
664 crate::endpoint::definition(
665 "adminUnlinkClientResource",
666 "adminUnlinkClientResource",
667 Some("/admin/oauth2/resources/:identifier/clients/:client_id"),
668 HttpMethod::Delete,
669 ),
670 crate::endpoint::definition(
671 "adminLinkClientResource",
672 "adminLinkClientResource",
673 Some("/admin/oauth2/resources/:identifier/clients/:client_id"),
674 HttpMethod::Post,
675 ),
676 crate::endpoint::definition(
677 "adminDeleteOAuthResource",
678 "adminDeleteOAuthResource",
679 Some("/admin/oauth2/resources/:identifier"),
680 HttpMethod::Delete,
681 ),
682 crate::endpoint::definition(
683 "adminUpdateOAuthResource",
684 "adminUpdateOAuthResource",
685 Some("/admin/oauth2/resources/:identifier"),
686 HttpMethod::Patch,
687 ),
688 crate::endpoint::definition(
689 "adminGetOAuthResource",
690 "adminGetOAuthResource",
691 Some("/admin/oauth2/resources/:identifier"),
692 HttpMethod::Get,
693 ),
694 crate::endpoint::definition(
695 "adminListOAuthResources",
696 "adminListOAuthResources",
697 Some("/admin/oauth2/resources"),
698 HttpMethod::Get,
699 ),
700 crate::endpoint::definition(
701 "adminCreateOAuthResource",
702 "adminCreateOAuthResource",
703 Some("/admin/oauth2/resources"),
704 HttpMethod::Post,
705 ),
706 crate::endpoint::definition(
707 "deleteOAuthConsent",
708 "deleteOAuthConsent",
709 Some("/oauth2/delete-consent"),
710 HttpMethod::Post,
711 ),
712 crate::endpoint::definition(
713 "updateOAuthConsent",
714 "updateOAuthConsent",
715 Some("/oauth2/update-consent"),
716 HttpMethod::Post,
717 ),
718 crate::endpoint::definition(
719 "getOAuthConsent",
720 "getOAuthConsent",
721 Some("/oauth2/get-consent"),
722 HttpMethod::Get,
723 ),
724 crate::endpoint::definition(
725 "getOAuthConsents",
726 "getOAuthConsents",
727 Some("/oauth2/get-consents"),
728 HttpMethod::Get,
729 ),
730 crate::endpoint::definition(
731 "oauth2Consent",
732 "oauth2Consent",
733 Some("/oauth2/consent"),
734 HttpMethod::Post,
735 ),
736 crate::endpoint::definition(
737 "getOAuthClientPublicPrelogin",
738 "getOAuthClientPublicPrelogin",
739 Some("/oauth2/public-client-prelogin"),
740 HttpMethod::Post,
741 ),
742 crate::endpoint::definition(
743 "oauth2Authorize",
744 "oauth2Authorize",
745 Some("/oauth2/authorize"),
746 HttpMethod::Get,
747 ),
748 crate::endpoint::definition(
749 "adminUpdateOAuthClient",
750 "adminUpdateOAuthClient",
751 Some("/admin/oauth2/update-client"),
752 HttpMethod::Patch,
753 ),
754 crate::endpoint::definition(
755 "adminCreateOAuthClient",
756 "adminCreateOAuthClient",
757 Some("/admin/oauth2/create-client"),
758 HttpMethod::Post,
759 ),
760 crate::endpoint::definition(
761 "registerOAuthClient",
762 "registerOAuthClient",
763 Some("/oauth2/register"),
764 HttpMethod::Post,
765 ),
766 crate::endpoint::definition(
767 "updateOAuthClient",
768 "updateOAuthClient",
769 Some("/oauth2/update-client"),
770 HttpMethod::Post,
771 ),
772 crate::endpoint::definition(
773 "rotateClientSecret",
774 "rotateClientSecret",
775 Some("/oauth2/client/rotate-secret"),
776 HttpMethod::Post,
777 ),
778 crate::endpoint::definition(
779 "rotateClientKeys",
780 "rotateClientKeys",
781 Some("/oauth2/client/rotate-keys"),
782 HttpMethod::Post,
783 ),
784 crate::endpoint::definition(
785 "deleteOAuthClient",
786 "deleteOAuthClient",
787 Some("/oauth2/delete-client"),
788 HttpMethod::Post,
789 ),
790 crate::endpoint::definition(
791 "getOAuthClients",
792 "getOAuthClients",
793 Some("/oauth2/get-clients"),
794 HttpMethod::Get,
795 ),
796 crate::endpoint::definition(
797 "getOAuthClientPublic",
798 "getOAuthClientPublic",
799 Some("/oauth2/public-client"),
800 HttpMethod::Get,
801 ),
802 crate::endpoint::definition(
803 "getOAuthClient",
804 "getOAuthClient",
805 Some("/oauth2/get-client"),
806 HttpMethod::Get,
807 ),
808 crate::endpoint::definition(
809 "createOAuthClient",
810 "createOAuthClient",
811 Some("/oauth2/create-client"),
812 HttpMethod::Post,
813 ),
814 crate::endpoint::definition(
815 "getOAuthServerConfig",
816 "getOAuthServerConfig",
817 Some("/.well-known/oauth-authorization-server"),
818 HttpMethod::Get,
819 ),
820 crate::endpoint::definition(
821 "getOpenIdConfig",
822 "getOpenIdConfig",
823 Some("/.well-known/openid-configuration"),
824 HttpMethod::Get,
825 ),
826 ]
827 }
828 fn validate_endpoint(
829 &self,
830 call: &EndpointCall,
831 _ctx: &AuthContext<S>,
832 ) -> AuthResult<EndpointInput> {
833 Ok(EndpointInput {
834 body: if call.operation_id() == "oauth2EndSessionConfirmation" {
835 Some(crate::endpoint::validate_fields(
836 call.body(),
837 "body",
838 &[crate::authentication_helpers::JsonField {
839 name: "action",
840 kind: crate::authentication_helpers::JsonFieldKind::OneOf(&["confirm"]),
841 required: true,
842 }],
843 )?)
844 } else if call.operation_id() == "oauth2EndSession" {
845 logout::validate(call.body(), "body")?
846 } else if call.operation_id() == "oauth2UserInfo" {
847 call.body()
848 .map(|body| {
849 crate::endpoint::validate_fields(
850 Some(body),
851 "body",
852 &[crate::authentication_helpers::JsonField::string(
853 "access_token",
854 false,
855 )],
856 )
857 })
858 .transpose()?
859 } else if call.operation_id() == "oauth2Continue" {
860 Some(continuation::validate(call.body())?)
861 } else if call.operation_id() == "adminUpdateOAuthResource" {
862 Some(resources::validate_update(call.body())?)
863 } else if call.operation_id() == "adminCreateOAuthResource" {
864 Some(resources::validate_create(call.body())?)
865 } else if call.operation_id() == "deleteOAuthConsent" {
866 Some(consent::validate_id(call.body(), "body")?)
867 } else if call.operation_id() == "updateOAuthConsent" {
868 Some(consent::validate_update(call.body())?)
869 } else if call.operation_id() == "oauth2Consent" {
870 Some(consent::validate(call.body())?)
871 } else if call.operation_id() == "getOAuthClientPublicPrelogin" {
872 Some(clients::validate_prelogin(call.body())?)
873 } else if call.operation_id() == "adminUpdateOAuthClient" {
874 Some(clients::validate_admin_update(call.body())?)
875 } else if call.operation_id() == "adminCreateOAuthClient" {
876 Some(clients::validate_admin_create(call.body())?)
877 } else if call.operation_id() == "updateOAuthClient" {
878 Some(clients::validate_update(call.body())?)
879 } else if matches!(
880 call.operation_id(),
881 "deleteOAuthClient" | "rotateClientSecret"
882 ) {
883 Some(clients::validate_delete(call.body())?)
884 } else if call.operation_id() == "createOAuthClient" {
885 Some(clients::validate(call.body())?)
886 } else {
887 call.body().cloned()
888 },
889 query: if call.operation_id() == "oauth2EndSession" {
890 logout::validate(call.query(), "query")?
891 } else if call.operation_id() == "getOAuthConsent" {
892 Some(consent::validate_id(call.query(), "query")?)
893 } else if matches!(
894 call.operation_id(),
895 "getOAuthClient" | "getOAuthClientPublic"
896 ) {
897 Some(clients::validate_read(call.query())?)
898 } else {
899 call.query().cloned()
900 },
901 })
902 }
903 async fn on_endpoint(
904 &self,
905 call: &EndpointCall,
906 ctx: &AuthContext<S>,
907 ) -> AuthResult<EndpointResponse> {
908 if call.operation_id() == "oauth2EndSessionConfirmation" {
909 return Box::pin(self.confirm_logout(call, ctx)).await;
910 }
911 if call.operation_id() == "oauth2EndSession" {
912 return Box::pin(self.end_session(call.query(), call.body(), call, ctx)).await;
913 }
914 if call.operation_id() == "oauth2UserInfo" {
915 return Box::pin(self.user_info(call.body(), call, ctx)).await;
916 }
917 if call.operation_id() == "oauth2Revoke" {
918 return Box::pin(self.revoke(call.body(), call, ctx)).await;
919 }
920 if call.operation_id() == "oauth2Introspect" {
921 return Box::pin(self.introspect(call.body(), call, ctx)).await;
922 }
923 if call.operation_id() == "oauth2Token" {
924 return Box::pin(self.token(call.body(), call, ctx)).await;
925 }
926 if call.operation_id() == "oauth2Continue" {
927 return Box::pin(self.continue_authorization(call.body_as()?, call, ctx)).await;
928 }
929 if call.operation_id() == "adminUnlinkClientResource" {
930 return Box::pin(self.unlink_resource(call.params(), call, ctx)).await;
931 }
932 if call.operation_id() == "adminLinkClientResource" {
933 return Box::pin(self.link_resource(call.params(), call, ctx)).await;
934 }
935 if call.operation_id() == "adminDeleteOAuthResource" {
936 return Box::pin(self.delete_resource(call.params(), call, ctx)).await;
937 }
938 if call.operation_id() == "adminUpdateOAuthResource" {
939 return Box::pin(self.update_resource(call.params(), call.body_as()?, call, ctx)).await;
940 }
941 if call.operation_id() == "adminGetOAuthResource" {
942 return Box::pin(self.read_resource(call.params(), call, ctx)).await;
943 }
944 if call.operation_id() == "adminListOAuthResources" {
945 return Box::pin(self.list_resources(call, ctx)).await;
946 }
947 if call.operation_id() == "adminCreateOAuthResource" {
948 return Box::pin(self.create_resource(call.body_as()?, call, ctx)).await;
949 }
950 if call.operation_id() == "deleteOAuthConsent" {
951 return Box::pin(self.delete_consent(call.body_as()?, call, ctx)).await;
952 }
953 if call.operation_id() == "updateOAuthConsent" {
954 return Box::pin(self.update_consent(call.body_as()?, call, ctx)).await;
955 }
956 if call.operation_id() == "getOAuthConsent" {
957 return Box::pin(self.read_consent(call.query_as()?, call, ctx)).await;
958 }
959 if call.operation_id() == "getOAuthConsents" {
960 return Box::pin(self.list_consents(call, ctx)).await;
961 }
962 if call.operation_id() == "oauth2Consent" {
963 return Box::pin(self.consent(call.body_as()?, call, ctx)).await;
964 }
965 if call.operation_id() == "getOAuthClientPublicPrelogin" {
966 return Box::pin(self.read_prelogin_client(call.body_as()?, ctx)).await;
967 }
968 if call.operation_id() == "oauth2Authorize" {
969 let value = if call.method() == Some(&HttpMethod::Post) {
970 call.body()
971 } else {
972 call.query()
973 };
974 return Box::pin(self.authorize(value.cloned(), call, ctx)).await;
975 }
976 if call.operation_id() == "adminUpdateOAuthClient" {
977 return Box::pin(self.update_client(call.body_as()?, call, ctx, true)).await;
978 }
979 if call.operation_id() == "adminCreateOAuthClient" {
980 return Box::pin(self.admin_create_client(call.body_as()?, call, ctx)).await;
981 }
982 if call.operation_id() == "registerOAuthClient" {
983 return Box::pin(self.register_client(call.body(), call, ctx)).await;
984 }
985 if call.operation_id() == "updateOAuthClient" {
986 return Box::pin(self.update_client(call.body_as()?, call, ctx, false)).await;
987 }
988 if call.operation_id() == "rotateClientSecret" {
989 return Box::pin(self.rotate_client_secret(call.body_as()?, call, ctx)).await;
990 }
991 if call.operation_id() == "rotateClientKeys" {
992 return Box::pin(self.rotate_client_keys(call.body(), call, ctx)).await;
993 }
994 if call.operation_id() == "deleteOAuthClient" {
995 return Box::pin(self.delete_client(call.body_as()?, call, ctx)).await;
996 }
997 if call.operation_id() == "getOAuthClients" {
998 return Box::pin(self.list_clients(call, ctx)).await;
999 }
1000 if call.operation_id() == "getOAuthClientPublic" {
1001 return Box::pin(self.read_public_client(call.query_as()?, call, ctx)).await;
1002 }
1003 if call.operation_id() == "getOAuthClient" {
1004 return Box::pin(self.read_client(call.query_as()?, call, ctx)).await;
1005 }
1006 if call.operation_id() == "createOAuthClient" {
1007 return Box::pin(self.create_client(call.body_as()?, call, ctx)).await;
1008 }
1009 if call.operation_id() == "getOpenIdConfig"
1010 && !self.config.scopes.iter().any(|scope| scope == "openid")
1011 {
1012 return Ok(EndpointResponse::error(AuthError::Api {
1013 status: 404,
1014 code: None,
1015 message: String::new(),
1016 })
1017 .without_error_body()
1018 .with_header("Content-Type", "application/json"));
1019 }
1020 EndpointResponse::json(&self.metadata(ctx)?)
1021 }
1022 async fn on_request(
1023 &self,
1024 req: &AuthRequest,
1025 ctx: &AuthContext<S>,
1026 ) -> AuthResult<Option<AuthResponse>> {
1027 if req.path() == "/oauth2/end-session/confirm" && *req.method() == HttpMethod::Post {
1028 let action = url::form_urlencoded::parse(req.body.as_deref().unwrap_or_default())
1029 .find(|(key, _)| key == "action")
1030 .map(|(_, value)| value.into_owned());
1031 if action.as_deref() != Some("confirm") {
1032 return Ok(Some(
1033 AuthError::bad_request("Invalid logout action").to_auth_response(),
1034 ));
1035 }
1036 return Ok(Some(
1037 Box::pin(self.confirm_logout(req, ctx))
1038 .await?
1039 .to_response()?,
1040 ));
1041 }
1042 if req.path() == "/oauth2/end-session"
1043 && matches!(req.method(), HttpMethod::Get | HttpMethod::Post)
1044 {
1045 let query = alibi_core::utils::json::parse_value(&serde_json::to_string(&req.query)?)?;
1046 let body = if *req.method() == HttpMethod::Post {
1047 Some(alibi_core::utils::json::parse_value(
1048 &serde_json::to_string(
1049 &url::form_urlencoded::parse(req.body.as_deref().unwrap_or_default())
1050 .into_owned()
1051 .collect::<std::collections::HashMap<_, _>>(),
1052 )?,
1053 )?)
1054 } else {
1055 None
1056 };
1057 return Ok(Some(
1058 Box::pin(self.end_session(Some(&query), body.as_ref(), req, ctx))
1059 .await?
1060 .to_response()?,
1061 ));
1062 }
1063 if req.path() == "/oauth2/userinfo"
1064 && matches!(req.method(), HttpMethod::Get | HttpMethod::Post)
1065 {
1066 let body = if *req.method() == HttpMethod::Post {
1067 Some(req.body_as_json::<alibi_core::utils::json::JsValue>()?)
1068 } else {
1069 None
1070 };
1071 let call = alibi_core::endpoint::current_endpoint_call_context().unwrap_or_else(|| {
1074 EndpointCall::from_http_request(
1075 req,
1076 &AuthRoute::get("/oauth2/userinfo", "oauth2UserInfo"),
1077 )
1078 });
1079 return Ok(Some(
1080 Box::pin(self.user_info(body.as_ref(), &call, ctx))
1081 .await?
1082 .to_response()?,
1083 ));
1084 }
1085 if req.path() == "/oauth2/token" && *req.method() == HttpMethod::Post {
1086 let body = req.body_as_json::<alibi_core::utils::json::JsValue>()?;
1090 let call = alibi_core::endpoint::current_endpoint_call_context().unwrap_or_else(|| {
1093 EndpointCall::from_http_request(
1094 req,
1095 &AuthRoute::post("/oauth2/token", "oauth2Token"),
1096 )
1097 });
1098 let call = call.handler_context(Some(body.clone()), call.query().cloned());
1099 return Ok(Some(
1100 Box::pin(self.token(Some(&body), &call, ctx))
1101 .await?
1102 .to_response()?,
1103 ));
1104 }
1105 if matches!(req.path(), "/oauth2/introspect" | "/oauth2/revoke")
1106 && *req.method() == HttpMethod::Post
1107 {
1108 let body = req.body_as_json::<alibi_core::utils::json::JsValue>()?;
1111 let revoking = req.path() == "/oauth2/revoke";
1112 let call = alibi_core::endpoint::current_endpoint_call_context().unwrap_or_else(|| {
1113 EndpointCall::from_http_request(
1114 req,
1115 &AuthRoute::post(
1116 req.path(),
1117 if revoking {
1118 "oauth2Revoke"
1119 } else {
1120 "oauth2Introspect"
1121 },
1122 ),
1123 )
1124 });
1125 let call = call.handler_context(Some(body.clone()), call.query().cloned());
1126 let response = if revoking {
1127 Box::pin(self.revoke(Some(&body), &call, ctx)).await?
1128 } else {
1129 Box::pin(self.introspect(Some(&body), &call, ctx)).await?
1130 };
1131 return Ok(Some(response.to_response()?));
1132 }
1133 if req.path() == "/oauth2/authorize"
1134 && matches!(req.method(), HttpMethod::Get | HttpMethod::Post)
1135 {
1136 let value = if *req.method() == HttpMethod::Post {
1137 req.body_as_json::<alibi_core::utils::json::JsValue>()?
1138 } else {
1139 req.query_as_js_value()
1140 };
1141 let call = alibi_core::endpoint::current_endpoint_call_context().unwrap_or_else(|| {
1145 EndpointCall::from_http_request(
1146 req,
1147 &AuthRoute::get("/oauth2/authorize", "oauth2Authorize"),
1148 )
1149 });
1150 let call = call.handler_context(call.body().cloned(), Some(value.clone()));
1151 return Ok(Some(
1152 alibi_core::endpoint::with_endpoint_call_context(
1153 call,
1154 Box::pin(self.authorize(Some(value), req, ctx)),
1155 )
1156 .await?
1157 .to_response()?,
1158 ));
1159 }
1160 if req.path() == "/oauth2/register" && *req.method() == HttpMethod::Post {
1161 let body: alibi_core::utils::json::JsValue = req.body_as_json()?;
1162 return Ok(Some(
1163 Box::pin(self.register_client(Some(&body), req, ctx))
1164 .await?
1165 .to_response()?,
1166 ));
1167 }
1168 if req.path() == "/oauth2/client/rotate-keys" && *req.method() == HttpMethod::Post {
1169 let body: alibi_core::utils::json::JsValue = req.body_as_json()?;
1170 return Ok(Some(
1171 Box::pin(self.rotate_client_keys(Some(&body), req, ctx))
1172 .await?
1173 .to_response()?,
1174 ));
1175 }
1176 if req.path() == "/oauth2/update-client" && *req.method() == HttpMethod::Post {
1177 let body: alibi_core::utils::json::JsValue = req.body_as_json()?;
1178 let input =
1179 alibi_core::utils::json::from_value(clients::validate_update(Some(&body))?)?;
1180 return Ok(Some(
1181 Box::pin(self.update_client(input, req, ctx, false))
1182 .await?
1183 .to_response()?,
1184 ));
1185 }
1186 if matches!(
1187 req.path(),
1188 "/oauth2/delete-client" | "/oauth2/client/rotate-secret"
1189 ) && *req.method() == HttpMethod::Post
1190 {
1191 let body: alibi_core::utils::json::JsValue = req.body_as_json()?;
1192 let input =
1193 alibi_core::utils::json::from_value(clients::validate_delete(Some(&body))?)?;
1194 if req.path() == "/oauth2/client/rotate-secret" {
1195 return Ok(Some(
1196 Box::pin(self.rotate_client_secret(input, req, ctx))
1197 .await?
1198 .to_response()?,
1199 ));
1200 }
1201 return Ok(Some(
1202 Box::pin(self.delete_client(input, req, ctx))
1203 .await?
1204 .to_response()?,
1205 ));
1206 }
1207 if req.path() == "/oauth2/public-client-prelogin" && *req.method() == HttpMethod::Post {
1208 let body = req.body_as_json()?;
1209 let input =
1210 alibi_core::utils::json::from_value(clients::validate_prelogin(Some(&body))?)?;
1211 return Ok(Some(
1212 Box::pin(self.read_prelogin_client(input, ctx))
1213 .await?
1214 .to_response()?,
1215 ));
1216 }
1217 if req.path() == "/oauth2/continue" && *req.method() == HttpMethod::Post {
1218 let body = req.body_as_json()?;
1219 let input = alibi_core::utils::json::from_value(continuation::validate(Some(&body))?)?;
1220 return Ok(Some(
1221 Box::pin(self.continue_authorization(input, req, ctx))
1222 .await?
1223 .to_response()?,
1224 ));
1225 }
1226 if req.path() == "/oauth2/consent" && *req.method() == HttpMethod::Post {
1227 let body = req.body_as_json()?;
1228 let input = alibi_core::utils::json::from_value(consent::validate(Some(&body))?)?;
1229 return Ok(Some(
1230 Box::pin(self.consent(input, req, ctx))
1231 .await?
1232 .to_response()?,
1233 ));
1234 }
1235 if req.path() == "/oauth2/delete-consent" && *req.method() == HttpMethod::Post {
1236 let body = req.body_as_json()?;
1237 let input =
1238 alibi_core::utils::json::from_value(consent::validate_id(Some(&body), "body")?)?;
1239 return Ok(Some(
1240 Box::pin(self.delete_consent(input, req, ctx))
1241 .await?
1242 .to_response()?,
1243 ));
1244 }
1245 if req.path() == "/oauth2/update-consent" && *req.method() == HttpMethod::Post {
1246 let body = req.body_as_json()?;
1247 let input =
1248 alibi_core::utils::json::from_value(consent::validate_update(Some(&body))?)?;
1249 return Ok(Some(
1250 Box::pin(self.update_consent(input, req, ctx))
1251 .await?
1252 .to_response()?,
1253 ));
1254 }
1255 if req.path() == "/oauth2/get-consent" && *req.method() == HttpMethod::Get {
1256 let query = alibi_core::utils::json::parse_value(&serde_json::to_string(&req.query)?)?;
1257 let input =
1258 alibi_core::utils::json::from_value(consent::validate_id(Some(&query), "query")?)?;
1259 return Ok(Some(
1260 Box::pin(self.read_consent(input, req, ctx))
1261 .await?
1262 .to_response()?,
1263 ));
1264 }
1265 if req.path() == "/oauth2/get-consents" && *req.method() == HttpMethod::Get {
1266 return Ok(Some(
1267 Box::pin(self.list_consents(req, ctx))
1268 .await?
1269 .to_response()?,
1270 ));
1271 }
1272 if req.path() == "/oauth2/get-clients" && *req.method() == HttpMethod::Get {
1273 return Ok(Some(
1274 Box::pin(self.list_clients(req, ctx)).await?.to_response()?,
1275 ));
1276 }
1277 if matches!(req.path(), "/oauth2/get-client" | "/oauth2/public-client")
1278 && *req.method() == HttpMethod::Get
1279 {
1280 let query = alibi_core::utils::json::parse_value(&serde_json::to_string(&req.query)?)?;
1281 let input = alibi_core::utils::json::from_value(clients::validate_read(Some(&query))?)?;
1282 if req.path() == "/oauth2/public-client" {
1283 return Ok(Some(
1284 Box::pin(self.read_public_client(input, req, ctx))
1285 .await?
1286 .to_response()?,
1287 ));
1288 }
1289 return Ok(Some(
1290 Box::pin(self.read_client(input, req, ctx))
1291 .await?
1292 .to_response()?,
1293 ));
1294 }
1295 if req.path() == "/oauth2/create-client" && *req.method() == HttpMethod::Post {
1296 let body: alibi_core::utils::json::JsValue = req.body_as_json()?;
1297 let input = alibi_core::utils::json::from_value(clients::validate(Some(&body))?)?;
1298 return Ok(Some(
1299 Box::pin(self.create_client(input, req, ctx))
1300 .await?
1301 .to_response()?,
1302 ));
1303 }
1304 Ok(None)
1305 }
1306 async fn on_init(&self, ctx: &mut alibi_core::AuthInitContext<S>) -> AuthResult<()> {
1307 self.validate_extensions()?;
1308 for scope in self
1309 .config
1310 .client_registration_allowed_scopes
1311 .iter()
1312 .flatten()
1313 .chain(
1314 self.config
1315 .client_registration_default_scopes
1316 .iter()
1317 .flatten(),
1318 )
1319 {
1320 if !self.config.scopes.contains(scope) {
1321 return Err(AuthError::config(format!(
1322 "clientRegistrationAllowedScope {scope} not found in scopes"
1323 )));
1324 }
1325 }
1326 if self
1327 .config
1328 .advertised_scopes
1329 .as_ref()
1330 .is_some_and(|scopes| {
1331 scopes
1332 .iter()
1333 .any(|scope| !self.config.scopes.contains(scope))
1334 })
1335 {
1336 return Err(AuthError::config(
1337 "advertisedMetadata.scopes_supported contains an unsupported scope",
1338 ));
1339 }
1340 let configured_resources = self
1341 .config
1342 .resources
1343 .iter()
1344 .filter_map(|resource| {
1345 resource
1346 .as_str()
1347 .or_else(|| resource.get("identifier").and_then(Value::as_str))
1348 })
1349 .collect::<std::collections::HashSet<_>>();
1350 for (option, identifiers) in [
1351 (
1352 "clientRegistrationDefaultResources",
1353 &self.config.client_registration_default_resources,
1354 ),
1355 (
1356 "clientRegistrationAllowedResources",
1357 &self.config.client_registration_allowed_resources,
1358 ),
1359 ] {
1360 for identifier in identifiers.iter().flatten() {
1361 if !configured_resources.contains(identifier.as_str()) {
1362 return Err(AuthError::config(format!(
1363 "{option} resource {identifier} not found in resources"
1364 )));
1365 }
1366 }
1367 }
1368 if self
1369 .config
1370 .pairwise_secret
1371 .as_ref()
1372 .is_some_and(|secret| !secret.is_empty() && secret.len() < 32)
1373 {
1374 return Err(AuthError::config(
1375 "pairwiseSecret must be at least 32 characters long for adequate HMAC-SHA256 security",
1376 ));
1377 }
1378 if self
1379 .config
1380 .grant_types
1381 .iter()
1382 .any(|grant| grant == "refresh_token")
1383 && !self
1384 .config
1385 .grant_types
1386 .iter()
1387 .any(|grant| grant == "authorization_code")
1388 {
1389 return Err(AuthError::config(
1390 "refresh_token grant requires authorization_code grant",
1391 ));
1392 }
1393 if self.config.disable_jwt_plugin && self.config.client_secret_hasher.is_some() {
1394 return Err(AuthError::config(
1395 "unable to store hashed secrets because id tokens will be signed with secret",
1396 ));
1397 }
1398 if !self.config.disable_jwt_plugin && self.config.client_secret_cipher.is_some() {
1399 return Err(AuthError::config(
1400 "encryption method not recommended, please use 'hashed' or the 'hash' function",
1401 ));
1402 }
1403 let mut discovery_ids = std::collections::HashSet::new();
1404 for discovery in &self.config.client_discoveries {
1405 if alibi_core::utils::javascript::trim(discovery.id()).is_empty() {
1406 return Err(AuthError::config(
1407 "OAuth Provider extension client discovery id cannot be empty",
1408 ));
1409 }
1410 if !discovery_ids.insert(discovery.id()) {
1411 return Err(AuthError::config(format!(
1412 "OAuth Provider extensions register client discovery id \"{}\" more than once. Extension contributions must be disjoint.",
1413 discovery.id()
1414 )));
1415 }
1416 }
1417 if self.config.allow_client_key_rotation && ctx.config.advanced.database.serial_ids() {
1420 return Err(AuthError::config(
1421 "`allowClientKeyRotation` requires single-use client assertions, which `advanced.database.generateId: \"serial\"` does not enforce",
1422 ));
1423 }
1424 if let Err(error) = self.seed_resources(ctx.database.as_ref()).await {
1427 resource_seed::log_startup_seed_failure(&error);
1428 }
1429 ctx.extensions.insert(resource_seed::SeedState::default());
1430 ctx.extensions.insert(discovery::TrustedClients {
1431 ids: self.config.cached_trusted_clients.clone(),
1432 cache: self.trusted_clients.clone(),
1433 });
1434 ctx.extensions.insert(discovery::ClientDiscoveries(
1435 self.config.client_discoveries.clone(),
1436 ));
1437 ctx.register_session_deletion_hook(std::sync::Arc::new(
1438 session_retirement::TokenRetirementHook(self.clone()),
1439 ));
1440 Ok(())
1441 }
1442}