206 lines
5.5 KiB
Markdown
206 lines
5.5 KiB
Markdown
# Porchlight
|
|
|
|
OpenID Connect Provider with user management, built with FastAPI.
|
|
|
|
Porchlight handles user registration (via magic links), credential management
|
|
(passwords and WebAuthn security keys), and issues OIDC tokens for relying
|
|
parties. It uses SQLite for storage, Jinja2 + HTMX for the UI, and idpyoidc
|
|
for the OIDC protocol layer.
|
|
|
|
## Production Setup
|
|
|
|
### Docker (recommended)
|
|
|
|
```bash
|
|
docker compose --profile prod up --build
|
|
```
|
|
|
|
This starts Porchlight on port 8000 with 4 uvicorn workers. Data is persisted
|
|
in a named Docker volume.
|
|
|
|
Set required environment variables in `docker-compose.yml` or via `.env`:
|
|
|
|
```bash
|
|
OIDC_OP_ISSUER=https://auth.example.com
|
|
OIDC_OP_SESSION_SECRET=<random-secret>
|
|
```
|
|
|
|
### Manual
|
|
|
|
Requires Python 3.13+ and [uv](https://docs.astral.sh/uv/).
|
|
|
|
```bash
|
|
uv sync --no-dev
|
|
|
|
OIDC_OP_ISSUER=https://auth.example.com \
|
|
OIDC_OP_SESSION_SECRET=$(python -c "import secrets; print(secrets.token_hex(32))") \
|
|
uv run uvicorn porchlight.app:create_app \
|
|
--factory --host 0.0.0.0 --port 8000 --workers 4
|
|
```
|
|
|
|
### Bootstrap the first admin user
|
|
|
|
After starting the app for the first time, create an admin account:
|
|
|
|
```bash
|
|
OIDC_OP_ISSUER=https://auth.example.com \
|
|
uv run porchlight initial-admin admin
|
|
```
|
|
|
|
This prints a one-time registration URL. Open it in a browser to set up
|
|
credentials (password or security key) for the admin user.
|
|
|
|
### CLI commands
|
|
|
|
Porchlight includes a CLI for administrative tasks. All commands read the same
|
|
`OIDC_OP_*` environment variables as the server.
|
|
|
|
**`porchlight create-invite <username>`** -- Generate a magic link registration
|
|
URL for a new user.
|
|
|
|
```bash
|
|
uv run porchlight create-invite alice
|
|
uv run porchlight create-invite alice --ttl 3600 --note "Onboarding"
|
|
```
|
|
|
|
| Option | Description |
|
|
|---|---|
|
|
| `--ttl SECONDS` | Link expiration (default: `OIDC_OP_INVITE_TTL`, 86400s) |
|
|
| `--note TEXT` | Optional note stored with the link |
|
|
|
|
**`porchlight initial-admin <username>`** -- Bootstrap the first admin user with
|
|
a registration link.
|
|
|
|
```bash
|
|
uv run porchlight initial-admin admin
|
|
uv run porchlight initial-admin admin --group admin --group superusers
|
|
```
|
|
|
|
| Option | Description |
|
|
|---|---|
|
|
| `--group TEXT` | Groups to assign (repeatable, default: `admin`, `users`) |
|
|
|
|
### Configuration
|
|
|
|
All settings are read from environment variables with the `OIDC_OP_` prefix.
|
|
Settings can also be provided via a TOML config file (see below). Environment
|
|
variables always take priority over file values.
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `OIDC_OP_ISSUER` | **required** | OIDC issuer URL (must match public URL) |
|
|
| `OIDC_OP_SESSION_SECRET` | random per process | Session cookie signing secret |
|
|
| `OIDC_OP_DEBUG` | `false` | Enable `/docs` Swagger UI |
|
|
| `OIDC_OP_SQLITE_PATH` | `data/oidc_op.db` | SQLite database path |
|
|
| `OIDC_OP_SIGNING_KEY_PATH` | `data/keys` | OIDC signing key storage |
|
|
| `OIDC_OP_INVITE_TTL` | `86400` | Magic link expiry in seconds |
|
|
| `OIDC_OP_MANAGE_CLIENT_ID` | `manage-app` | Client ID for the management UI |
|
|
| `OIDC_OP_CONFIG_FILE` | `porchlight.toml` | Path to TOML config file |
|
|
|
|
Database migrations run automatically on startup.
|
|
|
|
### Configuration file
|
|
|
|
Copy `porchlight.example.toml` to `porchlight.toml` and edit to suit your
|
|
deployment. The file supports all the same settings as environment variables
|
|
(without the `OIDC_OP_` prefix), plus OIDC client registrations.
|
|
|
|
```toml
|
|
issuer = "https://auth.example.com"
|
|
session_secret = "your-random-secret"
|
|
|
|
[clients.my-webapp]
|
|
client_secret = "change-me-to-a-long-random-string"
|
|
redirect_uris = ["https://app.example.com/callback"]
|
|
response_types = ["code"]
|
|
scope = ["openid", "profile", "email"]
|
|
token_endpoint_auth_method = "client_secret_basic"
|
|
```
|
|
|
|
Each `[clients.<client-id>]` section registers an OIDC Relying Party on
|
|
startup. Only `client_secret` and `redirect_uris` are required; the other
|
|
fields have sensible defaults (`response_types = ["code"]`,
|
|
`scope = ["openid"]`, `token_endpoint_auth_method = "client_secret_basic"`).
|
|
|
|
To use a config file at a different path:
|
|
|
|
```bash
|
|
export OIDC_OP_CONFIG_FILE=/etc/porchlight/config.toml
|
|
```
|
|
|
|
If the config file does not exist, it is silently ignored and all settings
|
|
fall back to environment variables and defaults.
|
|
|
|
## Development Setup
|
|
|
|
### Prerequisites
|
|
|
|
- Python 3.13+
|
|
- [uv](https://docs.astral.sh/uv/)
|
|
- Node.js (for e2e tests)
|
|
|
|
### Getting started
|
|
|
|
```bash
|
|
# Install dependencies (including dev tools)
|
|
uv sync
|
|
|
|
# Start the dev server with hot reload
|
|
OIDC_OP_ISSUER=http://localhost:8000 OIDC_OP_DEBUG=true \
|
|
uv run uvicorn porchlight.app:create_app \
|
|
--factory --host 127.0.0.1 --port 8000 --reload --reload-dir src
|
|
```
|
|
|
|
Or with Docker:
|
|
|
|
```bash
|
|
docker compose --profile dev up --build
|
|
```
|
|
|
|
### Running tests
|
|
|
|
```bash
|
|
# Unit/integration tests
|
|
uv run pytest
|
|
|
|
# Lint and format
|
|
uv run ruff check src/ tests/ --fix
|
|
uv run ruff format src/ tests/
|
|
|
|
# Type checking
|
|
uv run ty check src/
|
|
```
|
|
|
|
### End-to-end browser tests
|
|
|
|
The e2e suite uses Playwright (Node.js) to test all user-facing flows against a
|
|
running instance of the app.
|
|
|
|
```bash
|
|
# One-time setup: install Playwright and Chromium
|
|
cd tests/e2e
|
|
npm install && npm run setup
|
|
cd ../..
|
|
|
|
# Run all e2e tests
|
|
./tests/e2e/run.sh
|
|
|
|
# Run a specific test
|
|
./tests/e2e/run.sh tests/e2e/test_login.js
|
|
|
|
# Run with visible browser (not headless)
|
|
E2E_HEADLESS=0 ./tests/e2e/run.sh
|
|
```
|
|
|
|
The runner starts the app on port 8099, seeds test fixtures into a temporary
|
|
SQLite database, runs all `test_*.js` files, and tears everything down.
|
|
|
|
### Full quality check
|
|
|
|
```bash
|
|
uv run ruff format src/ tests/
|
|
uv run ruff check src/ tests/ --fix
|
|
uv run ty check src/
|
|
uv run pytest
|
|
./tests/e2e/run.sh
|
|
```
|