1use crate::{AuthError, AuthResult};
3use async_trait::async_trait;
4use chrono::{DateTime, Utc};
5use serde_json::Value;
6#[derive(Clone, Debug)]
7pub struct OAuthClient {
8 pub id: String,
9 pub client_id: String,
10 pub user_id: Option<String>,
11 pub client_secret: Option<String>,
12 pub metadata: Value,
15 pub created_at: Option<DateTime<Utc>>,
16 pub updated_at: Option<DateTime<Utc>>,
17}
18#[derive(Clone, Debug)]
19pub struct CreateOAuthClient {
20 pub client_id: String,
21 pub user_id: Option<String>,
22 pub client_secret: Option<String>,
23 pub metadata: Value,
24 pub created_at: DateTime<Utc>,
25}
26#[derive(Clone, Debug)]
29pub struct PersistOAuthClientRegistration {
30 pub client: CreateOAuthClient,
31 pub replacement: Option<OAuthClientRegistrationReplacement>,
33 pub resources: Vec<String>,
34 pub updated_at: DateTime<Utc>,
35}
36
37#[derive(Clone, Debug)]
39pub struct OAuthClientRegistrationReplacement {
40 pub discovery_id: String,
41 pub existing_created_at: Option<DateTime<Utc>>,
43}
44
45#[derive(Debug, thiserror::Error)]
46pub enum OAuthClientRegistrationWriteError {
47 #[error(transparent)]
48 Failure(#[from] AuthError),
49 #[error("client registration collision: {cause}")]
50 Collision {
51 cause: AuthError,
52 resource_id: Option<String>,
53 },
54}
55impl OAuthClientRegistrationWriteError {
56 #[must_use]
58 pub fn classify(cause: AuthError, resource_id: Option<String>) -> Self {
59 if matches!(&cause, AuthError::Database(crate::error::DatabaseError::Constraint(message)) if message.to_ascii_lowercase().contains("unique"))
60 {
61 Self::Collision { cause, resource_id }
62 } else {
63 Self::Failure(cause)
64 }
65 }
66}
67#[derive(Clone, Copy, Debug)]
69pub enum OAuthTokenKind {
70 Access,
71 Refresh,
72}
73#[derive(Clone, Debug)]
74pub struct OAuthToken {
75 pub id: String,
76 pub token: String,
77 pub client_id: String,
78 pub user_id: Option<String>,
79 pub session_id: Option<String>,
80 pub authorization_code_id: Option<String>,
81 pub metadata: Value,
82 pub created_at: DateTime<Utc>,
83 pub expires_at: DateTime<Utc>,
84}
85#[derive(Clone, Debug)]
86pub struct CreateOAuthToken {
87 pub token: String,
88 pub client_id: String,
89 pub user_id: Option<String>,
90 pub session_id: Option<String>,
91 pub authorization_code_id: Option<String>,
92 pub metadata: Value,
93 pub created_at: DateTime<Utc>,
94 pub expires_at: DateTime<Utc>,
95}
96#[async_trait]
97pub trait OAuthClientStore: Send + Sync {
98 async fn has_oauth_client_resource(
100 &self,
101 _client_id: &str,
102 _resource_id: &str,
103 ) -> AuthResult<bool> {
104 Err(AuthError::config(
105 "OAuth resource linkage is not supported by this store",
106 ))
107 }
108 async fn persist_oauth_client_registration(
112 &self,
113 _data: PersistOAuthClientRegistration,
114 ) -> Result<Option<OAuthClient>, OAuthClientRegistrationWriteError> {
115 Err(
116 AuthError::config("Atomic OAuth client registration is not supported by this store")
117 .into(),
118 )
119 }
120 async fn claim_oauth_client_assertion(
122 &self,
123 id: &str,
124 expires_at: DateTime<Utc>,
125 ) -> AuthResult<bool> {
126 let _ = (id, expires_at);
127 Err(AuthError::config(
128 "OAuth assertion persistence is not supported by this store",
129 ))
130 }
131
132 async fn store_oauth_refresh_replay(&self, id: &str, ciphertext: &str) -> AuthResult<()> {
134 let _ = (id, ciphertext);
135 Err(AuthError::config(
136 "OAuth token persistence is not supported by this store",
137 ))
138 }
139 async fn claim_oauth_refresh_rotation_by_id(
141 &self,
142 id: &str,
143 at: DateTime<Utc>,
144 replay_expires: Option<DateTime<Utc>>,
145 ) -> AuthResult<bool> {
146 let _ = (id, at, replay_expires);
147 Err(AuthError::config(
148 "OAuth refresh rotation by ID is not supported by this store",
149 ))
150 }
151 async fn claim_oauth_refresh_rotation(
152 &self,
153 _token: &str,
154 _at: DateTime<Utc>,
155 _replay_expires: Option<DateTime<Utc>>,
156 ) -> AuthResult<bool> {
157 Err(AuthError::config(
158 "OAuth token persistence is not supported by this store",
159 ))
160 }
161
162 async fn list_oauth_tokens_for_session(
163 &self,
164 _kind: OAuthTokenKind,
165 _session_id: &str,
166 ) -> AuthResult<Vec<OAuthToken>> {
167 Err(AuthError::config(
168 "OAuth token persistence is not supported by this store",
169 ))
170 }
171 async fn mark_oauth_token_revoked(
172 &self,
173 _kind: OAuthTokenKind,
174 _id: &str,
175 _at: DateTime<Utc>,
176 ) -> AuthResult<()> {
177 Err(AuthError::config(
178 "OAuth token persistence is not supported by this store",
179 ))
180 }
181
182 async fn delete_oauth_token(&self, _kind: OAuthTokenKind, _token: &str) -> AuthResult<()> {
183 Err(AuthError::config(
184 "OAuth token persistence is not supported by this store",
185 ))
186 }
187 async fn revoke_oauth_refresh_token(
189 &self,
190 _token: &str,
191 _revoked_at: DateTime<Utc>,
192 ) -> AuthResult<bool> {
193 Err(AuthError::config(
194 "OAuth token persistence is not supported by this store",
195 ))
196 }
197 async fn delete_oauth_access_tokens_for_refresh(&self, _refresh_id: &str) -> AuthResult<()> {
198 Err(AuthError::config(
199 "OAuth token persistence is not supported by this store",
200 ))
201 }
202 async fn delete_oauth_refresh_family(
203 &self,
204 _client_id: &str,
205 _user_id: &str,
206 ) -> AuthResult<()> {
207 Err(AuthError::config(
208 "OAuth token persistence is not supported by this store",
209 ))
210 }
211 async fn create_oauth_token(
212 &self,
213 _kind: OAuthTokenKind,
214 _data: CreateOAuthToken,
215 ) -> AuthResult<OAuthToken> {
216 Err(AuthError::config(
217 "OAuth token persistence is not supported by this store",
218 ))
219 }
220 async fn get_oauth_token(
221 &self,
222 _kind: OAuthTokenKind,
223 _token: &str,
224 ) -> AuthResult<Option<OAuthToken>> {
225 Err(AuthError::config(
226 "OAuth token persistence is not supported by this store",
227 ))
228 }
229 async fn delete_oauth_tokens_for_code(&self, _code: &str) -> AuthResult<()> {
230 Err(AuthError::config(
231 "OAuth token persistence is not supported by this store",
232 ))
233 }
234
235 async fn delete_oauth_client_resource(
236 &self,
237 _client_id: &str,
238 _resource_id: &str,
239 ) -> AuthResult<()> {
240 Err(AuthError::config(
241 "OAuth resource linkage is not supported by this store",
242 ))
243 }
244 async fn create_oauth_client_resource(
245 &self,
246 _data: CreateOAuthClientResource,
247 ) -> AuthResult<OAuthClientResource> {
248 Err(AuthError::config(
249 "OAuth resource linkage is not supported by this store",
250 ))
251 }
252 async fn delete_oauth_resource(&self, _identifier: &str) -> AuthResult<()> {
253 Err(AuthError::config(
254 "OAuth resource persistence is not supported by this store",
255 ))
256 }
257 async fn update_oauth_resource(
258 &self,
259 _identifier: &str,
260 _metadata: Value,
261 _updated_at: DateTime<Utc>,
262 ) -> AuthResult<Option<OAuthResource>> {
263 Err(AuthError::config(
264 "OAuth resource persistence is not supported by this store",
265 ))
266 }
267 async fn list_oauth_resources(&self) -> AuthResult<Vec<OAuthResource>> {
268 Err(AuthError::config(
269 "OAuth resource persistence is not supported by this store",
270 ))
271 }
272 async fn list_oauth_client_resources(
273 &self,
274 _client_id: &str,
275 ) -> AuthResult<Vec<OAuthClientResource>> {
276 Err(AuthError::config(
277 "OAuth resource linkage is not supported by this store",
278 ))
279 }
280 async fn create_oauth_resource(&self, _data: CreateOAuthResource) -> AuthResult<OAuthResource> {
281 Err(AuthError::config(
282 "OAuth resource persistence is not supported by this store",
283 ))
284 }
285 async fn get_oauth_resource(&self, _identifier: &str) -> AuthResult<Option<OAuthResource>> {
286 Err(AuthError::config(
287 "OAuth resource persistence is not supported by this store",
288 ))
289 }
290 async fn delete_oauth_consent(&self, _id: &str) -> AuthResult<()> {
291 Err(AuthError::config(
292 "OAuth consent persistence is not supported by this store",
293 ))
294 }
295 async fn get_oauth_consent(&self, _id: &str) -> AuthResult<Option<OAuthConsent>> {
296 Err(AuthError::config(
297 "OAuth consent persistence is not supported by this store",
298 ))
299 }
300 async fn create_oauth_consent(&self, _data: CreateOAuthConsent) -> AuthResult<OAuthConsent> {
301 Err(AuthError::config(
302 "OAuth consent persistence is not supported by this store",
303 ))
304 }
305 async fn list_oauth_consents_by_user(&self, _user_id: &str) -> AuthResult<Vec<OAuthConsent>> {
306 Err(AuthError::config(
307 "OAuth consent persistence is not supported by this store",
308 ))
309 }
310 async fn update_oauth_consent_metadata(
311 &self,
312 _id: &str,
313 _metadata: Value,
314 _updated_at: DateTime<Utc>,
315 ) -> AuthResult<Option<OAuthConsent>> {
316 Err(AuthError::config(
317 "OAuth consent persistence is not supported by this store",
318 ))
319 }
320 async fn create_oauth_client(&self, _data: CreateOAuthClient) -> AuthResult<OAuthClient> {
321 Err(AuthError::config(
322 "OAuth client persistence is not supported by this store",
323 ))
324 }
325 async fn get_oauth_client(&self, _client_id: &str) -> AuthResult<Option<OAuthClient>> {
326 Err(AuthError::config(
327 "OAuth client persistence is not supported by this store",
328 ))
329 }
330 async fn list_oauth_clients_by_user(&self, _user_id: &str) -> AuthResult<Vec<OAuthClient>> {
331 Err(AuthError::config(
332 "OAuth client persistence is not supported by this store",
333 ))
334 }
335 async fn delete_oauth_client(&self, _client_id: &str) -> AuthResult<()> {
336 Err(AuthError::config(
337 "OAuth client persistence is not supported by this store",
338 ))
339 }
340 async fn update_oauth_client_secret(
341 &self,
342 _client_id: &str,
343 _secret: String,
344 _updated_at: DateTime<Utc>,
345 ) -> AuthResult<Option<OAuthClient>> {
346 Err(AuthError::config(
347 "OAuth client persistence is not supported by this store",
348 ))
349 }
350 async fn update_oauth_client_metadata(
351 &self,
352 _client_id: &str,
353 _metadata: Value,
354 _updated_at: DateTime<Utc>,
355 ) -> AuthResult<Option<OAuthClient>> {
356 Err(AuthError::config(
357 "OAuth client persistence is not supported by this store",
358 ))
359 }
360}
361
362const CLIENT_FIELDS: &[(&str, &str)] = &[
364 ("client_discovery_id", "clientDiscoveryId"),
365 ("disabled", "disabled"),
366 ("skip_consent", "skipConsent"),
367 ("enable_end_session", "enableEndSession"),
368 ("subject_type", "subjectType"),
369 ("client_credentials_scopes", "clientCredentialsScopes"),
370 ("contacts", "contacts"),
371 ("software_id", "softwareId"),
372 ("software_version", "softwareVersion"),
373 ("software_statement", "softwareStatement"),
374 ("redirect_uris", "redirectUris"),
375 ("post_logout_redirect_uris", "postLogoutRedirectUris"),
376 ("backchannel_logout_uri", "backchannelLogoutUri"),
377 (
378 "backchannel_logout_session_required",
379 "backchannelLogoutSessionRequired",
380 ),
381 ("token_endpoint_auth_method", "tokenEndpointAuthMethod"),
382 ("application_type", "applicationType"),
383 ("jwks", "jwks"),
384 ("jwks_uri", "jwksUri"),
385 ("grant_types", "grantTypes"),
386 ("response_types", "responseTypes"),
387 ("require_pkce", "requirePKCE"),
388 ("dpop_bound_access_tokens", "dpopBoundAccessTokens"),
389 ("reference_id", "referenceId"),
390 ("client_name", "name"),
391 ("client_uri", "uri"),
392 ("logo_uri", "icon"),
393 ("tos_uri", "tos"),
394 ("policy_uri", "policy"),
395 ("scope", "scopes"),
396];
397
398pub fn metadata_to_columns(metadata: Value) -> AuthResult<Value> {
402 let Value::Object(mut metadata) = metadata else {
403 return Err(AuthError::bad_request(
404 "OAuth client metadata must be an object",
405 ));
406 };
407 let mut fields = serde_json::Map::new();
408 let extensions = metadata.remove("metadata");
411 for (rfc, column) in CLIENT_FIELDS {
412 if let Some(mut value) = metadata.remove(*rfc) {
413 if *rfc == "scope"
414 && let Some(scope) = value.as_str()
415 {
416 value = Value::Array(
417 scope
419 .split(' ')
420 .map(|scope| Value::String(scope.into()))
421 .collect(),
422 );
423 }
424 if *rfc == "jwks" && value.is_object() {
425 value = Value::String(serde_json::to_string(&value)?);
426 }
427 _ = fields.insert((*column).into(), value);
428 }
429 }
430 if !fields.contains_key("redirectUris") {
431 _ = fields.insert("redirectUris".into(), serde_json::json!([]));
432 }
433 if let Some(Value::Object(extensions)) = extensions {
434 metadata.extend(extensions);
435 }
436 _ = fields.insert(
437 "metadata".into(),
438 if metadata.is_empty() {
439 Value::Null
440 } else {
441 Value::Object(metadata)
442 },
443 );
444 Ok(Value::Object(fields))
445}
446
447pub fn metadata_from_columns(fields: &Value) -> AuthResult<Value> {
451 let fields = fields
452 .as_object()
453 .ok_or_else(|| AuthError::internal("Stored OAuth client must be an object"))?;
454 let mut metadata = fields
455 .get("metadata")
456 .and_then(Value::as_object)
457 .cloned()
458 .unwrap_or_default();
459 let mut extensions = serde_json::Map::new();
463 for (rfc, column) in CLIENT_FIELDS {
464 if let Some(value) = metadata.remove(*rfc) {
465 _ = extensions.insert((*rfc).into(), value);
466 }
467 if let Some(mut value) = fields
468 .get(*column)
469 .filter(|value| !value.is_null())
470 .cloned()
471 {
472 if *rfc == "scope"
473 && let Some(scopes) = value.as_array()
474 {
475 value = Value::String(
476 scopes
477 .iter()
478 .filter_map(Value::as_str)
479 .collect::<Vec<_>>()
480 .join(" "),
481 );
482 }
483 if *rfc == "jwks"
484 && let Some(jwks) = value.as_str()
485 {
486 value = serde_json::from_str(jwks)?;
487 }
488 _ = metadata.insert((*rfc).into(), value);
489 }
490 }
491 if !extensions.is_empty() {
492 _ = metadata.insert("metadata".into(), Value::Object(extensions));
493 }
494 Ok(Value::Object(metadata))
495}
496
497#[derive(Clone, Debug)]
499pub struct OAuthConsent {
500 pub id: String,
501 pub client_id: String,
502 pub user_id: Option<String>,
503 pub metadata: Value,
504 pub created_at: DateTime<Utc>,
505 pub updated_at: DateTime<Utc>,
506}
507#[derive(Clone, Debug)]
508pub struct CreateOAuthConsent {
509 pub client_id: String,
510 pub user_id: Option<String>,
511 pub metadata: Value,
512 pub created_at: DateTime<Utc>,
513}
514
515#[derive(Clone, Debug)]
516pub struct OAuthResource {
517 pub id: String,
518 pub identifier: String,
519 pub metadata: Value,
520 pub created_at: Option<DateTime<Utc>>,
521 pub updated_at: Option<DateTime<Utc>>,
522}
523#[derive(Clone, Debug)]
524pub struct CreateOAuthResource {
525 pub identifier: String,
526 pub metadata: Value,
527 pub created_at: DateTime<Utc>,
528}
529
530#[derive(Clone, Debug)]
531pub struct OAuthClientResource {
532 pub id: String,
533 pub client_id: String,
534 pub resource_id: String,
535 pub metadata: Value,
536 pub created_at: Option<DateTime<Utc>>,
537}
538
539#[derive(Clone, Debug)]
540pub struct CreateOAuthClientResource {
541 pub client_id: String,
542 pub resource_id: String,
543 pub created_at: DateTime<Utc>,
544}