Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ EXPLORER_ACCESS_DB="/var/lib/explorer/access.db"
# The __Host- cookie contract requires HTTPS; central mode fails startup if
# this is disabled. AUTH_ALLOW_INSECURE_HTTP exists only for development.
EXPLORER_AUTH_COOKIE_SECURE="1"
# Idle and absolute session lifetimes: 60 minutes and 12 hours.
EXPLORER_SESSION_IDLE_SECONDS="3600"
EXPLORER_SESSION_ABSOLUTE_SECONDS="43200"
# Idle and absolute session lifetimes: 12 hours and 24 hours. Short on
# purpose: expiry is a silent redirect while the central Auth session lives.
EXPLORER_SESSION_IDLE_SECONDS="43200"
EXPLORER_SESSION_ABSOLUTE_SECONDS="86400"

# ── Session and CSRF cookie ─────────────────────────────────────────────
# Signs the central-login state/PKCE transaction plus the short-lived cookie
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ Neither dotenv file is committed. `.env.example` is the annotated template:
| `EXPLORER_ACCESS_DB` | central mode | `/var/lib/explorer/access.db` | Deployment-local email access list and app sessions; contains no passwords. |
| `EXPLORER_AUTH_COOKIE_SECURE` | central mode | `1` | Requires the app-scoped cookie to travel over HTTPS. Keep enabled in production. |
| `EXPLORER_UI_COOKIE_SECURE` | no | `1` | Requires the search/CSRF cookie to travel over HTTPS. Keep enabled in production. |
| `EXPLORER_SESSION_IDLE_SECONDS` | no | `3600` | Central-mode Explorer session idle lifetime (60 minutes). |
| `EXPLORER_SESSION_ABSOLUTE_SECONDS` | no | `43200` | Central-mode Explorer session absolute lifetime (12 hours). |
| `EXPLORER_SESSION_IDLE_SECONDS` | no | `43200` | Central-mode Explorer session idle lifetime (12 hours). |
| `EXPLORER_SESSION_ABSOLUTE_SECONDS` | no | `86400` | Central-mode Explorer session absolute lifetime (24 hours). |
| `EXPLORER_SESSION_SECRET` | central: **yes**; Elcano: recommended | a dev placeholder | Signs central login state and the cookie scoping search jobs to one browser. Generate with `openssl rand -hex 32`. |

### Authentication
Expand Down Expand Up @@ -200,9 +200,12 @@ sudo explorer access revoke user@example.com
Allowed users receive a random 256-bit, app-only session. Only its SHA-256
hash is stored in `/var/lib/explorer/access.db`; the host-only
`__Host-explorer_session` cookie is `Secure`, `HttpOnly`, `SameSite=Lax`, and
scoped to `/`. Sessions expire after 60 minutes idle or 12 hours total; the
scoped to `/`. Sessions expire after 12 hours idle or 24 hours total; the
idle clock is refreshed at most once a minute (the Elcano convention for
service sessions), so a session can end up to a minute early but never late.
Expiry costs the user only a redirect: while their central Auth session (30
days) is live, the handoff signs them back in without a prompt. The short
app limit bounds a stolen cookie and re-checks the account with Auth daily.
Revoking an email immediately invalidates all of that email's Explorer
sessions. Logout is CSRF-protected and revokes only the current Explorer
session. Auth's signed back-channel endpoint also revokes every local session
Expand Down
13 changes: 10 additions & 3 deletions app/central_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,18 @@
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

