1use crate::oauth_access_token::{NewAccessTokenParams, OAuthAccessToken, TokenType};
2use crate::{library::oauth::Digest, prelude::*};
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use sqlx::{FromRow, PgConnection};
6use uuid::Uuid;
7
8#[derive(Debug, Serialize, Deserialize, FromRow)]
21pub struct OAuthRefreshTokens {
22 pub digest: Digest,
23 pub user_id: Uuid,
24 pub client_id: Uuid,
25 pub expires_at: DateTime<Utc>,
26 pub scopes: Vec<String>,
27 pub audience: Option<Vec<String>>,
28 pub jti: Uuid,
29 pub dpop_jkt: Option<String>,
31 pub metadata: serde_json::Value,
32 pub revoked: bool,
33 pub rotated_from: Option<Digest>,
34 pub created_at: DateTime<Utc>,
35 pub updated_at: DateTime<Utc>,
36}
37
38#[derive(Debug, Clone)]
39pub struct NewRefreshTokenParams<'a> {
40 pub digest: &'a Digest,
41 pub user_id: Uuid,
42 pub client_id: Uuid,
43 pub scopes: &'a [String],
44 pub audience: Option<&'a [String]>,
45 pub expires_at: DateTime<Utc>,
46 pub rotated_from: Option<&'a Digest>,
47 pub metadata: serde_json::Map<String, serde_json::Value>,
48 pub dpop_jkt: Option<&'a str>,
50}
51
52#[derive(Debug)]
54pub struct RotateRefreshTokenParams<'a> {
55 pub new_refresh_token_digest: &'a Digest,
56 pub new_access_token_digest: &'a Digest,
57 pub access_token_expires_at: DateTime<Utc>,
58 pub refresh_token_expires_at: DateTime<Utc>,
59 pub access_token_type: TokenType,
60 pub access_token_dpop_jkt: Option<&'a str>,
61 pub refresh_token_dpop_jkt: Option<&'a str>,
62 pub scopes: &'a [String],
65}
66
67#[derive(Debug, Clone)]
69pub struct IssueTokensFromAuthCodeParams<'a> {
70 pub user_id: Uuid,
71 pub client_id: Uuid,
72 pub scopes: &'a [String],
73 pub access_token_digest: &'a Digest,
74 pub refresh_token_digest: &'a Digest,
75 pub access_token_expires_at: DateTime<Utc>,
76 pub refresh_token_expires_at: DateTime<Utc>,
77 pub access_token_type: TokenType,
78 pub access_token_dpop_jkt: Option<&'a str>,
79 pub refresh_token_dpop_jkt: Option<&'a str>,
80}
81
82impl OAuthRefreshTokens {
83 pub async fn insert(
84 conn: &mut PgConnection,
85 params: NewRefreshTokenParams<'_>,
86 ) -> ModelResult<()> {
87 sqlx::query!(
88 r#"
89 INSERT INTO oauth_refresh_tokens
90 (digest, user_id, client_id, scopes, audience, jti, expires_at, revoked, rotated_from, metadata, dpop_jkt)
91 VALUES
92 ($1, $2, $3, $4, $5, gen_random_uuid(), $6, false, $7, $8, $9)
93 "#,
94 params.digest.as_bytes(),
95 params.user_id,
96 params.client_id,
97 params.scopes,
98 params.audience,
99 params.expires_at,
100 params.rotated_from.map(|d| d.as_bytes() as &[u8]),
101 serde_json::Value::Object(params.metadata),
102 params.dpop_jkt
103 )
104 .execute(conn)
105 .await?;
106 Ok(())
107 }
108
109 pub async fn find_valid(
110 conn: &mut PgConnection,
111 digest: Digest,
112 ) -> ModelResult<OAuthRefreshTokens> {
113 let mut tx = conn.begin().await?;
114 let token = sqlx::query_as!(
115 OAuthRefreshTokens,
116 r#"
117 SELECT *
118 FROM oauth_refresh_tokens
119 WHERE digest = $1
120 AND expires_at > now()
121 AND revoked = false
122 "#,
123 digest.as_bytes()
124 )
125 .fetch_one(&mut *tx)
126 .await?;
127 tx.commit().await?;
128 Ok(token)
129 }
130
131 pub async fn find_valid_for_sender(
133 conn: &mut PgConnection,
134 digest: Digest,
135 presented_jkt: Option<&str>,
136 ) -> ModelResult<OAuthRefreshTokens> {
137 let t = Self::find_valid(conn, digest).await?;
138 if let Some(expected) = t.dpop_jkt.as_deref() {
139 let Some(presented) = presented_jkt else {
140 return Err(ModelError::new(
141 ModelErrorType::PreconditionFailed,
142 "refresh token requires DPoP but no JKT presented",
143 None::<anyhow::Error>,
144 ));
145 };
146 if expected != presented {
147 return Err(ModelError::new(
148 ModelErrorType::PreconditionFailed,
149 "DPoP JKT mismatch for refresh token",
150 None::<anyhow::Error>,
151 ));
152 }
153 }
154 Ok(t)
155 }
156
157 pub async fn revoke_by_digest(conn: &mut PgConnection, digest: Digest) -> ModelResult<()> {
158 let mut tx = conn.begin().await?;
159 sqlx::query!(
160 r#"
161 UPDATE oauth_refresh_tokens
162 SET revoked = true
163 WHERE digest = $1
164 "#,
165 digest.as_bytes()
166 )
167 .execute(&mut *tx)
168 .await?;
169 tx.commit().await?;
170 Ok(())
171 }
172
173 pub async fn revoke_all_by_user_client(
174 conn: &mut PgConnection,
175 user_id: Uuid,
176 client_id: Uuid,
177 ) -> ModelResult<()> {
178 let mut tx = conn.begin().await?;
179 sqlx::query!(
180 r#"
181 UPDATE oauth_refresh_tokens
182 SET revoked = true
183 WHERE user_id = $1 AND client_id = $2
184 "#,
185 user_id,
186 client_id
187 )
188 .execute(&mut *tx)
189 .await?;
190 tx.commit().await?;
191 Ok(())
192 }
193
194 pub async fn find_any_by_digest(
198 conn: &mut PgConnection,
199 digest: Digest,
200 client_id: Uuid,
201 ) -> ModelResult<Option<OAuthRefreshTokens>> {
202 let row = sqlx::query_as!(
203 OAuthRefreshTokens,
204 r#"
205 SELECT *
206 FROM oauth_refresh_tokens
207 WHERE digest = $1
208 AND client_id = $2
209 "#,
210 digest.as_bytes(),
211 client_id
212 )
213 .fetch_optional(conn)
214 .await?;
215 Ok(row)
216 }
217
218 pub async fn revoke_grant(
228 conn: &mut PgConnection,
229 user_id: Uuid,
230 client_id: Uuid,
231 ) -> ModelResult<Vec<Digest>> {
232 let mut tx = conn.begin().await?;
233 let digests = Self::revoke_grant_in_transaction(&mut tx, user_id, client_id).await?;
234 tx.commit().await?;
235 Ok(digests)
236 }
237
238 pub async fn revoke_all_grants_of_user_in_transaction(
241 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
242 user_id: Uuid,
243 ) -> ModelResult<Vec<Digest>> {
244 sqlx::query!(
245 r#"UPDATE oauth_refresh_tokens SET revoked = true WHERE user_id = $1"#,
246 user_id
247 )
248 .execute(&mut **tx)
249 .await?;
250
251 let deleted_digests = sqlx::query_scalar!(
252 r#"DELETE FROM oauth_access_tokens WHERE user_id = $1 RETURNING digest"#,
253 user_id
254 )
255 .fetch_all(&mut **tx)
256 .await?;
257
258 sqlx::query!(
259 r#"DELETE FROM oauth_auth_codes WHERE user_id = $1"#,
260 user_id
261 )
262 .execute(&mut **tx)
263 .await?;
264
265 Ok(deleted_digests)
266 }
267
268 pub async fn revoke_grant_in_transaction(
270 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
271 user_id: Uuid,
272 client_id: Uuid,
273 ) -> ModelResult<Vec<Digest>> {
274 sqlx::query!(
275 r#"
276 UPDATE oauth_refresh_tokens
277 SET revoked = true
278 WHERE user_id = $1 AND client_id = $2
279 "#,
280 user_id,
281 client_id
282 )
283 .execute(&mut **tx)
284 .await?;
285
286 let deleted_digests = sqlx::query_scalar!(
287 r#"DELETE FROM oauth_access_tokens WHERE user_id = $1 AND client_id = $2 RETURNING digest"#,
288 user_id,
289 client_id
290 )
291 .fetch_all(&mut **tx)
292 .await?;
293
294 sqlx::query!(
295 r#"DELETE FROM oauth_auth_codes WHERE user_id = $1 AND client_id = $2"#,
296 user_id,
297 client_id
298 )
299 .execute(&mut **tx)
300 .await?;
301
302 Ok(deleted_digests)
303 }
304
305 pub async fn consume_in_transaction(
313 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
314 digest: Digest,
315 client_id: Uuid,
316 ) -> ModelResult<OAuthRefreshTokens> {
317 let row = sqlx::query_as!(
318 OAuthRefreshTokens,
319 r#"
320 UPDATE oauth_refresh_tokens
321 SET revoked = true
322 WHERE digest = $1
323 AND client_id = $2
324 AND revoked = false
325 AND expires_at > now()
326 RETURNING *
327 "#,
328 digest.as_bytes(),
329 client_id
330 )
331 .fetch_one(&mut **tx)
332 .await?;
333 Ok(row)
334 }
335
336 pub async fn complete_refresh_token_rotation_in_transaction(
344 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
345 old_token: &OAuthRefreshTokens,
346 params: RotateRefreshTokenParams<'_>,
347 ) -> ModelResult<()> {
348 sqlx::query!(
350 r#"
351 UPDATE oauth_refresh_tokens
352 SET revoked = true
353 WHERE user_id = $1 AND client_id = $2
354 "#,
355 old_token.user_id,
356 old_token.client_id
357 )
358 .execute(&mut **tx)
359 .await?;
360
361 sqlx::query!(
363 r#"
364 INSERT INTO oauth_refresh_tokens
365 (digest, user_id, client_id, scopes, audience, jti, expires_at, revoked, rotated_from, metadata, dpop_jkt)
366 VALUES
367 ($1, $2, $3, $4, $5, gen_random_uuid(), $6, false, $7, $8, $9)
368 "#,
369 params.new_refresh_token_digest.as_bytes(),
370 old_token.user_id,
371 old_token.client_id,
372 params.scopes,
373 old_token.audience.as_deref(),
374 params.refresh_token_expires_at,
375 old_token.digest.as_bytes(),
376 serde_json::Value::Object(serde_json::Map::new()),
377 params.refresh_token_dpop_jkt
378 )
379 .execute(&mut **tx)
380 .await?;
381
382 OAuthAccessToken::insert(
384 tx,
385 NewAccessTokenParams {
386 digest: params.new_access_token_digest,
387 user_id: Some(old_token.user_id),
388 client_id: old_token.client_id,
389 scopes: params.scopes,
390 audience: old_token.audience.as_deref(),
391 token_type: params.access_token_type,
392 dpop_jkt: params.access_token_dpop_jkt,
393 metadata: serde_json::Map::new(),
394 expires_at: params.access_token_expires_at,
395 },
396 )
397 .await?;
398
399 Ok(())
400 }
401
402 pub async fn issue_tokens_from_auth_code_in_transaction(
410 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
411 params: IssueTokensFromAuthCodeParams<'_>,
412 ) -> ModelResult<()> {
413 OAuthAccessToken::insert(
415 tx,
416 NewAccessTokenParams {
417 digest: params.access_token_digest,
418 user_id: Some(params.user_id),
419 client_id: params.client_id,
420 scopes: params.scopes,
421 audience: None,
422 token_type: params.access_token_type,
423 dpop_jkt: params.access_token_dpop_jkt,
424 metadata: serde_json::Map::new(),
425 expires_at: params.access_token_expires_at,
426 },
427 )
428 .await?;
429
430 sqlx::query!(
432 r#"
433 UPDATE oauth_refresh_tokens
434 SET revoked = true
435 WHERE user_id = $1 AND client_id = $2
436 "#,
437 params.user_id,
438 params.client_id
439 )
440 .execute(&mut **tx)
441 .await?;
442
443 sqlx::query!(
445 r#"
446 INSERT INTO oauth_refresh_tokens
447 (digest, user_id, client_id, scopes, audience, jti, expires_at, revoked, rotated_from, metadata, dpop_jkt)
448 VALUES
449 ($1, $2, $3, $4, $5, gen_random_uuid(), $6, false, NULL, $7, $8)
450 "#,
451 params.refresh_token_digest.as_bytes(),
452 params.user_id,
453 params.client_id,
454 params.scopes,
455 Option::<Vec<String>>::None as Option<Vec<String>>,
456 params.refresh_token_expires_at,
457 serde_json::Value::Object(serde_json::Map::new()),
458 params.refresh_token_dpop_jkt
459 )
460 .execute(&mut **tx)
461 .await?;
462
463 Ok(())
464 }
465}