Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions crates/aionui-auth/src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ use sha2::{Digest, Sha256};

use crate::error::AuthError;

/// JWT token lifetime: 24 hours.
const TOKEN_EXPIRY: Duration = Duration::from_secs(24 * 60 * 60);
/// JWT token lifetime: 30 days.
///
/// Stop-gap value aligned with the session cookie's `Max-Age`
/// (`COOKIE_MAX_AGE_DAYS`). The cookie shell used to outlive this JWT, so after
/// the previous 24h expiry every request carried a dead token, producing the
/// 401/reconnect loop seen on remote WebUI. This stays long until token refresh
/// lands, after which it returns to a short access-token lifetime.
const TOKEN_EXPIRY: Duration = Duration::from_secs(30 * 24 * 60 * 60);

/// JWT issuer claim value.
const JWT_ISSUER: &str = "aionui";
Expand Down Expand Up @@ -60,7 +66,7 @@ impl JwtService {
}
}

/// Sign a new JWT for the given user. The token expires after 24 hours.
/// Sign a new JWT for the given user. The token expires after 30 days.
pub fn sign(&self, user_id: &str, username: &str) -> Result<String, AuthError> {
self.sign_with_session_generation(user_id, username, 0)
}
Expand Down Expand Up @@ -248,6 +254,19 @@ mod tests {
assert!(payload.exp > payload.iat);
}

#[test]
fn token_lifetime_is_30_days() {
// Stop-gap: the JWT lifetime is aligned with the session cookie's
// 30-day Max-Age so the cookie no longer outlives the token. Returns to
// a short lifetime once token refresh lands; updating it then is an
// intentional contract change, not a regression.
let service = test_service();
let token = service.sign("user_1", "admin").unwrap();
let payload = service.verify(&token).unwrap();
assert_eq!(TOKEN_EXPIRY.as_secs(), 30 * 24 * 60 * 60);
assert_eq!(payload.exp - payload.iat, 30 * 24 * 60 * 60);
}

#[test]
fn sign_with_session_generation_roundtrip() {
let service = test_service();
Expand Down Expand Up @@ -392,7 +411,7 @@ mod tests {
assert_eq!(service.blacklist_size(), 1);

service.cleanup_blacklist();
// Token just signed with 24h expiry should still be in blacklist
// Token just signed with 30d expiry should still be in blacklist
assert_eq!(service.blacklist_size(), 1);
}

Expand Down
4 changes: 3 additions & 1 deletion crates/aionui-common/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ pub const WS_CLOSE_POLICY_VIOLATION: u16 = 1008;

// --- Authentication ---

pub const SESSION_EXPIRY: &str = "24h";
/// Human-readable session lifetime label. Currently unused; kept consistent
/// with the real JWT/cookie lifetime (30 days).
pub const SESSION_EXPIRY: &str = "30d";
pub const COOKIE_NAME: &str = "aionui-session";
pub const COOKIE_MAX_AGE_DAYS: u32 = 30;
pub const CSRF_COOKIE_NAME: &str = "aionui-csrf-token";
Expand Down
Loading