1mod authorization;
3mod clients;
4mod consent;
5mod continuation;
6mod credentials;
7mod id_token;
8mod introspection;
9mod logout;
10mod machine;
11mod refresh;
12mod resources;
13mod revocation;
14mod session_retirement;
15mod token;
16mod userinfo;
17use alibi_core::endpoint::{
18 EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
19};
20use alibi_core::{
21 AuthContext, AuthError, AuthPlugin, AuthRequest, AuthResponse, AuthResult, AuthRoute,
22 AuthSchema, HttpMethod,
23};
24use async_trait::async_trait;
25pub use clients::CreateOAuthClientInput;
26use serde_json::{Value, json};
27
28#[derive(Debug, Clone)]
30pub struct OAuthClientPrivilegeContext {
31 pub action: &'static str,
32 pub headers: alibi_core::Headers,
33 pub user: Value,
34 pub session: Value,
35}
36#[async_trait]
37pub trait OAuthClientPrivileges: Send + Sync + std::fmt::Debug {
38 async fn allowed(&self, context: &OAuthClientPrivilegeContext) -> AuthResult<bool>;
39}
40#[derive(Clone, Debug)]
42pub struct OAuthProviderConfig {
43 pub scopes: Vec<String>,
44 pub client_credentials_scope_privileges: Option<std::sync::Arc<dyn OAuthClientPrivileges>>,
46 pub login_page: String,
47 pub consent_page: String,
48 pub select_account_page: Option<String>,
49 pub code_expires_in: f64,
50 pub refresh_token_reuse_interval: f64,
51 pub allow_public_client_prelogin: bool,
52 pub enforce_per_client_resources: bool,
53 pub advertised_scopes: Option<Vec<String>>,
54 pub advertised_claims: Option<Vec<String>>,
55 pub grant_types: Vec<String>,
56 pub allow_dynamic_client_registration: bool,
57 pub allow_unauthenticated_client_registration: bool,
58 pub disable_jwt_plugin: bool,
59 pub pairwise_secret: Option<String>,
60 pub dpop_signing_algorithms: Option<Vec<String>>,
61}
62impl Default for OAuthProviderConfig {
63 fn default() -> Self {
64 Self {
65 client_credentials_scope_privileges: None,
66 login_page: "/login".into(),
67 consent_page: "/consent".into(),
68 select_account_page: None,
69 code_expires_in: 600.0,
70 refresh_token_reuse_interval: 0.0,
71 allow_public_client_prelogin: false,
72 enforce_per_client_resources: true,
73 scopes: ["openid", "profile", "email", "offline_access"]
74 .map(str::to_owned)
75 .into(),
76 advertised_scopes: None,
77 advertised_claims: None,
78 grant_types: ["authorization_code", "client_credentials", "refresh_token"]
79 .map(str::to_owned)
80 .into(),
81 allow_dynamic_client_registration: false,
82 allow_unauthenticated_client_registration: false,
83 disable_jwt_plugin: false,
84 pairwise_secret: None,
85 dpop_signing_algorithms: None,
86 }
87 }
88}
89#[derive(Clone, Debug, Default)]
90pub struct OAuthProviderPlugin {
91 config: OAuthProviderConfig,
92}
93impl OAuthProviderPlugin {
94 #[must_use]
95 pub fn new() -> Self {
96 Self::default()
97 }
98 #[must_use]
99 pub fn with_config(mut config: OAuthProviderConfig) -> Self {
100 config.scopes = unique(
101 config
102 .scopes
103 .into_iter()
104 .filter(|scope| !scope.is_empty())
105 .collect(),
106 );
107 Self { config }
108 }
109 #[must_use]
111 pub fn get_oauth_server_config_endpoint() -> ServerEndpoint<Value> {
112 ServerEndpoint::new("oauth-provider", "getOAuthServerConfig")
113 }
114 #[must_use]
116 pub fn get_open_id_config_endpoint() -> ServerEndpoint<Value> {
117 ServerEndpoint::new("oauth-provider", "getOpenIdConfig")
118 }
119 fn metadata<S: AuthSchema>(&self, ctx: &AuthContext<S>) -> AuthResult<Value> {
120 let jwt = if self.config.disable_jwt_plugin {
121 None
122 } else {
123 Some(
124 ctx.extensions
125 .get::<super::jwt::JwtDiscoveryOptions>()
126 .ok_or_else(|| AuthError::config("jwt_config"))?,
127 )
128 };
129 let base = format!(
130 "{}{}",
131 ctx.config.base_url.trim_end_matches('/'),
132 ctx.config.base_path
133 );
134 let issuer = jwt
135 .as_ref()
136 .and_then(|jwt| jwt.issuer.as_deref())
137 .unwrap_or(&base);
138 let issuer = normalize_issuer(issuer);
139 let grants = unique(self.config.grant_types.clone());
140 let mut token_auth = vec![
141 "client_secret_basic",
142 "client_secret_post",
143 "private_key_jwt",
144 ];
145 if self.config.allow_unauthenticated_client_registration {
146 token_auth.insert(0, "none");
147 }
148 let signing = [
149 "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512",
150 "EdDSA",
151 ];
152 let mut output: serde_json::Map<String, Value> = serde_json::from_value(json!({
153 "scopes_supported":self.config.advertised_scopes.as_ref().unwrap_or(&self.config.scopes),
154 "issuer":issuer,"authorization_endpoint":format!("{base}/oauth2/authorize"),"token_endpoint":format!("{base}/oauth2/token"),
155 "introspection_endpoint":format!("{base}/oauth2/introspect"),"revocation_endpoint":format!("{base}/oauth2/revoke"),
156 "response_types_supported":if grants.iter().any(|grant|grant=="authorization_code"){vec!["code"]}else{vec![]},
157 "response_modes_supported":["query"],"grant_types_supported":grants,
158 "token_endpoint_auth_methods_supported":token_auth,"token_endpoint_auth_signing_alg_values_supported":signing,
159 "introspection_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","private_key_jwt"],
160 "introspection_endpoint_auth_signing_alg_values_supported":signing,
161 "revocation_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","private_key_jwt"],
162 "revocation_endpoint_auth_signing_alg_values_supported":signing,
163 "code_challenge_methods_supported":["S256"],"authorization_response_iss_parameter_supported":true,
164 "dpop_signing_alg_values_supported":self.config.dpop_signing_algorithms.clone().unwrap_or_else(||["EdDSA","ES256","ES512","PS256","RS256"].map(str::to_owned).into()),
165 "backchannel_logout_supported":!self.config.disable_jwt_plugin,"backchannel_logout_session_supported":!self.config.disable_jwt_plugin,
166 }))?;
167 if let Some(jwt) = jwt.as_ref() {
168 _ = output.insert(
169 "jwks_uri".into(),
170 json!(
171 jwt.remote_url
172 .clone()
173 .unwrap_or_else(|| format!("{base}{}", jwt.jwks_path))
174 ),
175 );
176 }
177 if self.config.allow_dynamic_client_registration {
178 _ = output.insert(
179 "registration_endpoint".into(),
180 json!(format!("{base}/oauth2/register")),
181 );
182 }
183 if self.config.scopes.iter().any(|scope| scope == "openid") {
184 let mut claims = ["sub", "iss", "aud", "exp", "iat", "sid", "scope", "azp"]
185 .map(str::to_owned)
186 .to_vec();
187 for (scope, names) in [
188 (
189 "profile",
190 vec!["name", "picture", "given_name", "family_name"],
191 ),
192 ("email", vec!["email", "email_verified"]),
193 ] {
194 if self.config.scopes.iter().any(|value| value == scope) {
195 claims.extend(names.into_iter().map(str::to_owned));
196 }
197 }
198 _ = output.insert(
199 "claims_supported".into(),
200 json!(self.config.advertised_claims.as_ref().unwrap_or(&claims)),
201 );
202 _ = output.insert("claims_parameter_supported".into(), json!(true));
203 _ = output.insert(
204 "userinfo_endpoint".into(),
205 json!(format!("{base}/oauth2/userinfo")),
206 );
207 _ = output.insert(
208 "subject_types_supported".into(),
209 json!(if self
210 .config
211 .pairwise_secret
212 .as_ref()
213 .is_some_and(|secret| !secret.is_empty())
214 {
215 vec!["public", "pairwise"]
216 } else {
217 vec!["public"]
218 }),
219 );
220 _ = output.insert("acr_values_supported".into(), json!(["0"]));
221 _ = output.insert(
222 "id_token_signing_alg_values_supported".into(),
223 json!(jwt.as_ref().map_or_else(
224 || vec!["HS256".to_owned()],
225 |jwt| jwt.signing_algorithms.clone()
226 )),
227 );
228 _ = output.insert(
229 "end_session_endpoint".into(),
230 json!(format!("{base}/oauth2/end-session")),
231 );
232 _ = output.insert("request_parameter_supported".into(), json!(false));
233 _ = output.insert("request_uri_parameter_supported".into(), json!(false));
234 _ = output.insert(
235 "prompt_values_supported".into(),
236 json!(["login", "consent", "create", "select_account", "none"]),
237 );
238 }
239 Ok(Value::Object(output))
240 }
241}
242fn unique(values: Vec<String>) -> Vec<String> {
243 let mut result = Vec::new();
244 for value in values {
245 if !result.contains(&value) {
246 result.push(value);
247 }
248 }
249 result
250}
251fn normalize_issuer(value: &str) -> String {
252 let Ok(mut url) = url::Url::parse(value) else {
253 return value.into();
254 };
255 let loopback = url.host_str().is_some_and(|host| {
256 host == "localhost" || host == "127.0.0.1" || host == "[::1]" || host == "::1"
257 });
258 if url.scheme() != "https" && !loopback {
259 _ = url.set_scheme("https");
260 }
261 url.set_query(None);
262 url.set_fragment(None);
263 url.to_string().trim_end_matches('/').to_owned()
264}
265#[async_trait]
266impl<S: AuthSchema> AuthPlugin<S> for OAuthProviderPlugin {
267 fn name(&self) -> &'static str {
268 "oauth-provider"
269 }
270 fn allowed_media_types(&self, route: &AuthRoute) -> Vec<&'static str> {
271 match route.path.as_str() {
272 "/oauth2/end-session/confirm"
273 | "/oauth2/token"
274 | "/oauth2/introspect"
275 | "/oauth2/revoke"
276 | "/oauth2/userinfo" => vec!["application/x-www-form-urlencoded"],
277 "/oauth2/end-session" => vec!["application/json", "application/x-www-form-urlencoded"],
278 _ => vec!["application/json"],
279 }
280 }
281 fn routes(&self) -> Vec<AuthRoute> {
282 vec![
283 AuthRoute::post(
284 "/oauth2/end-session/confirm",
285 "oauth2EndSessionConfirmation",
286 ),
287 AuthRoute::get("/oauth2/end-session", "oauth2EndSession"),
288 AuthRoute::post("/oauth2/end-session", "oauth2EndSession"),
289 AuthRoute::get("/oauth2/userinfo", "oauth2UserInfo"),
290 AuthRoute::post("/oauth2/userinfo", "oauth2UserInfo"),
291 AuthRoute::post("/oauth2/revoke", "oauth2Revoke"),
292 AuthRoute::post("/oauth2/introspect", "oauth2Introspect"),
293 AuthRoute::post("/oauth2/token", "oauth2Token"),
294 AuthRoute::post("/oauth2/create-client", "createOAuthClient"),
295 AuthRoute::get("/oauth2/get-client", "getOAuthClient"),
296 AuthRoute::get("/oauth2/public-client", "getOAuthClientPublic"),
297 AuthRoute::post(
298 "/oauth2/public-client-prelogin",
299 "getOAuthClientPublicPrelogin",
300 ),
301 AuthRoute::get("/oauth2/get-clients", "getOAuthClients"),
302 AuthRoute::post("/oauth2/delete-client", "deleteOAuthClient"),
303 AuthRoute::post("/oauth2/update-client", "updateOAuthClient"),
304 AuthRoute::post("/oauth2/register", "registerOAuthClient"),
305 AuthRoute::post("/oauth2/consent", "oauth2Consent"),
306 AuthRoute::post("/oauth2/continue", "oauth2Continue"),
307 AuthRoute::get("/oauth2/get-consents", "getOAuthConsents"),
308 AuthRoute::get("/oauth2/get-consent", "getOAuthConsent"),
309 AuthRoute::post("/oauth2/update-consent", "updateOAuthConsent"),
310 AuthRoute::post("/oauth2/delete-consent", "deleteOAuthConsent"),
311 AuthRoute::get("/oauth2/authorize", "oauth2Authorize"),
312 AuthRoute::post("/oauth2/authorize", "oauth2Authorize"),
313 AuthRoute::post("/oauth2/client/rotate-secret", "rotateClientSecret"),
314 ]
315 }
316 fn server_endpoints(&self) -> Vec<EndpointDefinition> {
317 vec![
318 crate::endpoint::definition(
319 "oauth2EndSessionConfirmation",
320 "oauth2EndSessionConfirmation",
321 Some("/oauth2/end-session/confirm"),
322 HttpMethod::Post,
323 ),
324 crate::endpoint::definition(
325 "oauth2EndSession",
326 "oauth2EndSession",
327 Some("/oauth2/end-session"),
328 HttpMethod::Get,
329 ),
330 crate::endpoint::definition(
331 "oauth2UserInfo",
332 "oauth2UserInfo",
333 Some("/oauth2/userinfo"),
334 HttpMethod::Get,
335 ),
336 crate::endpoint::definition(
337 "oauth2Revoke",
338 "oauth2Revoke",
339 Some("/oauth2/revoke"),
340 HttpMethod::Post,
341 ),
342 crate::endpoint::definition(
343 "oauth2Introspect",
344 "oauth2Introspect",
345 Some("/oauth2/introspect"),
346 HttpMethod::Post,
347 ),
348 crate::endpoint::definition(
349 "oauth2Token",
350 "oauth2Token",
351 Some("/oauth2/token"),
352 HttpMethod::Post,
353 ),
354 crate::endpoint::definition(
355 "oauth2Continue",
356 "oauth2Continue",
357 Some("/oauth2/continue"),
358 HttpMethod::Post,
359 ),
360 crate::endpoint::definition(
361 "adminUnlinkClientResource",
362 "adminUnlinkClientResource",
363 Some("/admin/oauth2/resources/:identifier/clients/:client_id"),
364 HttpMethod::Delete,
365 ),
366 crate::endpoint::definition(
367 "adminLinkClientResource",
368 "adminLinkClientResource",
369 Some("/admin/oauth2/resources/:identifier/clients/:client_id"),
370 HttpMethod::Post,
371 ),
372 crate::endpoint::definition(
373 "adminDeleteOAuthResource",
374 "adminDeleteOAuthResource",
375 Some("/admin/oauth2/resources/:identifier"),
376 HttpMethod::Delete,
377 ),
378 crate::endpoint::definition(
379 "adminUpdateOAuthResource",
380 "adminUpdateOAuthResource",
381 Some("/admin/oauth2/resources/:identifier"),
382 HttpMethod::Patch,
383 ),
384 crate::endpoint::definition(
385 "adminGetOAuthResource",
386 "adminGetOAuthResource",
387 Some("/admin/oauth2/resources/:identifier"),
388 HttpMethod::Get,
389 ),
390 crate::endpoint::definition(
391 "adminListOAuthResources",
392 "adminListOAuthResources",
393 Some("/admin/oauth2/resources"),
394 HttpMethod::Get,
395 ),
396 crate::endpoint::definition(
397 "adminCreateOAuthResource",
398 "adminCreateOAuthResource",
399 Some("/admin/oauth2/resources"),
400 HttpMethod::Post,
401 ),
402 crate::endpoint::definition(
403 "deleteOAuthConsent",
404 "deleteOAuthConsent",
405 Some("/oauth2/delete-consent"),
406 HttpMethod::Post,
407 ),
408 crate::endpoint::definition(
409 "updateOAuthConsent",
410 "updateOAuthConsent",
411 Some("/oauth2/update-consent"),
412 HttpMethod::Post,
413 ),
414 crate::endpoint::definition(
415 "getOAuthConsent",
416 "getOAuthConsent",
417 Some("/oauth2/get-consent"),
418 HttpMethod::Get,
419 ),
420 crate::endpoint::definition(
421 "getOAuthConsents",
422 "getOAuthConsents",
423 Some("/oauth2/get-consents"),
424 HttpMethod::Get,
425 ),
426 crate::endpoint::definition(
427 "oauth2Consent",
428 "oauth2Consent",
429 Some("/oauth2/consent"),
430 HttpMethod::Post,
431 ),
432 crate::endpoint::definition(
433 "getOAuthClientPublicPrelogin",
434 "getOAuthClientPublicPrelogin",
435 Some("/oauth2/public-client-prelogin"),
436 HttpMethod::Post,
437 ),
438 crate::endpoint::definition(
439 "oauth2Authorize",
440 "oauth2Authorize",
441 Some("/oauth2/authorize"),
442 HttpMethod::Get,
443 ),
444 crate::endpoint::definition(
445 "adminUpdateOAuthClient",
446 "adminUpdateOAuthClient",
447 Some("/admin/oauth2/update-client"),
448 HttpMethod::Patch,
449 ),
450 crate::endpoint::definition(
451 "adminCreateOAuthClient",
452 "adminCreateOAuthClient",
453 Some("/admin/oauth2/create-client"),
454 HttpMethod::Post,
455 ),
456 crate::endpoint::definition(
457 "registerOAuthClient",
458 "registerOAuthClient",
459 Some("/oauth2/register"),
460 HttpMethod::Post,
461 ),
462 crate::endpoint::definition(
463 "updateOAuthClient",
464 "updateOAuthClient",
465 Some("/oauth2/update-client"),
466 HttpMethod::Post,
467 ),
468 crate::endpoint::definition(
469 "rotateClientSecret",
470 "rotateClientSecret",
471 Some("/oauth2/client/rotate-secret"),
472 HttpMethod::Post,
473 ),
474 crate::endpoint::definition(
475 "deleteOAuthClient",
476 "deleteOAuthClient",
477 Some("/oauth2/delete-client"),
478 HttpMethod::Post,
479 ),
480 crate::endpoint::definition(
481 "getOAuthClients",
482 "getOAuthClients",
483 Some("/oauth2/get-clients"),
484 HttpMethod::Get,
485 ),
486 crate::endpoint::definition(
487 "getOAuthClientPublic",
488 "getOAuthClientPublic",
489 Some("/oauth2/public-client"),
490 HttpMethod::Get,
491 ),
492 crate::endpoint::definition(
493 "getOAuthClient",
494 "getOAuthClient",
495 Some("/oauth2/get-client"),
496 HttpMethod::Get,
497 ),
498 crate::endpoint::definition(
499 "createOAuthClient",
500 "createOAuthClient",
501 Some("/oauth2/create-client"),
502 HttpMethod::Post,
503 ),
504 crate::endpoint::definition(
505 "getOAuthServerConfig",
506 "getOAuthServerConfig",
507 Some("/.well-known/oauth-authorization-server"),
508 HttpMethod::Get,
509 ),
510 crate::endpoint::definition(
511 "getOpenIdConfig",
512 "getOpenIdConfig",
513 Some("/.well-known/openid-configuration"),
514 HttpMethod::Get,
515 ),
516 ]
517 }
518 fn validate_endpoint(
519 &self,
520 call: &EndpointCall,
521 _ctx: &AuthContext<S>,
522 ) -> AuthResult<EndpointInput> {
523 Ok(EndpointInput {
524 body: if call.operation_id() == "oauth2EndSessionConfirmation" {
525 Some(crate::endpoint::validate_fields(
526 call.body(),
527 "body",
528 &[crate::authentication_helpers::JsonField {
529 name: "action",
530 kind: crate::authentication_helpers::JsonFieldKind::OneOf(&["confirm"]),
531 required: true,
532 }],
533 )?)
534 } else if call.operation_id() == "oauth2EndSession" {
535 logout::validate(call.body(), "body")?
536 } else if call.operation_id() == "oauth2UserInfo" {
537 call.body()
538 .map(|body| {
539 crate::endpoint::validate_fields(
540 Some(body),
541 "body",
542 &[crate::authentication_helpers::JsonField::string(
543 "access_token",
544 false,
545 )],
546 )
547 })
548 .transpose()?
549 } else if call.operation_id() == "oauth2Continue" {
550 Some(continuation::validate(call.body())?)
551 } else if call.operation_id() == "adminUpdateOAuthResource" {
552 Some(resources::validate_update(call.body())?)
553 } else if call.operation_id() == "adminCreateOAuthResource" {
554 Some(resources::validate_create(call.body())?)
555 } else if call.operation_id() == "deleteOAuthConsent" {
556 Some(consent::validate_id(call.body(), "body")?)
557 } else if call.operation_id() == "updateOAuthConsent" {
558 Some(consent::validate_update(call.body())?)
559 } else if call.operation_id() == "oauth2Consent" {
560 Some(consent::validate(call.body())?)
561 } else if call.operation_id() == "getOAuthClientPublicPrelogin" {
562 Some(clients::validate_prelogin(call.body())?)
563 } else if call.operation_id() == "adminUpdateOAuthClient" {
564 Some(clients::validate_admin_update(call.body())?)
565 } else if call.operation_id() == "adminCreateOAuthClient" {
566 Some(clients::validate_admin_create(call.body())?)
567 } else if call.operation_id() == "updateOAuthClient" {
568 Some(clients::validate_update(call.body())?)
569 } else if matches!(
570 call.operation_id(),
571 "deleteOAuthClient" | "rotateClientSecret"
572 ) {
573 Some(clients::validate_delete(call.body())?)
574 } else if call.operation_id() == "createOAuthClient" {
575 Some(clients::validate(call.body())?)
576 } else {
577 call.body().cloned()
578 },
579 query: if call.operation_id() == "oauth2EndSession" {
580 logout::validate(call.query(), "query")?
581 } else if call.operation_id() == "getOAuthConsent" {
582 Some(consent::validate_id(call.query(), "query")?)
583 } else if matches!(
584 call.operation_id(),
585 "getOAuthClient" | "getOAuthClientPublic"
586 ) {
587 Some(clients::validate_read(call.query())?)
588 } else {
589 call.query().cloned()
590 },
591 })
592 }
593 async fn on_endpoint(
594 &self,
595 call: &EndpointCall,
596 ctx: &AuthContext<S>,
597 ) -> AuthResult<EndpointResponse> {
598 if call.operation_id() == "oauth2EndSessionConfirmation" {
599 return Box::pin(self.confirm_logout(call, ctx)).await;
600 }
601 if call.operation_id() == "oauth2EndSession" {
602 return Box::pin(self.end_session(call.query(), call.body(), call, ctx)).await;
603 }
604 if call.operation_id() == "oauth2UserInfo" {
605 return Box::pin(self.user_info(call.body(), call, ctx)).await;
606 }
607 if call.operation_id() == "oauth2Revoke" {
608 return Box::pin(self.revoke(call.body(), call, ctx)).await;
609 }
610 if call.operation_id() == "oauth2Introspect" {
611 return Box::pin(self.introspect(call.body(), call, ctx)).await;
612 }
613 if call.operation_id() == "oauth2Token" {
614 return Box::pin(self.token(call.body(), call, ctx)).await;
615 }
616 if call.operation_id() == "oauth2Continue" {
617 return Box::pin(self.continue_authorization(call.body_as()?, call, ctx)).await;
618 }
619 if call.operation_id() == "adminUnlinkClientResource" {
620 return Box::pin(self.unlink_resource(call.params(), call, ctx)).await;
621 }
622 if call.operation_id() == "adminLinkClientResource" {
623 return Box::pin(self.link_resource(call.params(), call, ctx)).await;
624 }
625 if call.operation_id() == "adminDeleteOAuthResource" {
626 return Box::pin(self.delete_resource(call.params(), call, ctx)).await;
627 }
628 if call.operation_id() == "adminUpdateOAuthResource" {
629 return Box::pin(self.update_resource(call.params(), call.body_as()?, call, ctx)).await;
630 }
631 if call.operation_id() == "adminGetOAuthResource" {
632 return Box::pin(self.read_resource(call.params(), call, ctx)).await;
633 }
634 if call.operation_id() == "adminListOAuthResources" {
635 return Box::pin(self.list_resources(call, ctx)).await;
636 }
637 if call.operation_id() == "adminCreateOAuthResource" {
638 return Box::pin(self.create_resource(call.body_as()?, call, ctx)).await;
639 }
640 if call.operation_id() == "deleteOAuthConsent" {
641 return Box::pin(self.delete_consent(call.body_as()?, call, ctx)).await;
642 }
643 if call.operation_id() == "updateOAuthConsent" {
644 return Box::pin(self.update_consent(call.body_as()?, call, ctx)).await;
645 }
646 if call.operation_id() == "getOAuthConsent" {
647 return Box::pin(self.read_consent(call.query_as()?, call, ctx)).await;
648 }
649 if call.operation_id() == "getOAuthConsents" {
650 return Box::pin(self.list_consents(call, ctx)).await;
651 }
652 if call.operation_id() == "oauth2Consent" {
653 return Box::pin(self.consent(call.body_as()?, call, ctx)).await;
654 }
655 if call.operation_id() == "getOAuthClientPublicPrelogin" {
656 return Box::pin(self.read_prelogin_client(call.body_as()?, ctx)).await;
657 }
658 if call.operation_id() == "oauth2Authorize" {
659 let value = if call.method() == Some(&HttpMethod::Post) {
660 call.body()
661 } else {
662 call.query()
663 };
664 return Box::pin(self.authorize(value.cloned(), call, ctx)).await;
665 }
666 if call.operation_id() == "adminUpdateOAuthClient" {
667 return Box::pin(self.update_client(call.body_as()?, call, ctx, true)).await;
668 }
669 if call.operation_id() == "adminCreateOAuthClient" {
670 return Box::pin(self.admin_create_client(call.body_as()?, call, ctx)).await;
671 }
672 if call.operation_id() == "registerOAuthClient" {
673 return Box::pin(self.register_client(call.body(), call, ctx)).await;
674 }
675 if call.operation_id() == "updateOAuthClient" {
676 return Box::pin(self.update_client(call.body_as()?, call, ctx, false)).await;
677 }
678 if call.operation_id() == "rotateClientSecret" {
679 return Box::pin(self.rotate_client_secret(call.body_as()?, call, ctx)).await;
680 }
681 if call.operation_id() == "deleteOAuthClient" {
682 return Box::pin(self.delete_client(call.body_as()?, call, ctx)).await;
683 }
684 if call.operation_id() == "getOAuthClients" {
685 return Box::pin(self.list_clients(call, ctx)).await;
686 }
687 if call.operation_id() == "getOAuthClientPublic" {
688 return Box::pin(self.read_public_client(call.query_as()?, call, ctx)).await;
689 }
690 if call.operation_id() == "getOAuthClient" {
691 return Box::pin(self.read_client(call.query_as()?, call, ctx)).await;
692 }
693 if call.operation_id() == "createOAuthClient" {
694 return Box::pin(self.create_client(call.body_as()?, call, ctx)).await;
695 }
696 if call.operation_id() == "getOpenIdConfig"
697 && !self.config.scopes.iter().any(|scope| scope == "openid")
698 {
699 return Ok(EndpointResponse::error(AuthError::Api {
700 status: 404,
701 code: None,
702 message: String::new(),
703 })
704 .without_error_body()
705 .with_header("Content-Type", "application/json"));
706 }
707 EndpointResponse::json(&self.metadata(ctx)?)
708 }
709 async fn on_request(
710 &self,
711 req: &AuthRequest,
712 ctx: &AuthContext<S>,
713 ) -> AuthResult<Option<AuthResponse>> {
714 if req.path() == "/oauth2/end-session/confirm" && *req.method() == HttpMethod::Post {
715 let action = url::form_urlencoded::parse(req.body.as_deref().unwrap_or_default())
716 .find(|(key, _)| key == "action")
717 .map(|(_, value)| value.into_owned());
718 if action.as_deref() != Some("confirm") {
719 return Ok(Some(
720 AuthError::bad_request("Invalid logout action").to_auth_response(),
721 ));
722 }
723 return Ok(Some(
724 Box::pin(self.confirm_logout(req, ctx))
725 .await?
726 .to_response()?,
727 ));
728 }
729 if req.path() == "/oauth2/end-session"
730 && matches!(req.method(), HttpMethod::Get | HttpMethod::Post)
731 {
732 let query = alibi_core::utils::json::parse_value(&serde_json::to_string(&req.query)?)?;
733 let body = if *req.method() == HttpMethod::Post {
734 Some(alibi_core::utils::json::parse_value(
735 &serde_json::to_string(
736 &url::form_urlencoded::parse(req.body.as_deref().unwrap_or_default())
737 .into_owned()
738 .collect::<std::collections::HashMap<_, _>>(),
739 )?,
740 )?)
741 } else {
742 None
743 };
744 return Ok(Some(
745 Box::pin(self.end_session(Some(&query), body.as_ref(), req, ctx))
746 .await?
747 .to_response()?,
748 ));
749 }
750 if req.path() == "/oauth2/userinfo"
751 && matches!(req.method(), HttpMethod::Get | HttpMethod::Post)
752 {
753 let body = if *req.method() == HttpMethod::Post {
754 let fields = url::form_urlencoded::parse(req.body.as_deref().unwrap_or_default())
755 .map(|(key, value)| {
756 (
757 key.into_owned(),
758 alibi_core::utils::json::JsValue::String(value.into_owned()),
759 )
760 })
761 .collect();
762 Some(alibi_core::utils::json::JsValue::Object(fields))
763 } else {
764 None
765 };
766 return Ok(Some(
767 Box::pin(self.user_info(body.as_ref(), req, ctx))
768 .await?
769 .to_response()?,
770 ));
771 }
772 if matches!(
773 req.path(),
774 "/oauth2/token" | "/oauth2/introspect" | "/oauth2/revoke"
775 ) && *req.method() == HttpMethod::Post
776 {
777 let mut fields = indexmap::IndexMap::new();
778 for (key, value) in url::form_urlencoded::parse(req.body.as_deref().unwrap_or_default())
779 {
780 _ = fields.insert(
781 key.into_owned(),
782 alibi_core::utils::json::JsValue::String(value.into_owned()),
783 );
784 }
785 if req.path() == "/oauth2/revoke" {
786 return Ok(Some(
787 Box::pin(self.revoke(
788 Some(&alibi_core::utils::json::JsValue::Object(fields)),
789 req,
790 ctx,
791 ))
792 .await?
793 .to_response()?,
794 ));
795 }
796 if req.path() == "/oauth2/introspect" {
797 return Ok(Some(
798 Box::pin(self.introspect(
799 Some(&alibi_core::utils::json::JsValue::Object(fields)),
800 req,
801 ctx,
802 ))
803 .await?
804 .to_response()?,
805 ));
806 }
807 return Ok(Some(
808 Box::pin(self.token(
809 Some(&alibi_core::utils::json::JsValue::Object(fields)),
810 req,
811 ctx,
812 ))
813 .await?
814 .to_response()?,
815 ));
816 }
817 if req.path() == "/oauth2/authorize"
818 && matches!(req.method(), HttpMethod::Get | HttpMethod::Post)
819 {
820 let value = if *req.method() == HttpMethod::Post {
821 let mut fields = indexmap::IndexMap::new();
822 for (name, value) in
823 url::form_urlencoded::parse(req.body.as_deref().unwrap_or_default())
824 {
825 _ = fields.insert(
826 name.into_owned(),
827 alibi_core::utils::json::JsValue::String(value.into_owned()),
828 );
829 }
830 alibi_core::utils::json::JsValue::Object(fields)
831 } else {
832 alibi_core::utils::json::parse_value(&serde_json::to_string(&req.query)?)?
833 };
834 return Ok(Some(
835 Box::pin(self.authorize(Some(value), req, ctx))
836 .await?
837 .to_response()?,
838 ));
839 }
840 if req.path() == "/oauth2/register" && *req.method() == HttpMethod::Post {
841 let body: alibi_core::utils::json::JsValue = req.body_as_json()?;
842 return Ok(Some(
843 Box::pin(self.register_client(Some(&body), req, ctx))
844 .await?
845 .to_response()?,
846 ));
847 }
848 if req.path() == "/oauth2/update-client" && *req.method() == HttpMethod::Post {
849 let body: alibi_core::utils::json::JsValue = req.body_as_json()?;
850 let input =
851 alibi_core::utils::json::from_value(clients::validate_update(Some(&body))?)?;
852 return Ok(Some(
853 Box::pin(self.update_client(input, req, ctx, false))
854 .await?
855 .to_response()?,
856 ));
857 }
858 if matches!(
859 req.path(),
860 "/oauth2/delete-client" | "/oauth2/client/rotate-secret"
861 ) && *req.method() == HttpMethod::Post
862 {
863 let body: alibi_core::utils::json::JsValue = req.body_as_json()?;
864 let input =
865 alibi_core::utils::json::from_value(clients::validate_delete(Some(&body))?)?;
866 if req.path() == "/oauth2/client/rotate-secret" {
867 return Ok(Some(
868 Box::pin(self.rotate_client_secret(input, req, ctx))
869 .await?
870 .to_response()?,
871 ));
872 }
873 return Ok(Some(
874 Box::pin(self.delete_client(input, req, ctx))
875 .await?
876 .to_response()?,
877 ));
878 }
879 if req.path() == "/oauth2/public-client-prelogin" && *req.method() == HttpMethod::Post {
880 let body = req.body_as_json()?;
881 let input =
882 alibi_core::utils::json::from_value(clients::validate_prelogin(Some(&body))?)?;
883 return Ok(Some(
884 Box::pin(self.read_prelogin_client(input, ctx))
885 .await?
886 .to_response()?,
887 ));
888 }
889 if req.path() == "/oauth2/continue" && *req.method() == HttpMethod::Post {
890 let body = req.body_as_json()?;
891 let input = alibi_core::utils::json::from_value(continuation::validate(Some(&body))?)?;
892 return Ok(Some(
893 Box::pin(self.continue_authorization(input, req, ctx))
894 .await?
895 .to_response()?,
896 ));
897 }
898 if req.path() == "/oauth2/consent" && *req.method() == HttpMethod::Post {
899 let body = req.body_as_json()?;
900 let input = alibi_core::utils::json::from_value(consent::validate(Some(&body))?)?;
901 return Ok(Some(
902 Box::pin(self.consent(input, req, ctx))
903 .await?
904 .to_response()?,
905 ));
906 }
907 if req.path() == "/oauth2/delete-consent" && *req.method() == HttpMethod::Post {
908 let body = req.body_as_json()?;
909 let input =
910 alibi_core::utils::json::from_value(consent::validate_id(Some(&body), "body")?)?;
911 return Ok(Some(
912 Box::pin(self.delete_consent(input, req, ctx))
913 .await?
914 .to_response()?,
915 ));
916 }
917 if req.path() == "/oauth2/update-consent" && *req.method() == HttpMethod::Post {
918 let body = req.body_as_json()?;
919 let input =
920 alibi_core::utils::json::from_value(consent::validate_update(Some(&body))?)?;
921 return Ok(Some(
922 Box::pin(self.update_consent(input, req, ctx))
923 .await?
924 .to_response()?,
925 ));
926 }
927 if req.path() == "/oauth2/get-consent" && *req.method() == HttpMethod::Get {
928 let query = alibi_core::utils::json::parse_value(&serde_json::to_string(&req.query)?)?;
929 let input =
930 alibi_core::utils::json::from_value(consent::validate_id(Some(&query), "query")?)?;
931 return Ok(Some(
932 Box::pin(self.read_consent(input, req, ctx))
933 .await?
934 .to_response()?,
935 ));
936 }
937 if req.path() == "/oauth2/get-consents" && *req.method() == HttpMethod::Get {
938 return Ok(Some(
939 Box::pin(self.list_consents(req, ctx))
940 .await?
941 .to_response()?,
942 ));
943 }
944 if req.path() == "/oauth2/get-clients" && *req.method() == HttpMethod::Get {
945 return Ok(Some(
946 Box::pin(self.list_clients(req, ctx)).await?.to_response()?,
947 ));
948 }
949 if matches!(req.path(), "/oauth2/get-client" | "/oauth2/public-client")
950 && *req.method() == HttpMethod::Get
951 {
952 let query = alibi_core::utils::json::parse_value(&serde_json::to_string(&req.query)?)?;
953 let input = alibi_core::utils::json::from_value(clients::validate_read(Some(&query))?)?;
954 if req.path() == "/oauth2/public-client" {
955 return Ok(Some(
956 Box::pin(self.read_public_client(input, req, ctx))
957 .await?
958 .to_response()?,
959 ));
960 }
961 return Ok(Some(
962 Box::pin(self.read_client(input, req, ctx))
963 .await?
964 .to_response()?,
965 ));
966 }
967 if req.path() == "/oauth2/create-client" && *req.method() == HttpMethod::Post {
968 let body: alibi_core::utils::json::JsValue = req.body_as_json()?;
969 let input = alibi_core::utils::json::from_value(clients::validate(Some(&body))?)?;
970 return Ok(Some(
971 Box::pin(self.create_client(input, req, ctx))
972 .await?
973 .to_response()?,
974 ));
975 }
976 Ok(None)
977 }
978 async fn on_init(&self, ctx: &mut alibi_core::AuthInitContext<S>) -> AuthResult<()> {
979 if self
980 .config
981 .advertised_scopes
982 .as_ref()
983 .is_some_and(|scopes| {
984 scopes
985 .iter()
986 .any(|scope| !self.config.scopes.contains(scope))
987 })
988 {
989 return Err(AuthError::config(
990 "advertisedMetadata.scopes_supported contains an unsupported scope",
991 ));
992 }
993 if self
994 .config
995 .pairwise_secret
996 .as_ref()
997 .is_some_and(|secret| !secret.is_empty() && secret.len() < 32)
998 {
999 return Err(AuthError::config(
1000 "pairwiseSecret must be at least 32 characters long for adequate HMAC-SHA256 security",
1001 ));
1002 }
1003 if self
1004 .config
1005 .grant_types
1006 .iter()
1007 .any(|grant| grant == "refresh_token")
1008 && !self
1009 .config
1010 .grant_types
1011 .iter()
1012 .any(|grant| grant == "authorization_code")
1013 {
1014 return Err(AuthError::config(
1015 "refresh_token grant requires authorization_code grant",
1016 ));
1017 }
1018 ctx.register_session_deletion_hook(std::sync::Arc::new(
1019 session_retirement::TokenRetirementHook,
1020 ));
1021 Ok(())
1022 }
1023}