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,
13 pub created_at: Option<DateTime<Utc>>,
14 pub updated_at: Option<DateTime<Utc>>,
15}
16#[derive(Clone, Debug)]
17pub struct CreateOAuthClient {
18 pub client_id: String,
19 pub user_id: Option<String>,
20 pub client_secret: Option<String>,
21 pub metadata: Value,
22 pub created_at: DateTime<Utc>,
23}
24#[derive(Clone, Copy, Debug)]
26pub enum OAuthTokenKind {
27 Access,
28 Refresh,
29}
30#[derive(Clone, Debug)]
31pub struct OAuthToken {
32 pub id: String,
33 pub token: String,
34 pub client_id: String,
35 pub user_id: Option<String>,
36 pub session_id: Option<String>,
37 pub authorization_code_id: Option<String>,
38 pub metadata: Value,
39 pub created_at: DateTime<Utc>,
40 pub expires_at: DateTime<Utc>,
41}
42#[derive(Clone, Debug)]
43pub struct CreateOAuthToken {
44 pub token: String,
45 pub client_id: String,
46 pub user_id: Option<String>,
47 pub session_id: Option<String>,
48 pub authorization_code_id: Option<String>,
49 pub metadata: Value,
50 pub created_at: DateTime<Utc>,
51 pub expires_at: DateTime<Utc>,
52}
53#[async_trait]
54pub trait OAuthClientStore: Send + Sync {
55 async fn store_oauth_refresh_replay(&self, id: &str, ciphertext: &str) -> AuthResult<()> {
57 let _ = (id, ciphertext);
58 Err(AuthError::config(
59 "OAuth token persistence is not supported by this store",
60 ))
61 }
62 async fn claim_oauth_refresh_rotation(
63 &self,
64 _token: &str,
65 _at: DateTime<Utc>,
66 _replay_expires: Option<DateTime<Utc>>,
67 ) -> AuthResult<bool> {
68 Err(AuthError::config(
69 "OAuth token persistence is not supported by this store",
70 ))
71 }
72
73 async fn list_oauth_tokens_for_session(
74 &self,
75 _kind: OAuthTokenKind,
76 _session_id: &str,
77 ) -> AuthResult<Vec<OAuthToken>> {
78 Err(AuthError::config(
79 "OAuth token persistence is not supported by this store",
80 ))
81 }
82 async fn mark_oauth_token_revoked(
83 &self,
84 _kind: OAuthTokenKind,
85 _id: &str,
86 _at: DateTime<Utc>,
87 ) -> AuthResult<()> {
88 Err(AuthError::config(
89 "OAuth token persistence is not supported by this store",
90 ))
91 }
92
93 async fn delete_oauth_token(&self, _kind: OAuthTokenKind, _token: &str) -> AuthResult<()> {
94 Err(AuthError::config(
95 "OAuth token persistence is not supported by this store",
96 ))
97 }
98 async fn revoke_oauth_refresh_token(
100 &self,
101 _token: &str,
102 _revoked_at: DateTime<Utc>,
103 ) -> AuthResult<bool> {
104 Err(AuthError::config(
105 "OAuth token persistence is not supported by this store",
106 ))
107 }
108 async fn delete_oauth_access_tokens_for_refresh(&self, _refresh_id: &str) -> AuthResult<()> {
109 Err(AuthError::config(
110 "OAuth token persistence is not supported by this store",
111 ))
112 }
113 async fn delete_oauth_refresh_family(
114 &self,
115 _client_id: &str,
116 _user_id: &str,
117 ) -> AuthResult<()> {
118 Err(AuthError::config(
119 "OAuth token persistence is not supported by this store",
120 ))
121 }
122 async fn create_oauth_token(
123 &self,
124 _kind: OAuthTokenKind,
125 _data: CreateOAuthToken,
126 ) -> AuthResult<OAuthToken> {
127 Err(AuthError::config(
128 "OAuth token persistence is not supported by this store",
129 ))
130 }
131 async fn get_oauth_token(
132 &self,
133 _kind: OAuthTokenKind,
134 _token: &str,
135 ) -> AuthResult<Option<OAuthToken>> {
136 Err(AuthError::config(
137 "OAuth token persistence is not supported by this store",
138 ))
139 }
140 async fn delete_oauth_tokens_for_code(&self, _code: &str) -> AuthResult<()> {
141 Err(AuthError::config(
142 "OAuth token persistence is not supported by this store",
143 ))
144 }
145
146 async fn delete_oauth_client_resource(
147 &self,
148 _client_id: &str,
149 _resource_id: &str,
150 ) -> AuthResult<()> {
151 Err(AuthError::config(
152 "OAuth resource linkage is not supported by this store",
153 ))
154 }
155 async fn create_oauth_client_resource(
156 &self,
157 _data: CreateOAuthClientResource,
158 ) -> AuthResult<OAuthClientResource> {
159 Err(AuthError::config(
160 "OAuth resource linkage is not supported by this store",
161 ))
162 }
163 async fn delete_oauth_resource(&self, _identifier: &str) -> AuthResult<()> {
164 Err(AuthError::config(
165 "OAuth resource persistence is not supported by this store",
166 ))
167 }
168 async fn update_oauth_resource(
169 &self,
170 _identifier: &str,
171 _metadata: Value,
172 _updated_at: DateTime<Utc>,
173 ) -> AuthResult<Option<OAuthResource>> {
174 Err(AuthError::config(
175 "OAuth resource persistence is not supported by this store",
176 ))
177 }
178 async fn list_oauth_resources(&self) -> AuthResult<Vec<OAuthResource>> {
179 Err(AuthError::config(
180 "OAuth resource persistence is not supported by this store",
181 ))
182 }
183 async fn list_oauth_client_resources(
184 &self,
185 _client_id: &str,
186 ) -> AuthResult<Vec<OAuthClientResource>> {
187 Err(AuthError::config(
188 "OAuth resource linkage is not supported by this store",
189 ))
190 }
191 async fn create_oauth_resource(&self, _data: CreateOAuthResource) -> AuthResult<OAuthResource> {
192 Err(AuthError::config(
193 "OAuth resource persistence is not supported by this store",
194 ))
195 }
196 async fn get_oauth_resource(&self, _identifier: &str) -> AuthResult<Option<OAuthResource>> {
197 Err(AuthError::config(
198 "OAuth resource persistence is not supported by this store",
199 ))
200 }
201 async fn delete_oauth_consent(&self, _id: &str) -> AuthResult<()> {
202 Err(AuthError::config(
203 "OAuth consent persistence is not supported by this store",
204 ))
205 }
206 async fn get_oauth_consent(&self, _id: &str) -> AuthResult<Option<OAuthConsent>> {
207 Err(AuthError::config(
208 "OAuth consent persistence is not supported by this store",
209 ))
210 }
211 async fn create_oauth_consent(&self, _data: CreateOAuthConsent) -> AuthResult<OAuthConsent> {
212 Err(AuthError::config(
213 "OAuth consent persistence is not supported by this store",
214 ))
215 }
216 async fn list_oauth_consents_by_user(&self, _user_id: &str) -> AuthResult<Vec<OAuthConsent>> {
217 Err(AuthError::config(
218 "OAuth consent persistence is not supported by this store",
219 ))
220 }
221 async fn update_oauth_consent_metadata(
222 &self,
223 _id: &str,
224 _metadata: Value,
225 _updated_at: DateTime<Utc>,
226 ) -> AuthResult<Option<OAuthConsent>> {
227 Err(AuthError::config(
228 "OAuth consent persistence is not supported by this store",
229 ))
230 }
231 async fn create_oauth_client(&self, _data: CreateOAuthClient) -> AuthResult<OAuthClient> {
232 Err(AuthError::config(
233 "OAuth client persistence is not supported by this store",
234 ))
235 }
236 async fn get_oauth_client(&self, _client_id: &str) -> AuthResult<Option<OAuthClient>> {
237 Err(AuthError::config(
238 "OAuth client persistence is not supported by this store",
239 ))
240 }
241 async fn list_oauth_clients_by_user(&self, _user_id: &str) -> AuthResult<Vec<OAuthClient>> {
242 Err(AuthError::config(
243 "OAuth client persistence is not supported by this store",
244 ))
245 }
246 async fn delete_oauth_client(&self, _client_id: &str) -> AuthResult<()> {
247 Err(AuthError::config(
248 "OAuth client persistence is not supported by this store",
249 ))
250 }
251 async fn update_oauth_client_secret(
252 &self,
253 _client_id: &str,
254 _secret: String,
255 _updated_at: DateTime<Utc>,
256 ) -> AuthResult<Option<OAuthClient>> {
257 Err(AuthError::config(
258 "OAuth client persistence is not supported by this store",
259 ))
260 }
261 async fn update_oauth_client_metadata(
262 &self,
263 _client_id: &str,
264 _metadata: Value,
265 _updated_at: DateTime<Utc>,
266 ) -> AuthResult<Option<OAuthClient>> {
267 Err(AuthError::config(
268 "OAuth client persistence is not supported by this store",
269 ))
270 }
271}
272
273const CLIENT_FIELDS: &[(&str, &str)] = &[
275 ("client_discovery_id", "clientDiscoveryId"),
276 ("disabled", "disabled"),
277 ("skip_consent", "skipConsent"),
278 ("enable_end_session", "enableEndSession"),
279 ("subject_type", "subjectType"),
280 ("client_credentials_scopes", "clientCredentialsScopes"),
281 ("contacts", "contacts"),
282 ("software_id", "softwareId"),
283 ("software_version", "softwareVersion"),
284 ("software_statement", "softwareStatement"),
285 ("redirect_uris", "redirectUris"),
286 ("post_logout_redirect_uris", "postLogoutRedirectUris"),
287 ("backchannel_logout_uri", "backchannelLogoutUri"),
288 (
289 "backchannel_logout_session_required",
290 "backchannelLogoutSessionRequired",
291 ),
292 ("token_endpoint_auth_method", "tokenEndpointAuthMethod"),
293 ("application_type", "applicationType"),
294 ("jwks", "jwks"),
295 ("jwks_uri", "jwksUri"),
296 ("grant_types", "grantTypes"),
297 ("response_types", "responseTypes"),
298 ("require_pkce", "requirePKCE"),
299 ("dpop_bound_access_tokens", "dpopBoundAccessTokens"),
300 ("reference_id", "referenceId"),
301 ("client_name", "name"),
302 ("client_uri", "uri"),
303 ("logo_uri", "icon"),
304 ("tos_uri", "tos"),
305 ("policy_uri", "policy"),
306 ("scope", "scopes"),
307];
308
309pub fn metadata_to_columns(metadata: Value) -> AuthResult<Value> {
313 let Value::Object(mut metadata) = metadata else {
314 return Err(AuthError::bad_request(
315 "OAuth client metadata must be an object",
316 ));
317 };
318 let mut fields = serde_json::Map::new();
319 for (rfc, column) in CLIENT_FIELDS {
320 if let Some(mut value) = metadata.remove(*rfc) {
321 if *rfc == "scope"
322 && let Some(scope) = value.as_str()
323 {
324 value = Value::Array(
325 scope
326 .split_whitespace()
327 .map(|scope| Value::String(scope.into()))
328 .collect(),
329 );
330 }
331 if *rfc == "jwks" && value.is_object() {
332 value = Value::String(serde_json::to_string(&value)?);
333 }
334 _ = fields.insert((*column).into(), value);
335 }
336 }
337 if !fields.contains_key("redirectUris") {
338 _ = fields.insert("redirectUris".into(), serde_json::json!([]));
339 }
340 _ = fields.insert(
341 "metadata".into(),
342 if metadata.is_empty() {
343 Value::Null
344 } else {
345 Value::Object(metadata)
346 },
347 );
348 Ok(Value::Object(fields))
349}
350
351pub fn metadata_from_columns(fields: &Value) -> AuthResult<Value> {
355 let fields = fields
356 .as_object()
357 .ok_or_else(|| AuthError::internal("Stored OAuth client must be an object"))?;
358 let mut metadata = fields
359 .get("metadata")
360 .and_then(Value::as_object)
361 .cloned()
362 .unwrap_or_default();
363 for (rfc, column) in CLIENT_FIELDS {
364 if let Some(mut value) = fields
365 .get(*column)
366 .filter(|value| !value.is_null())
367 .cloned()
368 {
369 if *rfc == "scope"
370 && let Some(scopes) = value.as_array()
371 {
372 value = Value::String(
373 scopes
374 .iter()
375 .filter_map(Value::as_str)
376 .collect::<Vec<_>>()
377 .join(" "),
378 );
379 }
380 if *rfc == "jwks"
381 && let Some(jwks) = value.as_str()
382 {
383 value = serde_json::from_str(jwks)?;
384 }
385 _ = metadata.insert((*rfc).into(), value);
386 }
387 }
388 Ok(Value::Object(metadata))
389}
390
391#[derive(Clone, Debug)]
393pub struct OAuthConsent {
394 pub id: String,
395 pub client_id: String,
396 pub user_id: Option<String>,
397 pub metadata: Value,
398 pub created_at: DateTime<Utc>,
399 pub updated_at: DateTime<Utc>,
400}
401#[derive(Clone, Debug)]
402pub struct CreateOAuthConsent {
403 pub client_id: String,
404 pub user_id: Option<String>,
405 pub metadata: Value,
406 pub created_at: DateTime<Utc>,
407}
408
409#[derive(Clone, Debug)]
410pub struct OAuthResource {
411 pub id: String,
412 pub identifier: String,
413 pub metadata: Value,
414 pub created_at: Option<DateTime<Utc>>,
415 pub updated_at: Option<DateTime<Utc>>,
416}
417#[derive(Clone, Debug)]
418pub struct CreateOAuthResource {
419 pub identifier: String,
420 pub metadata: Value,
421 pub created_at: DateTime<Utc>,
422}
423
424#[derive(Clone, Debug)]
425pub struct OAuthClientResource {
426 pub id: String,
427 pub client_id: String,
428 pub resource_id: String,
429 pub metadata: Value,
430 pub created_at: Option<DateTime<Utc>>,
431}
432
433#[derive(Clone, Debug)]
434pub struct CreateOAuthClientResource {
435 pub client_id: String,
436 pub resource_id: String,
437 pub created_at: DateTime<Utc>,
438}