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: Uuid,
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: Uuid,
367}
368
369impl AccessTokenIntrospectResponse {
370 pub fn inactive(session_id: Uuid) -> Self {
371 AccessTokenIntrospectResponse {
372 active: false,
373 scope: BTreeSet::default(),
374 client_id: None,
375 username: None,
376 token_type: None,
377 exp: None,
378 iat: None,
379 nbf: None,
380 sub: None,
381 aud: None,
382 iss: None,
383 jti: session_id,
384 }
385 }
386}
387
388#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
389#[serde(rename_all = "snake_case")]
390pub enum ResponseType {
391 Code,
394 Token,
397 IdToken,
399}
400
401#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
402#[serde(rename_all = "snake_case")]
403pub enum ResponseMode {
404 Query,
405 Fragment,
406 FormPost,
407 #[serde(other, deserialize_with = "deserialize_ignore_any")]
408 Invalid,
409}
410
411fn response_modes_supported_default() -> Vec<ResponseMode> {
412 vec![ResponseMode::Query, ResponseMode::Fragment]
413}
414
415#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
416#[serde(rename_all = "snake_case")]
417pub enum GrantType {
418 #[serde(rename = "authorization_code")]
419 AuthorisationCode,
420 Implicit,
421}
422
423fn grant_types_supported_default() -> Vec<GrantType> {
424 vec![GrantType::AuthorisationCode, GrantType::Implicit]
425}
426
427#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
428#[serde(rename_all = "snake_case")]
429pub enum SubjectType {
430 Pairwise,
431 Public,
432}
433
434#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
435pub enum PkceAlg {
436 S256,
437}
438
439#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
440#[serde(rename_all = "UPPERCASE")]
441pub enum IdTokenSignAlg {
443 ES256,
445 RS256,
446}
447
448#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
449#[serde(rename_all = "snake_case")]
450pub enum TokenEndpointAuthMethod {
451 ClientSecretPost,
452 ClientSecretBasic,
453 ClientSecretJwt,
454 PrivateKeyJwt,
455}
456
457fn token_endpoint_auth_methods_supported_default() -> Vec<TokenEndpointAuthMethod> {
458 vec![TokenEndpointAuthMethod::ClientSecretBasic]
459}
460
461#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
462#[serde(rename_all = "snake_case")]
463pub enum DisplayValue {
464 Page,
465 Popup,
466 Touch,
467 Wap,
468}
469
470#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
471#[serde(rename_all = "snake_case")]
472pub enum ClaimType {
474 Normal,
475 Aggregated,
476 Distributed,
477}
478
479fn claim_types_supported_default() -> Vec<ClaimType> {
480 vec![ClaimType::Normal]
481}
482
483fn claims_parameter_supported_default() -> bool {
484 false
485}
486
487fn request_parameter_supported_default() -> bool {
488 false
489}
490
491fn request_uri_parameter_supported_default() -> bool {
492 false
493}
494
495fn require_request_uri_parameter_supported_default() -> bool {
496 false
497}
498
499#[derive(Serialize, Deserialize, Debug)]
500pub struct OidcWebfingerRel {
501 pub rel: String,
502 pub href: String,
503}
504
505#[skip_serializing_none]
508#[derive(Serialize, Deserialize, Debug)]
509pub struct OidcWebfingerResponse {
510 pub subject: String,
511 pub links: Vec<OidcWebfingerRel>,
512}
513
514#[skip_serializing_none]
517#[derive(Serialize, Deserialize, Debug)]
518pub struct OidcDiscoveryResponse {
519 pub issuer: Url,
520 pub authorization_endpoint: Url,
521 pub token_endpoint: Url,
522 pub userinfo_endpoint: Option<Url>,
523 pub jwks_uri: Url,
524 pub registration_endpoint: Option<Url>,
525 pub scopes_supported: Option<Vec<String>>,
526 pub response_types_supported: Vec<ResponseType>,
528 #[serde(default = "response_modes_supported_default")]
530 pub response_modes_supported: Vec<ResponseMode>,
531 #[serde(default = "grant_types_supported_default")]
533 pub grant_types_supported: Vec<GrantType>,
534 pub acr_values_supported: Option<Vec<String>>,
535 pub subject_types_supported: Vec<SubjectType>,
537 pub id_token_signing_alg_values_supported: Vec<IdTokenSignAlg>,
538 pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
539 pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
540 pub userinfo_signing_alg_values_supported: Option<Vec<String>>,
541 pub userinfo_encryption_alg_values_supported: Option<Vec<String>>,
542 pub userinfo_encryption_enc_values_supported: Option<Vec<String>>,
543 pub request_object_signing_alg_values_supported: Option<Vec<String>>,
544 pub request_object_encryption_alg_values_supported: Option<Vec<String>>,
545 pub request_object_encryption_enc_values_supported: Option<Vec<String>>,
546 #[serde(default = "token_endpoint_auth_methods_supported_default")]
548 pub token_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
549 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
550 pub display_values_supported: Option<Vec<DisplayValue>>,
552 #[serde(default = "claim_types_supported_default")]
554 pub claim_types_supported: Vec<ClaimType>,
555 pub claims_supported: Option<Vec<String>>,
556 pub service_documentation: Option<Url>,
557 pub claims_locales_supported: Option<Vec<String>>,
558 pub ui_locales_supported: Option<Vec<String>>,
559 #[serde(default = "claims_parameter_supported_default")]
561 pub claims_parameter_supported: bool,
562
563 pub op_policy_uri: Option<Url>,
564 pub op_tos_uri: Option<Url>,
565
566 #[serde(default = "request_parameter_supported_default")]
568 pub request_parameter_supported: bool,
569 #[serde(default = "request_uri_parameter_supported_default")]
570 pub request_uri_parameter_supported: bool,
571 #[serde(default = "require_request_uri_parameter_supported_default")]
572 pub require_request_uri_registration: bool,
573
574 pub code_challenge_methods_supported: Vec<PkceAlg>,
575
576 pub revocation_endpoint: Option<Url>,
584 pub revocation_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
585
586 pub introspection_endpoint: Option<Url>,
588 pub introspection_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
589 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
590
591 pub device_authorization_endpoint: Option<Url>,
593}
594
595#[skip_serializing_none]
597#[derive(Serialize, Deserialize, Debug)]
598pub struct Oauth2Rfc8414MetadataResponse {
599 pub issuer: Url,
600 pub authorization_endpoint: Url,
601 pub token_endpoint: Url,
602
603 pub jwks_uri: Option<Url>,
604
605 pub registration_endpoint: Option<Url>,
607
608 pub scopes_supported: Option<Vec<String>>,
609
610 pub response_types_supported: Vec<ResponseType>,
612 #[serde(default = "response_modes_supported_default")]
613 pub response_modes_supported: Vec<ResponseMode>,
614 #[serde(default = "grant_types_supported_default")]
615 pub grant_types_supported: Vec<GrantType>,
616
617 #[serde(default = "token_endpoint_auth_methods_supported_default")]
618 pub token_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
619
620 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
621
622 pub service_documentation: Option<Url>,
623 pub ui_locales_supported: Option<Vec<String>>,
624
625 pub op_policy_uri: Option<Url>,
626 pub op_tos_uri: Option<Url>,
627
628 pub revocation_endpoint: Option<Url>,
630 pub revocation_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
631
632 pub introspection_endpoint: Option<Url>,
634 pub introspection_endpoint_auth_methods_supported: Vec<TokenEndpointAuthMethod>,
635 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
636
637 pub code_challenge_methods_supported: Vec<PkceAlg>,
639}
640
641#[skip_serializing_none]
642#[derive(Serialize, Deserialize, Debug, Default)]
643pub struct ErrorResponse {
644 pub error: String,
645 pub error_description: Option<String>,
646 pub error_uri: Option<Url>,
647}
648
649#[derive(Debug, Serialize, Deserialize)]
650pub struct DeviceAuthorizationResponse {
652 device_code: String,
654 user_code: String,
656 verification_uri: Url,
657 verification_uri_complete: Url,
658 expires_in: u64,
659 interval: u64,
660}
661
662impl DeviceAuthorizationResponse {
663 pub fn new(verification_uri: Url, device_code: [u8; 16], user_code: String) -> Self {
664 let mut verification_uri_complete = verification_uri.clone();
665 verification_uri_complete
666 .query_pairs_mut()
667 .append_pair("user_code", &user_code);
668
669 let device_code = STANDARD.encode(device_code);
670
671 Self {
672 verification_uri_complete,
673 device_code,
674 user_code,
675 verification_uri,
676 expires_in: OAUTH2_DEVICE_CODE_EXPIRY_SECONDS,
677 interval: OAUTH2_DEVICE_CODE_INTERVAL_SECONDS,
678 }
679 }
680}
681
682#[cfg(test)]
683mod tests {
684 use super::{AccessTokenRequest, GrantTypeReq};
685 use url::Url;
686
687 #[test]
688 fn test_oauth2_access_token_req() {
689 let atr: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
690 code: "demo code".to_string(),
691 redirect_uri: Url::parse("http://[::1]").unwrap(),
692 code_verifier: None,
693 }
694 .into();
695
696 println!("{:?}", serde_json::to_string(&atr).expect("JSON failure"));
697 }
698
699 #[test]
700 fn test_oauth2_access_token_type_serde() {
701 for testcase in ["bearer", "Bearer", "BeArEr"] {
702 let at: super::AccessTokenType =
703 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
704 assert_eq!(at, super::AccessTokenType::Bearer);
705 }
706
707 for testcase in ["dpop", "dPoP", "DPOP", "DPoP"] {
708 let at: super::AccessTokenType =
709 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
710 assert_eq!(at, super::AccessTokenType::DPoP);
711 }
712
713 {
714 let testcase = "cheese";
715 let at = serde_json::from_str::<super::AccessTokenType>(&format!("\"{testcase}\""));
716 assert!(at.is_err())
717 }
718 }
719}