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

@@ -0,0 +1,46 @@
-- Vault secret manager tables
CREATE TABLE vault_config (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
master_key_salt TEXT NOT NULL,
master_key_hash TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE vault_category (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
created_by UUID REFERENCES "user"(id),
is_public BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE vault_category_permission (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
category_id UUID NOT NULL REFERENCES vault_category(id) ON DELETE CASCADE,
role_id UUID REFERENCES role(id),
user_id UUID REFERENCES "user"(id),
access_level storage_folder_access NOT NULL DEFAULT 'view',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE vault_entry (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
category_id UUID NOT NULL REFERENCES vault_category(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
username VARCHAR(255),
url VARCHAR(1000),
notes TEXT,
encrypted_value TEXT,
iv TEXT,
created_by UUID REFERENCES "user"(id),
updated_by UUID REFERENCES "user"(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_vault_category_permission_category ON vault_category_permission(category_id);
CREATE INDEX idx_vault_entry_category ON vault_entry(category_id);

View File

@@ -176,6 +176,13 @@
"when": 1774840000000,
"tag": "0024_add_traverse_access_level",
"breakpoints": true
},
{
"idx": 25,
"version": "7",
"when": 1774850000000,
"tag": "0025_vault",
"breakpoints": true
}
]
}