Add vault secret manager backend with AES-256-GCM encryption

Secrets are encrypted at rest in the database. The derived encryption
key is held in memory only — on reboot, an authorized user must enter
the master password to unlock. Admins can also manually lock the vault.

- vault_config, vault_category, vault_category_permission, vault_entry tables
- AES-256-GCM encryption with PBKDF2-derived key + per-entry IV
- Master password initialize/unlock/lock/change lifecycle
- Category CRUD with role/user permission model (view/edit/admin)
- Entry CRUD with reveal endpoint (POST to avoid caching)
- Secret values never returned in list/detail responses
- vault.view/edit/admin RBAC permissions seeded
- 19 API integration tests covering full lifecycle
This commit is contained in:
Ryan Moon
2026-03-30 06:11:33 -05:00
parent 748ea59c80
commit 7246587955
8 changed files with 993 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ import { rbacRoutes } from './routes/v1/rbac.js'
import { repairRoutes } from './routes/v1/repairs.js'
import { storageRoutes } from './routes/v1/storage.js'
import { storeRoutes } from './routes/v1/store.js'
import { vaultRoutes } from './routes/v1/vault.js'
import { webdavRoutes } from './routes/webdav/index.js'
import { RbacService } from './services/rbac.service.js'
@@ -72,6 +73,7 @@ export async function buildApp() {
await app.register(repairRoutes, { prefix: '/v1' })
await app.register(storageRoutes, { prefix: '/v1' })
await app.register(storeRoutes, { prefix: '/v1' })
await app.register(vaultRoutes, { prefix: '/v1' })
// Register WebDAV custom HTTP methods before routes
app.addHttpMethod('PROPFIND', { hasBody: true })
app.addHttpMethod('PROPPATCH', { hasBody: true })