porchlight/docs/plans/2026-02-17-cli-module-design.md
2026-02-17 14:09:14 +01:00

73 lines
2.6 KiB
Markdown

# CLI Module Design
## Problem
`pyproject.toml` declares `porchlight = "porchlight.cli:main"` with `typer>=0.15` as a
dependency, but no `cli.py` exists. The app has no CLI entry point for administrative
tasks -- operators must use the Python shell or raw SQL to create invite links or
bootstrap initial users.
## Decision
Create `src/porchlight/cli.py` with two commands: `create-invite` and `initial-admin`.
Skip a `serve` command since Docker/uvicorn handles that already.
## Commands
### `porchlight create-invite <username>`
Generate a magic link registration URL for a new user.
Options:
- `--ttl SECONDS` -- link expiration (default: from settings, 86400s)
- `--note TEXT` -- optional note stored with the link
Behavior:
- Opens SQLite DB via existing `open_db()` context manager
- Creates `MagicLinkService` with `SQLiteMagicLinkRepository`
- Calls `service.create(username=username, note=note, created_by="cli")`
- Prints full URL: `{issuer}/register/{token}`
- Reads `OIDC_OP_ISSUER` env var for base URL (required)
- Reads `OIDC_OP_SQLITE_PATH` for DB path (default: `data/oidc_op.db`)
### `porchlight initial-admin <username>`
Bootstrap the first admin user with a registration link.
Options:
- `--group TEXT` -- groups to assign (default: `["admin", "users"]`), repeatable
Behavior:
- Opens SQLite DB
- Checks if username already exists -- error if so
- Generates unique userid via `generate_unique_userid()`
- Creates user with specified groups
- Creates a magic link so the admin can visit the URL to set up credentials
- Prints full URL: `{issuer}/register/{token}`
## Route change: `/register/{token}`
Modify `register_magic_link` to handle existing users:
- Before creating the user, call `user_repo.get_by_username(link.username)`
- If user exists: skip creation, log them in, redirect to `/manage/credentials?setup=1`
- If user doesn't exist: create as before with `groups=["users"]`
This makes the registration route work for both fresh invites and admin-created users
who need to set up credentials.
## Implementation notes
- Both commands use `asyncio.run()` to call async helpers
- Config loaded via `Settings()` (pydantic-settings reads env vars)
- No new dependencies required -- Typer is already declared
- No schema changes needed
## Files changed
| File | Change |
|------|--------|
| `src/porchlight/cli.py` | New -- Typer app with two commands |
| `src/porchlight/authn/routes.py` | Modify `register_magic_link` for existing users |
| `tests/test_cli.py` | New -- tests for both CLI commands |
| `tests/test_auth_routes/test_register_magic_link.py` | Add test for existing user registration |