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}