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}