From 591be589f0eaa1eafa778f45f5e2c05a59575263 Mon Sep 17 00:00:00 2001 From: Ryan Moon Date: Sun, 29 Mar 2026 12:17:25 -0500 Subject: [PATCH] Add vault password manager planning doc (on-prem only) --- planning/27_Vault_Password_Manager.md | 298 ++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 planning/27_Vault_Password_Manager.md diff --git a/planning/27_Vault_Password_Manager.md b/planning/27_Vault_Password_Manager.md new file mode 100644 index 0000000..a1375f8 --- /dev/null +++ b/planning/27_Vault_Password_Manager.md @@ -0,0 +1,298 @@ +Music Store Management Platform + +Vault — On-Premise Password Manager + +Version 1.0 | Draft + + + +# 1. Overview + +Forte includes a built-in password vault for on-premise deployments. Music stores deal with dozens of credentials — supplier portals, distributor accounts, payment processor dashboards, shipping services, insurance logins, ASCAP/BMI licensing portals — and most store owners are not technical enough to adopt a separate password manager. + +The vault is a simple, encrypted credential store baked into Forte. It encrypts all secrets at rest using AES-256-GCM with a key derived from a master passphrase. The key exists only in server memory and is discarded on restart. + +**On-premise only.** Cloud/hosted deployments would require a more sophisticated key management approach (HSM, envelope encryption) and are out of scope for this design. + + + +# 2. Security Model + +## 2.1 Encryption + +- Algorithm: AES-256-GCM (authenticated encryption) +- Key derivation: PBKDF2 or Argon2id from master passphrase + stored salt +- Each entry encrypted individually (not a single encrypted blob) +- Each entry gets its own random IV/nonce +- Encrypted fields: username, password, notes +- Plaintext fields: entry name, URL, category, tags (searchable) + +## 2.2 Key Lifecycle + +1. **First-time setup:** Admin sets master passphrase via the admin UI. System generates a random salt, derives the encryption key, encrypts a known verification string, and stores the salt + verification ciphertext in the database. +2. **Server start:** Vault is locked. Encryption key is not in memory. All vault API endpoints return `423 Locked`. +3. **Unlock:** Admin enters master passphrase via the UI. System re-derives the key using the stored salt, verifies it against the verification ciphertext. If correct, key is held in memory. Vault endpoints become functional. +4. **Server restart:** Key is lost. Vault returns to locked state. +5. **Lock (manual):** Admin can lock the vault without restarting. Key is wiped from memory. + +## 2.3 What's Encrypted vs. Plaintext + +| Field | Storage | Reason | +|-------|---------|--------| +| `name` | Plaintext | Searchable, displayed in list | +| `url` | Plaintext | Searchable, displayed in list | +| `category` | Plaintext | Filterable | +| `username` | **Encrypted** | Credential | +| `password` | **Encrypted** | Credential | +| `notes` | **Encrypted** | May contain secrets | +| `created_at` | Plaintext | Metadata | +| `updated_at` | Plaintext | Metadata | +| `created_by` | Plaintext | Audit | + +## 2.4 Development Mode + +In development, support a `VAULT_PASSPHRASE` environment variable to auto-unlock on startup. This avoids blocking the dev workflow. Never use this in production. + + + +# 3. Database Schema + +## 3.1 vault_config + +Stores the salt and verification data. One row per company. + +``` +vault_config + id uuid PK + company_id uuid FK → company + salt bytea -- random salt for key derivation + verification bytea -- encrypted known string for passphrase verification + kdf_algorithm varchar(20) -- 'argon2id' or 'pbkdf2' + kdf_iterations integer -- iteration count / cost parameter + created_at timestamptz + updated_at timestamptz +``` + +## 3.2 vault_entry + +Individual credential entries. + +``` +vault_entry + id uuid PK + company_id uuid FK → company + name varchar(255) -- plaintext, searchable + url varchar(500) -- plaintext, searchable + category varchar(100) -- plaintext, filterable + username_enc bytea -- AES-256-GCM encrypted + username_iv bytea -- nonce for username + password_enc bytea -- AES-256-GCM encrypted + password_iv bytea -- nonce for password + notes_enc bytea -- AES-256-GCM encrypted (nullable) + notes_iv bytea -- nonce for notes (nullable) + created_by uuid FK → user + updated_by uuid FK → user + created_at timestamptz + updated_at timestamptz +``` + +## 3.3 vault_access_log + +Append-only audit trail for credential access. + +``` +vault_access_log + id uuid PK + company_id uuid FK → company + entry_id uuid FK → vault_entry + user_id uuid FK → user + action varchar(20) -- 'view', 'copy', 'create', 'update', 'delete' + field varchar(20) -- 'password', 'username', 'notes' (for view/copy) + ip_address varchar(45) + created_at timestamptz +``` + + + +# 4. API Endpoints + +All vault endpoints are prefixed with `/v1/vault`. + +## 4.1 Vault Status & Management + +| Method | Path | Permission | Description | +|--------|------|------------|-------------| +| GET | `/vault/status` | Any authenticated | Returns `{ locked: bool, initialized: bool }` | +| POST | `/vault/initialize` | `vault.admin` | Set master passphrase (first time only) | +| POST | `/vault/unlock` | `vault.admin` | Unlock vault with passphrase | +| POST | `/vault/lock` | `vault.admin` | Lock vault (wipe key from memory) | +| POST | `/vault/change-passphrase` | `vault.admin` | Change master passphrase (re-encrypts all entries) | + +## 4.2 Entries + +All return `423 Locked` when vault is locked. + +| Method | Path | Permission | Description | +|--------|------|------------|-------------| +| GET | `/vault/entries` | `vault.view` | List entries (paginated, name/url/category only) | +| POST | `/vault/entries` | `vault.edit` | Create entry | +| GET | `/vault/entries/:id` | `vault.view` | Get entry (decrypts username, password, notes) | +| PATCH | `/vault/entries/:id` | `vault.edit` | Update entry | +| DELETE | `/vault/entries/:id` | `vault.edit` | Delete entry | +| POST | `/vault/entries/:id/copy` | `vault.view` | Log that user copied a credential | + +## 4.3 Audit + +| Method | Path | Permission | Description | +|--------|------|------------|-------------| +| GET | `/vault/audit` | `vault.admin` | Access log (paginated, filterable by user/entry) | + +## 4.4 Locked Response + +When vault is locked, all entry/audit endpoints return: + +```json +{ + "error": { + "message": "Vault is locked", + "statusCode": 423 + } +} +``` + + + +# 5. Permissions + +New permission domain: `vault` + +| Slug | Description | +|------|-------------| +| `vault.view` | View entry list, decrypt and view credentials | +| `vault.edit` | Create, update, delete entries | +| `vault.admin` | Initialize, unlock, lock, change passphrase, view audit log | + +Permission inheritance: `vault.admin` → `vault.edit` → `vault.view` + +Default role assignments: +- **Admin:** `vault.admin` +- **Manager:** `vault.edit` +- **All others:** No vault access by default + + + +# 6. Frontend + +## 6.1 Sidebar + +New "Vault" item in the Admin section (visible only with `vault.view`). Shows a lock icon when locked. + +## 6.2 Pages + +### Vault List Page + +- Search by name/URL +- Filter by category +- Table columns: Name, URL, Category, Updated +- Click row to view details +- "New Entry" button (requires `vault.edit`) + +### Vault Entry Detail + +- Shows name, URL, category +- Username field with copy button (masked by default, click to reveal) +- Password field with copy button (masked by default, click to reveal) +- Notes (hidden by default, click to reveal) +- Edit/Delete buttons (requires `vault.edit`) +- Every reveal/copy action is logged + +### Vault Locked Page + +- Shown when navigating to vault and it's locked +- Passphrase input + "Unlock" button (requires `vault.admin`) +- If not initialized: "Set Master Passphrase" flow with confirmation input + +### Vault Setup (First Time) + +- Enter master passphrase (min 16 characters) +- Confirm passphrase +- Warning: "This passphrase cannot be recovered. If lost, all vault entries are permanently inaccessible." + +## 6.3 UI Behaviors + +- Password fields use `type="password"` with a toggle eye icon +- Copy-to-clipboard auto-clears after 30 seconds +- Toast notification on copy: "Password copied — clipboard will clear in 30s" +- Reveal auto-hides after 30 seconds +- No browser autocomplete on vault forms (`autocomplete="off"`) + + + +# 7. Operational Considerations + +## 7.1 Passphrase Recovery + +There is no recovery mechanism by design. If the master passphrase is lost, all encrypted entries are permanently inaccessible. The vault_config and vault_entry rows can be deleted to start fresh, but the old data is gone. + +Document this clearly in the UI and in the setup flow. + +## 7.2 Backup + +Database backups include encrypted vault data. Without the master passphrase, the encrypted fields are useless. This is the correct behavior — a stolen backup should not expose credentials. + +## 7.3 Passphrase Change + +Changing the master passphrase requires: +1. Verify old passphrase +2. Derive new key from new passphrase + new salt +3. Decrypt every entry with old key, re-encrypt with new key (transaction) +4. Update vault_config with new salt + verification +5. Replace in-memory key + +This is an expensive operation (scales with entry count) but should be rare. + +## 7.4 Server Restart Behavior + +After restart: +- Vault status: locked +- All vault API calls: 423 +- Everything else in Forte: fully functional +- Admin unlocks when ready — no urgency, no data loss + +## 7.5 Multiple Servers + +If running multiple backend instances behind a load balancer, each instance needs to be unlocked independently (since the key is in-process memory). For multi-instance deployments, consider a shared unlock via Redis (store the derived key in Valkey with a TTL matching the server session). This is an optional enhancement — most on-prem music stores run a single instance. + + + +# 8. Categories (Defaults) + +Pre-populated categories that stores can customize: + +- Suppliers +- Distributors +- Payment Processing +- Shipping & Freight +- Insurance +- Licensing (ASCAP, BMI, SESAC) +- Banking +- Software & Services +- Utilities +- Social Media +- Website & Hosting +- Other + + + +# 9. Implementation Order + +1. Database schema + migration +2. Encryption service (encrypt, decrypt, key derivation, verification) +3. Vault status/initialize/unlock/lock endpoints +4. Entry CRUD endpoints +5. Audit logging +6. Add `vault.*` permissions to RBAC seed +7. Frontend: locked page + unlock flow +8. Frontend: entry list + detail + create/edit +9. Frontend: audit log page +10. Tests: encryption unit tests, API integration tests