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;
19
20#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
21pub enum CodeChallengeMethod {
22 S256,
26}
27
28#[serde_as]
29#[derive(Serialize, Deserialize, Debug, Clone)]
30pub struct PkceRequest {
31 #[serde_as(as = "Base64<UrlSafe, formats::Unpadded>")]
32 pub code_challenge: Vec<u8>,
33 pub code_challenge_method: CodeChallengeMethod,
34}
35
36#[serde_as]
39#[skip_serializing_none]
40#[derive(Serialize, Deserialize, Debug, Clone)]
41pub struct AuthorisationRequest {
42 pub response_type: ResponseType,
44 pub response_mode: Option<ResponseMode>,
53 pub client_id: String,
54 pub state: Option<String>,
55 #[serde(flatten)]
56 pub pkce_request: Option<PkceRequest>,
57 pub redirect_uri: Url,
58 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
59 pub scope: BTreeSet<String>,
60 pub nonce: Option<String>,
62 #[serde(flatten)]
64 pub oidc_ext: AuthorisationRequestOidc,
65 pub max_age: Option<i64>,
67 #[serde(flatten)]
68 pub unknown_keys: BTreeMap<String, serde_json::value::Value>,
69}
70
71impl AuthorisationRequest {
72 pub const fn get_response_mode(&self) -> Option<ResponseMode> {
80 match (self.response_mode, self.response_type) {
81 (None, ResponseType::IdToken) => Some(ResponseMode::Fragment),
85 (Some(ResponseMode::Query), ResponseType::IdToken) => None,
86
87 (None, ResponseType::Code) => Some(ResponseMode::Query),
89 (None, ResponseType::Token) => Some(ResponseMode::Fragment),
91
92 (Some(ResponseMode::Query), ResponseType::Token) => None,
97
98 (Some(m), _) => Some(m),
100 }
101 }
102}
103
104#[skip_serializing_none]
107#[derive(Serialize, Deserialize, Debug, Clone, Default)]
108pub struct AuthorisationRequestOidc {
109 pub display: Option<String>,
110 pub prompt: Option<String>,
111 pub ui_locales: Option<()>,
112 pub claims_locales: Option<()>,
113 pub id_token_hint: Option<String>,
114 pub login_hint: Option<String>,
115 pub acr: Option<String>,
116}
117
118#[derive(Serialize, Deserialize, Debug, Clone)]
122pub enum AuthorisationResponse {
123 ConsentRequested {
124 client_name: String,
126 scopes: BTreeSet<String>,
128 pii_scopes: BTreeSet<String>,
130 consent_token: String,
134 },
135 Permitted,
136}
137
138#[serde_as]
139#[skip_serializing_none]
140#[derive(Serialize, Deserialize, Debug)]
141#[serde(tag = "grant_type", rename_all = "snake_case")]
142pub enum GrantTypeReq {
143 AuthorizationCode {
144 code: String,
146 redirect_uri: Url,
148 code_verifier: Option<String>,
149 },
150 ClientCredentials {
151 #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
152 scope: Option<BTreeSet<String>>,
153 },
154 RefreshToken {
155 refresh_token: String,
156 #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
157 scope: Option<BTreeSet<String>>,
158 },
159 #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
161 DeviceCode {
162 device_code: String,
163 scope: Option<BTreeSet<String>>,
165 },
166}
167
168#[skip_serializing_none]
170#[derive(Serialize, Deserialize, Debug)]
171pub struct AccessTokenRequest {
172 #[serde(flatten)]
173 pub grant_type: GrantTypeReq,
174 #[serde(flatten)]
177 pub client_post_auth: ClientPostAuth,
178}
179
180impl From<GrantTypeReq> for AccessTokenRequest {
181 fn from(req: GrantTypeReq) -> AccessTokenRequest {
182 AccessTokenRequest {
183 grant_type: req,
184 client_post_auth: ClientPostAuth::default(),
185 }
186 }
187}
188
189#[derive(Serialize, Debug, Clone, Deserialize)]
190#[skip_serializing_none]
191pub struct OAuth2RFC9068Token<V>
192where
193 V: Clone,
194{
195 pub iss: String,
197 pub sub: Uuid,
199 pub aud: String,
201 pub exp: i64,
203 pub nbf: i64,
205 pub iat: i64,
207 pub jti: Option<String>,
209 pub client_id: String,
210 #[serde(flatten)]
211 pub extensions: V,
212}
213
214#[serde_as]
216#[skip_serializing_none]
217#[derive(Serialize, Deserialize, Debug, Clone)]
218pub struct OAuth2RFC9068TokenExtensions {
219 pub auth_time: Option<i64>,
220 pub acr: Option<String>,
221 pub amr: Option<Vec<String>>,
222
223 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
224 pub scope: BTreeSet<String>,
225
226 pub nonce: Option<String>,
227
228 pub session_id: Uuid,
229 pub parent_session_id: Option<Uuid>,
230}
231
232#[serde_as]
234#[skip_serializing_none]
235#[derive(Serialize, Deserialize, Debug)]
236pub struct AccessTokenResponse {
237 pub access_token: String,
238 pub token_type: AccessTokenType,
239 pub expires_in: u32,
241 pub refresh_token: Option<String>,
242 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
245 pub scope: BTreeSet<String>,
246 pub id_token: Option<String>,
248}
249
250#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
252#[serde(try_from = "&str")]
253pub enum AccessTokenType {
254 Bearer,
255 PoP,
256 #[serde(rename = "N_A")]
257 NA,
258 DPoP,
259}
260
261impl TryFrom<&str> for AccessTokenType {
262 type Error = String;
263
264 fn try_from(s: &str) -> Result<Self, Self::Error> {
265 match s.to_lowercase().as_str() {
266 "bearer" => Ok(AccessTokenType::Bearer),
267 "pop" => Ok(AccessTokenType::PoP),
268 "n_a" => Ok(AccessTokenType::NA),
269 "dpop" => Ok(AccessTokenType::DPoP),
270 _ => Err(format!("Unknown AccessTokenType: {s}")),
271 }
272 }
273}
274
275#[skip_serializing_none]
278#[derive(Serialize, Deserialize, Debug)]
279pub struct TokenRevokeRequest {
280 pub token: String,
281 pub token_type_hint: Option<String>,
284
285 #[serde(flatten)]
286 pub client_post_auth: ClientPostAuth,
287}
288
289#[skip_serializing_none]
290#[derive(Serialize, Deserialize, Debug, Default)]
291pub struct ClientPostAuth {
293 pub client_id: Option<String>,
294 pub client_secret: Option<String>,
295}
296
297impl From<(String, Option<String>)> for ClientPostAuth {
298 fn from((client_id, client_secret): (String, Option<String>)) -> Self {
299 ClientPostAuth {
300 client_id: Some(client_id),
301 client_secret,
302 }
303 }
304}
305
306impl From<(&str, Option<&str>)> for ClientPostAuth {
307 fn from((client_id, client_secret): (&str, Option<&str>)) -> Self {
308 ClientPostAuth {
309 client_id: Some(client_id.to_string()),
310 client_secret: client_secret.map(|s| s.to_string()),
311 }
312 }
313}
314
315#[skip_serializing_none]
316#[derive(Serialize, Deserialize, Debug, Default)]
317pub struct ClientAuth {
319 pub client_id: String,
320 pub client_secret: Option<String>,
321}
322
323impl From<(&str, Option<&str>)> for ClientAuth {
324 fn from((client_id, client_secret): (&str, Option<&str>)) -> Self {
325 ClientAuth {
326 client_id: client_id.to_string(),
327 client_secret: client_secret.map(|s| s.to_string()),
328 }
329 }
330}
331
332#[skip_serializing_none]
334#[derive(Serialize, Deserialize, Debug)]
335pub struct AccessTokenIntrospectRequest {
336 pub token: String,
337 pub token_type_hint: Option<String>,
340
341 #[serde(flatten)]
344 pub client_post_auth: ClientPostAuth,
345}
346
347#[serde_as]
350#[skip_serializing_none]
351#[derive(Serialize, Deserialize, Debug)]
352pub struct AccessTokenIntrospectResponse {
353 pub active: bool,
354 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
355 pub scope: BTreeSet<String>,
356 pub client_id: Option<String>,
357 pub username: Option<String>,
358 pub token_type: Option<AccessTokenType>,
359 pub exp: Option<i64>,
360 pub iat: Option<i64>,
361 pub nbf: Option<i64>,
362 pub sub: Option<String>,
363 pub aud: Option<String>,
364 pub iss: Option<String>,
365 pub jti: Option<String>,
366}
367
368impl AccessTokenIntrospectResponse {
369 pub fn inactive() -> Self {
370 AccessTokenIntrospectResponse {
371 active: false,
372 scope: BTreeSet::default(),
373 client_id: None,
374 username: None,
375 token_type: None,
376 exp: None,
377 iat: None,
378 nbf: None,
379 sub: None,
380 aud: None,
381 iss: None,
382 jti: None,
383 }
384 }
385}
386
387#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
388#[serde(rename_all = "snake_case")]
389pub enum ResponseType {
390 Code,
393 Token,
396 IdToken,
398}
399
400#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
401#[serde(rename_all = "snake_case")]
402pub enum ResponseMode {
403 Query,
404 Fragment,
405 FormPost,
406 #[serde(other, deserialize_with = "deserialize_ignore_any")]
407 Invalid,
408}
409
410fn response_modes_supported_default() -> Vec<ResponseMode> {
411 vec![ResponseMode::Query, ResponseMode::Fragment]
412}
413
414#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
415#[serde(rename_all = "snake_case")]
416pub enum GrantType {
417 #[serde(rename = "authorization_code")]
418 AuthorisationCode,
419 Implicit,
420}
421
422fn grant_types_supported_default() -> Vec<GrantType> {
423 vec![GrantType::AuthorisationCode, GrantType::Implicit]
424}
425
426#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
427#[serde(rename_all = "snake_case")]
428pub enum SubjectType {
429 Pairwise,
430 Public,
431}
432
433#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
434pub enum PkceAlg {
435 S256,
436}
437
438#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
439#[serde(rename_all = "UPPERCASE")]
440pub enum IdTokenSignAlg {
442 ES256,
444 RS256,
445}
446
447#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
448#[serde(rename_all = "snake_case")]
449pub enum TokenEndpointAuthMethod {
450 ClientSecretPost,
451 ClientSecretBasic,
452 ClientSecretJwt,
453 PrivateKeyJwt,
454}
455
456fn token_endpoint_auth_methods_supported_default() -> Vec<TokenEndpointAuthMethod> {
457 vec![TokenEndpointAuthMethod::ClientSecretBasic]
458}
459
460#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
461#[serde(rename_all = "snake_case")]
462pub enum DisplayValue {
463 Page,
464 Popup,
465 Touch,
466 Wap,
467}
468
469#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
470#[serde(rename_all = "snake_case")]
471pub enum ClaimType {
473 Normal,
474 Aggregated,
475 Distributed,
476}
477
478fn claim_types_supported_default() -> Vec<ClaimType> {
479 vec![ClaimType::Normal]
480}
481
482fn claims_parameter_supported_default() -> bool {
483 false
484}
485
486fn request_parameter_supported_default() -> bool {
487 false
488}
489
490fn request_uri_parameter_supported_default() -> bool {
491 false
492}
493
494fn require_request_uri_parameter_supported_default() -> bool {
495 false
496}
497
498#[derive(Serialize, Deserialize, Debug)]
499pub struct OidcWebfingerRel {
500 pub rel: String,
501 pub href: String,
502}
503
504#[skip_serializing_none]
507#[derive(Serialize, Deserialize, Debug)]
508pub struct OidcWebfingerResponse {
509 pub subject: String,
510 pub links: Vec<OidcWebfingerRel>,
511}
512
513#[skip_serializing_none]
516#[derive(Serialize, Deserialize, Debug)]
517pub struct OidcDiscoveryResponse {
518 pub issuer: Url,
519 pub authorization_endpoint: Url,
520 pub token_endpoint: Url,
521 pub userinfo_endpoint: Option<Url>,
522 pub jwks_uri: Url,
523 pub registration_endpoint: Option<Url>,
524 pub scopes_supported: Option<Vec<String>>,
525 pub response_types_supported: Vec<ResponseType>,
527 #[serde(default = "response_modes_supported_default")]
529 pub response_modes_supported: Vec<ResponseMode>,
530 #[serde(default = "grant_types_supported_default")]
532 pub grant_types_supported: Vec<GrantType>,
533 pub acr_values_supported: Option<Vec<String>>,
534 pub subject_types_supported: Vec<SubjectType>,
536 pub id_token_signing_alg_values_supported: Vec<IdTokenSignAlg>,
537 pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
538 pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
539 pub userinfo_signing_alg_values_supported: Option<Vec<String>>,
540 pub userinfo_encryption_alg_values_supported: Option<Vec<String>>,
541 pub userinfo_encryption_enc_values_supported: Option<Vec<String>>,
542 pub request_object_signing_alg_values_supported: Option<Vec<String>>,
543 pub request_object_encryption_alg_values_supported: Option<Vec<String>>,
544 pub request_object_encryption_enc_values_supported: Option<Vec<String>>,
545 #[serde(default = "token_endpoint_auth_methods_supported_default")]
547 pub token_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
548 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
549 pub display_values_supported: Option<Vec<DisplayValue>>,
551 #[serde(default = "claim_types_supported_default")]
553 pub claim_types_supported: Vec<ClaimType>,
554 pub claims_supported: Option<Vec<String>>,
555 pub service_documentation: Option<Url>,
556 pub claims_locales_supported: Option<Vec<String>>,
557 pub ui_locales_supported: Option<Vec<String>>,
558 #[serde(default = "claims_parameter_supported_default")]
560 pub claims_parameter_supported: bool,
561
562 pub op_policy_uri: Option<Url>,
563 pub op_tos_uri: Option<Url>,
564
565 #[serde(default = "request_parameter_supported_default")]
567 pub request_parameter_supported: bool,
568 #[serde(default = "request_uri_parameter_supported_default")]
569 pub request_uri_parameter_supported: bool,
570 #[serde(default = "require_request_uri_parameter_supported_default")]
571 pub require_request_uri_registration: bool,
572
573 pub code_challenge_methods_supported: Vec<PkceAlg>,
574
575 pub revocation_endpoint: Option<Url>,
583 pub revocation_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
584
585 pub introspection_endpoint: Option<Url>,
587 pub introspection_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
588 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
589
590 pub device_authorization_endpoint: Option<Url>,
592}
593
594#[skip_serializing_none]
596#[derive(Serialize, Deserialize, Debug)]
597pub struct Oauth2Rfc8414MetadataResponse {
598 pub issuer: Url,
599 pub authorization_endpoint: Url,
600 pub token_endpoint: Url,
601
602 pub jwks_uri: Option<Url>,
603
604 pub registration_endpoint: Option<Url>,
606
607 pub scopes_supported: Option<Vec<String>>,
608
609 pub response_types_supported: Vec<ResponseType>,
611 #[serde(default = "response_modes_supported_default")]
612 pub response_modes_supported: Vec<ResponseMode>,
613 #[serde(default = "grant_types_supported_default")]
614 pub grant_types_supported: Vec<GrantType>,
615
616 #[serde(default = "token_endpoint_auth_methods_supported_default")]
617 pub token_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
618
619 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
620
621 pub service_documentation: Option<Url>,
622 pub ui_locales_supported: Option<Vec<String>>,
623
624 pub op_policy_uri: Option<Url>,
625 pub op_tos_uri: Option<Url>,
626
627 pub revocation_endpoint: Option<Url>,
629 pub revocation_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
630
631 pub introspection_endpoint: Option<Url>,
633 pub introspection_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
634 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
635
636 pub code_challenge_methods_supported: Vec<PkceAlg>,
638}
639
640#[skip_serializing_none]
641#[derive(Serialize, Deserialize, Debug, Default)]
642pub struct ErrorResponse {
643 pub error: String,
644 pub error_description: Option<String>,
645 pub error_uri: Option<Url>,
646}
647
648#[derive(Debug, Serialize, Deserialize)]
649pub struct DeviceAuthorizationResponse {
651 device_code: String,
653 user_code: String,
655 verification_uri: Url,
656 verification_uri_complete: Url,
657 expires_in: u64,
658 interval: u64,
659}
660
661impl DeviceAuthorizationResponse {
662 pub fn new(verification_uri: Url, device_code: [u8; 16], user_code: String) -> Self {
663 let mut verification_uri_complete = verification_uri.clone();
664 verification_uri_complete
665 .query_pairs_mut()
666 .append_pair("user_code", &user_code);
667
668 let device_code = STANDARD.encode(device_code);
669
670 Self {
671 verification_uri_complete,
672 device_code,
673 user_code,
674 verification_uri,
675 expires_in: OAUTH2_DEVICE_CODE_EXPIRY_SECONDS,
676 interval: OAUTH2_DEVICE_CODE_INTERVAL_SECONDS,
677 }
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use super::{AccessTokenRequest, GrantTypeReq};
684 use url::Url;
685
686 #[test]
687 fn test_oauth2_access_token_req() {
688 let atr: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
689 code: "demo code".to_string(),
690 redirect_uri: Url::parse("http://[::1]").unwrap(),
691 code_verifier: None,
692 }
693 .into();
694
695 println!("{:?}", serde_json::to_string(&atr).expect("JSON failure"));
696 }
697
698 #[test]
699 fn test_oauth2_access_token_type_serde() {
700 for testcase in ["bearer", "Bearer", "BeArEr"] {
701 let at: super::AccessTokenType =
702 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
703 assert_eq!(at, super::AccessTokenType::Bearer);
704 }
705
706 for testcase in ["dpop", "dPoP", "DPOP", "DPoP"] {
707 let at: super::AccessTokenType =
708 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
709 assert_eq!(at, super::AccessTokenType::DPoP);
710 }
711
712 {
713 let testcase = "cheese";
714 let at = serde_json::from_str::<super::AccessTokenType>(&format!("\"{testcase}\""));
715 assert!(at.is_err())
716 }
717 }
718}