73 lines
2.6 KiB
Markdown
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 |
|