feat(workspaces): encrypted write-only credential storage
Workspaces Payload collection with the PAT as a write-only field: access.read () => false strips it from every access-controlled read path, and a field-level beforeChange hook encrypts on write with AES-256-GCM (scrypt key from VEECT_SECRET, random per-value salt + IV, v1 storage format) via node:crypto only. The real repository replaces the phase-1 stub with payload create/findByID; toDomain never maps the credential, and getDecryptedCredential(id) is the single server-side decrypt path for the runner handoff (story 07). Contract suite now covers create, write-only behaviour, and the decrypt path against both the mock and the Payload impl (stub runs the real collection hooks). Missing VEECT_SECRET fails production bind/boot with an actionable message; dev-seed boots without it. Env declared in turbo.json globalEnv + .env.example. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
@@ -47,6 +47,7 @@ describe("connectWorkspaceUseCase", () => {
|
||||
it("strips a credential leaked by a misbehaving repository", async () => {
|
||||
const leakyRepo = {
|
||||
getWorkspace: async () => null,
|
||||
getDecryptedCredential: async () => null,
|
||||
createWorkspace: async () => ({
|
||||
id: "ws-1",
|
||||
name: "Acme Web",
|
||||
@@ -99,6 +100,7 @@ describe("connectWorkspaceUseCase", () => {
|
||||
it("does not audit when the repository write fails", async () => {
|
||||
const failingRepo = {
|
||||
getWorkspace: async () => null,
|
||||
getDecryptedCredential: async () => null,
|
||||
createWorkspace: async () => {
|
||||
throw new Error("boom");
|
||||
},
|
||||
@@ -120,6 +122,7 @@ describe("connectWorkspaceUseCase", () => {
|
||||
it("throws ZodError when the repository returns malformed data", async () => {
|
||||
const malformedRepo = {
|
||||
getWorkspace: async () => null,
|
||||
getDecryptedCredential: async () => null,
|
||||
createWorkspace: async () => ({ id: "", name: "x" }) as never,
|
||||
};
|
||||
const useCase = connectWorkspaceUseCase(malformedRepo);
|
||||
|
||||
Reference in New Issue
Block a user