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
299 lines
10 KiB
Markdown
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
|