feat(phase-2): workstream 1 — HashiCorp Vault credential storage

Vault is optional — server falls back to bcrypt (Phase 1 behaviour)
when VAULT_ADDR is not set. Full coexistence: existing bcrypt credentials
continue to work until rotated.

Changes:
- src/vault/VaultClient.ts — wraps node-vault KV v2; writeSecret,
  readSecret, verifySecret (constant-time), deleteSecret
- src/db/migrations/005_add_vault_path.sql — vault_path column on credentials
- CredentialRepository — createWithVaultPath, updateVaultPath methods
- CredentialService — routes generate/rotate through Vault when configured;
  bcrypt path unchanged
- OAuth2Service — verifies via Vault when vaultPath set, bcrypt otherwise
- src/app.ts — createVaultClientFromEnv() wired into service layer
- ICredentialRow — vaultPath field added
- docs/devops/environment-variables.md — VAULT_ADDR, VAULT_TOKEN, VAULT_MOUNT
- docs/devops/vault-setup.md — dev quickstart, production config, migration guide
- tests: 33/33 unit tests pass (VaultClient + CredentialService Vault path)
- node-vault + @types/node-vault installed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SentryAgent.ai Developer
2026-03-28 15:02:33 +00:00
parent 7593bfe1c1
commit 90a4addb21
14 changed files with 1064 additions and 36 deletions

View File

@@ -122,9 +122,16 @@ export interface ICredentialWithSecret extends ICredential {
clientSecret: string;
}
/** Database row for a credential, including the bcrypt hash. */
/** Database row for a credential, including the bcrypt hash and optional Vault path. */
export interface ICredentialRow extends ICredential {
/** bcrypt hash of the secret — populated for Phase 1 (bcrypt-only) credentials. */
secretHash: string;
/**
* Vault KV v2 data path for this credential.
* When present, the secret is stored in Vault and secretHash is an empty placeholder.
* When null, bcrypt verification via secretHash is used (Phase 1 behaviour).
*/
vaultPath: string | null;
}
/** Request body for generating or rotating a credential. */