-- auth.lua — password-based session authentication using argon2id. -- -- Sessions are stored in the `sessions` SQLite table so they survive -- Redbean's per-request fork model (each request gets a fresh process, -- so in-memory tables cannot be shared between requests). -- -- The admin password hash is stored in the `settings` table under the -- key "admin_password_hash". Use setup_admin.lua to set it. local db = require "db" local M = {} local SESSION_TTL = 7 * 24 * 3600 -- 7 days in seconds -- ── Utilities ───────────────────────────────────────────────────────── local function to_hex(s) return (s:gsub(".", function(c) return string.format("%02x", c:byte()) end)) end -- Generate a cryptographically random 64-char hex token (32 bytes). local function random_token() local okg, rnd = pcall(GetRandomBytes, 32) if okg and rnd and #rnd == 32 then return to_hex(rnd) end local ok, bytes = pcall(unix.getrandom, 32) if ok and bytes and #bytes == 32 then return to_hex(bytes) end local f = io.open("/dev/urandom", "rb") if f then local b = f:read(32); f:close() if b and #b == 32 then return to_hex(b) end end error("no source of randomness available") end -- Returns the raw token if the current request carries a live DB session. local function current_token() local tok = GetCookie("session") if not tok or tok == "" then return nil end local row = db.get_session(tok) return row and tok or nil end -- ── Public API ──────────────────────────────────────────────────────── --- Returns true when the current request has a valid admin session. function M.has_session() return current_token() ~= nil end --- Enforce a valid admin session. --- Returns nil when authorised. --- When the session is missing, sets 302 headers and returns a non-empty --- body (" ") so that Fullmoon calls Write(), which flushes SetHeader/ --- SetStatus to the socket. (Returning "" skips Write and the headers --- are never sent.) function M.require_session() if current_token() then return nil end SetStatus(302) SetHeader("Location", "/login") return " " end --- Verify a password and, if correct, create a DB-backed session. --- Returns: token (string), nil on success. --- nil, error_msg on failure. function M.login(password) if not password or password == "" then return nil, "Password is required." end local hash = db.get_setting("admin_password_hash") if not hash or hash == "" then return nil, "Admin account not configured. Run setup_admin.lua first." end local ok, result = pcall(function() return argon2.verify(hash, password) end) if not ok then Log(kLogWarn, "argon2.verify error: " .. tostring(result)) return nil, "Internal error during authentication." end if not result then return nil, "Incorrect password." end -- Clean up stale sessions before creating a new one. pcall(db.prune_sessions) local tok = random_token() db.create_session(tok, os.time() + SESSION_TTL) return tok, nil end --- Invalidate a session (logout). function M.logout(token) if token then pcall(db.delete_session, token) end end return M