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}