Skip to main content

oauth_provider_rs/
types.rs

1use crate::error::OAuthError;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5// --- AuthRequest カスタム serializer/deserializer --------------------------------------
6fn serialize_scope<S>(scope: &Vec<String>, serializer: S) -> Result<S::Ok, S::Error>
7where
8    S: serde::Serializer,
9{
10    let scope_str = scope.join(" ");
11    serializer.serialize_str(&scope_str)
12}
13
14fn deserialize_scope<'de, D>(d: D) -> Result<Vec<String>, D::Error>
15where
16    D: serde::Deserializer<'de>,
17{
18    let s = String::deserialize(d)?; // "read write"
19    Ok(s.split_whitespace() // ← 空白で分割
20        .map(|s| s.to_owned())
21        .collect())
22}
23
24fn serialize_scope_option<S>(scope: &Vec<String>, serializer: S) -> Result<S::Ok, S::Error>
25where
26    S: serde::Serializer,
27{
28    if scope.is_empty() {
29        serializer.serialize_none()
30    } else {
31        let scope_str = scope.join(" ");
32        serializer.serialize_str(&scope_str)
33    }
34}
35
36fn deserialize_scope_option<'de, D>(d: D) -> Result<Vec<String>, D::Error>
37where
38    D: serde::Deserializer<'de>,
39{
40    let s = Option::<String>::deserialize(d)?;
41    match s {
42        Some(s) if !s.is_empty() => Ok(s.split_whitespace()
43            .map(|s| s.to_owned())
44            .collect()),
45        _ => Ok(Vec::new()),
46    }
47}
48
49/// Parsed OAuth authorization request parameters
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct AuthRequest {
52    /// OAuth response type (e.g., "code" for authorization code flow)
53    pub response_type: String,
54
55    /// Client identifier for the OAuth client
56    pub client_id: String,
57
58    /// URL to redirect to after authorization
59    pub redirect_uri: String,
60
61    /// Array of requested permission scopes
62    #[serde(
63        serialize_with = "serialize_scope_option",
64        deserialize_with = "deserialize_scope_option",
65        default
66    )]
67    pub scope: Vec<String>,
68
69    /// Client state value to be returned in the redirect
70    pub state: Option<String>,
71
72    /// PKCE code challenge (RFC 7636)
73    pub code_challenge: Option<String>,
74
75    /// PKCE code challenge method (plain or S256)
76    pub code_challenge_method: Option<String>,
77}
78
79/// OAuth client registration information
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ClientInfo {
82    /// Unique identifier for the client
83    pub client_id: String,
84
85    /// Secret used to authenticate the client (stored as a hash)
86    /// Only present for confidential clients; None for public clients.
87    pub client_secret: Option<String>,
88
89    /// List of allowed redirect URIs for the client
90    pub redirect_uris: Vec<String>,
91
92    /// Human-readable name of the client application
93    pub client_name: Option<String>,
94
95    /// URL to the client's logo
96    pub logo_uri: Option<String>,
97
98    /// URL to the client's homepage
99    pub client_uri: Option<String>,
100
101    /// URL to the client's privacy policy
102    pub policy_uri: Option<String>,
103
104    /// URL to the client's terms of service
105    pub tos_uri: Option<String>,
106
107    /// URL to the client's JSON Web Key Set for validating signatures
108    pub jwks_uri: Option<String>,
109
110    /// List of email addresses for contacting the client developers
111    pub contacts: Option<Vec<String>>,
112
113    /// List of grant types the client supports
114    pub grant_types: Option<Vec<String>>,
115
116    /// List of response types the client supports
117    pub response_types: Option<Vec<String>>,
118
119    /// Unix timestamp when the client was registered
120    pub registration_date: Option<i64>,
121
122    /// The authentication method used by the client at the token endpoint.
123    /// Values include:
124    /// - 'client_secret_basic': Uses HTTP Basic Auth with client ID and secret (default for confidential clients)
125    /// - 'client_secret_post': Uses POST parameters for client authentication
126    /// - 'none': Used for public clients that can't securely store secrets (SPAs, mobile apps, etc.)
127    ///
128    /// Public clients use 'none', while confidential clients use either 'client_secret_basic' or 'client_secret_post'.
129    pub token_endpoint_auth_method: String,
130}
131
132/// Options for completing an authorization request
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct CompleteAuthorizationOptions {
135    /// The original parsed authorization request
136    pub request: AuthRequest,
137
138    /// Identifier for the user granting the authorization
139    pub user_id: String,
140
141    /// Application-specific metadata to associate with this grant
142    pub metadata: Value,
143
144    /// List of scopes that were actually granted (may differ from requested scopes)
145    pub scope: Vec<String>,
146
147    /// Application-specific properties to include with API requests
148    /// authorized by this grant
149    pub props: Value,
150}
151
152/// Authorization grant record
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct Grant {
155    /// Unique identifier for the grant
156    pub id: String,
157
158    /// Client that received this grant
159    pub client_id: String,
160
161    /// User who authorized this grant
162    pub user_id: String,
163
164    /// List of scopes that were granted
165    pub scope: Vec<String>,
166
167    /// Application-specific metadata associated with this grant
168    pub metadata: Value,
169
170    /// Encrypted application-specific properties
171    pub encrypted_props: String,
172
173    /// Unix timestamp when the grant was created
174    pub created_at: i64,
175
176    /// The hash of the current refresh token associated with this grant
177    pub refresh_token_id: Option<String>,
178
179    /// Wrapped encryption key for the current refresh token
180    pub refresh_token_wrapped_key: Option<String>,
181
182    /// The hash of the previous refresh token associated with this grant
183    /// This token is still valid until the new token is first used
184    pub previous_refresh_token_id: Option<String>,
185
186    /// Wrapped encryption key for the previous refresh token
187    pub previous_refresh_token_wrapped_key: Option<String>,
188
189    /// The hash of the authorization code associated with this grant
190    /// Only present during the authorization code exchange process
191    pub auth_code_id: Option<String>,
192
193    /// Wrapped encryption key for the authorization code
194    /// Only present during the authorization code exchange process
195    pub auth_code_wrapped_key: Option<String>,
196
197    /// PKCE code challenge for this authorization
198    /// Only present during the authorization code exchange process
199    pub code_challenge: Option<String>,
200
201    /// PKCE code challenge method (plain or S256)
202    /// Only present during the authorization code exchange process
203    pub code_challenge_method: Option<String>,
204}
205
206/// Token record stored in storage
207/// Note: The actual token format is "{userId}:{grantId}:{random-secret}"
208/// but we only store the hash of the full token string.
209/// This contains only access tokens; refresh tokens are stored within the grant records.
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct Token {
212    /// Unique identifier for the token (hash of the actual token)
213    pub id: String,
214
215    /// Identifier of the grant this token is associated with
216    pub grant_id: String,
217
218    /// User ID associated with this token
219    pub user_id: String,
220
221    /// Unix timestamp when the token was created
222    pub created_at: i64,
223
224    /// Unix timestamp when the token expires
225    pub expires_at: i64,
226
227    /// The encryption key for props, wrapped with this token
228    pub wrapped_encryption_key: String,
229
230    /// Denormalized grant information for faster access
231    pub grant: TokenGrant,
232}
233
234/// Denormalized grant information stored within a token
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct TokenGrant {
237    /// Client that received this grant
238    pub client_id: String,
239
240    /// List of scopes that were granted
241    pub scope: Vec<String>,
242
243    /// Encrypted application-specific properties
244    pub encrypted_props: String,
245}
246
247/// Options for listing operations that support pagination
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct OAuthListOptions {
250    /// Maximum number of items to return (max 1000)
251    pub limit: Option<usize>,
252
253    /// Cursor for pagination (from a previous listing operation)
254    pub cursor: Option<String>,
255}
256
257/// Result of a listing operation with pagination support
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct OAuthListResult<T> {
260    /// The list of items
261    pub items: Vec<T>,
262
263    /// Cursor to get the next page of results, if there are more results
264    pub cursor: Option<String>,
265}
266
267/// Public representation of a grant, with sensitive data removed
268/// Used for list operations where the complete grant data isn't needed
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct GrantSummary {
271    /// Unique identifier for the grant
272    pub id: String,
273
274    /// Client that received this grant
275    pub client_id: String,
276
277    /// User who authorized this grant
278    pub user_id: String,
279
280    /// List of scopes that were granted
281    pub scope: Vec<String>,
282
283    /// Application-specific metadata associated with this grant
284    pub metadata: Value,
285
286    /// Unix timestamp when the grant was created
287    pub created_at: i64,
288}
289
290/// Result of a token exchange callback function.
291/// Allows updating the props stored in both the access token and the grant.
292#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct TokenExchangeCallbackResult {
294    /// New props to be stored specifically with the access token.
295    /// If not provided but new_props is, the access token will use new_props.
296    /// If neither is provided, the original props will be used.
297    pub access_token_props: Option<Value>,
298
299    /// New props to replace the props stored in the grant itself.
300    /// These props will be used for all future token refreshes.
301    /// If access_token_props is not provided, these props will also be used for the current access token.
302    /// If not provided, the original props will be used.
303    pub new_props: Option<Value>,
304
305    /// Override the default access token TTL (time-to-live) for this specific token.
306    /// This is especially useful when the application is also an OAuth client to another service
307    /// and wants to match its access token TTL to the upstream access token TTL.
308    /// Value should be in seconds.
309    pub access_token_ttl: Option<u64>,
310}
311
312/// Options for token exchange callback functions
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct TokenExchangeCallbackOptions {
315    /// The type of grant being processed.
316    /// 'authorization_code' for initial code exchange,
317    /// 'refresh_token' for refresh token exchange.
318    pub grant_type: GrantType,
319
320    /// Client that received this grant
321    pub client_id: String,
322
323    /// User who authorized this grant
324    pub user_id: String,
325
326    /// List of scopes that were granted
327    pub scope: Vec<String>,
328
329    /// Application-specific properties currently associated with this grant
330    pub props: Value,
331}
332
333/// OAuth grant types
334#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
335pub enum GrantType {
336    #[serde(rename = "authorization_code")]
337    AuthorizationCode,
338    #[serde(rename = "refresh_token")]
339    RefreshToken,
340}
341
342/// PKCE code challenge methods
343#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
344pub enum CodeChallengeMethod {
345    #[serde(rename = "plain")]
346    Plain,
347    #[serde(rename = "S256")]
348    S256,
349}
350
351/// Storage key types for different OAuth entities
352#[derive(Debug, Clone)]
353pub enum StorageKey {
354    Client(String),
355    Grant(String),
356    Token(String),
357    AuthCode(String),
358    RefreshToken(String),
359}
360
361impl StorageKey {
362    pub fn as_str(&self) -> String {
363        match self {
364            StorageKey::Client(id) => format!("client:{}", id),
365            StorageKey::Grant(id) => format!("grant:{}", id),
366            StorageKey::Token(id) => format!("token:{}", id),
367            StorageKey::AuthCode(id) => format!("auth_code:{}", id),
368            StorageKey::RefreshToken(id) => format!("refresh_token:{}", id),
369        }
370    }
371}
372
373/// User context for authenticated requests
374/// Contains information about the authenticated user extracted from a valid OAuth token
375#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct UserContext {
377    /// The user ID extracted from the token
378    pub user_id: String,
379    /// The client ID that received this token
380    pub client_id: String,
381    /// List of scopes granted to this token
382    pub scope: Vec<String>,
383    /// Application-specific properties associated with this token
384    pub props: Value,
385}
386
387impl UserContext {
388    /// Check if the user has a specific scope
389    pub fn has_scope(&self, scope: &str) -> bool {
390        self.scope.contains(&scope.to_string())
391    }
392
393    /// Check if the user has any of the specified scopes
394    pub fn has_any_scope(&self, scopes: &[&str]) -> bool {
395        scopes.iter().any(|scope| self.has_scope(scope))
396    }
397
398    /// Check if the user has all of the specified scopes
399    pub fn has_all_scopes(&self, scopes: &[&str]) -> bool {
400        scopes.iter().all(|scope| self.has_scope(scope))
401    }
402
403    /// Get a property from the user props
404    pub fn get_prop<T>(&self, key: &str) -> Option<T>
405    where
406        T: serde::de::DeserializeOwned,
407    {
408        self.props
409            .get(key)
410            .and_then(|v| serde_json::from_value(v.clone()).ok())
411    }
412
413    /// Require a specific scope, returning an error if not present
414    pub fn require_scope(&self, scope: &str) -> Result<(), OAuthError> {
415        if self.has_scope(scope) {
416            Ok(())
417        } else {
418            Err(OAuthError::insufficient_scope(format!(
419                "Required scope '{}' not found",
420                scope
421            )))
422        }
423    }
424
425    /// Require any of the specified scopes, returning an error if none are present
426    pub fn require_any_scope(&self, scopes: &[&str]) -> Result<(), OAuthError> {
427        if self.has_any_scope(scopes) {
428            Ok(())
429        } else {
430            Err(OAuthError::insufficient_scope(format!(
431                "Required one of scopes: {:?}",
432                scopes
433            )))
434        }
435    }
436}