Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ build/
# Local SQLite (modulr-core default database_path)
data/

# Keymaster — local vault (encrypted blobs still belong only on disk; keeps clones clean)
keymaster_data/
keymaster/keymaster_data/
.modulr/keymaster/

# Testing / tools
.pytest_cache/
.ruff_cache/
Expand Down
42 changes: 25 additions & 17 deletions keymaster/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# Keymaster (local)

Local **Ed25519** identity tool: generate **named** key profiles, **password-protect** the vault, browse via a **loopback web UI**.
Local **Ed25519** identity tool: **encrypted** `vault.json`, **password-protect** the vault, browse via a **loopback web UI**.

**Full plan:** [`plan/keymaster_local_wallet.md`](../plan/keymaster_local_wallet.md)

**Design context:** [`docs/identity_encryption_and_org_policy.md`](../docs/identity_encryption_and_org_policy.md)

## Status

The **web UI shell** is implemented (FastAPI + Jinja + static CSS aligned with the Modulr customer shell). Background matches the Core shell **fireflies** preset (canvas + gradient). With **`prefers-reduced-motion: reduce`**, only the static gradient runs. Vault encryption, key generation, and persistence are **not** wired yet — you will see an “UI preview” banner.
- **Vault file:** `vault.json` under **`%USERPROFILE%\.modulr\keymaster\`** (Windows) or **`~/.modulr/keymaster/`** (macOS/Linux). Override with env **`KEYMASTER_VAULT_PATH`** (full file path) or **`KEYMASTER_VAULT_DIR`** (directory; filename remains `vault.json`).
- **Crypto:** Argon2id key derivation + AES-GCM envelope. Inner JSON holds a `profiles` array (empty after first create until add-identity work lands).
- **Session:** After unlock or create, an **httpOnly** cookie holds an opaque session id; **private keys stay in server RAM** only until **Lock vault** or process exit.
- **UI:** FastAPI + Jinja + static CSS aligned with the Modulr customer shell; **fireflies** background (static gradient only if `prefers-reduced-motion: reduce`).

## Run (development)

Expand All @@ -19,26 +22,31 @@ pip install -e ./keymaster
modulr-keymaster --reload
```

Or from `keymaster/`:
Install dev deps for tests: `pip install -e "./keymaster[dev]"`.

```powershell
pip install -e .
modulr-keymaster --reload
```

Defaults: **127.0.0.1:8765**. Open [http://127.0.0.1:8765](http://127.0.0.1:8765) (redirects to `/unlock`).
Defaults: **127.0.0.1:8765**. Open [http://127.0.0.1:8765](http://127.0.0.1:8765): no vault → **Create vault**; vault present → **Unlock**.

- **`--port`** — listen port
- **`--host`** — bind address (avoid `0.0.0.0`; this tool is meant for loopback only)
- **`--reload`** — auto-reload on code changes

## Layout
## Tests

From `keymaster/`:

```powershell
pytest
```

## Routes

| Path | Screen |
|------|--------|
| `/unlock` | Unlock vault |
| `/setup` | First-run / create vault |
| `/identities` | Dashboard (mock profiles) |
| `/identities/{id}` | Profile detail + copy public key |
| Path | Method | Behavior |
|------|--------|----------|
| `/` | GET | Redirect: no vault → `/setup`, else `/unlock` |
| `/setup` | GET/POST | Create `vault.json` (passphrase ≥ 12 chars, confirm match); then session + redirect `/identities` |
| `/unlock` | GET/POST | Decrypt vault; POST sets session → `/identities` |
| `/lock` | POST | Clear session → `/unlock` |
| `/identities` | GET | Dashboard (requires session) |
| `/identities/{id}` | GET | Profile + public key (requires session) |

Static theme files live under `src/modulr_keymaster/static/`; templates under `templates/`.
Static assets: `src/modulr_keymaster/static/`; templates: `templates/`.
24 changes: 24 additions & 0 deletions keymaster/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,32 @@ readme = "README.md"
requires-python = ">=3.11"
license = { text = "BSL-1.1" }
authors = [{ name = "Modulr" }]

# Runtime dependencies — install only from PyPI (or your own index) after verifying the project page.
# Check: https://pypi.org/project/<name>/ (maintainer, homepage, release history). Pin upgrades deliberately.
#
# Vault / crypto (added for encrypted vault.json):
# cryptography — PyCA; Ed25519 + AES-GCM. Same stack as Modulr.Core (root pyproject). Repo: github.com/pyca/cryptography
# argon2-cffi — Hynek Schlawack; Argon2id KDF for passphrase → key. Repo: github.com/hynek/argon2-cffi
#
# Web / forms:
# python-multipart — Andrew Dunham; parses multipart form bodies (POST from our HTML forms). Repo: github.com/Kludex/python-multipart
# fastapi — Sebastián Ramírez; ASGI framework. Repo: github.com/fastapi/fastapi
# jinja2 — Pallets; server-side HTML templates. Repo: github.com/pallets/jinja
# uvicorn — Encode; ASGI server. Repo: github.com/Kludex/uvicorn
dependencies = [
"argon2-cffi>=23.1.0",
"cryptography>=42.0.0",
"fastapi>=0.115.0",
"jinja2>=3.1.0",
"python-multipart>=0.0.9",
"uvicorn[standard]>=0.30.0",
]

# Dev-only: pytest (tests), httpx (FastAPI TestClient). PyPI: pytest, httpx.
[project.optional-dependencies]
dev = ["pytest>=8.0", "httpx>=0.27.0"]

[project.scripts]
modulr-keymaster = "modulr_keymaster.cli:main"

Expand All @@ -26,6 +46,10 @@ packages = ["src/modulr_keymaster"]
"src/modulr_keymaster/static" = "modulr_keymaster/static"
"src/modulr_keymaster/templates" = "modulr_keymaster/templates"

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]

[tool.ruff]
target-version = "py311"
line-length = 88
Expand Down
Loading
Loading