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