oauth2_types/
requests.rs

1// Copyright 2024, 2025 New Vector Ltd.
2// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5// Please see LICENSE files in the repository root for full details.
6
7//! Requests and response types to interact with the [OAuth 2.0] specification.
8//!
9//! [OAuth 2.0]: https://oauth.net/2/
10
11use std::{collections::HashSet, fmt, hash::Hash, num::NonZeroU32};
12
13use chrono::{DateTime, Duration, Utc};
14use language_tags::LanguageTag;
15use mas_iana::oauth::{OAuthAccessTokenType, OAuthTokenTypeHint};
16use serde::{Deserialize, Serialize};
17use serde_with::{
18    DeserializeFromStr, DisplayFromStr, DurationSeconds, SerializeDisplay, StringWithSeparator,
19    TimestampSeconds, formats::SpaceSeparator, serde_as, skip_serializing_none,
20};
21use url::Url;
22
23use crate::{response_type::ResponseType, scope::Scope};
24
25// ref: https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml
26
27/// The mechanism to be used for returning Authorization Response parameters
28/// from the Authorization Endpoint.
29///
30/// Defined in [OAuth 2.0 Multiple Response Type Encoding Practices](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes).
31#[derive(
32    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
33)]
34#[non_exhaustive]
35pub enum ResponseMode {
36    /// Authorization Response parameters are encoded in the query string added
37    /// to the `redirect_uri`.
38    Query,
39
40    /// Authorization Response parameters are encoded in the fragment added to
41    /// the `redirect_uri`.
42    Fragment,
43
44    /// Authorization Response parameters are encoded as HTML form values that
45    /// are auto-submitted in the User Agent, and thus are transmitted via the
46    /// HTTP `POST` method to the Client, with the result parameters being
47    /// encoded in the body using the `application/x-www-form-urlencoded`
48    /// format.
49    ///
50    /// Defined in [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html).
51    FormPost,
52
53    /// An unknown value.
54    Unknown(String),
55}
56
57impl core::fmt::Display for ResponseMode {
58    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59        match self {
60            ResponseMode::Query => f.write_str("query"),
61            ResponseMode::Fragment => f.write_str("fragment"),
62            ResponseMode::FormPost => f.write_str("form_post"),
63            ResponseMode::Unknown(s) => f.write_str(s),
64        }
65    }
66}
67
68impl core::str::FromStr for ResponseMode {
69    type Err = core::convert::Infallible;
70
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        match s {
73            "query" => Ok(ResponseMode::Query),
74            "fragment" => Ok(ResponseMode::Fragment),
75            "form_post" => Ok(ResponseMode::FormPost),
76            s => Ok(ResponseMode::Unknown(s.to_owned())),
77        }
78    }
79}
80
81/// Value that specifies how the Authorization Server displays the
82/// authentication and consent user interface pages to the End-User.
83///
84/// Defined in [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).
85#[derive(
86    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
87)]
88#[non_exhaustive]
89#[derive(Default)]
90pub enum Display {
91    /// The Authorization Server should display the authentication and consent
92    /// UI consistent with a full User Agent page view.
93    ///
94    /// This is the default display mode.
95    #[default]
96    Page,
97
98    /// The Authorization Server should display the authentication and consent
99    /// UI consistent with a popup User Agent window.
100    Popup,
101
102    /// The Authorization Server should display the authentication and consent
103    /// UI consistent with a device that leverages a touch interface.
104    Touch,
105
106    /// The Authorization Server should display the authentication and consent
107    /// UI consistent with a "feature phone" type display.
108    Wap,
109
110    /// An unknown value.
111    Unknown(String),
112}
113
114impl core::fmt::Display for Display {
115    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116        match self {
117            Display::Page => f.write_str("page"),
118            Display::Popup => f.write_str("popup"),
119            Display::Touch => f.write_str("touch"),
120            Display::Wap => f.write_str("wap"),
121            Display::Unknown(s) => f.write_str(s),
122        }
123    }
124}
125
126impl core::str::FromStr for Display {
127    type Err = core::convert::Infallible;
128
129    fn from_str(s: &str) -> Result<Self, Self::Err> {
130        match s {
131            "page" => Ok(Display::Page),
132            "popup" => Ok(Display::Popup),
133            "touch" => Ok(Display::Touch),
134            "wap" => Ok(Display::Wap),
135            s => Ok(Display::Unknown(s.to_owned())),
136        }
137    }
138}
139
140/// Value that specifies whether the Authorization Server prompts the End-User
141/// for reauthentication and consent.
142///
143/// Defined in [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).
144#[derive(
145    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
146)]
147#[non_exhaustive]
148pub enum Prompt {
149    /// The Authorization Server must not display any authentication or consent
150    /// user interface pages.
151    None,
152
153    /// The Authorization Server should prompt the End-User for
154    /// reauthentication.
155    Login,
156
157    /// The Authorization Server should prompt the End-User for consent before
158    /// returning information to the Client.
159    Consent,
160
161    /// The Authorization Server should prompt the End-User to select a user
162    /// account.
163    ///
164    /// This enables an End-User who has multiple accounts at the Authorization
165    /// Server to select amongst the multiple accounts that they might have
166    /// current sessions for.
167    SelectAccount,
168
169    /// The Authorization Server should prompt the End-User to create a user
170    /// account.
171    ///
172    /// Defined in [Initiating User Registration via OpenID Connect](https://openid.net/specs/openid-connect-prompt-create-1_0.html).
173    Create,
174
175    /// An unknown value.
176    Unknown(String),
177}
178
179impl core::fmt::Display for Prompt {
180    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
181        match self {
182            Prompt::None => f.write_str("none"),
183            Prompt::Login => f.write_str("login"),
184            Prompt::Consent => f.write_str("consent"),
185            Prompt::SelectAccount => f.write_str("select_account"),
186            Prompt::Create => f.write_str("create"),
187            Prompt::Unknown(s) => f.write_str(s),
188        }
189    }
190}
191
192impl core::str::FromStr for Prompt {
193    type Err = core::convert::Infallible;
194
195    fn from_str(s: &str) -> Result<Self, Self::Err> {
196        match s {
197            "none" => Ok(Prompt::None),
198            "login" => Ok(Prompt::Login),
199            "consent" => Ok(Prompt::Consent),
200            "select_account" => Ok(Prompt::SelectAccount),
201            "create" => Ok(Prompt::Create),
202            s => Ok(Prompt::Unknown(s.to_owned())),
203        }
204    }
205}
206
207/// The body of a request to the [Authorization Endpoint].
208///
209/// [Authorization Endpoint]: https://www.rfc-editor.org/rfc/rfc6749.html#section-3.1
210#[skip_serializing_none]
211#[serde_as]
212#[derive(Serialize, Deserialize, Clone)]
213pub struct AuthorizationRequest {
214    /// OAuth 2.0 Response Type value that determines the authorization
215    /// processing flow to be used.
216    pub response_type: ResponseType,
217
218    /// OAuth 2.0 Client Identifier valid at the Authorization Server.
219    pub client_id: String,
220
221    /// Redirection URI to which the response will be sent.
222    ///
223    /// This field is required when using a response type returning an
224    /// authorization code.
225    ///
226    /// This URI must have been pre-registered with the OpenID Provider.
227    pub redirect_uri: Option<Url>,
228
229    /// The scope of the access request.
230    ///
231    /// OpenID Connect requests must contain the `openid` scope value.
232    pub scope: Scope,
233
234    /// Opaque value used to maintain state between the request and the
235    /// callback.
236    pub state: Option<String>,
237
238    /// The mechanism to be used for returning parameters from the Authorization
239    /// Endpoint.
240    ///
241    /// This use of this parameter is not recommended when the Response Mode
242    /// that would be requested is the default mode specified for the Response
243    /// Type.
244    pub response_mode: Option<ResponseMode>,
245
246    /// String value used to associate a Client session with an ID Token, and to
247    /// mitigate replay attacks.
248    pub nonce: Option<String>,
249
250    /// How the Authorization Server should display the authentication and
251    /// consent user interface pages to the End-User.
252    pub display: Option<Display>,
253
254    /// Whether the Authorization Server should prompt the End-User for
255    /// reauthentication and consent.
256    ///
257    /// If [`Prompt::None`] is used, it must be the only value.
258    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, Prompt>>")]
259    #[serde(default)]
260    pub prompt: Option<Vec<Prompt>>,
261
262    /// The allowable elapsed time in seconds since the last time the End-User
263    /// was actively authenticated by the OpenID Provider.
264    #[serde(default)]
265    #[serde_as(as = "Option<DisplayFromStr>")]
266    pub max_age: Option<NonZeroU32>,
267
268    /// End-User's preferred languages and scripts for the user interface.
269    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, LanguageTag>>")]
270    #[serde(default)]
271    pub ui_locales: Option<Vec<LanguageTag>>,
272
273    /// ID Token previously issued by the Authorization Server being passed as a
274    /// hint about the End-User's current or past authenticated session with the
275    /// Client.
276    pub id_token_hint: Option<String>,
277
278    /// Hint to the Authorization Server about the login identifier the End-User
279    /// might use to log in.
280    pub login_hint: Option<String>,
281
282    /// Requested Authentication Context Class Reference values.
283    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
284    #[serde(default)]
285    pub acr_values: Option<HashSet<String>>,
286
287    /// A JWT that contains the request's parameter values, called a [Request
288    /// Object].
289    ///
290    /// [Request Object]: https://openid.net/specs/openid-connect-core-1_0.html#RequestObject
291    pub request: Option<String>,
292
293    /// A URI referencing a [Request Object] or a [Pushed Authorization
294    /// Request].
295    ///
296    /// [Request Object]: https://openid.net/specs/openid-connect-core-1_0.html#RequestUriParameter
297    /// [Pushed Authorization Request]: https://datatracker.ietf.org/doc/html/rfc9126
298    pub request_uri: Option<Url>,
299
300    /// A JSON object containing the Client Metadata when interacting with a
301    /// [Self-Issued OpenID Provider].
302    ///
303    /// [Self-Issued OpenID Provider]: https://openid.net/specs/openid-connect-core-1_0.html#SelfIssued
304    pub registration: Option<String>,
305}
306
307impl AuthorizationRequest {
308    /// Creates a basic `AuthorizationRequest`.
309    #[must_use]
310    pub fn new(response_type: ResponseType, client_id: String, scope: Scope) -> Self {
311        Self {
312            response_type,
313            client_id,
314            redirect_uri: None,
315            scope,
316            state: None,
317            response_mode: None,
318            nonce: None,
319            display: None,
320            prompt: None,
321            max_age: None,
322            ui_locales: None,
323            id_token_hint: None,
324            login_hint: None,
325            acr_values: None,
326            request: None,
327            request_uri: None,
328            registration: None,
329        }
330    }
331}
332
333impl fmt::Debug for AuthorizationRequest {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        f.debug_struct("AuthorizationRequest")
336            .field("response_type", &self.response_type)
337            .field("redirect_uri", &self.redirect_uri)
338            .field("scope", &self.scope)
339            .field("response_mode", &self.response_mode)
340            .field("display", &self.display)
341            .field("prompt", &self.prompt)
342            .field("max_age", &self.max_age)
343            .field("ui_locales", &self.ui_locales)
344            .field("login_hint", &self.login_hint)
345            .field("acr_values", &self.acr_values)
346            .field("request", &self.request)
347            .field("request_uri", &self.request_uri)
348            .field("registration", &self.registration)
349            .finish_non_exhaustive()
350    }
351}
352
353/// A successful response from the [Authorization Endpoint].
354///
355/// [Authorization Endpoint]: https://www.rfc-editor.org/rfc/rfc6749.html#section-3.1
356#[skip_serializing_none]
357#[serde_as]
358#[derive(Serialize, Deserialize, Default, Clone)]
359pub struct AuthorizationResponse {
360    /// The authorization code generated by the authorization server.
361    pub code: Option<String>,
362
363    /// The access token to access the requested scope.
364    pub access_token: Option<String>,
365
366    /// The type of the access token.
367    pub token_type: Option<OAuthAccessTokenType>,
368
369    /// ID Token value associated with the authenticated session.
370    pub id_token: Option<String>,
371
372    /// The duration for which the access token is valid.
373    #[serde_as(as = "Option<DurationSeconds<i64>>")]
374    pub expires_in: Option<Duration>,
375}
376
377impl fmt::Debug for AuthorizationResponse {
378    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379        f.debug_struct("AuthorizationResponse")
380            .field("token_type", &self.token_type)
381            .field("id_token", &self.id_token)
382            .field("expires_in", &self.expires_in)
383            .finish_non_exhaustive()
384    }
385}
386
387/// A request to the [Device Authorization Endpoint].
388///
389/// [Device Authorization Endpoint]: https://www.rfc-editor.org/rfc/rfc8628
390#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
391pub struct DeviceAuthorizationRequest {
392    /// The scope of the access request.
393    pub scope: Option<Scope>,
394}
395
396/// The default value of the `interval` between polling requests, if it is not
397/// set.
398pub const DEFAULT_DEVICE_AUTHORIZATION_INTERVAL: Duration = Duration::microseconds(5 * 1000 * 1000);
399
400/// A successful response from the [Device Authorization Endpoint].
401///
402/// [Device Authorization Endpoint]: https://www.rfc-editor.org/rfc/rfc8628
403#[serde_as]
404#[skip_serializing_none]
405#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
406pub struct DeviceAuthorizationResponse {
407    /// The device verification code.
408    pub device_code: String,
409
410    /// The end-user verification code.
411    pub user_code: String,
412
413    /// The end-user verification URI on the authorization server.
414    ///
415    /// The URI should be short and easy to remember as end users will be asked
416    /// to manually type it into their user agent.
417    pub verification_uri: Url,
418
419    /// A verification URI that includes the `user_code` (or other information
420    /// with the same function as the `user_code`), which is designed for
421    /// non-textual transmission.
422    pub verification_uri_complete: Option<Url>,
423
424    /// The lifetime of the `device_code` and `user_code`.
425    #[serde_as(as = "DurationSeconds<i64>")]
426    pub expires_in: Duration,
427
428    /// The minimum amount of time in seconds that the client should wait
429    /// between polling requests to the token endpoint.
430    ///
431    /// Defaults to [`DEFAULT_DEVICE_AUTHORIZATION_INTERVAL`].
432    #[serde_as(as = "Option<DurationSeconds<i64>>")]
433    pub interval: Option<Duration>,
434}
435
436impl DeviceAuthorizationResponse {
437    /// The minimum amount of time in seconds that the client should wait
438    /// between polling requests to the token endpoint.
439    ///
440    /// Defaults to [`DEFAULT_DEVICE_AUTHORIZATION_INTERVAL`].
441    #[must_use]
442    pub fn interval(&self) -> Duration {
443        self.interval
444            .unwrap_or(DEFAULT_DEVICE_AUTHORIZATION_INTERVAL)
445    }
446}
447
448impl fmt::Debug for DeviceAuthorizationResponse {
449    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450        f.debug_struct("DeviceAuthorizationResponse")
451            .field("verification_uri", &self.verification_uri)
452            .field("expires_in", &self.expires_in)
453            .field("interval", &self.interval)
454            .finish_non_exhaustive()
455    }
456}
457
458/// A request to the [Token Endpoint] for the [Authorization Code] grant type.
459///
460/// [Token Endpoint]: https://www.rfc-editor.org/rfc/rfc6749#section-3.2
461/// [Authorization Code]: https://www.rfc-editor.org/rfc/rfc6749#section-4.1
462#[skip_serializing_none]
463#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
464pub struct AuthorizationCodeGrant {
465    /// The authorization code that was returned from the authorization
466    /// endpoint.
467    pub code: String,
468
469    /// The `redirect_uri` that was included in the authorization request.
470    ///
471    /// This field must match exactly the value passed to the authorization
472    /// endpoint.
473    pub redirect_uri: Option<Url>,
474
475    /// The code verifier that matches the code challenge that was sent to the
476    /// authorization endpoint.
477    // TODO: move this somehow in the pkce module
478    pub code_verifier: Option<String>,
479}
480
481impl fmt::Debug for AuthorizationCodeGrant {
482    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483        f.debug_struct("AuthorizationCodeGrant")
484            .field("redirect_uri", &self.redirect_uri)
485            .finish_non_exhaustive()
486    }
487}
488
489/// A request to the [Token Endpoint] for [refreshing an access token].
490///
491/// [Token Endpoint]: https://www.rfc-editor.org/rfc/rfc6749#section-3.2
492/// [refreshing an access token]: https://www.rfc-editor.org/rfc/rfc6749#section-6
493#[skip_serializing_none]
494#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
495pub struct RefreshTokenGrant {
496    /// The refresh token issued to the client.
497    pub refresh_token: String,
498
499    /// The scope of the access request.
500    ///
501    /// The requested scope must not include any scope not originally granted by
502    /// the resource owner, and if omitted is treated as equal to the scope
503    /// originally granted by the resource owner.
504    pub scope: Option<Scope>,
505}
506
507impl fmt::Debug for RefreshTokenGrant {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        f.debug_struct("RefreshTokenGrant")
510            .field("scope", &self.scope)
511            .finish_non_exhaustive()
512    }
513}
514
515/// A request to the [Token Endpoint] for the [Client Credentials] grant type.
516///
517/// [Token Endpoint]: https://www.rfc-editor.org/rfc/rfc6749#section-3.2
518/// [Client Credentials]: https://www.rfc-editor.org/rfc/rfc6749#section-4.4
519#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
520pub struct ClientCredentialsGrant {
521    /// The scope of the access request.
522    pub scope: Option<Scope>,
523}
524
525/// A request to the [Token Endpoint] for the [Device Authorization] grant type.
526///
527/// [Token Endpoint]: https://www.rfc-editor.org/rfc/rfc6749#section-3.2
528/// [Device Authorization]: https://www.rfc-editor.org/rfc/rfc8628
529#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
530pub struct DeviceCodeGrant {
531    /// The device verification code, from the device authorization response.
532    pub device_code: String,
533}
534
535impl fmt::Debug for DeviceCodeGrant {
536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537        f.debug_struct("DeviceCodeGrant").finish_non_exhaustive()
538    }
539}
540
541/// All possible values for the `grant_type` parameter.
542#[derive(
543    Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr,
544)]
545pub enum GrantType {
546    /// [`authorization_code`](https://www.rfc-editor.org/rfc/rfc6749#section-4.1)
547    AuthorizationCode,
548
549    /// [`refresh_token`](https://www.rfc-editor.org/rfc/rfc6749#section-6)
550    RefreshToken,
551
552    /// [`implicit`](https://www.rfc-editor.org/rfc/rfc6749#section-4.2)
553    Implicit,
554
555    /// [`client_credentials`](https://www.rfc-editor.org/rfc/rfc6749#section-4.4)
556    ClientCredentials,
557
558    /// [`password`](https://www.rfc-editor.org/rfc/rfc6749#section-4.3)
559    Password,
560
561    /// [`urn:ietf:params:oauth:grant-type:device_code`](https://www.rfc-editor.org/rfc/rfc8628)
562    DeviceCode,
563
564    /// [`https://datatracker.ietf.org/doc/html/rfc7523#section-2.1`](https://www.rfc-editor.org/rfc/rfc7523#section-2.1)
565    JwtBearer,
566
567    /// [`urn:openid:params:grant-type:ciba`](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)
568    ClientInitiatedBackchannelAuthentication,
569
570    /// An unknown value.
571    Unknown(String),
572}
573
574impl core::fmt::Display for GrantType {
575    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
576        match self {
577            GrantType::AuthorizationCode => f.write_str("authorization_code"),
578            GrantType::RefreshToken => f.write_str("refresh_token"),
579            GrantType::Implicit => f.write_str("implicit"),
580            GrantType::ClientCredentials => f.write_str("client_credentials"),
581            GrantType::Password => f.write_str("password"),
582            GrantType::DeviceCode => f.write_str("urn:ietf:params:oauth:grant-type:device_code"),
583            GrantType::JwtBearer => f.write_str("urn:ietf:params:oauth:grant-type:jwt-bearer"),
584            GrantType::ClientInitiatedBackchannelAuthentication => {
585                f.write_str("urn:openid:params:grant-type:ciba")
586            }
587            GrantType::Unknown(s) => f.write_str(s),
588        }
589    }
590}
591
592impl core::str::FromStr for GrantType {
593    type Err = core::convert::Infallible;
594
595    fn from_str(s: &str) -> Result<Self, Self::Err> {
596        match s {
597            "authorization_code" => Ok(GrantType::AuthorizationCode),
598            "refresh_token" => Ok(GrantType::RefreshToken),
599            "implicit" => Ok(GrantType::Implicit),
600            "client_credentials" => Ok(GrantType::ClientCredentials),
601            "password" => Ok(GrantType::Password),
602            "urn:ietf:params:oauth:grant-type:device_code" => Ok(GrantType::DeviceCode),
603            "urn:ietf:params:oauth:grant-type:jwt-bearer" => Ok(GrantType::JwtBearer),
604            "urn:openid:params:grant-type:ciba" => {
605                Ok(GrantType::ClientInitiatedBackchannelAuthentication)
606            }
607            s => Ok(GrantType::Unknown(s.to_owned())),
608        }
609    }
610}
611
612/// An enum representing the possible requests to the [Token Endpoint].
613///
614/// [Token Endpoint]: https://www.rfc-editor.org/rfc/rfc6749#section-3.2
615#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
616#[serde(tag = "grant_type", rename_all = "snake_case")]
617#[non_exhaustive]
618pub enum AccessTokenRequest {
619    /// A request in the Authorization Code flow.
620    AuthorizationCode(AuthorizationCodeGrant),
621
622    /// A request to refresh an access token.
623    RefreshToken(RefreshTokenGrant),
624
625    /// A request in the Client Credentials flow.
626    ClientCredentials(ClientCredentialsGrant),
627
628    /// A request in the Device Code flow.
629    #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
630    DeviceCode(DeviceCodeGrant),
631
632    /// An unsupported request.
633    #[serde(skip_serializing, other)]
634    Unsupported,
635}
636
637impl AccessTokenRequest {
638    /// Returns the string representation of the grant type of the request.
639    #[must_use]
640    pub fn grant_type(&self) -> &'static str {
641        match self {
642            Self::AuthorizationCode(_) => "authorization_code",
643            Self::RefreshToken(_) => "refresh_token",
644            Self::ClientCredentials(_) => "client_credentials",
645            Self::DeviceCode(_) => "urn:ietf:params:oauth:grant-type:device_code",
646            Self::Unsupported => "unsupported",
647        }
648    }
649}
650
651/// A successful response from the [Token Endpoint].
652///
653/// [Token Endpoint]: https://www.rfc-editor.org/rfc/rfc6749#section-3.2
654#[serde_as]
655#[skip_serializing_none]
656#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
657pub struct AccessTokenResponse {
658    /// The access token to access the requested scope.
659    pub access_token: String,
660
661    /// The token to refresh the access token when it expires.
662    pub refresh_token: Option<String>,
663
664    /// ID Token value associated with the authenticated session.
665    // TODO: this should be somewhere else
666    pub id_token: Option<String>,
667
668    /// The type of the access token.
669    pub token_type: OAuthAccessTokenType,
670
671    /// The duration for which the access token is valid.
672    #[serde_as(as = "Option<DurationSeconds<i64>>")]
673    pub expires_in: Option<Duration>,
674
675    /// The scope of the access token.
676    pub scope: Option<Scope>,
677}
678
679impl AccessTokenResponse {
680    /// Creates a new `AccessTokenResponse` with the given access token.
681    #[must_use]
682    pub fn new(access_token: String) -> AccessTokenResponse {
683        AccessTokenResponse {
684            access_token,
685            refresh_token: None,
686            id_token: None,
687            token_type: OAuthAccessTokenType::Bearer,
688            expires_in: None,
689            scope: None,
690        }
691    }
692
693    /// Adds a refresh token to an `AccessTokenResponse`.
694    #[must_use]
695    pub fn with_refresh_token(mut self, refresh_token: String) -> Self {
696        self.refresh_token = Some(refresh_token);
697        self
698    }
699
700    /// Adds an ID token to an `AccessTokenResponse`.
701    #[must_use]
702    pub fn with_id_token(mut self, id_token: String) -> Self {
703        self.id_token = Some(id_token);
704        self
705    }
706
707    /// Adds a scope to an `AccessTokenResponse`.
708    #[must_use]
709    pub fn with_scope(mut self, scope: Scope) -> Self {
710        self.scope = Some(scope);
711        self
712    }
713
714    /// Adds an expiration duration to an `AccessTokenResponse`.
715    #[must_use]
716    pub fn with_expires_in(mut self, expires_in: Duration) -> Self {
717        self.expires_in = Some(expires_in);
718        self
719    }
720}
721
722impl fmt::Debug for AccessTokenResponse {
723    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
724        f.debug_struct("AccessTokenResponse")
725            .field("token_type", &self.token_type)
726            .field("expires_in", &self.expires_in)
727            .field("scope", &self.scope)
728            .finish_non_exhaustive()
729    }
730}
731
732/// A request to the [Introspection Endpoint].
733///
734/// [Introspection Endpoint]: https://www.rfc-editor.org/rfc/rfc7662#section-2
735#[skip_serializing_none]
736#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
737pub struct IntrospectionRequest {
738    /// The value of the token.
739    pub token: String,
740
741    /// A hint about the type of the token submitted for introspection.
742    pub token_type_hint: Option<OAuthTokenTypeHint>,
743}
744
745impl fmt::Debug for IntrospectionRequest {
746    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
747        f.debug_struct("IntrospectionRequest")
748            .field("token_type_hint", &self.token_type_hint)
749            .finish_non_exhaustive()
750    }
751}
752
753/// A successful response from the [Introspection Endpoint].
754///
755/// [Introspection Endpoint]: https://www.rfc-editor.org/rfc/rfc7662#section-2
756#[serde_as]
757#[skip_serializing_none]
758#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
759pub struct IntrospectionResponse {
760    /// Whether or not the presented token is currently active.
761    pub active: bool,
762
763    /// The scope associated with the token.
764    pub scope: Option<Scope>,
765
766    /// Client identifier for the OAuth 2.0 client that requested this token.
767    pub client_id: Option<String>,
768
769    /// Human-readable identifier for the resource owner who authorized this
770    /// token.
771    pub username: Option<String>,
772
773    /// Type of the token.
774    pub token_type: Option<OAuthTokenTypeHint>,
775
776    /// Timestamp indicating when the token will expire.
777    #[serde_as(as = "Option<TimestampSeconds>")]
778    pub exp: Option<DateTime<Utc>>,
779
780    /// Relative timestamp indicating when the token will expire,
781    /// in seconds from the current instant.
782    #[serde_as(as = "Option<DurationSeconds<i64>>")]
783    pub expires_in: Option<Duration>,
784
785    /// Timestamp indicating when the token was issued.
786    #[serde_as(as = "Option<TimestampSeconds>")]
787    pub iat: Option<DateTime<Utc>>,
788
789    /// Timestamp indicating when the token is not to be used before.
790    #[serde_as(as = "Option<TimestampSeconds>")]
791    pub nbf: Option<DateTime<Utc>>,
792
793    /// Subject of the token.
794    pub sub: Option<String>,
795
796    /// Intended audience of the token.
797    pub aud: Option<String>,
798
799    /// Issuer of the token.
800    pub iss: Option<String>,
801
802    /// String identifier for the token.
803    pub jti: Option<String>,
804
805    /// MAS extension: explicit device ID
806    pub device_id: Option<String>,
807}
808
809/// A request to the [Revocation Endpoint].
810///
811/// [Revocation Endpoint]: https://www.rfc-editor.org/rfc/rfc7009#section-2
812#[skip_serializing_none]
813#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
814pub struct RevocationRequest {
815    /// The value of the token.
816    pub token: String,
817
818    /// A hint about the type of the token submitted for introspection.
819    pub token_type_hint: Option<OAuthTokenTypeHint>,
820}
821
822impl fmt::Debug for RevocationRequest {
823    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
824        f.debug_struct("RevocationRequest")
825            .field("token_type_hint", &self.token_type_hint)
826            .finish_non_exhaustive()
827    }
828}
829
830/// A successful response from the [Pushed Authorization Request Endpoint].
831///
832/// Note that there is no request type because it is by definition the same as
833/// [`AuthorizationRequest`].
834///
835/// [Pushed Authorization Request Endpoint]: https://datatracker.ietf.org/doc/html/rfc9126
836#[serde_as]
837#[skip_serializing_none]
838#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
839pub struct PushedAuthorizationResponse {
840    /// The `request_uri` to use for the request to the authorization endpoint.
841    pub request_uri: String,
842
843    /// The duration for which the request URI is valid.
844    #[serde_as(as = "DurationSeconds<i64>")]
845    pub expires_in: Duration,
846}
847
848#[cfg(test)]
849mod tests {
850    use serde_json::json;
851
852    use super::*;
853    use crate::{scope::OPENID, test_utils::assert_serde_json};
854
855    #[test]
856    fn serde_refresh_token_grant() {
857        let expected = json!({
858            "grant_type": "refresh_token",
859            "refresh_token": "abcd",
860            "scope": "openid",
861        });
862
863        // TODO: insert multiple scopes and test it. It's a bit tricky to test since
864        // HashSet have no guarantees regarding the ordering of items, so right
865        // now the output is unstable.
866        let scope: Option<Scope> = Some(vec![OPENID].into_iter().collect());
867
868        let req = AccessTokenRequest::RefreshToken(RefreshTokenGrant {
869            refresh_token: "abcd".into(),
870            scope,
871        });
872
873        assert_serde_json(&req, expected);
874    }
875
876    #[test]
877    fn serde_authorization_code_grant() {
878        let expected = json!({
879            "grant_type": "authorization_code",
880            "code": "abcd",
881            "redirect_uri": "https://example.com/redirect",
882        });
883
884        let req = AccessTokenRequest::AuthorizationCode(AuthorizationCodeGrant {
885            code: "abcd".into(),
886            redirect_uri: Some("https://example.com/redirect".parse().unwrap()),
887            code_verifier: None,
888        });
889
890        assert_serde_json(&req, expected);
891    }
892
893    #[test]
894    fn serialize_grant_type() {
895        assert_eq!(
896            serde_json::to_string(&GrantType::AuthorizationCode).unwrap(),
897            "\"authorization_code\""
898        );
899        assert_eq!(
900            serde_json::to_string(&GrantType::RefreshToken).unwrap(),
901            "\"refresh_token\""
902        );
903        assert_eq!(
904            serde_json::to_string(&GrantType::Implicit).unwrap(),
905            "\"implicit\""
906        );
907        assert_eq!(
908            serde_json::to_string(&GrantType::ClientCredentials).unwrap(),
909            "\"client_credentials\""
910        );
911        assert_eq!(
912            serde_json::to_string(&GrantType::Password).unwrap(),
913            "\"password\""
914        );
915        assert_eq!(
916            serde_json::to_string(&GrantType::DeviceCode).unwrap(),
917            "\"urn:ietf:params:oauth:grant-type:device_code\""
918        );
919        assert_eq!(
920            serde_json::to_string(&GrantType::ClientInitiatedBackchannelAuthentication).unwrap(),
921            "\"urn:openid:params:grant-type:ciba\""
922        );
923    }
924
925    #[test]
926    fn deserialize_grant_type() {
927        assert_eq!(
928            serde_json::from_str::<GrantType>("\"authorization_code\"").unwrap(),
929            GrantType::AuthorizationCode
930        );
931        assert_eq!(
932            serde_json::from_str::<GrantType>("\"refresh_token\"").unwrap(),
933            GrantType::RefreshToken
934        );
935        assert_eq!(
936            serde_json::from_str::<GrantType>("\"implicit\"").unwrap(),
937            GrantType::Implicit
938        );
939        assert_eq!(
940            serde_json::from_str::<GrantType>("\"client_credentials\"").unwrap(),
941            GrantType::ClientCredentials
942        );
943        assert_eq!(
944            serde_json::from_str::<GrantType>("\"password\"").unwrap(),
945            GrantType::Password
946        );
947        assert_eq!(
948            serde_json::from_str::<GrantType>("\"urn:ietf:params:oauth:grant-type:device_code\"")
949                .unwrap(),
950            GrantType::DeviceCode
951        );
952        assert_eq!(
953            serde_json::from_str::<GrantType>("\"urn:openid:params:grant-type:ciba\"").unwrap(),
954            GrantType::ClientInitiatedBackchannelAuthentication
955        );
956    }
957
958    #[test]
959    fn serialize_response_mode() {
960        assert_eq!(
961            serde_json::to_string(&ResponseMode::Query).unwrap(),
962            "\"query\""
963        );
964        assert_eq!(
965            serde_json::to_string(&ResponseMode::Fragment).unwrap(),
966            "\"fragment\""
967        );
968        assert_eq!(
969            serde_json::to_string(&ResponseMode::FormPost).unwrap(),
970            "\"form_post\""
971        );
972    }
973
974    #[test]
975    fn deserialize_response_mode() {
976        assert_eq!(
977            serde_json::from_str::<ResponseMode>("\"query\"").unwrap(),
978            ResponseMode::Query
979        );
980        assert_eq!(
981            serde_json::from_str::<ResponseMode>("\"fragment\"").unwrap(),
982            ResponseMode::Fragment
983        );
984        assert_eq!(
985            serde_json::from_str::<ResponseMode>("\"form_post\"").unwrap(),
986            ResponseMode::FormPost
987        );
988    }
989
990    #[test]
991    fn serialize_display() {
992        assert_eq!(serde_json::to_string(&Display::Page).unwrap(), "\"page\"");
993        assert_eq!(serde_json::to_string(&Display::Popup).unwrap(), "\"popup\"");
994        assert_eq!(serde_json::to_string(&Display::Touch).unwrap(), "\"touch\"");
995        assert_eq!(serde_json::to_string(&Display::Wap).unwrap(), "\"wap\"");
996    }
997
998    #[test]
999    fn deserialize_display() {
1000        assert_eq!(
1001            serde_json::from_str::<Display>("\"page\"").unwrap(),
1002            Display::Page
1003        );
1004        assert_eq!(
1005            serde_json::from_str::<Display>("\"popup\"").unwrap(),
1006            Display::Popup
1007        );
1008        assert_eq!(
1009            serde_json::from_str::<Display>("\"touch\"").unwrap(),
1010            Display::Touch
1011        );
1012        assert_eq!(
1013            serde_json::from_str::<Display>("\"wap\"").unwrap(),
1014            Display::Wap
1015        );
1016    }
1017
1018    #[test]
1019    fn serialize_prompt() {
1020        assert_eq!(serde_json::to_string(&Prompt::None).unwrap(), "\"none\"");
1021        assert_eq!(serde_json::to_string(&Prompt::Login).unwrap(), "\"login\"");
1022        assert_eq!(
1023            serde_json::to_string(&Prompt::Consent).unwrap(),
1024            "\"consent\""
1025        );
1026        assert_eq!(
1027            serde_json::to_string(&Prompt::SelectAccount).unwrap(),
1028            "\"select_account\""
1029        );
1030        assert_eq!(
1031            serde_json::to_string(&Prompt::Create).unwrap(),
1032            "\"create\""
1033        );
1034    }
1035
1036    #[test]
1037    fn deserialize_prompt() {
1038        assert_eq!(
1039            serde_json::from_str::<Prompt>("\"none\"").unwrap(),
1040            Prompt::None
1041        );
1042        assert_eq!(
1043            serde_json::from_str::<Prompt>("\"login\"").unwrap(),
1044            Prompt::Login
1045        );
1046        assert_eq!(
1047            serde_json::from_str::<Prompt>("\"consent\"").unwrap(),
1048            Prompt::Consent
1049        );
1050        assert_eq!(
1051            serde_json::from_str::<Prompt>("\"select_account\"").unwrap(),
1052            Prompt::SelectAccount
1053        );
1054        assert_eq!(
1055            serde_json::from_str::<Prompt>("\"create\"").unwrap(),
1056            Prompt::Create
1057        );
1058    }
1059}