feat: add refresh token rotation flow

This commit is contained in:
2026-04-21 15:27:04 +08:00
parent 70dbefda2b
commit 584a77e572
16 changed files with 1048 additions and 85 deletions

View File

@@ -6,6 +6,7 @@ use jsonwebtoken::{
};
use rand_core::OsRng;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use time::{Duration, OffsetDateTime};
use uuid::Uuid;
@@ -403,6 +404,12 @@ pub fn create_refresh_session_token() -> String {
Uuid::new_v4().simple().to_string()
}
pub fn hash_refresh_session_token(token: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(token.as_bytes());
format!("{:x}", hasher.finalize())
}
pub fn build_refresh_session_set_cookie(token: &str, config: &RefreshCookieConfig) -> String {
let mut parts = vec![
format!(
@@ -426,6 +433,22 @@ pub fn build_refresh_session_set_cookie(token: &str, config: &RefreshCookieConfi
parts.join("; ")
}
pub fn build_refresh_session_clear_cookie(config: &RefreshCookieConfig) -> String {
let mut parts = vec![
format!("{}=", config.cookie_name()),
format!("Path={}", config.cookie_path()),
"HttpOnly".to_string(),
format!("SameSite={}", config.cookie_same_site().as_str()),
"Max-Age=0".to_string(),
];
if config.cookie_secure() {
parts.push("Secure".to_string());
}
parts.join("; ")
}
impl fmt::Display for JwtError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -651,4 +674,25 @@ mod tests {
assert!(cookie.contains("SameSite=Lax"));
assert!(cookie.contains("Max-Age=2592000"));
}
#[test]
fn hash_refresh_session_token_matches_sha256_hex() {
let hash = hash_refresh_session_token("refresh-token-01");
assert_eq!(
hash,
"0b6901f0dcee3f50df4115ecb29214f7740f8173919f94cc1f5eb92ff2481ce8"
);
}
#[test]
fn build_refresh_session_clear_cookie_respects_config() {
let cookie = build_refresh_session_clear_cookie(&build_refresh_cookie_config());
assert!(cookie.contains("genarrative_refresh_session="));
assert!(cookie.contains("Path=/api/auth"));
assert!(cookie.contains("HttpOnly"));
assert!(cookie.contains("SameSite=Lax"));
assert!(cookie.contains("Max-Age=0"));
}
}