1use std::collections::{BTreeMap, BTreeSet};
4
5use base64::{engine::general_purpose::STANDARD, Engine as _};
6use serde::{Deserialize, Serialize};
7use serde_with::base64::{Base64, UrlSafe};
8use serde_with::formats::SpaceSeparator;
9use serde_with::{
10 formats, rust::deserialize_ignore_any, serde_as, skip_serializing_none, StringWithSeparator,
11};
12use url::Url;
13use uuid::Uuid;
14
15pub const OAUTH2_DEVICE_CODE_EXPIRY_SECONDS: u64 = 300;
17pub const OAUTH2_DEVICE_CODE_INTERVAL_SECONDS: u64 = 5;
19pub const OAUTH2_TOKEN_TYPE_ACCESS_TOKEN: &str = "urn:ietf:params:oauth:token-type:access_token";
21
22#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
23pub enum CodeChallengeMethod {
24 S256,
28}
29
30#[serde_as]
31#[derive(Serialize, Deserialize, Debug, Clone)]
32pub struct PkceRequest {
33 #[serde_as(as = "Base64<UrlSafe, formats::Unpadded>")]
34 pub code_challenge: Vec<u8>,
35 pub code_challenge_method: CodeChallengeMethod,
36}
37
38#[serde_as]
41#[skip_serializing_none]
42#[derive(Serialize, Deserialize, Debug, Clone)]
43pub struct AuthorisationRequest {
44 pub response_type: ResponseType,
46 pub response_mode: Option<ResponseMode>,
55 pub client_id: String,
56 pub state: Option<String>,
57 #[serde(flatten)]
58 pub pkce_request: Option<PkceRequest>,
59 pub redirect_uri: Url,
60 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
61 pub scope: BTreeSet<String>,
62 pub nonce: Option<String>,
64 #[serde(flatten)]
66 pub oidc_ext: AuthorisationRequestOidc,
67 pub max_age: Option<i64>,
69 #[serde(flatten)]
70 pub unknown_keys: BTreeMap<String, serde_json::value::Value>,
71}
72
73impl AuthorisationRequest {
74 pub const fn get_response_mode(&self) -> Option<ResponseMode> {
82 match (self.response_mode, self.response_type) {
83 (None, ResponseType::IdToken) => Some(ResponseMode::Fragment),
87 (Some(ResponseMode::Query), ResponseType::IdToken) => None,
88
89 (None, ResponseType::Code) => Some(ResponseMode::Query),
91 (None, ResponseType::Token) => Some(ResponseMode::Fragment),
93
94 (Some(ResponseMode::Query), ResponseType::Token) => None,
99
100 (Some(m), _) => Some(m),
102 }
103 }
104}
105
106#[skip_serializing_none]
109#[derive(Serialize, Deserialize, Debug, Clone, Default)]
110pub struct AuthorisationRequestOidc {
111 pub display: Option<String>,
112 pub prompt: Option<String>,
113 pub ui_locales: Option<()>,
114 pub claims_locales: Option<()>,
115 pub id_token_hint: Option<String>,
116 pub login_hint: Option<String>,
117 pub acr: Option<String>,
118}
119
120#[derive(Serialize, Deserialize, Debug, Clone)]
124pub enum AuthorisationResponse {
125 ConsentRequested {
126 client_name: String,
128 scopes: BTreeSet<String>,
130 pii_scopes: BTreeSet<String>,
132 consent_token: String,
136 },
137 Permitted,
138}
139
140#[serde_as]
141#[skip_serializing_none]
142#[derive(Serialize, Deserialize, Debug)]
143#[serde(tag = "grant_type", rename_all = "snake_case")]
144pub enum GrantTypeReq {
145 AuthorizationCode {
146 code: String,
148 redirect_uri: Url,
150 code_verifier: Option<String>,
151 },
152 ClientCredentials {
153 #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
154 scope: Option<BTreeSet<String>>,
155 },
156 RefreshToken {
157 refresh_token: String,
158 #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
159 scope: Option<BTreeSet<String>>,
160 },
161 #[serde(rename = "urn:ietf:params:oauth:grant-type:token-exchange")]
162 TokenExchange {
163 subject_token: String,
164 subject_token_type: String,
165 requested_token_type: Option<String>,
166 audience: Option<String>,
167 resource: Option<String>,
168 actor_token: Option<String>,
169 actor_token_type: Option<String>,
170 #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
171 scope: Option<BTreeSet<String>>,
172 },
173 #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
175 DeviceCode {
176 device_code: String,
177 scope: Option<BTreeSet<String>>,
179 },
180}
181
182#[skip_serializing_none]
184#[derive(Serialize, Deserialize, Debug)]
185pub struct AccessTokenRequest {
186 #[serde(flatten)]
187 pub grant_type: GrantTypeReq,
188 #[serde(flatten)]
191 pub client_post_auth: ClientPostAuth,
192}
193
194impl From<GrantTypeReq> for AccessTokenRequest {
195 fn from(req: GrantTypeReq) -> AccessTokenRequest {
196 AccessTokenRequest {
197 grant_type: req,
198 client_post_auth: ClientPostAuth::default(),
199 }
200 }
201}
202
203#[derive(Serialize, Debug, Clone, Deserialize)]
204#[skip_serializing_none]
205pub struct OAuth2RFC9068Token<V>
206where
207 V: Clone,
208{
209 pub iss: String,
211 pub sub: Uuid,
213 pub aud: String,
215 pub exp: i64,
217 pub nbf: i64,
219 pub iat: i64,
221 pub jti: Uuid,
223 pub client_id: String,
224 #[serde(flatten)]
225 pub extensions: V,
226}
227
228#[serde_as]
230#[skip_serializing_none]
231#[derive(Serialize, Deserialize, Debug, Clone)]
232pub struct OAuth2RFC9068TokenExtensions {
233 pub auth_time: Option<i64>,
234 pub acr: Option<String>,
235 pub amr: Option<Vec<String>>,
236
237 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
238 pub scope: BTreeSet<String>,
239
240 pub nonce: Option<String>,
241
242 pub session_id: Uuid,
243 pub parent_session_id: Option<Uuid>,
244}
245
246#[derive(Serialize, Deserialize, Debug, PartialEq)]
247pub enum IssuedTokenType {
248 AccessToken,
249 RefreshToken,
250 IdToken,
251 Saml1,
252 Saml2,
253}
254
255#[serde_as]
257#[skip_serializing_none]
258#[derive(Serialize, Deserialize, Debug)]
259pub struct AccessTokenResponse {
260 pub access_token: String,
261 pub token_type: AccessTokenType,
262 pub issued_token_type: Option<IssuedTokenType>,
264 pub expires_in: u32,
266 pub refresh_token: Option<String>,
267 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
270 pub scope: BTreeSet<String>,
271 pub id_token: Option<String>,
273}
274
275#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
277#[serde(try_from = "&str")]
278pub enum AccessTokenType {
279 Bearer,
280 PoP,
281 #[serde(rename = "N_A")]
282 NA,
283 DPoP,
284}
285
286impl TryFrom<&str> for AccessTokenType {
287 type Error = String;
288
289 fn try_from(s: &str) -> Result<Self, Self::Error> {
290 match s.to_lowercase().as_str() {
291 "bearer" => Ok(AccessTokenType::Bearer),
292 "pop" => Ok(AccessTokenType::PoP),
293 "n_a" => Ok(AccessTokenType::NA),
294 "dpop" => Ok(AccessTokenType::DPoP),
295 _ => Err(format!("Unknown AccessTokenType: {s}")),
296 }
297 }
298}
299
300#[skip_serializing_none]
303#[derive(Serialize, Deserialize, Debug)]
304pub struct TokenRevokeRequest {
305 pub token: String,
306 pub token_type_hint: Option<String>,
309
310 #[serde(flatten)]
311 pub client_post_auth: ClientPostAuth,
312}
313
314#[skip_serializing_none]
315#[derive(Serialize, Deserialize, Debug, Default)]
316pub struct ClientPostAuth {
318 pub client_id: Option<String>,
319 pub client_secret: Option<String>,
320}
321
322impl From<(String, Option<String>)> for ClientPostAuth {
323 fn from((client_id, client_secret): (String, Option<String>)) -> Self {
324 ClientPostAuth {
325 client_id: Some(client_id),
326 client_secret,
327 }
328 }
329}
330
331impl From<(&str, Option<&str>)> for ClientPostAuth {
332 fn from((client_id, client_secret): (&str, Option<&str>)) -> Self {
333 ClientPostAuth {
334 client_id: Some(client_id.to_string()),
335 client_secret: client_secret.map(|s| s.to_string()),
336 }
337 }
338}
339
340#[skip_serializing_none]
341#[derive(Serialize, Deserialize, Debug, Default)]
342pub struct ClientAuth {
344 pub client_id: String,
345 pub client_secret: Option<String>,
346}
347
348impl From<(&str, Option<&str>)> for ClientAuth {
349 fn from((client_id, client_secret): (&str, Option<&str>)) -> Self {
350 ClientAuth {
351 client_id: client_id.to_string(),
352 client_secret: client_secret.map(|s| s.to_string()),
353 }
354 }
355}
356
357#[skip_serializing_none]
359#[derive(Serialize, Deserialize, Debug)]
360pub struct AccessTokenIntrospectRequest {
361 pub token: String,
362 pub token_type_hint: Option<String>,
365
366 #[serde(flatten)]
369 pub client_post_auth: ClientPostAuth,
370}
371
372#[serde_as]
375#[skip_serializing_none]
376#[derive(Serialize, Deserialize, Debug)]
377pub struct AccessTokenIntrospectResponse {
378 pub active: bool,
379 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
380 pub scope: BTreeSet<String>,
381 pub client_id: Option<String>,
382 pub username: Option<String>,
383 pub token_type: Option<AccessTokenType>,
384 pub exp: Option<i64>,
385 pub iat: Option<i64>,
386 pub nbf: Option<i64>,
387 pub sub: Option<String>,
388 pub aud: Option<String>,
389 pub iss: Option<String>,
390 pub jti: Uuid,
392}
393
394impl AccessTokenIntrospectResponse {
395 pub fn inactive(session_id: Uuid) -> Self {
396 AccessTokenIntrospectResponse {
397 active: false,
398 scope: BTreeSet::default(),
399 client_id: None,
400 username: None,
401 token_type: None,
402 exp: None,
403 iat: None,
404 nbf: None,
405 sub: None,
406 aud: None,
407 iss: None,
408 jti: session_id,
409 }
410 }
411}
412
413#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
414#[serde(rename_all = "snake_case")]
415pub enum ResponseType {
416 Code,
419 Token,
422 IdToken,
424}
425
426#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
427#[serde(rename_all = "snake_case")]
428pub enum ResponseMode {
429 Query,
430 Fragment,
431 FormPost,
432 #[serde(other, deserialize_with = "deserialize_ignore_any")]
433 Invalid,
434}
435
436fn response_modes_supported_default() -> Vec<ResponseMode> {
437 vec![ResponseMode::Query, ResponseMode::Fragment]
438}
439
440#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
441#[serde(rename_all = "snake_case")]
442pub enum GrantType {
443 #[serde(rename = "authorization_code")]
444 AuthorisationCode,
445 Implicit,
446 #[serde(rename = "urn:ietf:params:oauth:grant-type:token-exchange")]
447 TokenExchange,
448}
449
450fn grant_types_supported_default() -> Vec<GrantType> {
451 vec![
452 GrantType::AuthorisationCode,
453 GrantType::Implicit,
454 GrantType::TokenExchange,
455 ]
456}
457
458#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
459#[serde(rename_all = "snake_case")]
460pub enum SubjectType {
461 Pairwise,
462 Public,
463}
464
465#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
466pub enum PkceAlg {
467 S256,
468}
469
470#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
471#[serde(rename_all = "UPPERCASE")]
472pub enum IdTokenSignAlg {
474 ES256,
476 RS256,
477}
478
479#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
480#[serde(rename_all = "snake_case")]
481pub enum TokenEndpointAuthMethod {
482 ClientSecretPost,
483 ClientSecretBasic,
484 ClientSecretJwt,
485 PrivateKeyJwt,
486}
487
488fn token_endpoint_auth_methods_supported_default() -> Vec<TokenEndpointAuthMethod> {
489 vec![TokenEndpointAuthMethod::ClientSecretBasic]
490}
491
492#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
493#[serde(rename_all = "snake_case")]
494pub enum DisplayValue {
495 Page,
496 Popup,
497 Touch,
498 Wap,
499}
500
501#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
502#[serde(rename_all = "snake_case")]
503pub enum ClaimType {
505 Normal,
506 Aggregated,
507 Distributed,
508}
509
510fn claim_types_supported_default() -> Vec<ClaimType> {
511 vec![ClaimType::Normal]
512}
513
514fn claims_parameter_supported_default() -> bool {
515 false
516}
517
518fn request_parameter_supported_default() -> bool {
519 false
520}
521
522fn request_uri_parameter_supported_default() -> bool {
523 false
524}
525
526fn require_request_uri_parameter_supported_default() -> bool {
527 false
528}
529
530#[derive(Serialize, Deserialize, Debug)]
531pub struct OidcWebfingerRel {
532 pub rel: String,
533 pub href: String,
534}
535
536#[skip_serializing_none]
539#[derive(Serialize, Deserialize, Debug)]
540pub struct OidcWebfingerResponse {
541 pub subject: String,
542 pub links: Vec<OidcWebfingerRel>,
543}
544
545#[skip_serializing_none]
548#[derive(Serialize, Deserialize, Debug)]
549pub struct OidcDiscoveryResponse {
550 pub issuer: Url,
551 pub authorization_endpoint: Url,
552 pub token_endpoint: Url,
553 pub userinfo_endpoint: Option<Url>,
554 pub jwks_uri: Url,
555 pub registration_endpoint: Option<Url>,
556 pub scopes_supported: Option<Vec<String>>,
557 pub response_types_supported: Vec<ResponseType>,
559 #[serde(default = "response_modes_supported_default")]
561 pub response_modes_supported: Vec<ResponseMode>,
562 #[serde(default = "grant_types_supported_default")]
564 pub grant_types_supported: Vec<GrantType>,
565 pub acr_values_supported: Option<Vec<String>>,
566 pub subject_types_supported: Vec<SubjectType>,
568 pub id_token_signing_alg_values_supported: Vec<IdTokenSignAlg>,
569 pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
570 pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
571 pub userinfo_signing_alg_values_supported: Option<Vec<String>>,
572 pub userinfo_encryption_alg_values_supported: Option<Vec<String>>,
573 pub userinfo_encryption_enc_values_supported: Option<Vec<String>>,
574 pub request_object_signing_alg_values_supported: Option<Vec<String>>,
575 pub request_object_encryption_alg_values_supported: Option<Vec<String>>,
576 pub request_object_encryption_enc_values_supported: Option<Vec<String>>,
577 #[serde(default = "token_endpoint_auth_methods_supported_default")]
579 pub token_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
580 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
581 pub display_values_supported: Option<Vec<DisplayValue>>,
583 #[serde(default = "claim_types_supported_default")]
585 pub claim_types_supported: Vec<ClaimType>,
586 pub claims_supported: Option<Vec<String>>,
587 pub service_documentation: Option<Url>,
588 pub claims_locales_supported: Option<Vec<String>>,
589 pub ui_locales_supported: Option<Vec<String>>,
590 #[serde(default = "claims_parameter_supported_default")]
592 pub claims_parameter_supported: bool,
593
594 pub op_policy_uri: Option<Url>,
595 pub op_tos_uri: Option<Url>,
596
597 #[serde(default = "request_parameter_supported_default")]
599 pub request_parameter_supported: bool,
600 #[serde(default = "request_uri_parameter_supported_default")]
601 pub request_uri_parameter_supported: bool,
602 #[serde(default = "require_request_uri_parameter_supported_default")]
603 pub require_request_uri_registration: bool,
604
605 pub code_challenge_methods_supported: Vec<PkceAlg>,
606
607 pub revocation_endpoint: Option<Url>,
615 pub revocation_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
616
617 pub introspection_endpoint: Option<Url>,
619 pub introspection_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
620 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
621
622 pub device_authorization_endpoint: Option<Url>,
624}
625
626#[skip_serializing_none]
628#[derive(Serialize, Deserialize, Debug)]
629pub struct Oauth2Rfc8414MetadataResponse {
630 pub issuer: Url,
631 pub authorization_endpoint: Url,
632 pub token_endpoint: Url,
633
634 pub jwks_uri: Option<Url>,
635
636 pub registration_endpoint: Option<Url>,
638
639 pub scopes_supported: Option<Vec<String>>,
640
641 pub response_types_supported: Vec<ResponseType>,
643 #[serde(default = "response_modes_supported_default")]
644 pub response_modes_supported: Vec<ResponseMode>,
645 #[serde(default = "grant_types_supported_default")]
646 pub grant_types_supported: Vec<GrantType>,
647
648 #[serde(default = "token_endpoint_auth_methods_supported_default")]
649 pub token_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
650
651 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
652
653 pub service_documentation: Option<Url>,
654 pub ui_locales_supported: Option<Vec<String>>,
655
656 pub op_policy_uri: Option<Url>,
657 pub op_tos_uri: Option<Url>,
658
659 pub revocation_endpoint: Option<Url>,
661 pub revocation_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
662
663 pub introspection_endpoint: Option<Url>,
665 pub introspection_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
666 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
667
668 pub code_challenge_methods_supported: Vec<PkceAlg>,
670}
671
672#[skip_serializing_none]
673#[derive(Serialize, Deserialize, Debug, Default)]
674pub struct ErrorResponse {
675 pub error: String,
676 pub error_description: Option<String>,
677 pub error_uri: Option<Url>,
678}
679
680#[derive(Debug, Serialize, Deserialize)]
681pub struct DeviceAuthorizationResponse {
683 device_code: String,
685 user_code: String,
687 verification_uri: Url,
688 verification_uri_complete: Url,
689 expires_in: u64,
690 interval: u64,
691}
692
693impl DeviceAuthorizationResponse {
694 pub fn new(verification_uri: Url, device_code: [u8; 16], user_code: String) -> Self {
695 let mut verification_uri_complete = verification_uri.clone();
696 verification_uri_complete
697 .query_pairs_mut()
698 .append_pair("user_code", &user_code);
699
700 let device_code = STANDARD.encode(device_code);
701
702 Self {
703 verification_uri_complete,
704 device_code,
705 user_code,
706 verification_uri,
707 expires_in: OAUTH2_DEVICE_CODE_EXPIRY_SECONDS,
708 interval: OAUTH2_DEVICE_CODE_INTERVAL_SECONDS,
709 }
710 }
711}
712
713#[cfg(test)]
714mod tests {
715 use super::{AccessTokenRequest, GrantTypeReq, OAUTH2_TOKEN_TYPE_ACCESS_TOKEN};
716 use std::collections::BTreeSet;
717 use url::Url;
718
719 #[test]
720 fn test_oauth2_access_token_req() {
721 let atr: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
722 code: "demo code".to_string(),
723 redirect_uri: Url::parse("http://[::1]").unwrap(),
724 code_verifier: None,
725 }
726 .into();
727
728 println!("{:?}", serde_json::to_string(&atr).expect("JSON failure"));
729 }
730
731 #[test]
732 fn test_oauth2_access_token_type_serde() {
733 for testcase in ["bearer", "Bearer", "BeArEr"] {
734 let at: super::AccessTokenType =
735 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
736 assert_eq!(at, super::AccessTokenType::Bearer);
737 }
738
739 for testcase in ["dpop", "dPoP", "DPOP", "DPoP"] {
740 let at: super::AccessTokenType =
741 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
742 assert_eq!(at, super::AccessTokenType::DPoP);
743 }
744
745 {
746 let testcase = "cheese";
747 let at = serde_json::from_str::<super::AccessTokenType>(&format!("\"{testcase}\""));
748 assert!(at.is_err())
749 }
750 }
751
752 #[test]
753 fn test_token_exchange_grant_serialization() {
754 let scopes: BTreeSet<String> = ["groups", "openid"]
755 .into_iter()
756 .map(str::to_string)
757 .collect();
758
759 let atr = AccessTokenRequest {
760 grant_type: GrantTypeReq::TokenExchange {
761 subject_token: "subject".to_string(),
762 subject_token_type: OAUTH2_TOKEN_TYPE_ACCESS_TOKEN.to_string(),
763 requested_token_type: None,
764 audience: Some("test_resource_server".to_string()),
765 resource: None,
766 actor_token: None,
767 actor_token_type: None,
768 scope: Some(scopes.clone()),
769 },
770 client_post_auth: Default::default(),
771 };
772
773 let json = serde_json::to_string(&atr).expect("JSON failure");
774 let de: AccessTokenRequest = serde_json::from_str(&json).expect("Roundtrip failure");
775
776 match de.grant_type {
777 GrantTypeReq::TokenExchange {
778 subject_token,
779 subject_token_type,
780 requested_token_type,
781 audience,
782 actor_token,
783 actor_token_type,
784 scope: descope,
785 ..
786 } => {
787 assert_eq!(subject_token, "subject");
788 assert_eq!(subject_token_type, OAUTH2_TOKEN_TYPE_ACCESS_TOKEN);
789 assert_eq!(requested_token_type, None);
790 assert_eq!(audience.as_deref(), Some("test_resource_server"));
791 assert_eq!(actor_token, None);
792 assert_eq!(actor_token_type, None);
793 assert_eq!(descope, Some(scopes));
794 }
795 _ => panic!("Wrong grant type"),
796 }
797 }
798}