oauth2_types/
requests.rs

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