feat: implement B.1 — persist login sessions in SQLite with sliding idle timeout

Sessions move from an in-memory map (absolute 12h TTL) to a `sessions`
table (migration 0002), storing only the SHA-256 of the token. Expiry is
now a sliding idle window (PANEL_SESSION_IDLE_DAYS, default 7, no
absolute cap), extended at most once an hour and never by the
monitoring screens' background polling (GET + HX-Request), so a
forgotten open tab doesn't keep a session alive indefinitely. A login
now survives a container restart or redeploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 23:21:49 +03:00
parent 750a65d5ee
commit 82ec287ba1
14 changed files with 331 additions and 51 deletions
+1
View File
@@ -37,6 +37,7 @@ func serveHTTP(ctx context.Context, cfg config, st *store.Store) error {
TLSCertFile: cfg.tlsCertFile,
OpenDKIMSocket: cfg.opendkimSocket,
JournalSocket: cfg.journalSocket,
SessionIdleDays: cfg.sessionIdleDays,
}, cfg.setupTokenPath)
if err != nil {
return err
+4
View File
@@ -60,6 +60,7 @@ type config struct {
cookieSecure bool
submissionEnabled bool
trustedProxies []*net.IPNet
sessionIdleDays int
// Read-only inputs to the panel's status page: the certificate Postfix
// serves and the two milter sockets it connects to. The defaults mirror
@@ -103,6 +104,9 @@ func loadConfig() config {
// XFF header is trivially forgeable, so it's ignored unless the panel is
// told which proxy to trust.
trustedProxies: parseTrustedProxies(os.Getenv("TRUSTED_PROXY_CIDR")),
// Sliding session idle timeout (spec 7.6.6, plan B.1). Non-positive/invalid
// falls back to the 7-day default inside internal/web.
sessionIdleDays: envInt("PANEL_SESSION_IDLE_DAYS", 7),
tlsCertFile: envDefault("TLS_CERT_FILE", "/etc/postfix/tls/fullchain.pem"),
opendkimSocket: envDefault("OPENDKIM_SOCKET", "/run/opendkim/opendkim.sock"),