106 lines
3.0 KiB
Rust
106 lines
3.0 KiB
Rust
use hmac::{Hmac, Mac};
|
|
use iam_service::application::services::{AuthService, TenantService};
|
|
use iam_service::models::{CreateTenantRequest, CreateUserRequest, LoginRequest};
|
|
use sha2::Sha256;
|
|
use sqlx::PgPool;
|
|
use uuid::Uuid;
|
|
|
|
fn fingerprint(pepper: &str, token: &str) -> String {
|
|
let mut mac = Hmac::<Sha256>::new_from_slice(pepper.as_bytes()).unwrap();
|
|
mac.update(token.as_bytes());
|
|
hex::encode(mac.finalize().into_bytes())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn refresh_token_rotate_and_expire_cases() -> Result<(), Box<dyn std::error::Error>> {
|
|
let database_url = match std::env::var("DATABASE_URL") {
|
|
Ok(v) if !v.trim().is_empty() => v,
|
|
_ => return Ok(()),
|
|
};
|
|
|
|
let pool = PgPool::connect(&database_url).await?;
|
|
|
|
let has_fingerprint: bool = sqlx::query_scalar(
|
|
r#"
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_schema='public' AND table_name='refresh_tokens' AND column_name='token_fingerprint'
|
|
)
|
|
"#,
|
|
)
|
|
.fetch_one(&pool)
|
|
.await?;
|
|
if !has_fingerprint {
|
|
return Ok(());
|
|
}
|
|
|
|
let pepper = "test-pepper";
|
|
let tenant_service = TenantService::new(pool.clone());
|
|
let auth_service = AuthService::new(pool.clone(), pepper.to_string());
|
|
|
|
let tenant = tenant_service
|
|
.create_tenant(CreateTenantRequest {
|
|
name: format!("refresh-{}", Uuid::new_v4()),
|
|
config: None,
|
|
})
|
|
.await?;
|
|
|
|
let user = auth_service
|
|
.register(
|
|
tenant.id,
|
|
CreateUserRequest {
|
|
email: format!("u-{}@example.com", Uuid::new_v4()),
|
|
password: "Password12345".to_string(),
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
let login = auth_service
|
|
.login(
|
|
tenant.id,
|
|
LoginRequest {
|
|
email: user.email.clone(),
|
|
password: "Password12345".to_string(),
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
let old_refresh = login.refresh_token.clone();
|
|
|
|
let refreshed = auth_service
|
|
.refresh_access_token(old_refresh.clone())
|
|
.await?;
|
|
assert_ne!(refreshed.refresh_token, old_refresh);
|
|
assert!(!refreshed.access_token.is_empty());
|
|
|
|
let second = auth_service.refresh_access_token(old_refresh.clone()).await;
|
|
assert!(second.is_err());
|
|
|
|
let invalid = auth_service
|
|
.refresh_access_token("not-a-real-token".to_string())
|
|
.await;
|
|
assert!(invalid.is_err());
|
|
|
|
let login2 = auth_service
|
|
.login(
|
|
tenant.id,
|
|
LoginRequest {
|
|
email: user.email.clone(),
|
|
password: "Password12345".to_string(),
|
|
},
|
|
)
|
|
.await?;
|
|
let fp = fingerprint(pepper, &login2.refresh_token);
|
|
sqlx::query("UPDATE refresh_tokens SET expires_at = NOW() - INTERVAL '1 second' WHERE token_fingerprint = $1")
|
|
.bind(fp)
|
|
.execute(&pool)
|
|
.await?;
|
|
|
|
let expired = auth_service
|
|
.refresh_access_token(login2.refresh_token.clone())
|
|
.await;
|
|
assert!(expired.is_err());
|
|
|
|
Ok(())
|
|
}
|