CENTRAL_AUTH_COOKIE_NAME = "__Host-explorer_session"
DEFAULT_IDLE_SECONDS = 60 * 60
DEFAULT_ABSOLUTE_SECONDS = 12 * 60 * 60
# Application sessions are deliberately short. Expiry costs the user only a
# redirect: the code handoff signs them back in silently while the 30-day
# central Auth session is live. The short limit bounds a stolen Explorer
# cookie and forces a daily re-check with Auth that the account is still
# enabled. One day absolute, 12 hours idle is the Elcano convention for every
# application session (owner decision 2026-09-15; see Auth's
# docs/AUTH_V2_IMPLEMENTATION.md "Application session conventions").
DEFAULT_IDLE_SECONDS = 12 * 60 * 60
DEFAULT_ABSOLUTE_SECONDS = 24 * 60 * 60
# How often a validated session rewrites last_seen_at / idle_expires_at. Every
# request reads the session; only a request more than this long after the
# previous touch writes. The idle limit therefore behaves as "60 minutes minus
# previous touch writes. The idle limit therefore behaves as "12 hours minus
# at most one minute", never longer, and a page's burst of requests costs one
# SQLite write instead of one per request. One minute is the convention for
# every Elcano service with its own sessions (Auth, Explorer, Lens, and
Expand Down
4 changes: 2 additions & 2 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ but lose to `.env`.
| `AUTH_SIGNING_PREVIOUS_PUBKEYS` | no | *(empty)* | Comma-separated prior Ed25519 public keys. Rarely needed now: in central mode Explorer also reads Auth's published `/jwks.json` (cached 10 minutes, refreshed once when a token names an unknown key), so an Auth key rotation needs no env edit here. |
| `EXPLORER_ACCESS_DB` | central mode | `/var/lib/explorer/access.db` | SQLite email access list and Explorer session hashes. Keep it outside the application tree and mode `0600`. |
| `EXPLORER_AUTH_COOKIE_SECURE` | central mode | `1` | Controls `Secure` on `__Host-explorer_session`. Central mode refuses an insecure setting in production. |
| `EXPLORER_SESSION_IDLE_SECONDS` | no | `3600` | Explorer app-session idle lifetime. Activity refreshes this deadline but never extends the absolute deadline. |
| `EXPLORER_SESSION_ABSOLUTE_SECONDS` | no | `43200` | Explorer app-session absolute lifetime. |
| `EXPLORER_SESSION_IDLE_SECONDS` | no | `43200` | Explorer app-session idle lifetime. Activity refreshes this deadline but never extends the absolute deadline. |
| `EXPLORER_SESSION_ABSOLUTE_SECONDS` | no | `86400` | Explorer app-session absolute lifetime. |

Central mode owns no passwords. Manage only the local authorization list:

Expand Down
4 changes: 2 additions & 2 deletions scripts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ AUTH_SIGNING_PUBKEY="${AUTH_SIGNING_PUBKEY:-}"
AUTH_SIGNING_PUBKEY="$(prompt AUTH_SIGNING_PUBKEY "auth service AUTH_SIGNING_PUBKEY, base64 (run 'auth pubkey' on the auth host; blank to set later)" "$AUTH_SIGNING_PUBKEY")"

EXPLORER_ACCESS_DB="${EXPLORER_ACCESS_DB:-/var/lib/explorer/access.db}"
EXPLORER_SESSION_IDLE_SECONDS="${EXPLORER_SESSION_IDLE_SECONDS:-3600}"
EXPLORER_SESSION_ABSOLUTE_SECONDS="${EXPLORER_SESSION_ABSOLUTE_SECONDS:-43200}"
EXPLORER_SESSION_IDLE_SECONDS="${EXPLORER_SESSION_IDLE_SECONDS:-43200}"
EXPLORER_SESSION_ABSOLUTE_SECONDS="${EXPLORER_SESSION_ABSOLUTE_SECONDS:-86400}"

# ── Caddy / TLS intent (actual install happens in step 7) ────────
# Collect answers now so the rest of the run has no surprise prompts.
Expand Down
2 changes: 1 addition & 1 deletion tests/test_central_auth_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def test_callback_issues_app_scoped_cookie_for_allowlisted_email(
assert "Secure" in cookie
assert "SameSite=lax" in cookie
assert "Path=/" in cookie
assert "Max-Age=43200" in cookie
assert "Max-Age=86400" in cookie
assert "Domain=" not in cookie


Expand Down