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 async fn replace_oauth_client_jwks(
363 &self,
364 _client_id: &str,
365 _expected_jwks: &str,
366 _jwks: String,
367 _updated_at: DateTime<Utc>,
368 ) -> AuthResult<Option<OAuthClient>> {
369 Err(AuthError::config(
370 "OAuth client persistence is not supported by this store",
371 ))
372 }
373}
374
375const CLIENT_FIELDS: &[(&str, &str)] = &[
377 ("client_discovery_id", "clientDiscoveryId"),
378 ("disabled", "disabled"),
379 ("skip_consent", "skipConsent"),
380 ("enable_end_session", "enableEndSession"),
381 ("subject_type", "subjectType"),
382 ("client_credentials_scopes", "clientCredentialsScopes"),
383 ("contacts", "contacts"),
384 ("software_id", "softwareId"),
385 ("software_version", "softwareVersion"),
386 ("software_statement", "softwareStatement"),
387 ("redirect_uris", "redirectUris"),
388 ("post_logout_redirect_uris", "postLogoutRedirectUris"),
389 ("backchannel_logout_uri", "backchannelLogoutUri"),
390 (
391 "backchannel_logout_session_required",
392 "backchannelLogoutSessionRequired",
393 ),
394 ("token_endpoint_auth_method", "tokenEndpointAuthMethod"),
395 ("application_type", "applicationType"),
396 ("jwks", "jwks"),
397 ("jwks_uri", "jwksUri"),
398 ("grant_types", "grantTypes"),
399 ("response_types", "responseTypes"),
400 ("require_pkce", "requirePKCE"),
401 ("dpop_bound_access_tokens", "dpopBoundAccessTokens"),
402 ("reference_id", "referenceId"),
403 ("client_name", "name"),
404 ("client_uri", "uri"),
405 ("logo_uri", "icon"),
406 ("tos_uri", "tos"),
407 ("policy_uri", "policy"),
408 ("scope", "scopes"),
409];
410
411pub fn metadata_to_columns(metadata: Value) -> AuthResult<Value> {
415 let Value::Object(mut metadata) = metadata else {
416 return Err(AuthError::bad_request(
417 "OAuth client metadata must be an object",
418 ));
419 };
420 let mut fields = serde_json::Map::new();
421 let extensions = metadata.remove("metadata");
424 for (rfc, column) in CLIENT_FIELDS {
425 if let Some(mut value) = metadata.remove(*rfc) {
426 if *rfc == "scope"
427 && let Some(scope) = value.as_str()
428 {
429 value = Value::Array(
430 scope
432 .split(' ')
433 .map(|scope| Value::String(scope.into()))
434 .collect(),
435 );
436 }
437 if *rfc == "jwks" && value.is_object() {
438 value = Value::String(serde_json::to_string(&value)?);
439 }
440 _ = fields.insert((*column).into(), value);
441 }
442 }
443 if !fields.contains_key("redirectUris") {
444 _ = fields.insert("redirectUris".into(), serde_json::json!([]));
445 }
446 if let Some(Value::Object(extensions)) = extensions {
447 metadata.extend(extensions);
448 }
449 _ = fields.insert(
450 "metadata".into(),
451 if metadata.is_empty() {
452 Value::Null
453 } else {
454 Value::Object(metadata)
455 },
456 );
457 Ok(Value::Object(fields))
458}
459
460pub fn metadata_from_columns(fields: &Value) -> AuthResult<Value> {
464 let fields = fields
465 .as_object()
466 .ok_or_else(|| AuthError::internal("Stored OAuth client must be an object"))?;
467 let mut metadata = fields
468 .get("metadata")
469 .and_then(Value::as_object)
470 .cloned()
471 .unwrap_or_default();
472 let mut extensions = serde_json::Map::new();
476 for (rfc, column) in CLIENT_FIELDS {
477 if let Some(value) = metadata.remove(*rfc) {
478 _ = extensions.insert((*rfc).into(), value);
479 }
480 if let Some(mut value) = fields
481 .get(*column)
482 .filter(|value| !value.is_null())
483 .cloned()
484 {
485 if *rfc == "scope"
486 && let Some(scopes) = value.as_array()
487 {
488 value = Value::String(
489 scopes
490 .iter()
491 .filter_map(Value::as_str)
492 .collect::<Vec<_>>()
493 .join(" "),
494 );
495 }
496 if *rfc == "jwks"
497 && let Some(jwks) = value.as_str()
498 {
499 value = serde_json::from_str(jwks)?;
500 }
501 _ = metadata.insert((*rfc).into(), value);
502 }
503 }
504 if !extensions.is_empty() {
505 _ = metadata.insert("metadata".into(), Value::Object(extensions));
506 }
507 Ok(Value::Object(metadata))
508}
509
510#[derive(Clone, Debug)]
512pub struct OAuthConsent {
513 pub id: String,
514 pub client_id: String,
515 pub user_id: Option<String>,
516 pub metadata: Value,
517 pub created_at: DateTime<Utc>,
518 pub updated_at: DateTime<Utc>,
519}
520#[derive(Clone, Debug)]
521pub struct CreateOAuthConsent {
522 pub client_id: String,
523 pub user_id: Option<String>,
524 pub metadata: Value,
525 pub created_at: DateTime<Utc>,
526}
527
528#[derive(Clone, Debug)]
529pub struct OAuthResource {
530 pub id: String,
531 pub identifier: String,
532 pub metadata: Value,
533 pub created_at: Option<DateTime<Utc>>,
534 pub updated_at: Option<DateTime<Utc>>,
535}
536#[derive(Clone, Debug)]
537pub struct CreateOAuthResource {
538 pub identifier: String,
539 pub metadata: Value,
540 pub created_at: DateTime<Utc>,
541}
542
543#[derive(Clone, Debug)]
544pub struct OAuthClientResource {
545 pub id: String,
546 pub client_id: String,
547 pub resource_id: String,
548 pub metadata: Value,
549 pub created_at: Option<DateTime<Utc>>,
550}
551
552#[derive(Clone, Debug)]
553pub struct CreateOAuthClientResource {
554 pub client_id: String,
555 pub resource_id: String,
556 pub created_at: DateTime<Utc>,
557}