Sessions now slide on activity (AUTH_IDLE_MINUTES, default 30) capped by a hard ceiling from original sign-in (AUTH_SESSION_HOURS, meaning changed, default 12 -> proposed 8). login_at carried across reissues so the ceiling survives refreshes; pre-D18 tokens with no login_at fall back to iat. Refresh is throttled (~IDLE_MINUTES/3) so the cookie isn't rewritten on every request. Wired into auth_gate (server/app.py) - no DB hit, reads only the already-validated claims. Verified: 7 unit-level checks (fresh-token expiry, past-ceiling refusal, throttling, mid-session extension, legacy-token fallback both live and expired, idle cutoff itself) all pass, plus the full 27-check smoke suite still passes end to end through the new middleware path.
99 lines
5.7 KiB
Plaintext
99 lines
5.7 KiB
Plaintext
# Copy to .env (dev) or set these in the systemd unit (prod).
|
|
|
|
# PostgreSQL connection (production). Format:
|
|
# postgresql+psycopg://USER:PASSWORD@HOST:5432/DBNAME
|
|
DATABASE_URL=postgresql+psycopg://wpsuite:CHANGE_ME@localhost:5432/wpsuite
|
|
|
|
# If DATABASE_URL is omitted entirely, the API falls back to a local SQLite
|
|
# file (sqlite:///./wpsuite.db) — handy for trying it out without Postgres.
|
|
|
|
# Only needed for CROSS-ORIGIN local development (comma-separated). In
|
|
# production the site is same-origin via NGINX, so leave this unset.
|
|
# CORS_ORIGINS=http://localhost:5500
|
|
|
|
# ── Authentication ────────────────────────────────────────────────────────────
|
|
# There is no local password (D15/D16) — Okta OIDC is the only way in. Sign-in
|
|
# still ends the same way it always did: a signed JWT in an HttpOnly session
|
|
# cookie, which is what the four vars right below this line are for. The five
|
|
# OKTA_* vars after that are what makes the actual sign-in possible; without
|
|
# them the API starts (this is not a hard failure like AUTH_SECRET_KEY), but
|
|
# describe()'s startup log line says so and nobody can sign in.
|
|
|
|
# Secret used to sign session cookies (JWTs), AFTER Okta has confirmed who
|
|
# someone is — this app still decides roles/authorization locally, unchanged
|
|
# by Okta (see server/okta_auth.py). REQUIRED in production: if unset, the API
|
|
# falls back to a random per-process key, so logins reset on every restart and
|
|
# break across multiple gunicorn workers. Generate a strong one:
|
|
# python -c "import secrets; print(secrets.token_urlsafe(48))"
|
|
AUTH_SECRET_KEY=CHANGE_ME_run_the_command_above
|
|
|
|
# D18 (2026-09-23): a session slides on activity, capped by a hard ceiling
|
|
# underneath - not one flat lifetime. Both defaults are proposed, not
|
|
# confirmed against this tenant's actual Okta SSO session policy - if Okta's
|
|
# own session outlives either number, re-auth here is likely a fast redirect
|
|
# rather than a real login screen, so these cost less than they look like.
|
|
#
|
|
# No request for this many minutes ends the session outright.
|
|
# AUTH_IDLE_MINUTES=30
|
|
#
|
|
# The absolute ceiling from original sign-in, regardless of activity - no
|
|
# session outlives this no matter how continuously active it is. Default 8.
|
|
# AUTH_SESSION_HOURS=8
|
|
|
|
# ── Okta OIDC (required — this is the only sign-in path) ───────────────────────
|
|
# The Okta *authorization server* issuer, e.g. https://yourorg.okta.com/oauth2/default
|
|
# or a custom authorization server URL. The API discovers the authorize/token/
|
|
# jwks endpoints from <OKTA_ISSUER>/.well-known/openid-configuration — nothing
|
|
# else about Okta's endpoints is hand-entered.
|
|
OKTA_ISSUER=https://your-org.okta.com/oauth2/default
|
|
|
|
# Client ID and secret from the Okta app integration (Sign-in method: OIDC -
|
|
# Authorization Code, Application type: Web Application). The secret is exactly
|
|
# that — treat it like AUTH_SECRET_KEY, never commit it.
|
|
OKTA_CLIENT_ID=CHANGE_ME
|
|
OKTA_CLIENT_SECRET=CHANGE_ME
|
|
|
|
# Must exactly match a "Sign-in redirect URI" registered on the Okta app
|
|
# integration, scheme and path included, e.g.:
|
|
# https://wp-suite.company.local/api/auth/okta/callback
|
|
OKTA_REDIRECT_URI=CHANGE_ME
|
|
|
|
# Which ID token claim carries this person's directory identity, matched
|
|
# against the local users.username column (server/app.py's okta_callback()).
|
|
# preferred_username is Okta's usual default for an AD-imported user; override
|
|
# it if your security team's Okta configuration uses a different claim (upn,
|
|
# a custom claim, …) — no code change needed, just this value.
|
|
# OKTA_IDENTITY_CLAIM=preferred_username
|
|
|
|
# ── Email notifications (optional) ─────────────────────────────────────────────
|
|
# WP-assignment emails are OFF by default and are turned on from the Admin
|
|
# console (Notifications & email card), where the SMTP host/port/from-address
|
|
# live. The one secret that must NOT be stored in the database — the SMTP
|
|
# password — is read from this environment variable instead. Leave it unset
|
|
# until you have the SMTP details; the toggle stays effectively off (queued
|
|
# notifications are marked "skipped", nothing is sent) until both the toggle is
|
|
# on and SMTP is configured.
|
|
# SMTP_PASSWORD=your-smtp-app-password
|
|
|
|
# ── Micron asset catalog (optional) ───────────────────────────────────────────
|
|
# Backs the searchable asset picker in the work package creator. READ-ONLY: the
|
|
# app only ever runs the single SELECT in server/assets_db.py, so give it a
|
|
# db_datareader login and nothing more.
|
|
#
|
|
# Leave this unset and the suite works normally — the picker reports that no
|
|
# catalog is configured and people type asset tags in by hand.
|
|
#
|
|
# URL-encode special characters in the password (@ = %40, # = %23, / = %2F …).
|
|
# MICRON_DB_URL=mssql+pymssql://readonly_user:PASSWORD@sqlhost.example.com:1433/MicronDB
|
|
#
|
|
# To use pyodbc instead of pymssql you must also add pyodbc to requirements.txt
|
|
# and install the Microsoft ODBC driver in the image:
|
|
# MICRON_DB_URL=mssql+pyodbc://readonly_user:PASSWORD@sqlhost.example.com/MicronDB?driver=ODBC+Driver+18+for+SQL+Server
|
|
#
|
|
# Two things to check when the picker says the catalog is unreachable:
|
|
# 1. The table/column names in ASSET_QUERY (server/assets_db.py) match the real
|
|
# Micron schema — that one constant is the whole schema contract.
|
|
# 2. The api container is on the `outbound` network in docker-compose.yml. The
|
|
# `internal` network has no default gateway, which blocks the VPN as well as
|
|
# the internet.
|