From 8d9ea905e9eb185668fc33b63d10dfd332d9e8b7 Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Sat, 7 Nov 2026 14:02:41 -1000 Subject: Check when a session expires Nothing read the expiry signed into a granted session. validate_value treats 0 as never expiring, so a login that signed its session with 0 passed, since the Max-Age the browser is told comes from set_cookie alone. The expiry second itself is now held to be too late, with the clock held still, as a comparison of < instead of <= passed too. --- tests/extensions/test-auth.lua | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/extensions/test-auth.lua b/tests/extensions/test-auth.lua index 2234fac..18017ff 100644 --- a/tests/extensions/test-auth.lua +++ b/tests/extensions/test-auth.lua @@ -259,6 +259,18 @@ h.equals("an expiration of zero never expires", validate_value("redirect", cooki cookie = secure_value("username", "alice", now - 10) h.equals("an expired value is rejected", validate_value("username", cookie), nil) +-- The expiry second itself is already too late, which only a clock held +-- still can show. +local real_time = os.time +os.time = function() + return now +end +h.equals("a value is rejected at its expiry second", + validate_value("username", secure_value("username", "alice", now)), nil) +h.equals("and accepted the second before", + validate_value("username", secure_value("username", "alice", now + 1)), "alice") +os.time = real_time + cookie = secure_value("username", "alice", now + 3600) local flipped = cookie:sub(1, -2) .. (cookie:sub(-1) == "0" and "1" or "0") h.equals("a tampered signature is rejected", validate_value("username", flipped), nil) @@ -441,11 +453,21 @@ load_script() local way_back = secure_value("redirect", "/repo/", 0) +-- A session's expiry is signed into it, apart from the Max-Age the browser +-- is told, and is what still counts once a visitor keeps the cookie longer. +local function session_expiry(token) + return tonumber(token and token:match("^[^|]+|[^|]+|(%d+)|")) +end + +local before = os.time() out = login("Alice", password, way_back) h.contains("a good login redirects back", out, "Status: 302") h.contains("to where the form said", out, "Location: /repo/\n") local granted = out:match("Set%-Cookie: __Host%-cgitauth=([^;]*);") h.equals("and grants a session that verifies", granted and validate_value("username", granted, account_hash), "Alice") +local expires = session_expiry(granted) +h.check("which expires a week after the login", + expires and expires >= before + 604800 and expires <= os.time() + 604800, tostring(expires)) h.equals("and logs nothing", h.error_log(), "") out = login("Alice", "wrong", way_back) @@ -503,10 +525,14 @@ load_script() -- worked out with a fraction is a float, which tostring writes with a ".0". load_script({ { "local session_seconds = 7 * 24 * 60 * 60", "local session_seconds = 1.5 * 24 * 60 * 60" } }) +before = os.time() out = login("Alice", password, way_back) granted = out:match("Set%-Cookie: __Host%-cgitauth=([^;]*);") h.equals("a session a day and a half long still verifies", granted and validate_value("username", granted, account_hash), "Alice") +expires = session_expiry(granted) +h.check("and expires as long after the login", + expires and expires >= before + 129600 and expires <= os.time() + 129600, tostring(expires)) h.contains("and its cookie lasts as many whole seconds", out, "; Max-Age=129600\n") load_script() -- cgit 2.12.1