1use base64ct::{Base64UrlUnpadded, Encoding};
2use chrono::{DateTime, Utc};
3use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
4use rand::TryRngCore;
5use rand::rngs::OsRng;
6use serde::Serialize;
7use sha2::{Digest, Sha256};
8
9use crate::applications::Application;
10use crate::authorization::hash_authorization_code;
11use crate::db::Db;
12use crate::error::AuthError;
13use crate::signing_keys::SigningKey;
14use crate::types::{ApplicationId, AuthorizationCodeId, RefreshTokenId, TokenHash, UserId};
15
16#[derive(Debug)]
26pub enum TokenError {
27 InvalidRequest(String),
28 InvalidClient(String),
29 InvalidGrant(String),
30 UnsupportedGrantType,
31 ServerError(String),
32}
33
34#[derive(Debug, Serialize)]
40pub struct TokenResponse {
41 pub access_token: String,
42 pub token_type: &'static str,
43 pub expires_in: i64,
44 pub refresh_token: String,
45 pub id_token: String,
46}
47
48#[derive(Debug, Clone, sqlx::FromRow)]
53pub struct RefreshToken {
54 pub id: RefreshTokenId,
55 pub application_id: ApplicationId,
56 pub user_id: UserId,
57 pub token_hash: TokenHash,
58 pub scopes: String,
59 pub authorization_code_id: Option<AuthorizationCodeId>,
60 pub expires_at: DateTime<Utc>,
61 pub revoked_at: Option<DateTime<Utc>>,
62 pub created_at: DateTime<Utc>,
63}
64
65#[derive(Debug, Serialize)]
70struct AccessTokenJwtClaims {
71 sub: String,
72 iss: String,
73 aud: String,
74 exp: i64,
75 iat: i64,
76 scope: String,
77 email: String,
78 email_verified: bool,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 username: Option<String>,
81 roles: Vec<String>,
82 permissions: Vec<String>,
83}
84
85#[derive(Debug, Serialize)]
86struct IdTokenJwtClaims {
87 sub: String,
88 iss: String,
89 aud: String,
90 exp: i64,
91 iat: i64,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 nonce: Option<String>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 at_hash: Option<String>,
96 auth_time: i64,
97}
98
99pub fn verify_pkce_s256(code_verifier: &str, code_challenge: &str) -> bool {
107 let digest = Sha256::digest(code_verifier.as_bytes());
108 let computed = Base64UrlUnpadded::encode_string(&digest);
109 computed == code_challenge
110}
111
112pub fn compute_at_hash(access_token_jwt: &str) -> String {
120 let digest = Sha256::digest(access_token_jwt.as_bytes());
121 Base64UrlUnpadded::encode_string(&digest[..16])
122}
123
124#[allow(clippy::too_many_arguments)]
134pub fn mint_access_token(
135 sub: UserId,
136 issuer: &str,
137 audience: &str,
138 scope: &str,
139 kid: &str,
140 private_key_pem: &str,
141 ttl_secs: i64,
142 email: &str,
143 email_verified: bool,
144 username: Option<&str>,
145 roles: &[String],
146 permissions: &[String],
147) -> Result<String, AuthError> {
148 let now = Utc::now().timestamp();
149 let claims = AccessTokenJwtClaims {
150 sub: sub.to_string(),
151 iss: issuer.to_owned(),
152 aud: audience.to_owned(),
153 exp: now + ttl_secs,
154 iat: now,
155 scope: scope.to_owned(),
156 email: email.to_owned(),
157 email_verified,
158 username: username.map(|u| u.to_owned()),
159 roles: roles.to_vec(),
160 permissions: permissions.to_vec(),
161 };
162 let mut header = Header::new(Algorithm::RS256);
163 header.kid = Some(kid.to_owned());
164 header.typ = Some("at+jwt".into());
165
166 let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes())
167 .map_err(|e| AuthError::SigningKey(e.to_string()))?;
168 encode(&header, &claims, &key).map_err(|e| AuthError::SigningKey(e.to_string()))
169}
170
171#[allow(clippy::too_many_arguments)]
176pub fn mint_id_token(
177 sub: UserId,
178 issuer: &str,
179 audience: &str,
180 nonce: Option<&str>,
181 at_hash: &str,
182 auth_time: i64,
183 kid: &str,
184 private_key_pem: &str,
185 ttl_secs: i64,
186) -> Result<String, AuthError> {
187 let now = Utc::now().timestamp();
188 let claims = IdTokenJwtClaims {
189 sub: sub.to_string(),
190 iss: issuer.to_owned(),
191 aud: audience.to_owned(),
192 exp: now + ttl_secs,
193 iat: now,
194 nonce: nonce.map(|s| s.to_owned()),
195 at_hash: Some(at_hash.to_owned()),
196 auth_time,
197 };
198 let mut header = Header::new(Algorithm::RS256);
199 header.kid = Some(kid.to_owned());
200 header.typ = Some("JWT".into());
201
202 let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes())
203 .map_err(|e| AuthError::SigningKey(e.to_string()))?;
204 encode(&header, &claims, &key).map_err(|e| AuthError::SigningKey(e.to_string()))
205}
206
207pub fn generate_refresh_token() -> String {
213 let mut bytes = [0u8; 32];
214 OsRng
215 .try_fill_bytes(&mut bytes)
216 .expect("OS RNG unavailable");
217 Base64UrlUnpadded::encode_string(&bytes)
218}
219
220pub fn hash_refresh_token(raw: &str) -> TokenHash {
222 let digest = Sha256::digest(raw.as_bytes());
223 TokenHash::new_unchecked(format!("{digest:x}"))
224}
225
226impl Db {
231 pub async fn create_refresh_token(
233 &self,
234 application_id: ApplicationId,
235 user_id: UserId,
236 token_hash: &TokenHash,
237 scopes: &[String],
238 authorization_code_id: Option<AuthorizationCodeId>,
239 ) -> Result<RefreshToken, AuthError> {
240 let id = RefreshTokenId::new();
241 let scopes_json = serde_json::to_string(scopes).expect("Vec<String> serializes to JSON");
242 let now = Utc::now();
243 let now_str = now.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
244 let expires_at = now + chrono::Duration::days(30);
245 let expires_str = expires_at.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
246
247 sqlx::query(
248 "INSERT INTO allowthem_refresh_tokens \
249 (id, application_id, user_id, token_hash, scopes, \
250 authorization_code_id, expires_at, created_at) \
251 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
252 )
253 .bind(id)
254 .bind(application_id)
255 .bind(user_id)
256 .bind(token_hash)
257 .bind(&scopes_json)
258 .bind(authorization_code_id)
259 .bind(&expires_str)
260 .bind(&now_str)
261 .execute(self.pool())
262 .await?;
263
264 sqlx::query_as::<_, RefreshToken>(
265 "SELECT id, application_id, user_id, token_hash, scopes, \
266 authorization_code_id, expires_at, revoked_at, created_at \
267 FROM allowthem_refresh_tokens WHERE id = ?",
268 )
269 .bind(id)
270 .fetch_one(self.pool())
271 .await
272 .map_err(AuthError::Database)
273 }
274
275 pub async fn revoke_refresh_tokens_by_auth_code(
279 &self,
280 authorization_code_id: AuthorizationCodeId,
281 ) -> Result<u64, AuthError> {
282 let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
283 let result = sqlx::query(
284 "UPDATE allowthem_refresh_tokens \
285 SET revoked_at = ? \
286 WHERE authorization_code_id = ? AND revoked_at IS NULL",
287 )
288 .bind(&now)
289 .bind(authorization_code_id)
290 .execute(self.pool())
291 .await?;
292
293 Ok(result.rows_affected())
294 }
295
296 pub async fn get_refresh_token_by_hash(
301 &self,
302 token_hash: &TokenHash,
303 ) -> Result<Option<RefreshToken>, AuthError> {
304 sqlx::query_as::<_, RefreshToken>(
305 "SELECT id, application_id, user_id, token_hash, scopes, \
306 authorization_code_id, expires_at, revoked_at, created_at \
307 FROM allowthem_refresh_tokens WHERE token_hash = ?",
308 )
309 .bind(token_hash)
310 .fetch_optional(self.pool())
311 .await
312 .map_err(AuthError::Database)
313 }
314
315 pub async fn revoke_refresh_token(&self, id: RefreshTokenId) -> Result<(), AuthError> {
320 let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
321 sqlx::query("UPDATE allowthem_refresh_tokens SET revoked_at = ? WHERE id = ?")
322 .bind(&now)
323 .bind(id)
324 .execute(self.pool())
325 .await?;
326 Ok(())
327 }
328}
329
330#[allow(clippy::too_many_arguments)]
346pub async fn exchange_authorization_code(
347 db: &Db,
348 code: &str,
349 redirect_uri: &str,
350 code_verifier: &str,
351 application: &Application,
352 issuer: &str,
353 signing_key: &SigningKey,
354 private_key_pem: &str,
355) -> Result<TokenResponse, TokenError> {
356 let code_hash = hash_authorization_code(code);
358 let auth_code = db
359 .get_authorization_code_by_hash(&code_hash)
360 .await
361 .map_err(|e| TokenError::ServerError(e.to_string()))?
362 .ok_or_else(|| TokenError::InvalidGrant("invalid authorization code".into()))?;
363
364 if auth_code.used_at.is_some() {
366 let _ = db.revoke_refresh_tokens_by_auth_code(auth_code.id).await;
367 return Err(TokenError::InvalidGrant(
368 "authorization code already used".into(),
369 ));
370 }
371
372 db.mark_authorization_code_used(auth_code.id)
374 .await
375 .map_err(|e| TokenError::ServerError(e.to_string()))?;
376
377 if auth_code.expires_at < Utc::now() {
379 return Err(TokenError::InvalidGrant(
380 "authorization code expired".into(),
381 ));
382 }
383
384 if auth_code.application_id != application.id {
386 return Err(TokenError::InvalidGrant(
387 "code was issued to a different client".into(),
388 ));
389 }
390
391 if auth_code.redirect_uri != redirect_uri {
393 return Err(TokenError::InvalidGrant("redirect_uri mismatch".into()));
394 }
395
396 if !verify_pkce_s256(code_verifier, &auth_code.code_challenge) {
398 return Err(TokenError::InvalidGrant("PKCE verification failed".into()));
399 }
400
401 let scopes: Vec<String> = serde_json::from_str(&auth_code.scopes)
403 .map_err(|e| TokenError::ServerError(e.to_string()))?;
404 let scopes_str = scopes.join(" ");
405
406 let user = db
408 .get_user(auth_code.user_id)
409 .await
410 .map_err(|e| TokenError::ServerError(e.to_string()))?;
411 let user_roles = db
412 .get_user_roles(&auth_code.user_id)
413 .await
414 .map_err(|e| TokenError::ServerError(e.to_string()))?;
415 let user_perms = db
416 .get_user_permissions(&auth_code.user_id)
417 .await
418 .map_err(|e| TokenError::ServerError(e.to_string()))?;
419 let role_names: Vec<String> = user_roles
420 .iter()
421 .map(|r| r.name.as_str().to_owned())
422 .collect();
423 let perm_names: Vec<String> = user_perms
424 .iter()
425 .map(|p| p.name.as_str().to_owned())
426 .collect();
427
428 let kid = signing_key.id.to_string();
430 let access_token = mint_access_token(
431 auth_code.user_id,
432 issuer,
433 application.client_id.as_str(),
434 &scopes_str,
435 &kid,
436 private_key_pem,
437 3600,
438 user.email.as_str(),
439 user.email_verified,
440 user.username.as_ref().map(|u| u.as_str()),
441 &role_names,
442 &perm_names,
443 )
444 .map_err(|e| TokenError::ServerError(e.to_string()))?;
445
446 let at_hash = compute_at_hash(&access_token);
448 let auth_time = auth_code.created_at.timestamp();
449
450 let id_token = mint_id_token(
452 auth_code.user_id,
453 issuer,
454 application.client_id.as_str(),
455 auth_code.nonce.as_deref(),
456 &at_hash,
457 auth_time,
458 &kid,
459 private_key_pem,
460 3600,
461 )
462 .map_err(|e| TokenError::ServerError(e.to_string()))?;
463
464 let raw_refresh = generate_refresh_token();
466 let refresh_hash = hash_refresh_token(&raw_refresh);
467 db.create_refresh_token(
468 application.id,
469 auth_code.user_id,
470 &refresh_hash,
471 &scopes,
472 Some(auth_code.id),
473 )
474 .await
475 .map_err(|e| TokenError::ServerError(e.to_string()))?;
476
477 Ok(TokenResponse {
478 access_token,
479 token_type: "Bearer",
480 expires_in: 3600,
481 refresh_token: raw_refresh,
482 id_token,
483 })
484}
485
486pub async fn exchange_refresh_token(
496 db: &Db,
497 raw_token: &str,
498 requested_scopes: Option<&str>,
499 application: &Application,
500 issuer: &str,
501 signing_key: &SigningKey,
502 private_key_pem: &str,
503) -> Result<TokenResponse, TokenError> {
504 let hash = hash_refresh_token(raw_token);
506 let stored = db
507 .get_refresh_token_by_hash(&hash)
508 .await
509 .map_err(|e| TokenError::ServerError(e.to_string()))?
510 .ok_or_else(|| TokenError::InvalidGrant("invalid refresh token".into()))?;
511
512 if stored.revoked_at.is_some() {
514 return Err(TokenError::InvalidGrant(
515 "refresh token has been revoked".into(),
516 ));
517 }
518
519 if stored.expires_at < Utc::now() {
521 return Err(TokenError::InvalidGrant("refresh token has expired".into()));
522 }
523
524 if stored.application_id != application.id {
526 return Err(TokenError::InvalidGrant(
527 "refresh token was issued to a different client".into(),
528 ));
529 }
530
531 let original_scopes: Vec<String> =
533 serde_json::from_str(&stored.scopes).map_err(|e| TokenError::ServerError(e.to_string()))?;
534
535 let effective_scopes = match requested_scopes {
536 Some(s) if !s.is_empty() => {
537 let requested: Vec<&str> = s.split_whitespace().collect();
538 for scope in &requested {
539 if !original_scopes.iter().any(|orig| orig == scope) {
540 return Err(TokenError::InvalidGrant(
541 "requested scope exceeds original grant".into(),
542 ));
543 }
544 }
545 requested.iter().map(|s| s.to_string()).collect::<Vec<_>>()
546 }
547 _ => original_scopes.clone(),
548 };
549
550 let scopes_str = effective_scopes.join(" ");
551
552 db.revoke_refresh_token(stored.id)
554 .await
555 .map_err(|e| TokenError::ServerError(e.to_string()))?;
556
557 let user = db
559 .get_user(stored.user_id)
560 .await
561 .map_err(|e| TokenError::ServerError(e.to_string()))?;
562 let user_roles = db
563 .get_user_roles(&stored.user_id)
564 .await
565 .map_err(|e| TokenError::ServerError(e.to_string()))?;
566 let user_perms = db
567 .get_user_permissions(&stored.user_id)
568 .await
569 .map_err(|e| TokenError::ServerError(e.to_string()))?;
570 let role_names: Vec<String> = user_roles
571 .iter()
572 .map(|r| r.name.as_str().to_owned())
573 .collect();
574 let perm_names: Vec<String> = user_perms
575 .iter()
576 .map(|p| p.name.as_str().to_owned())
577 .collect();
578
579 let kid = signing_key.id.to_string();
581 let access_token = mint_access_token(
582 stored.user_id,
583 issuer,
584 application.client_id.as_str(),
585 &scopes_str,
586 &kid,
587 private_key_pem,
588 3600,
589 user.email.as_str(),
590 user.email_verified,
591 user.username.as_ref().map(|u| u.as_str()),
592 &role_names,
593 &perm_names,
594 )
595 .map_err(|e| TokenError::ServerError(e.to_string()))?;
596
597 let at_hash = compute_at_hash(&access_token);
599 let auth_time = stored.created_at.timestamp();
600 let id_token = mint_id_token(
601 stored.user_id,
602 issuer,
603 application.client_id.as_str(),
604 None,
605 &at_hash,
606 auth_time,
607 &kid,
608 private_key_pem,
609 3600,
610 )
611 .map_err(|e| TokenError::ServerError(e.to_string()))?;
612
613 let new_raw = generate_refresh_token();
615 let new_hash = hash_refresh_token(&new_raw);
616 db.create_refresh_token(
617 application.id,
618 stored.user_id,
619 &new_hash,
620 &effective_scopes,
621 stored.authorization_code_id,
622 )
623 .await
624 .map_err(|e| TokenError::ServerError(e.to_string()))?;
625
626 Ok(TokenResponse {
627 access_token,
628 token_type: "Bearer",
629 expires_in: 3600,
630 refresh_token: new_raw,
631 id_token,
632 })
633}
634
635#[cfg(test)]
640mod tests {
641 use super::*;
642 use crate::signing_keys::decrypt_private_key;
643 use crate::types::Email;
644 use jsonwebtoken::Algorithm;
645 use sqlx::SqlitePool;
646 use sqlx::sqlite::SqliteConnectOptions;
647 use std::str::FromStr;
648
649 const ENC_KEY: [u8; 32] = [0x42; 32];
650 const ISSUER: &str = "https://auth.example.com";
651
652 async fn test_db() -> Db {
653 let opts = SqliteConnectOptions::from_str("sqlite::memory:")
654 .unwrap()
655 .pragma("foreign_keys", "ON");
656 let pool = SqlitePool::connect_with(opts).await.unwrap();
657 Db::new(pool).await.unwrap()
658 }
659
660 #[test]
663 fn verify_pkce_s256_valid() {
664 let verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
665 let digest = Sha256::digest(verifier.as_bytes());
666 let challenge = Base64UrlUnpadded::encode_string(&digest);
667 assert!(verify_pkce_s256(verifier, &challenge));
668 }
669
670 #[test]
671 fn verify_pkce_s256_wrong_verifier() {
672 let verifier = "correct_verifier";
673 let digest = Sha256::digest(verifier.as_bytes());
674 let challenge = Base64UrlUnpadded::encode_string(&digest);
675 assert!(!verify_pkce_s256("wrong_verifier", &challenge));
676 }
677
678 #[test]
679 fn verify_pkce_s256_empty_verifier() {
680 let verifier = "actual_verifier";
681 let digest = Sha256::digest(verifier.as_bytes());
682 let challenge = Base64UrlUnpadded::encode_string(&digest);
683 assert!(!verify_pkce_s256("", &challenge));
684 }
685
686 #[test]
689 fn compute_at_hash_deterministic() {
690 let input = "eyJhbGciOiJSUzI1NiJ9.test.sig";
691 let h1 = compute_at_hash(input);
692 let h2 = compute_at_hash(input);
693 assert_eq!(h1, h2);
694 }
695
696 #[test]
697 fn compute_at_hash_known_value() {
698 let hash = compute_at_hash("test");
702 let digest = Sha256::digest(b"test");
703 let expected = Base64UrlUnpadded::encode_string(&digest[..16]);
704 assert_eq!(hash, expected);
705 }
706
707 #[test]
710 fn refresh_token_is_43_chars() {
711 let token = generate_refresh_token();
712 assert_eq!(token.len(), 43, "32 bytes base64url = 43 chars");
713 }
714
715 #[test]
716 fn refresh_token_is_url_safe() {
717 let token = generate_refresh_token();
718 assert!(
719 token
720 .chars()
721 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
722 "token must be URL-safe: got {token}"
723 );
724 }
725
726 #[test]
727 fn hash_refresh_token_deterministic() {
728 let token = generate_refresh_token();
729 let h1 = hash_refresh_token(&token);
730 let h2 = hash_refresh_token(&token);
731 assert_eq!(format!("{h1:?}"), format!("{h2:?}"));
732 }
733
734 #[tokio::test]
737 async fn mint_access_token_roundtrip() {
738 let db = test_db().await;
739 let key = db.create_signing_key(&ENC_KEY).await.unwrap();
740 db.activate_signing_key(key.id).await.unwrap();
741 let pem = decrypt_private_key(&key, &ENC_KEY).unwrap();
742 let kid = key.id.to_string();
743
744 let user_id = UserId::new();
745 let token = mint_access_token(
746 user_id,
747 ISSUER,
748 "ath_test_client",
749 "openid profile",
750 &kid,
751 &pem,
752 3600,
753 "test@example.com",
754 true,
755 Some("testuser"),
756 &["admin".to_string()],
757 &["posts:write".to_string()],
758 )
759 .unwrap();
760
761 let claims = db.validate_access_token(&token, ISSUER).await.unwrap();
763 assert_eq!(claims.sub, user_id);
764 assert_eq!(claims.email, "test@example.com");
765 assert!(claims.email_verified);
766 assert_eq!(claims.username.as_deref(), Some("testuser"));
767 assert_eq!(claims.roles, vec!["admin"]);
768 assert_eq!(claims.permissions, vec!["posts:write"]);
769 assert_eq!(claims.scope, "openid profile");
770 assert_eq!(claims.iss, ISSUER);
771 }
772
773 #[tokio::test]
774 async fn mint_id_token_contains_nonce() {
775 let db = test_db().await;
776 let key = db.create_signing_key(&ENC_KEY).await.unwrap();
777 let pem = decrypt_private_key(&key, &ENC_KEY).unwrap();
778 let kid = key.id.to_string();
779
780 let user_id = UserId::new();
781 let token = mint_id_token(
782 user_id,
783 ISSUER,
784 "ath_test_client",
785 Some("test_nonce_123"),
786 "test_at_hash",
787 1234567890,
788 &kid,
789 &pem,
790 3600,
791 )
792 .unwrap();
793
794 let parts: Vec<&str> = token.splitn(3, '.').collect();
796 let payload = base64ct::Base64UrlUnpadded::decode_vec(parts[1]).unwrap();
797 let claims: serde_json::Value = serde_json::from_slice(&payload).unwrap();
798 assert_eq!(claims["nonce"], "test_nonce_123");
799 assert_eq!(claims["at_hash"], "test_at_hash");
800 assert_eq!(claims["auth_time"], 1234567890);
801 }
802
803 #[tokio::test]
804 async fn mint_id_token_omits_nonce_when_none() {
805 let db = test_db().await;
806 let key = db.create_signing_key(&ENC_KEY).await.unwrap();
807 let pem = decrypt_private_key(&key, &ENC_KEY).unwrap();
808 let kid = key.id.to_string();
809
810 let user_id = UserId::new();
811 let token = mint_id_token(
812 user_id,
813 ISSUER,
814 "ath_test_client",
815 None,
816 "hash",
817 0,
818 &kid,
819 &pem,
820 3600,
821 )
822 .unwrap();
823
824 let parts: Vec<&str> = token.splitn(3, '.').collect();
825 let payload = base64ct::Base64UrlUnpadded::decode_vec(parts[1]).unwrap();
826 let claims: serde_json::Value = serde_json::from_slice(&payload).unwrap();
827 assert!(claims.get("nonce").is_none());
828 }
829
830 async fn setup_exchange(db: &Db) -> (Application, SigningKey, String, String, String, String) {
834 let email = Email::new("exchange@example.com".into()).unwrap();
835 let user = db
836 .create_user(email, "password123", None, None)
837 .await
838 .unwrap();
839
840 let (app, _secret) = db
841 .create_application(
842 "ExchangeApp".to_string(),
843 vec!["https://example.com/callback".to_string()],
844 false,
845 Some(user.id),
846 None,
847 None,
848 )
849 .await
850 .unwrap();
851
852 let key = db.create_signing_key(&ENC_KEY).await.unwrap();
853 db.activate_signing_key(key.id).await.unwrap();
854 let pem = decrypt_private_key(&key, &ENC_KEY).unwrap();
855
856 let code_verifier = "test_verifier_string_with_enough_entropy_1234567890";
858 let digest = Sha256::digest(code_verifier.as_bytes());
859 let code_challenge = Base64UrlUnpadded::encode_string(&digest);
860
861 let raw_code = crate::authorization::generate_authorization_code();
862 let code_hash = hash_authorization_code(&raw_code);
863 db.create_authorization_code(
864 app.id,
865 user.id,
866 &code_hash,
867 "https://example.com/callback",
868 &["openid".to_string(), "profile".to_string()],
869 &code_challenge,
870 "S256",
871 Some("test_nonce"),
872 )
873 .await
874 .unwrap();
875
876 (
877 app,
878 key,
879 pem,
880 raw_code,
881 code_verifier.to_string(),
882 "https://example.com/callback".to_string(),
883 )
884 }
885
886 #[tokio::test]
887 async fn exchange_valid_code() {
888 let db = test_db().await;
889 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
890
891 let resp = exchange_authorization_code(
892 &db,
893 &raw_code,
894 &redirect_uri,
895 &verifier,
896 &app,
897 ISSUER,
898 &key,
899 &pem,
900 )
901 .await
902 .unwrap();
903
904 assert_eq!(resp.token_type, "Bearer");
905 assert_eq!(resp.expires_in, 3600);
906 assert!(!resp.access_token.is_empty());
907 assert!(!resp.refresh_token.is_empty());
908 assert!(!resp.id_token.is_empty());
909
910 let claims = db
912 .validate_access_token(&resp.access_token, ISSUER)
913 .await
914 .unwrap();
915 assert_eq!(claims.scope, "openid profile");
916 }
917
918 #[tokio::test]
919 async fn exchange_used_code_triggers_revocation() {
920 let db = test_db().await;
921 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
922
923 let _resp = exchange_authorization_code(
925 &db,
926 &raw_code,
927 &redirect_uri,
928 &verifier,
929 &app,
930 ISSUER,
931 &key,
932 &pem,
933 )
934 .await
935 .unwrap();
936
937 let err = exchange_authorization_code(
939 &db,
940 &raw_code,
941 &redirect_uri,
942 &verifier,
943 &app,
944 ISSUER,
945 &key,
946 &pem,
947 )
948 .await
949 .unwrap_err();
950 assert!(matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("already used")));
951 }
952
953 #[tokio::test]
954 async fn exchange_wrong_redirect_uri() {
955 let db = test_db().await;
956 let (app, key, pem, raw_code, verifier, _) = setup_exchange(&db).await;
957
958 let err = exchange_authorization_code(
959 &db,
960 &raw_code,
961 "https://evil.example.com/callback",
962 &verifier,
963 &app,
964 ISSUER,
965 &key,
966 &pem,
967 )
968 .await
969 .unwrap_err();
970 assert!(matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("redirect_uri")));
971 }
972
973 #[tokio::test]
974 async fn exchange_bad_pkce() {
975 let db = test_db().await;
976 let (app, key, pem, raw_code, _, redirect_uri) = setup_exchange(&db).await;
977
978 let err = exchange_authorization_code(
979 &db,
980 &raw_code,
981 &redirect_uri,
982 "wrong_verifier",
983 &app,
984 ISSUER,
985 &key,
986 &pem,
987 )
988 .await
989 .unwrap_err();
990 assert!(matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("PKCE")));
991 }
992
993 #[tokio::test]
994 async fn exchange_invalid_code() {
995 let db = test_db().await;
996 let (app, key, pem, _, verifier, redirect_uri) = setup_exchange(&db).await;
997
998 let err = exchange_authorization_code(
999 &db,
1000 "nonexistent_code",
1001 &redirect_uri,
1002 &verifier,
1003 &app,
1004 ISSUER,
1005 &key,
1006 &pem,
1007 )
1008 .await
1009 .unwrap_err();
1010 assert!(matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("invalid")));
1011 }
1012
1013 #[tokio::test]
1014 async fn exchange_expired_code() {
1015 let db = test_db().await;
1016 let email = Email::new("expired@example.com".into()).unwrap();
1017 let user = db
1018 .create_user(email, "password123", None, None)
1019 .await
1020 .unwrap();
1021
1022 let (app, _) = db
1023 .create_application(
1024 "ExpiredApp".to_string(),
1025 vec!["https://example.com/callback".to_string()],
1026 false,
1027 Some(user.id),
1028 None,
1029 None,
1030 )
1031 .await
1032 .unwrap();
1033
1034 let key = db.create_signing_key(&ENC_KEY).await.unwrap();
1035 db.activate_signing_key(key.id).await.unwrap();
1036 let pem = decrypt_private_key(&key, &ENC_KEY).unwrap();
1037
1038 let code_verifier = "test_verifier_expired";
1039 let digest = Sha256::digest(code_verifier.as_bytes());
1040 let code_challenge = Base64UrlUnpadded::encode_string(&digest);
1041
1042 let raw_code = crate::authorization::generate_authorization_code();
1043 let code_hash = hash_authorization_code(&raw_code);
1044 db.create_authorization_code(
1045 app.id,
1046 user.id,
1047 &code_hash,
1048 "https://example.com/callback",
1049 &["openid".to_string()],
1050 &code_challenge,
1051 "S256",
1052 None,
1053 )
1054 .await
1055 .unwrap();
1056
1057 sqlx::query(
1059 "UPDATE allowthem_authorization_codes SET expires_at = '2020-01-01T00:00:00.000Z' WHERE code_hash = ?",
1060 )
1061 .bind(&code_hash)
1062 .execute(db.pool())
1063 .await
1064 .unwrap();
1065
1066 let err = exchange_authorization_code(
1067 &db,
1068 &raw_code,
1069 "https://example.com/callback",
1070 code_verifier,
1071 &app,
1072 ISSUER,
1073 &key,
1074 &pem,
1075 )
1076 .await
1077 .unwrap_err();
1078 assert!(matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("expired")));
1079 }
1080
1081 #[tokio::test]
1082 async fn exchange_wrong_client() {
1083 let db = test_db().await;
1084 let (_, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1085
1086 let email_b = Email::new("other@example.com".into()).unwrap();
1087 let user_b = db
1088 .create_user(email_b, "password123", None, None)
1089 .await
1090 .unwrap();
1091 let (app_b, _) = db
1092 .create_application(
1093 "OtherApp".to_string(),
1094 vec!["https://other.example.com/callback".to_string()],
1095 false,
1096 Some(user_b.id),
1097 None,
1098 None,
1099 )
1100 .await
1101 .unwrap();
1102
1103 let err = exchange_authorization_code(
1104 &db,
1105 &raw_code,
1106 &redirect_uri,
1107 &verifier,
1108 &app_b,
1109 ISSUER,
1110 &key,
1111 &pem,
1112 )
1113 .await
1114 .unwrap_err();
1115 assert!(
1116 matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("different client"))
1117 );
1118 }
1119
1120 #[tokio::test]
1121 async fn access_token_has_correct_typ_header() {
1122 let db = test_db().await;
1123 let key = db.create_signing_key(&ENC_KEY).await.unwrap();
1124 let pem = decrypt_private_key(&key, &ENC_KEY).unwrap();
1125
1126 let token = mint_access_token(
1127 UserId::new(),
1128 ISSUER,
1129 "client",
1130 "openid",
1131 &key.id.to_string(),
1132 &pem,
1133 3600,
1134 "t@example.com",
1135 true,
1136 None,
1137 &[],
1138 &[],
1139 )
1140 .unwrap();
1141
1142 let header = jsonwebtoken::decode_header(&token).unwrap();
1143 assert_eq!(header.typ.as_deref(), Some("at+jwt"));
1144 assert_eq!(header.alg, Algorithm::RS256);
1145 assert!(header.kid.is_some());
1146 }
1147
1148 #[tokio::test]
1149 async fn id_token_has_correct_typ_header() {
1150 let db = test_db().await;
1151 let key = db.create_signing_key(&ENC_KEY).await.unwrap();
1152 let pem = decrypt_private_key(&key, &ENC_KEY).unwrap();
1153
1154 let token = mint_id_token(
1155 UserId::new(),
1156 ISSUER,
1157 "client",
1158 None,
1159 "hash",
1160 0,
1161 &key.id.to_string(),
1162 &pem,
1163 3600,
1164 )
1165 .unwrap();
1166
1167 let header = jsonwebtoken::decode_header(&token).unwrap();
1168 assert_eq!(header.typ.as_deref(), Some("JWT"));
1169 assert_eq!(header.alg, Algorithm::RS256);
1170 }
1171
1172 #[tokio::test]
1173 async fn exchange_id_token_at_hash_matches_access_token() {
1174 let db = test_db().await;
1175 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1176
1177 let resp = exchange_authorization_code(
1178 &db,
1179 &raw_code,
1180 &redirect_uri,
1181 &verifier,
1182 &app,
1183 ISSUER,
1184 &key,
1185 &pem,
1186 )
1187 .await
1188 .unwrap();
1189
1190 let parts: Vec<&str> = resp.id_token.splitn(3, '.').collect();
1191 let payload = base64ct::Base64UrlUnpadded::decode_vec(parts[1]).unwrap();
1192 let claims: serde_json::Value = serde_json::from_slice(&payload).unwrap();
1193
1194 let expected = compute_at_hash(&resp.access_token);
1195 assert_eq!(claims["at_hash"].as_str().unwrap(), expected);
1196 }
1197
1198 #[tokio::test]
1199 async fn exchange_creates_refresh_token_in_db() {
1200 let db = test_db().await;
1201 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1202
1203 let resp = exchange_authorization_code(
1204 &db,
1205 &raw_code,
1206 &redirect_uri,
1207 &verifier,
1208 &app,
1209 ISSUER,
1210 &key,
1211 &pem,
1212 )
1213 .await
1214 .unwrap();
1215
1216 let refresh_hash = hash_refresh_token(&resp.refresh_token);
1217 let count: (i64,) =
1218 sqlx::query_as("SELECT COUNT(*) FROM allowthem_refresh_tokens WHERE token_hash = ?")
1219 .bind(&refresh_hash)
1220 .fetch_one(db.pool())
1221 .await
1222 .unwrap();
1223 assert_eq!(count.0, 1, "refresh token should be stored in DB");
1224 }
1225
1226 #[tokio::test]
1229 async fn get_refresh_token_by_hash_returns_token() {
1230 let db = test_db().await;
1231 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1232
1233 let resp = exchange_authorization_code(
1234 &db,
1235 &raw_code,
1236 &redirect_uri,
1237 &verifier,
1238 &app,
1239 ISSUER,
1240 &key,
1241 &pem,
1242 )
1243 .await
1244 .unwrap();
1245
1246 let hash = hash_refresh_token(&resp.refresh_token);
1247 let stored = db.get_refresh_token_by_hash(&hash).await.unwrap().unwrap();
1248 assert_eq!(stored.application_id, app.id);
1249 assert_eq!(stored.revoked_at, None);
1250 }
1251
1252 #[tokio::test]
1253 async fn get_refresh_token_by_hash_returns_none_for_unknown() {
1254 let db = test_db().await;
1255 let unknown = hash_refresh_token("nonexistent_raw_token");
1256 let result = db.get_refresh_token_by_hash(&unknown).await.unwrap();
1257 assert!(result.is_none());
1258 }
1259
1260 #[tokio::test]
1261 async fn revoke_refresh_token_sets_revoked_at() {
1262 let db = test_db().await;
1263 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1264
1265 let resp = exchange_authorization_code(
1266 &db,
1267 &raw_code,
1268 &redirect_uri,
1269 &verifier,
1270 &app,
1271 ISSUER,
1272 &key,
1273 &pem,
1274 )
1275 .await
1276 .unwrap();
1277
1278 let hash = hash_refresh_token(&resp.refresh_token);
1279 let stored = db.get_refresh_token_by_hash(&hash).await.unwrap().unwrap();
1280 assert!(stored.revoked_at.is_none());
1281
1282 db.revoke_refresh_token(stored.id).await.unwrap();
1283
1284 let after = db.get_refresh_token_by_hash(&hash).await.unwrap().unwrap();
1285 assert!(after.revoked_at.is_some());
1286 }
1287
1288 #[tokio::test]
1289 async fn revoke_refresh_token_idempotent() {
1290 let db = test_db().await;
1291 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1292
1293 let resp = exchange_authorization_code(
1294 &db,
1295 &raw_code,
1296 &redirect_uri,
1297 &verifier,
1298 &app,
1299 ISSUER,
1300 &key,
1301 &pem,
1302 )
1303 .await
1304 .unwrap();
1305
1306 let hash = hash_refresh_token(&resp.refresh_token);
1307 let stored = db.get_refresh_token_by_hash(&hash).await.unwrap().unwrap();
1308
1309 db.revoke_refresh_token(stored.id).await.unwrap();
1310 db.revoke_refresh_token(stored.id).await.unwrap();
1311 }
1312
1313 #[tokio::test]
1316 async fn exchange_refresh_token_valid() {
1317 let db = test_db().await;
1318 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1319
1320 let initial = exchange_authorization_code(
1321 &db,
1322 &raw_code,
1323 &redirect_uri,
1324 &verifier,
1325 &app,
1326 ISSUER,
1327 &key,
1328 &pem,
1329 )
1330 .await
1331 .unwrap();
1332
1333 let resp =
1334 exchange_refresh_token(&db, &initial.refresh_token, None, &app, ISSUER, &key, &pem)
1335 .await
1336 .unwrap();
1337
1338 assert!(!resp.access_token.is_empty());
1339 assert!(!resp.refresh_token.is_empty());
1340 assert_ne!(resp.refresh_token, initial.refresh_token);
1341 assert_eq!(resp.token_type, "Bearer");
1342 assert_eq!(resp.expires_in, 3600);
1343
1344 let claims = db
1345 .validate_access_token(&resp.access_token, ISSUER)
1346 .await
1347 .unwrap();
1348 assert_eq!(claims.scope, "openid profile");
1349 }
1350
1351 #[tokio::test]
1352 async fn exchange_refresh_token_revokes_old_token() {
1353 let db = test_db().await;
1354 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1355
1356 let initial = exchange_authorization_code(
1357 &db,
1358 &raw_code,
1359 &redirect_uri,
1360 &verifier,
1361 &app,
1362 ISSUER,
1363 &key,
1364 &pem,
1365 )
1366 .await
1367 .unwrap();
1368
1369 let old_hash = hash_refresh_token(&initial.refresh_token);
1370 exchange_refresh_token(&db, &initial.refresh_token, None, &app, ISSUER, &key, &pem)
1371 .await
1372 .unwrap();
1373
1374 let old_stored = db
1375 .get_refresh_token_by_hash(&old_hash)
1376 .await
1377 .unwrap()
1378 .unwrap();
1379 assert!(
1380 old_stored.revoked_at.is_some(),
1381 "old token should be revoked"
1382 );
1383 }
1384
1385 #[tokio::test]
1386 async fn exchange_refresh_token_revoked_token_fails() {
1387 let db = test_db().await;
1388 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1389
1390 let initial = exchange_authorization_code(
1391 &db,
1392 &raw_code,
1393 &redirect_uri,
1394 &verifier,
1395 &app,
1396 ISSUER,
1397 &key,
1398 &pem,
1399 )
1400 .await
1401 .unwrap();
1402
1403 exchange_refresh_token(&db, &initial.refresh_token, None, &app, ISSUER, &key, &pem)
1405 .await
1406 .unwrap();
1407
1408 let err =
1410 exchange_refresh_token(&db, &initial.refresh_token, None, &app, ISSUER, &key, &pem)
1411 .await
1412 .unwrap_err();
1413 assert!(matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("revoked")));
1414 }
1415
1416 #[tokio::test]
1417 async fn exchange_refresh_token_expired_fails() {
1418 let db = test_db().await;
1419 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1420
1421 let initial = exchange_authorization_code(
1422 &db,
1423 &raw_code,
1424 &redirect_uri,
1425 &verifier,
1426 &app,
1427 ISSUER,
1428 &key,
1429 &pem,
1430 )
1431 .await
1432 .unwrap();
1433
1434 let hash = hash_refresh_token(&initial.refresh_token);
1435 sqlx::query(
1436 "UPDATE allowthem_refresh_tokens SET expires_at = '2020-01-01T00:00:00.000Z' WHERE token_hash = ?",
1437 )
1438 .bind(&hash)
1439 .execute(db.pool())
1440 .await
1441 .unwrap();
1442
1443 let err =
1444 exchange_refresh_token(&db, &initial.refresh_token, None, &app, ISSUER, &key, &pem)
1445 .await
1446 .unwrap_err();
1447 assert!(matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("expired")));
1448 }
1449
1450 #[tokio::test]
1451 async fn exchange_refresh_token_wrong_client_fails() {
1452 let db = test_db().await;
1453 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1454
1455 let initial = exchange_authorization_code(
1456 &db,
1457 &raw_code,
1458 &redirect_uri,
1459 &verifier,
1460 &app,
1461 ISSUER,
1462 &key,
1463 &pem,
1464 )
1465 .await
1466 .unwrap();
1467
1468 let email_b = Email::new("other_refresh@example.com".into()).unwrap();
1469 let user_b = db
1470 .create_user(email_b, "password123", None, None)
1471 .await
1472 .unwrap();
1473 let (app_b, _) = db
1474 .create_application(
1475 "OtherRefreshApp".to_string(),
1476 vec!["https://other.example.com/callback".to_string()],
1477 false,
1478 Some(user_b.id),
1479 None,
1480 None,
1481 )
1482 .await
1483 .unwrap();
1484
1485 let err = exchange_refresh_token(
1486 &db,
1487 &initial.refresh_token,
1488 None,
1489 &app_b,
1490 ISSUER,
1491 &key,
1492 &pem,
1493 )
1494 .await
1495 .unwrap_err();
1496 assert!(
1497 matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("different client"))
1498 );
1499 }
1500
1501 #[tokio::test]
1502 async fn exchange_refresh_token_scope_subset_succeeds() {
1503 let db = test_db().await;
1504 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1505
1506 let initial = exchange_authorization_code(
1507 &db,
1508 &raw_code,
1509 &redirect_uri,
1510 &verifier,
1511 &app,
1512 ISSUER,
1513 &key,
1514 &pem,
1515 )
1516 .await
1517 .unwrap();
1518
1519 let resp = exchange_refresh_token(
1520 &db,
1521 &initial.refresh_token,
1522 Some("openid"),
1523 &app,
1524 ISSUER,
1525 &key,
1526 &pem,
1527 )
1528 .await
1529 .unwrap();
1530
1531 let claims = db
1532 .validate_access_token(&resp.access_token, ISSUER)
1533 .await
1534 .unwrap();
1535 assert_eq!(claims.scope, "openid");
1536 }
1537
1538 #[tokio::test]
1539 async fn exchange_refresh_token_scope_escalation_fails() {
1540 let db = test_db().await;
1541 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1542
1543 let initial = exchange_authorization_code(
1544 &db,
1545 &raw_code,
1546 &redirect_uri,
1547 &verifier,
1548 &app,
1549 ISSUER,
1550 &key,
1551 &pem,
1552 )
1553 .await
1554 .unwrap();
1555
1556 let err = exchange_refresh_token(
1557 &db,
1558 &initial.refresh_token,
1559 Some("openid admin"),
1560 &app,
1561 ISSUER,
1562 &key,
1563 &pem,
1564 )
1565 .await
1566 .unwrap_err();
1567 assert!(matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("exceeds")));
1568 }
1569
1570 #[tokio::test]
1571 async fn exchange_refresh_token_no_scope_uses_original() {
1572 let db = test_db().await;
1573 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1574
1575 let initial = exchange_authorization_code(
1576 &db,
1577 &raw_code,
1578 &redirect_uri,
1579 &verifier,
1580 &app,
1581 ISSUER,
1582 &key,
1583 &pem,
1584 )
1585 .await
1586 .unwrap();
1587
1588 let resp =
1589 exchange_refresh_token(&db, &initial.refresh_token, None, &app, ISSUER, &key, &pem)
1590 .await
1591 .unwrap();
1592
1593 let claims = db
1594 .validate_access_token(&resp.access_token, ISSUER)
1595 .await
1596 .unwrap();
1597 assert_eq!(claims.scope, "openid profile");
1598 }
1599
1600 #[tokio::test]
1601 async fn exchange_refresh_token_invalid_hash_fails() {
1602 let db = test_db().await;
1603 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1604 let _ = exchange_authorization_code(
1605 &db,
1606 &raw_code,
1607 &redirect_uri,
1608 &verifier,
1609 &app,
1610 ISSUER,
1611 &key,
1612 &pem,
1613 )
1614 .await
1615 .unwrap();
1616
1617 let err = exchange_refresh_token(
1618 &db,
1619 "totally_invalid_garbage_token",
1620 None,
1621 &app,
1622 ISSUER,
1623 &key,
1624 &pem,
1625 )
1626 .await
1627 .unwrap_err();
1628 assert!(matches!(err, TokenError::InvalidGrant(ref msg) if msg.contains("invalid")));
1629 }
1630
1631 #[tokio::test]
1632 async fn exchange_refresh_token_propagates_authorization_code_id() {
1633 let db = test_db().await;
1634 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1635
1636 let initial = exchange_authorization_code(
1637 &db,
1638 &raw_code,
1639 &redirect_uri,
1640 &verifier,
1641 &app,
1642 ISSUER,
1643 &key,
1644 &pem,
1645 )
1646 .await
1647 .unwrap();
1648
1649 let first_hash = hash_refresh_token(&initial.refresh_token);
1650 let first_stored = db
1651 .get_refresh_token_by_hash(&first_hash)
1652 .await
1653 .unwrap()
1654 .unwrap();
1655 let original_auth_code_id = first_stored.authorization_code_id;
1656
1657 let rotated =
1658 exchange_refresh_token(&db, &initial.refresh_token, None, &app, ISSUER, &key, &pem)
1659 .await
1660 .unwrap();
1661
1662 let second_hash = hash_refresh_token(&rotated.refresh_token);
1663 let second_stored = db
1664 .get_refresh_token_by_hash(&second_hash)
1665 .await
1666 .unwrap()
1667 .unwrap();
1668
1669 assert_eq!(
1670 second_stored.authorization_code_id, original_auth_code_id,
1671 "authorization_code_id must propagate through rotation"
1672 );
1673 }
1674
1675 #[tokio::test]
1676 async fn exchange_refresh_token_chained_rotation_succeeds() {
1677 let db = test_db().await;
1678 let (app, key, pem, raw_code, verifier, redirect_uri) = setup_exchange(&db).await;
1679
1680 let initial = exchange_authorization_code(
1681 &db,
1682 &raw_code,
1683 &redirect_uri,
1684 &verifier,
1685 &app,
1686 ISSUER,
1687 &key,
1688 &pem,
1689 )
1690 .await
1691 .unwrap();
1692
1693 let second =
1694 exchange_refresh_token(&db, &initial.refresh_token, None, &app, ISSUER, &key, &pem)
1695 .await
1696 .unwrap();
1697 assert_ne!(second.refresh_token, initial.refresh_token);
1698
1699 let third =
1700 exchange_refresh_token(&db, &second.refresh_token, None, &app, ISSUER, &key, &pem)
1701 .await
1702 .unwrap();
1703 assert_ne!(third.refresh_token, second.refresh_token);
1704
1705 let first_hash = hash_refresh_token(&initial.refresh_token);
1706 let first_stored = db
1707 .get_refresh_token_by_hash(&first_hash)
1708 .await
1709 .unwrap()
1710 .unwrap();
1711 assert!(
1712 first_stored.revoked_at.is_some(),
1713 "first token must be revoked"
1714 );
1715
1716 let second_hash = hash_refresh_token(&second.refresh_token);
1717 let second_stored = db
1718 .get_refresh_token_by_hash(&second_hash)
1719 .await
1720 .unwrap()
1721 .unwrap();
1722 assert!(
1723 second_stored.revoked_at.is_some(),
1724 "second token must be revoked"
1725 );
1726 }
1727}