# PPS Quarantine Manager A web app for operators to manage a Proofpoint Protection Server (PPS) email quarantine: list quarantined mail by folder, read messages (headers, body, attachments, raw headers), and run bulk actions — **Release**, **Report & Release**, **Delete**, **Move** — as **asynchronous background jobs**, so slow PPS calls never block the operator. - **Okta OIDC login** with an allowed-users list and roles (admin/user). - **Admin panel** (`/admin`, admin-only) to manage users, the PPS connection, folders, defaults, the Report & Release pipeline, background jobs, and logs — editing `config.toml` live (comment-preserving), with write-only credentials. - **Per-user preferences** (show limit, default folder, sort) layered over admin defaults. - **Audit log** of every message acted on, attributed to the acting user. ## Quick start ```bash python3 -m venv venv venv/bin/pip install -r requirements.txt cp config.example.toml config.toml # edit: [okta], [pps] creds, [[auth.users]], secret_key venv/bin/python wsgi.py # http://127.0.0.1:8080 ``` Generate a session key: `python -c "import secrets; print(secrets.token_urlsafe(48))"`. **Local dev without Okta:** set `[auth] mode = "static"`, use `[auth.static]` credentials, and run with `PPSQ_CONFIG=/tmp/dev.toml PPSQ_ALLOW_INSECURE=1 venv/bin/python wsgi.py`. See [docs/development.md](docs/development.md). ## How it works `wsgi.py` is the single-process entrypoint: it builds the config store, the SQLite-backed job queue, and the per-user prefs store, starts one background worker thread, and serves via waitress. The browser fires an action and gets an immediate `202`; the worker drains the queue and calls PPS. Jobs survive a restart. **Run as a single process only.** Report & Release is a configurable pipeline of steps (`move → release`, pick either or both) — move files the mail into a report folder, release delivers it from there. It never deletes; Delete is its own action. Configured with checkboxes in the admin panel. Between steps the job waits `step_delay_seconds` for PPS's backend to settle and then re-reads where the messages actually are; while it waits it is parked back on the queue, so other jobs keep running. ## Documentation | Doc | | |-----|--| | [AGENTS.md](AGENTS.md) | **Start here.** Module map + the invariants you must not break. | | [docs/architecture.md](docs/architecture.md) | System design, request/job lifecycle, hot-reload. | | [docs/configuration.md](docs/configuration.md) | Every config key, zones, live-vs-restart. | | [docs/api.md](docs/api.md) | HTTP API reference. | | [docs/okta-setup.md](docs/okta-setup.md) | Okta OIDC app setup + access model. | | [docs/operations.md](docs/operations.md) | Deploy, logs, backup, credential rotation, troubleshooting. | | [docs/development.md](docs/development.md) | Dev setup, running without Okta/PPS, tests. | | [docs/security.md](docs/security.md) | Auth, sessions/CSRF, secrets, known limitations. | ## Tests ```bash venv/bin/pip install -r requirements-dev.txt venv/bin/python -m pytest # ~57 tests, no Okta tenant or real config needed ``` ## Requirements Python 3.11+ (uses `tomllib`/tomlkit; developed on 3.12). Flask, Authlib, tomlkit, requests, waitress — see `requirements.txt` (ranges) / `requirements.lock` (pinned). ## Status Internal production release, evolved from the original PoC. Single-process; secrets are plaintext-on-disk (0600); roles are app-managed, not Okta groups. See [docs/security.md](docs/security.md) for the accepted limitations.