Files
lunarfront-app/planning/27_Vault_Password_Manager.md
Ryan Moon 9400828f62 Rename Forte to LunarFront, generalize for any small business
Rebrand from Forte (music-store-specific) to LunarFront (any small business):
- Package namespace @forte/* → @lunarfront/*
- Database forte/forte_test → lunarfront/lunarfront_test
- Docker containers, volumes, connection strings
- UI branding, localStorage keys, test emails
- All documentation and planning docs

Generalize music-specific terminology:
- instrumentDescription → itemDescription
- instrumentCount → itemCount
- instrumentType → itemCategory (on service templates)
- New migration 0027_generalize_terminology for column renames
- Seed data updated with generic examples
- RBAC descriptions updated
2026-03-30 08:51:54 -05:00

299 lines
10 KiB
Markdown

Music Store Management Platform
Vault — On-Premise Password Manager
Version 1.0 | Draft
# 1. Overview
LunarFront 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 LunarFront. 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 LunarFront: 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