docs: add CLI module design and implementation plan
This commit is contained in:
parent
cd9469342b
commit
a817fdb0f6
2 changed files with 446 additions and 0 deletions
73
docs/plans/2026-02-17-cli-module-design.md
Normal file
73
docs/plans/2026-02-17-cli-module-design.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# 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 |
|
||||
Loading…
Add table
Add a link
Reference in a new issue