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