code wiki / (root) / nx_session_ttl.nx

nx_session_ttl.nx source

↩ module page · 44 lines · 2826 B

1// nx_session_ttl.nx -- SESSION IDLE + ABSOLUTE TIMEOUT (closes hosting_research gap #8 "session-rotate- 2// timeout", 2/0 CONFIRMED). nx_cms_admin issues a constant-time 32-byte session token (HttpOnly+SameSite= 3// Strict+Secure, 5-strike lockout) but checks ONLY token-match with NO timestamp -> a captured cookie is 4// valid until the daemon restarts. OWASP ASVS V3 requires BOTH an idle timeout (re-auth after inactivity) 5// and an absolute timeout (hard cap on session age), plus token rotation on login. This is the sovereign, 6// DETERMINISTIC (caller-supplies `now`, so a session lifecycle is replayable) timeout primitive the daemon 7// composes. Session record layout (caller-owned, reuses the existing 64-byte sess buffer): 8// [0..32) token [32..40) issued_at_ms (i64) [40..48) last_seen_ms (i64) 9// 10// module: nishi-core.auth.session_ttl capability: ACCESS_CONTROL / auth 11import "nx_syscalls.nx" 12 13const SESS_VALID: i64 = 0 14const SESS_EXPIRED_IDLE: i64 = 1 // inactive too long -> re-auth 15const SESS_EXPIRED_ABS: i64 = 2 // older than the hard cap -> re-auth even if active 16const SESS_IDLE_TTL_MS: i64 = 1800000 // 30 min inactivity (OWASP: 15-30 min for sensitive apps) 17const SESS_ABS_TTL_MS: i64 = 43200000 // 12 h hard cap regardless of activity 18 19// verdict for a session given its timestamps and now (all ms). Absolute cap takes priority over idle so an 20// always-active session still cannot outlive abs_ttl. Boundaries are INCLUSIVE-expire (>= ttl = expired). 21func sess_check(issued_at: i64, last_seen: i64, now: i64, idle_ttl: i64, abs_ttl: i64) -> i64 { 22 if now - issued_at >= abs_ttl { return SESS_EXPIRED_ABS } 23 if now - last_seen >= idle_ttl { return SESS_EXPIRED_IDLE } 24 return SESS_VALID 25} 26 27// timestamps packed into the sess buffer at [32..40) issued_at, [40..48) last_seen. 28func sess_issued(rec: *u8) -> i64 { let p: *i64 = (rec + 32) as *i64; return p[0] } 29func sess_lastseen(rec: *u8) -> i64 { let p: *i64 = (rec + 40) as *i64; return p[0] } 30// stamp a fresh session: caller has already written the new 32-byte token into rec[0..32] (ROTATION = 31// always a new token at login); we set issued_at = last_seen = now. 32func sess_issue(rec: *u8, now: i64) -> i64 { 33 let pi: *i64 = (rec + 32) as *i64; pi[0] = now 34 let pl: *i64 = (rec + 40) as *i64; pl[0] = now 35 return 0 36} 37// slide the idle window forward on an authenticated request. 38func sess_touch(rec: *u8, now: i64) -> i64 { let pl: *i64 = (rec + 40) as *i64; pl[0] = now; return 0 } 39// verify a session record against the production TTLs. VALID also slides the idle window (sess_touch). 40func sess_verify(rec: *u8, now: i64) -> i64 { 41 let v: i64 = sess_check(sess_issued(rec), sess_lastseen(rec), now, SESS_IDLE_TTL_MS, SESS_ABS_TTL_MS) 42 if v == SESS_VALID { sess_touch(rec, now) } 43 return v 44}