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 pub audience: Option<&'a [String]>,
82}
83
84impl OAuthRefreshTokens {
85 pub async fn insert(
86 conn: &mut PgConnection,
87 params: NewRefreshTokenParams<'_>,
88 ) -> ModelResult<()> {
89 sqlx::query!(
90 r#"
91 INSERT INTO oauth_refresh_tokens
92 (digest, user_id, client_id, scopes, audience, jti, expires_at, revoked, rotated_from, metadata, dpop_jkt)
93 VALUES
94 ($1, $2, $3, $4, $5, gen_random_uuid(), $6, false, $7, $8, $9)
95 "#,
96 params.digest.as_bytes(),
97 params.user_id,
98 params.client_id,
99 params.scopes,
100 params.audience,
101 params.expires_at,
102 params.rotated_from.map(|d| d.as_bytes() as &[u8]),
103 serde_json::Value::Object(params.metadata),
104 params.dpop_jkt
105 )
106 .execute(conn)
107 .await?;
108 Ok(())
109 }
110
111 pub async fn find_valid(
112 conn: &mut PgConnection,
113 digest: Digest,
114 ) -> ModelResult<OAuthRefreshTokens> {
115 let mut tx = conn.begin().await?;
116 let token = sqlx::query_as!(
117 OAuthRefreshTokens,
118 r#"
119 SELECT *
120 FROM oauth_refresh_tokens
121 WHERE digest = $1
122 AND expires_at > now()
123 AND revoked = false
124 "#,
125 digest.as_bytes()
126 )
127 .fetch_one(&mut *tx)
128 .await?;
129 tx.commit().await?;
130 Ok(token)
131 }
132
133 pub async fn find_valid_for_sender(
135 conn: &mut PgConnection,
136 digest: Digest,
137 presented_jkt: Option<&str>,
138 ) -> ModelResult<OAuthRefreshTokens> {
139 let t = Self::find_valid(conn, digest).await?;
140 if let Some(expected) = t.dpop_jkt.as_deref() {
141 let Some(presented) = presented_jkt else {
142 return Err(ModelError::new(
143 ModelErrorType::PreconditionFailed,
144 "refresh token requires DPoP but no JKT presented",
145 None::<anyhow::Error>,
146 ));
147 };
148 if expected != presented {
149 return Err(ModelError::new(
150 ModelErrorType::PreconditionFailed,
151 "DPoP JKT mismatch for refresh token",
152 None::<anyhow::Error>,
153 ));
154 }
155 }
156 Ok(t)
157 }
158
159 pub async fn revoke_by_digest(conn: &mut PgConnection, digest: Digest) -> ModelResult<()> {
160 let mut tx = conn.begin().await?;
161 sqlx::query!(
162 r#"
163 UPDATE oauth_refresh_tokens
164 SET revoked = true
165 WHERE digest = $1
166 "#,
167 digest.as_bytes()
168 )
169 .execute(&mut *tx)
170 .await?;
171 tx.commit().await?;
172 Ok(())
173 }
174
175 pub async fn revoke_all_by_user_client(
176 conn: &mut PgConnection,
177 user_id: Uuid,
178 client_id: Uuid,
179 ) -> ModelResult<()> {
180 let mut tx = conn.begin().await?;
181 sqlx::query!(
182 r#"
183 UPDATE oauth_refresh_tokens
184 SET revoked = true
185 WHERE user_id = $1 AND client_id = $2
186 "#,
187 user_id,
188 client_id
189 )
190 .execute(&mut *tx)
191 .await?;
192 tx.commit().await?;
193 Ok(())
194 }
195
196 pub async fn find_any_by_digest(
200 conn: &mut PgConnection,
201 digest: Digest,
202 client_id: Uuid,
203 ) -> ModelResult<Option<OAuthRefreshTokens>> {
204 let row = sqlx::query_as!(
205 OAuthRefreshTokens,
206 r#"
207 SELECT *
208 FROM oauth_refresh_tokens
209 WHERE digest = $1
210 AND client_id = $2
211 "#,
212 digest.as_bytes(),
213 client_id
214 )
215 .fetch_optional(conn)
216 .await?;
217 Ok(row)
218 }
219
220 pub async fn revoke_grant(
230 conn: &mut PgConnection,
231 user_id: Uuid,
232 client_id: Uuid,
233 ) -> ModelResult<Vec<Digest>> {
234 let mut tx = conn.begin().await?;
235 let digests = Self::revoke_grant_in_transaction(&mut tx, user_id, client_id).await?;
236 tx.commit().await?;
237 Ok(digests)
238 }
239
240 pub async fn revoke_all_grants_of_user_in_transaction(
243 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
244 user_id: Uuid,
245 ) -> ModelResult<Vec<Digest>> {
246 sqlx::query!(
247 r#"UPDATE oauth_refresh_tokens SET revoked = true WHERE user_id = $1"#,
248 user_id
249 )
250 .execute(&mut **tx)
251 .await?;
252
253 let deleted_digests = sqlx::query_scalar!(
254 r#"DELETE FROM oauth_access_tokens WHERE user_id = $1 RETURNING digest"#,
255 user_id
256 )
257 .fetch_all(&mut **tx)
258 .await?;
259
260 sqlx::query!(
261 r#"DELETE FROM oauth_auth_codes WHERE user_id = $1"#,
262 user_id
263 )
264 .execute(&mut **tx)
265 .await?;
266
267 Ok(deleted_digests)
268 }
269
270 pub async fn revoke_grant_in_transaction(
272 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
273 user_id: Uuid,
274 client_id: Uuid,
275 ) -> ModelResult<Vec<Digest>> {
276 sqlx::query!(
277 r#"
278 UPDATE oauth_refresh_tokens
279 SET revoked = true
280 WHERE user_id = $1 AND client_id = $2
281 "#,
282 user_id,
283 client_id
284 )
285 .execute(&mut **tx)
286 .await?;
287
288 let deleted_digests = sqlx::query_scalar!(
289 r#"DELETE FROM oauth_access_tokens WHERE user_id = $1 AND client_id = $2 RETURNING digest"#,
290 user_id,
291 client_id
292 )
293 .fetch_all(&mut **tx)
294 .await?;
295
296 sqlx::query!(
297 r#"DELETE FROM oauth_auth_codes WHERE user_id = $1 AND client_id = $2"#,
298 user_id,
299 client_id
300 )
301 .execute(&mut **tx)
302 .await?;
303
304 Ok(deleted_digests)
305 }
306
307 pub async fn consume_in_transaction(
315 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
316 digest: Digest,
317 client_id: Uuid,
318 ) -> ModelResult<OAuthRefreshTokens> {
319 let row = sqlx::query_as!(
320 OAuthRefreshTokens,
321 r#"
322 UPDATE oauth_refresh_tokens
323 SET revoked = true
324 WHERE digest = $1
325 AND client_id = $2
326 AND revoked = false
327 AND expires_at > now()
328 RETURNING *
329 "#,
330 digest.as_bytes(),
331 client_id
332 )
333 .fetch_one(&mut **tx)
334 .await?;
335 Ok(row)
336 }
337
338 pub async fn complete_refresh_token_rotation_in_transaction(
346 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
347 old_token: &OAuthRefreshTokens,
348 params: RotateRefreshTokenParams<'_>,
349 ) -> ModelResult<()> {
350 sqlx::query!(
352 r#"
353 UPDATE oauth_refresh_tokens
354 SET revoked = true
355 WHERE user_id = $1 AND client_id = $2
356 "#,
357 old_token.user_id,
358 old_token.client_id
359 )
360 .execute(&mut **tx)
361 .await?;
362
363 sqlx::query!(
365 r#"
366 INSERT INTO oauth_refresh_tokens
367 (digest, user_id, client_id, scopes, audience, jti, expires_at, revoked, rotated_from, metadata, dpop_jkt)
368 VALUES
369 ($1, $2, $3, $4, $5, gen_random_uuid(), $6, false, $7, $8, $9)
370 "#,
371 params.new_refresh_token_digest.as_bytes(),
372 old_token.user_id,
373 old_token.client_id,
374 params.scopes,
375 old_token.audience.as_deref(),
376 params.refresh_token_expires_at,
377 old_token.digest.as_bytes(),
378 serde_json::Value::Object(serde_json::Map::new()),
379 params.refresh_token_dpop_jkt
380 )
381 .execute(&mut **tx)
382 .await?;
383
384 OAuthAccessToken::insert(
386 tx,
387 NewAccessTokenParams {
388 digest: params.new_access_token_digest,
389 user_id: Some(old_token.user_id),
390 client_id: old_token.client_id,
391 scopes: params.scopes,
392 audience: old_token.audience.as_deref(),
393 token_type: params.access_token_type,
394 dpop_jkt: params.access_token_dpop_jkt,
395 metadata: serde_json::Map::new(),
396 expires_at: params.access_token_expires_at,
397 },
398 )
399 .await?;
400
401 Ok(())
402 }
403
404 pub async fn issue_tokens_from_auth_code_in_transaction(
412 tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
413 params: IssueTokensFromAuthCodeParams<'_>,
414 ) -> ModelResult<()> {
415 OAuthAccessToken::insert(
417 tx,
418 NewAccessTokenParams {
419 digest: params.access_token_digest,
420 user_id: Some(params.user_id),
421 client_id: params.client_id,
422 scopes: params.scopes,
423 audience: params.audience,
424 token_type: params.access_token_type,
425 dpop_jkt: params.access_token_dpop_jkt,
426 metadata: serde_json::Map::new(),
427 expires_at: params.access_token_expires_at,
428 },
429 )
430 .await?;
431
432 sqlx::query!(
434 r#"
435 UPDATE oauth_refresh_tokens
436 SET revoked = true
437 WHERE user_id = $1 AND client_id = $2
438 "#,
439 params.user_id,
440 params.client_id
441 )
442 .execute(&mut **tx)
443 .await?;
444
445 sqlx::query!(
447 r#"
448 INSERT INTO oauth_refresh_tokens
449 (digest, user_id, client_id, scopes, audience, jti, expires_at, revoked, rotated_from, metadata, dpop_jkt)
450 VALUES
451 ($1, $2, $3, $4, $5, gen_random_uuid(), $6, false, NULL, $7, $8)
452 "#,
453 params.refresh_token_digest.as_bytes(),
454 params.user_id,
455 params.client_id,
456 params.scopes,
457 params.audience,
458 params.refresh_token_expires_at,
459 serde_json::Value::Object(serde_json::Map::new()),
460 params.refresh_token_dpop_jkt
461 )
462 .execute(&mut **tx)
463 .await?;
464
465 Ok(())
466 }
467}