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, NoneAsEmptyString,
11 StringWithSeparator,
12};
13use url::Url;
14use uuid::Uuid;
15
16pub const OAUTH2_DEVICE_CODE_EXPIRY_SECONDS: u64 = 300;
18pub const OAUTH2_DEVICE_CODE_INTERVAL_SECONDS: u64 = 5;
20
21#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
22pub enum CodeChallengeMethod {
23 S256,
27}
28
29#[serde_as]
30#[derive(Serialize, Deserialize, Debug, Clone)]
31pub struct PkceRequest {
32 #[serde_as(as = "Base64<UrlSafe, formats::Unpadded>")]
33 pub code_challenge: Vec<u8>,
34 pub code_challenge_method: CodeChallengeMethod,
35}
36
37#[serde_as]
40#[skip_serializing_none]
41#[derive(Serialize, Deserialize, Debug, Clone)]
42pub struct AuthorisationRequest {
43 pub response_type: ResponseType,
45 pub response_mode: Option<ResponseMode>,
54 pub client_id: String,
55 #[serde_as(as = "NoneAsEmptyString")]
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:device_code")]
163 DeviceCode {
164 device_code: String,
165 scope: Option<BTreeSet<String>>,
167 },
168}
169
170#[skip_serializing_none]
172#[derive(Serialize, Deserialize, Debug)]
173pub struct AccessTokenRequest {
174 #[serde(flatten)]
175 pub grant_type: GrantTypeReq,
176 pub client_id: Option<String>,
179 pub client_secret: Option<String>,
180}
181
182impl From<GrantTypeReq> for AccessTokenRequest {
183 fn from(req: GrantTypeReq) -> AccessTokenRequest {
184 AccessTokenRequest {
185 grant_type: req,
186 client_id: None,
187 client_secret: None,
188 }
189 }
190}
191
192#[derive(Serialize, Debug, Clone, Deserialize)]
193#[skip_serializing_none]
194pub struct OAuth2RFC9068Token<V>
195where
196 V: Clone,
197{
198 pub iss: String,
200 pub sub: Uuid,
202 pub aud: String,
204 pub exp: i64,
206 pub nbf: i64,
208 pub iat: i64,
210 pub jti: Option<String>,
212 pub client_id: String,
213 #[serde(flatten)]
214 pub extensions: V,
215}
216
217#[serde_as]
219#[skip_serializing_none]
220#[derive(Serialize, Deserialize, Debug, Clone)]
221pub struct OAuth2RFC9068TokenExtensions {
222 pub auth_time: Option<i64>,
223 pub acr: Option<String>,
224 pub amr: Option<Vec<String>>,
225
226 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
227 pub scope: BTreeSet<String>,
228
229 pub nonce: Option<String>,
230
231 pub session_id: Uuid,
232 pub parent_session_id: Option<Uuid>,
233}
234
235#[serde_as]
237#[skip_serializing_none]
238#[derive(Serialize, Deserialize, Debug)]
239pub struct AccessTokenResponse {
240 pub access_token: String,
241 pub token_type: AccessTokenType,
242 pub expires_in: u32,
244 pub refresh_token: Option<String>,
245 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
248 pub scope: BTreeSet<String>,
249 pub id_token: Option<String>,
251}
252
253#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
255#[serde(try_from = "&str")]
256pub enum AccessTokenType {
257 Bearer,
258 PoP,
259 #[serde(rename = "N_A")]
260 NA,
261 DPoP,
262}
263
264impl TryFrom<&str> for AccessTokenType {
265 type Error = String;
266
267 fn try_from(s: &str) -> Result<Self, Self::Error> {
268 match s.to_lowercase().as_str() {
269 "bearer" => Ok(AccessTokenType::Bearer),
270 "pop" => Ok(AccessTokenType::PoP),
271 "n_a" => Ok(AccessTokenType::NA),
272 "dpop" => Ok(AccessTokenType::DPoP),
273 _ => Err(format!("Unknown AccessTokenType: {}", s)),
274 }
275 }
276}
277
278#[skip_serializing_none]
281#[derive(Serialize, Deserialize, Debug)]
282pub struct TokenRevokeRequest {
283 pub token: String,
284 pub token_type_hint: Option<String>,
287}
288
289#[skip_serializing_none]
291#[derive(Serialize, Deserialize, Debug)]
292pub struct AccessTokenIntrospectRequest {
293 pub token: String,
294 pub token_type_hint: Option<String>,
297}
298
299#[serde_as]
302#[skip_serializing_none]
303#[derive(Serialize, Deserialize, Debug)]
304pub struct AccessTokenIntrospectResponse {
305 pub active: bool,
306 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
307 pub scope: BTreeSet<String>,
308 pub client_id: Option<String>,
309 pub username: Option<String>,
310 pub token_type: Option<AccessTokenType>,
311 pub exp: Option<i64>,
312 pub iat: Option<i64>,
313 pub nbf: Option<i64>,
314 pub sub: Option<String>,
315 pub aud: Option<String>,
316 pub iss: Option<String>,
317 pub jti: Option<String>,
318}
319
320impl AccessTokenIntrospectResponse {
321 pub fn inactive() -> Self {
322 AccessTokenIntrospectResponse {
323 active: false,
324 scope: BTreeSet::default(),
325 client_id: None,
326 username: None,
327 token_type: None,
328 exp: None,
329 iat: None,
330 nbf: None,
331 sub: None,
332 aud: None,
333 iss: None,
334 jti: None,
335 }
336 }
337}
338
339#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
340#[serde(rename_all = "snake_case")]
341pub enum ResponseType {
342 Code,
345 Token,
348 IdToken,
350}
351
352#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
353#[serde(rename_all = "snake_case")]
354pub enum ResponseMode {
355 Query,
356 Fragment,
357 FormPost,
358 #[serde(other, deserialize_with = "deserialize_ignore_any")]
359 Invalid,
360}
361
362fn response_modes_supported_default() -> Vec<ResponseMode> {
363 vec![ResponseMode::Query, ResponseMode::Fragment]
364}
365
366#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
367#[serde(rename_all = "snake_case")]
368pub enum GrantType {
369 #[serde(rename = "authorization_code")]
370 AuthorisationCode,
371 Implicit,
372}
373
374fn grant_types_supported_default() -> Vec<GrantType> {
375 vec![GrantType::AuthorisationCode, GrantType::Implicit]
376}
377
378#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
379#[serde(rename_all = "snake_case")]
380pub enum SubjectType {
381 Pairwise,
382 Public,
383}
384
385#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
386pub enum PkceAlg {
387 S256,
388}
389
390#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
391#[serde(rename_all = "UPPERCASE")]
392pub enum IdTokenSignAlg {
394 ES256,
396 RS256,
397}
398
399#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
400#[serde(rename_all = "snake_case")]
401pub enum TokenEndpointAuthMethod {
402 ClientSecretPost,
403 ClientSecretBasic,
404 ClientSecretJwt,
405 PrivateKeyJwt,
406}
407
408fn token_endpoint_auth_methods_supported_default() -> Vec<TokenEndpointAuthMethod> {
409 vec![TokenEndpointAuthMethod::ClientSecretBasic]
410}
411
412#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
413#[serde(rename_all = "snake_case")]
414pub enum DisplayValue {
415 Page,
416 Popup,
417 Touch,
418 Wap,
419}
420
421#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
422#[serde(rename_all = "snake_case")]
423pub enum ClaimType {
425 Normal,
426 Aggregated,
427 Distributed,
428}
429
430fn claim_types_supported_default() -> Vec<ClaimType> {
431 vec![ClaimType::Normal]
432}
433
434fn claims_parameter_supported_default() -> bool {
435 false
436}
437
438fn request_parameter_supported_default() -> bool {
439 false
440}
441
442fn request_uri_parameter_supported_default() -> bool {
443 false
444}
445
446fn require_request_uri_parameter_supported_default() -> bool {
447 false
448}
449
450#[derive(Serialize, Deserialize, Debug)]
451pub struct OidcWebfingerRel {
452 pub rel: String,
453 pub href: String,
454}
455
456#[skip_serializing_none]
459#[derive(Serialize, Deserialize, Debug)]
460pub struct OidcWebfingerResponse {
461 pub subject: String,
462 pub links: Vec<OidcWebfingerRel>,
463}
464
465#[skip_serializing_none]
468#[derive(Serialize, Deserialize, Debug)]
469pub struct OidcDiscoveryResponse {
470 pub issuer: Url,
471 pub authorization_endpoint: Url,
472 pub token_endpoint: Url,
473 pub userinfo_endpoint: Option<Url>,
474 pub jwks_uri: Url,
475 pub registration_endpoint: Option<Url>,
476 pub scopes_supported: Option<Vec<String>>,
477 pub response_types_supported: Vec<ResponseType>,
479 #[serde(default = "response_modes_supported_default")]
481 pub response_modes_supported: Vec<ResponseMode>,
482 #[serde(default = "grant_types_supported_default")]
484 pub grant_types_supported: Vec<GrantType>,
485 pub acr_values_supported: Option<Vec<String>>,
486 pub subject_types_supported: Vec<SubjectType>,
488 pub id_token_signing_alg_values_supported: Vec<IdTokenSignAlg>,
489 pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
490 pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
491 pub userinfo_signing_alg_values_supported: Option<Vec<String>>,
492 pub userinfo_encryption_alg_values_supported: Option<Vec<String>>,
493 pub userinfo_encryption_enc_values_supported: Option<Vec<String>>,
494 pub request_object_signing_alg_values_supported: Option<Vec<String>>,
495 pub request_object_encryption_alg_values_supported: Option<Vec<String>>,
496 pub request_object_encryption_enc_values_supported: Option<Vec<String>>,
497 #[serde(default = "token_endpoint_auth_methods_supported_default")]
499 pub token_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
500 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
501 pub display_values_supported: Option<Vec<DisplayValue>>,
503 #[serde(default = "claim_types_supported_default")]
505 pub claim_types_supported: Vec<ClaimType>,
506 pub claims_supported: Option<Vec<String>>,
507 pub service_documentation: Option<Url>,
508 pub claims_locales_supported: Option<Vec<String>>,
509 pub ui_locales_supported: Option<Vec<String>>,
510 #[serde(default = "claims_parameter_supported_default")]
512 pub claims_parameter_supported: bool,
513
514 pub op_policy_uri: Option<Url>,
515 pub op_tos_uri: Option<Url>,
516
517 #[serde(default = "request_parameter_supported_default")]
519 pub request_parameter_supported: bool,
520 #[serde(default = "request_uri_parameter_supported_default")]
521 pub request_uri_parameter_supported: bool,
522 #[serde(default = "require_request_uri_parameter_supported_default")]
523 pub require_request_uri_registration: bool,
524
525 pub code_challenge_methods_supported: Vec<PkceAlg>,
526
527 pub revocation_endpoint: Option<Url>,
535 pub revocation_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
536
537 pub introspection_endpoint: Option<Url>,
539 pub introspection_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
540 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
541
542 pub device_authorization_endpoint: Option<Url>,
544}
545
546#[skip_serializing_none]
548#[derive(Serialize, Deserialize, Debug)]
549pub struct Oauth2Rfc8414MetadataResponse {
550 pub issuer: Url,
551 pub authorization_endpoint: Url,
552 pub token_endpoint: Url,
553
554 pub jwks_uri: Option<Url>,
555
556 pub registration_endpoint: Option<Url>,
558
559 pub scopes_supported: Option<Vec<String>>,
560
561 pub response_types_supported: Vec<ResponseType>,
563 #[serde(default = "response_modes_supported_default")]
564 pub response_modes_supported: Vec<ResponseMode>,
565 #[serde(default = "grant_types_supported_default")]
566 pub grant_types_supported: Vec<GrantType>,
567
568 #[serde(default = "token_endpoint_auth_methods_supported_default")]
569 pub token_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
570
571 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
572
573 pub service_documentation: Option<Url>,
574 pub ui_locales_supported: Option<Vec<String>>,
575
576 pub op_policy_uri: Option<Url>,
577 pub op_tos_uri: Option<Url>,
578
579 pub revocation_endpoint: Option<Url>,
581 pub revocation_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
582
583 pub introspection_endpoint: Option<Url>,
585 pub introspection_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
586 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
587
588 pub code_challenge_methods_supported: Vec<PkceAlg>,
590}
591
592#[skip_serializing_none]
593#[derive(Serialize, Deserialize, Debug, Default)]
594pub struct ErrorResponse {
595 pub error: String,
596 pub error_description: Option<String>,
597 pub error_uri: Option<Url>,
598}
599
600#[derive(Debug, Serialize, Deserialize)]
601pub struct DeviceAuthorizationResponse {
603 device_code: String,
605 user_code: String,
607 verification_uri: Url,
608 verification_uri_complete: Url,
609 expires_in: u64,
610 interval: u64,
611}
612
613impl DeviceAuthorizationResponse {
614 pub fn new(verification_uri: Url, device_code: [u8; 16], user_code: String) -> Self {
615 let mut verification_uri_complete = verification_uri.clone();
616 verification_uri_complete
617 .query_pairs_mut()
618 .append_pair("user_code", &user_code);
619
620 let device_code = STANDARD.encode(device_code);
621
622 Self {
623 verification_uri_complete,
624 device_code,
625 user_code,
626 verification_uri,
627 expires_in: OAUTH2_DEVICE_CODE_EXPIRY_SECONDS,
628 interval: OAUTH2_DEVICE_CODE_INTERVAL_SECONDS,
629 }
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::{AccessTokenRequest, GrantTypeReq};
636 use url::Url;
637
638 #[test]
639 fn test_oauth2_access_token_req() {
640 let atr: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
641 code: "demo code".to_string(),
642 redirect_uri: Url::parse("http://[::1]").unwrap(),
643 code_verifier: None,
644 }
645 .into();
646
647 println!("{:?}", serde_json::to_string(&atr).expect("JSON failure"));
648 }
649
650 #[test]
651 fn test_oauth2_access_token_type_serde() {
652 for testcase in ["bearer", "Bearer", "BeArEr"] {
653 let at: super::AccessTokenType =
654 serde_json::from_str(&format!("\"{}\"", testcase)).expect("Failed to parse");
655 assert_eq!(at, super::AccessTokenType::Bearer);
656 }
657
658 for testcase in ["dpop", "dPoP", "DPOP", "DPoP"] {
659 let at: super::AccessTokenType =
660 serde_json::from_str(&format!("\"{}\"", testcase)).expect("Failed to parse");
661 assert_eq!(at, super::AccessTokenType::DPoP);
662 }
663
664 {
665 let testcase = "cheese";
666 let at = serde_json::from_str::<super::AccessTokenType>(&format!("\"{}\"", testcase));
667 assert!(at.is_err())
668 }
669 }
670}