io_oauth/2.0/
issue-access-token.rs

1//! Module dedicated to the section 5: Issuing an Access Token.
2//!
3//! Refs: https://datatracker.ietf.org/doc/html/rfc6749#section-5
4
5use std::time::SystemTime;
6
7use secrecy::{ExposeSecret, SecretString};
8use serde::{Deserialize, Serialize, Serializer};
9
10pub type AccessTokenResponse = Result<IssueAccessTokenSuccessParams, IssueAccessTokenErrorParams>;
11
12/// The response returned by the authorization server when the access
13/// token request is valid and authorized.
14///
15/// The authorization server issues an access token and optional
16/// refresh token, and constructs the response by adding the following
17/// parameters to the entity-body of the HTTP response with a 200 (OK)
18/// status code.
19///
20/// Refs: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct IssueAccessTokenSuccessParams {
23    /// The access token issued by the authorization server.
24    #[serde(serialize_with = "serialize_secret_string")]
25    pub access_token: SecretString,
26
27    /// The type of the token issued.
28    ///
29    /// Refs: https://datatracker.ietf.org/doc/html/rfc6749#section-7.1
30    pub token_type: String,
31
32    /// The lifetime in seconds of the access token.
33    ///
34    /// For example, the value "3600" denotes that the access token
35    /// will expire in one hour from the time the response was
36    /// generated. If omitted, the authorization server SHOULD provide
37    /// the expiration time via other means or document the default
38    /// value.
39    pub expires_in: Option<usize>,
40
41    /// The refresh token.
42    ///
43    /// The refresh token, which can be used to obtain new access
44    /// tokens using the same authorization grant.
45    ///
46    /// Refs: https://datatracker.ietf.org/doc/html/rfc6749#section-6
47    #[serde(serialize_with = "serialize_opt_secret_string")]
48    pub refresh_token: Option<SecretString>,
49
50    /// The scope of the access token.
51    ///
52    /// OPTIONAL, if identical to the scope requested by the client;
53    /// otherwise, REQUIRED.
54    ///
55    /// Refs: https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
56    pub scope: Option<String>,
57
58    /// Time the access token was issued at.
59    ///
60    /// This field does not belong to the specs, its sole purpose is
61    /// to track whenever the token is expired or not.
62    #[serde(default = "SystemTime::now")]
63    pub issued_at: SystemTime,
64}
65
66impl IssueAccessTokenSuccessParams {
67    pub fn sync_expires_in(&mut self) {
68        let Some(expires_in) = &mut self.expires_in else {
69            return;
70        };
71
72        let Ok(elapsed) = self.issued_at.elapsed() else {
73            return;
74        };
75
76        let elapsed = elapsed.as_secs() as usize;
77
78        *expires_in -= elapsed.min(*expires_in);
79    }
80}
81
82/// Serializes success params into JSON string.
83// SAFETY: exposes access and refresh tokens
84impl TryFrom<&IssueAccessTokenSuccessParams> for String {
85    type Error = serde_json::Error;
86
87    fn try_from(params: &IssueAccessTokenSuccessParams) -> Result<Self, Self::Error> {
88        serde_json::to_string(params)
89    }
90}
91
92/// Deserializes success params from JSON bytes.
93impl TryFrom<&[u8]> for IssueAccessTokenSuccessParams {
94    type Error = serde_json::Error;
95
96    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
97        serde_json::from_slice(bytes)
98    }
99}
100
101/// The response returned by the authorization server when the access
102/// token request is not valid or unauthorized.
103///
104/// The authorization server responds with an HTTP 400 (Bad Request)
105/// status code (unless specified otherwise) and includes the
106/// following parameters with the response.
107///
108/// Refs: https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
109#[derive(Clone, Debug, Deserialize)]
110pub struct IssueAccessTokenErrorParams {
111    /// A single ASCII error code.
112    pub error: IssueAccessTokenErrorCode,
113
114    /// Human-readable ASCII text providing additional information,
115    /// used to assist the client developer in understanding the error
116    /// that occurred.
117    pub error_description: Option<String>,
118
119    /// A URI identifying a human-readable web page with information
120    /// about the error, used to provide the client developer with
121    /// additional information about the error.
122    pub error_uri: Option<String>,
123}
124
125/// Parses error params for JSON bytes.
126impl TryFrom<&[u8]> for IssueAccessTokenErrorParams {
127    type Error = serde_json::Error;
128
129    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
130        serde_json::from_slice(bytes)
131    }
132}
133
134/// The error code of the [`IssueAccessTokenErrorParams`].
135#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
136#[serde(rename_all = "snake_case")]
137pub enum IssueAccessTokenErrorCode {
138    /// Client authentication failed (e.g., unknown client, no client
139    /// authentication included, or unsupported authentication
140    /// method).  The authorization server MAY return an HTTP 401
141    /// (Unauthorized) status code to indicate which HTTP
142    /// authentication schemes are supported.  If the client attempted
143    /// to authenticate via the "Authorization" request header field,
144    /// the authorization server MUST respond with an HTTP 401
145    /// (Unauthorized) status code and include the "WWW-Authenticate"
146    /// response header field matching the authentication scheme used
147    /// by the client.
148    InvalidClient,
149
150    /// The provided authorization grant (e.g., authorization code,
151    /// resource owner credentials) or refresh token is invalid,
152    /// expired, revoked, does not match the redirection URI used in
153    /// the authorization request, or was issued to another client.
154    InvalidGrant,
155
156    /// The request is missing a required parameter, includes an
157    /// unsupported parameter value (other than grant type), repeats a
158    /// parameter, includes multiple credentials, utilizes more than
159    /// one mechanism for authenticating the client, or is otherwise
160    /// malformed.
161    InvalidRequest,
162
163    /// The requested scope is invalid, unknown, malformed, or exceeds
164    /// the scope granted by the resource owner.
165    InvalidScope,
166
167    /// The authenticated client is not authorized to use this
168    /// authorization grant type.
169    UnauthorizedClient,
170
171    /// The authorization grant type is not supported by the
172    /// authorization server.
173    UnsupportedGrantType,
174}
175
176fn serialize_secret_string<S: Serializer>(secret: &SecretString, s: S) -> Result<S::Ok, S::Error> {
177    s.serialize_str(secret.expose_secret())
178}
179
180fn serialize_opt_secret_string<S: Serializer>(
181    secret: &Option<SecretString>,
182    s: S,
183) -> Result<S::Ok, S::Error> {
184    match secret {
185        Some(secret) => serialize_secret_string(secret, s),
186        None => s.serialize_none(),
187    }
188}