@shipfox/api-secrets 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +116 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/core/crypto.d.ts +25 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +85 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/dek-manager.d.ts +16 -0
- package/dist/core/dek-manager.d.ts.map +1 -0
- package/dist/core/dek-manager.js +85 -0
- package/dist/core/dek-manager.js.map +1 -0
- package/dist/core/errors.d.ts +53 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +86 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/fingerprint.d.ts +2 -0
- package/dist/core/fingerprint.d.ts.map +1 -0
- package/dist/core/fingerprint.js +9 -0
- package/dist/core/fingerprint.js.map +1 -0
- package/dist/core/index.d.ts +30 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +97 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/key-provider.d.ts +17 -0
- package/dist/core/key-provider.d.ts.map +1 -0
- package/dist/core/key-provider.js +45 -0
- package/dist/core/key-provider.js.map +1 -0
- package/dist/core/local-secret-store.d.ts +19 -0
- package/dist/core/local-secret-store.d.ts.map +1 -0
- package/dist/core/local-secret-store.js +40 -0
- package/dist/core/local-secret-store.js.map +1 -0
- package/dist/core/management.d.ts +37 -0
- package/dist/core/management.d.ts.map +1 -0
- package/dist/core/management.js +403 -0
- package/dist/core/management.js.map +1 -0
- package/dist/core/rotate-kek.d.ts +10 -0
- package/dist/core/rotate-kek.d.ts.map +1 -0
- package/dist/core/rotate-kek.js +94 -0
- package/dist/core/rotate-kek.js.map +1 -0
- package/dist/core/secret-store.d.ts +33 -0
- package/dist/core/secret-store.d.ts.map +1 -0
- package/dist/core/secret-store.js +209 -0
- package/dist/core/secret-store.js.map +1 -0
- package/dist/core/store-resolver.d.ts +18 -0
- package/dist/core/store-resolver.d.ts.map +1 -0
- package/dist/core/store-resolver.js +10 -0
- package/dist/core/store-resolver.js.map +1 -0
- package/dist/core/store-validation.d.ts +11 -0
- package/dist/core/store-validation.d.ts.map +1 -0
- package/dist/core/store-validation.js +30 -0
- package/dist/core/store-validation.js.map +1 -0
- package/dist/core/value-cipher.d.ts +10 -0
- package/dist/core/value-cipher.d.ts.map +1 -0
- package/dist/core/value-cipher.js +22 -0
- package/dist/core/value-cipher.js.map +1 -0
- package/dist/core/variable-store.d.ts +24 -0
- package/dist/core/variable-store.d.ts.map +1 -0
- package/dist/core/variable-store.js +194 -0
- package/dist/core/variable-store.js.map +1 -0
- package/dist/db/cap.d.ts +8 -0
- package/dist/db/cap.d.ts.map +1 -0
- package/dist/db/cap.js +39 -0
- package/dist/db/cap.js.map +1 -0
- package/dist/db/data-keys.d.ts +24 -0
- package/dist/db/data-keys.d.ts.map +1 -0
- package/dist/db/data-keys.js +61 -0
- package/dist/db/data-keys.js.map +1 -0
- package/dist/db/db.d.ts +1254 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +24 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/index.d.ts +13 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +15 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/management.d.ts +50 -0
- package/dist/db/management.d.ts.map +1 -0
- package/dist/db/management.js +127 -0
- package/dist/db/management.js.map +1 -0
- package/dist/db/schema/common.d.ts +3 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +8 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/data-keys.d.ts +103 -0
- package/dist/db/schema/data-keys.d.ts.map +1 -0
- package/dist/db/schema/data-keys.js +24 -0
- package/dist/db/schema/data-keys.js.map +1 -0
- package/dist/db/schema/outbox.d.ts +195 -0
- package/dist/db/schema/outbox.d.ts.map +1 -0
- package/dist/db/schema/outbox.js +5 -0
- package/dist/db/schema/outbox.js.map +1 -0
- package/dist/db/schema/values.d.ts +193 -0
- package/dist/db/schema/values.d.ts.map +1 -0
- package/dist/db/schema/values.js +43 -0
- package/dist/db/schema/values.js.map +1 -0
- package/dist/db/schema/variables.d.ts +175 -0
- package/dist/db/schema/variables.d.ts.map +1 -0
- package/dist/db/schema/variables.js +41 -0
- package/dist/db/schema/variables.js.map +1 -0
- package/dist/db/scope.d.ts +20 -0
- package/dist/db/scope.d.ts.map +1 -0
- package/dist/db/scope.js +19 -0
- package/dist/db/scope.js.map +1 -0
- package/dist/db/values.d.ts +33 -0
- package/dist/db/values.d.ts.map +1 -0
- package/dist/db/values.js +119 -0
- package/dist/db/values.js.map +1 -0
- package/dist/db/variables.d.ts +32 -0
- package/dist/db/variables.d.ts.map +1 -0
- package/dist/db/variables.js +117 -0
- package/dist/db/variables.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +3 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +4 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +39 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +151 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/metrics/service.d.ts +2 -0
- package/dist/metrics/service.d.ts.map +1 -0
- package/dist/metrics/service.js +21 -0
- package/dist/metrics/service.js.map +1 -0
- package/dist/presentation/dto/index.d.ts +2 -0
- package/dist/presentation/dto/index.d.ts.map +1 -0
- package/dist/presentation/dto/index.js +3 -0
- package/dist/presentation/dto/index.js.map +1 -0
- package/dist/presentation/dto/management.d.ts +7 -0
- package/dist/presentation/dto/management.d.ts.map +1 -0
- package/dist/presentation/dto/management.js +32 -0
- package/dist/presentation/dto/management.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-secret.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-secret.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-secret.js +34 -0
- package/dist/presentation/e2eRoutes/create-secret.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-variable.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-variable.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-variable.js +34 -0
- package/dist/presentation/e2eRoutes/create-variable.js.map +1 -0
- package/dist/presentation/e2eRoutes/index.d.ts +3 -0
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/index.js +11 -0
- package/dist/presentation/e2eRoutes/index.js.map +1 -0
- package/dist/presentation/routes/auth.d.ts +16 -0
- package/dist/presentation/routes/auth.d.ts.map +1 -0
- package/dist/presentation/routes/auth.js +42 -0
- package/dist/presentation/routes/auth.js.map +1 -0
- package/dist/presentation/routes/batch-secrets.d.ts +2 -0
- package/dist/presentation/routes/batch-secrets.d.ts.map +1 -0
- package/dist/presentation/routes/batch-secrets.js +44 -0
- package/dist/presentation/routes/batch-secrets.js.map +1 -0
- package/dist/presentation/routes/batch-variables.d.ts +2 -0
- package/dist/presentation/routes/batch-variables.d.ts.map +1 -0
- package/dist/presentation/routes/batch-variables.js +44 -0
- package/dist/presentation/routes/batch-variables.js.map +1 -0
- package/dist/presentation/routes/cursor.d.ts +3 -0
- package/dist/presentation/routes/cursor.d.ts.map +1 -0
- package/dist/presentation/routes/cursor.js +19 -0
- package/dist/presentation/routes/cursor.js.map +1 -0
- package/dist/presentation/routes/delete-secret.d.ts +2 -0
- package/dist/presentation/routes/delete-secret.d.ts.map +1 -0
- package/dist/presentation/routes/delete-secret.js +40 -0
- package/dist/presentation/routes/delete-secret.js.map +1 -0
- package/dist/presentation/routes/delete-variable.d.ts +2 -0
- package/dist/presentation/routes/delete-variable.d.ts.map +1 -0
- package/dist/presentation/routes/delete-variable.js +40 -0
- package/dist/presentation/routes/delete-variable.js.map +1 -0
- package/dist/presentation/routes/errors.d.ts +2 -0
- package/dist/presentation/routes/errors.d.ts.map +1 -0
- package/dist/presentation/routes/errors.js +73 -0
- package/dist/presentation/routes/errors.js.map +1 -0
- package/dist/presentation/routes/get-variable.d.ts +2 -0
- package/dist/presentation/routes/get-variable.d.ts.map +1 -0
- package/dist/presentation/routes/get-variable.js +42 -0
- package/dist/presentation/routes/get-variable.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +3 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +29 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/list-secrets.d.ts +2 -0
- package/dist/presentation/routes/list-secrets.d.ts.map +1 -0
- package/dist/presentation/routes/list-secrets.js +50 -0
- package/dist/presentation/routes/list-secrets.js.map +1 -0
- package/dist/presentation/routes/list-variables.d.ts +2 -0
- package/dist/presentation/routes/list-variables.d.ts.map +1 -0
- package/dist/presentation/routes/list-variables.js +50 -0
- package/dist/presentation/routes/list-variables.js.map +1 -0
- package/dist/presentation/routes/put-secret.d.ts +2 -0
- package/dist/presentation/routes/put-secret.d.ts.map +1 -0
- package/dist/presentation/routes/put-secret.js +52 -0
- package/dist/presentation/routes/put-secret.js.map +1 -0
- package/dist/presentation/routes/put-variable.d.ts +2 -0
- package/dist/presentation/routes/put-variable.d.ts.map +1 -0
- package/dist/presentation/routes/put-variable.js +52 -0
- package/dist/presentation/routes/put-variable.js.map +1 -0
- package/dist/presentation/routes/warnings.d.ts +5 -0
- package/dist/presentation/routes/warnings.d.ts.map +1 -0
- package/dist/presentation/routes/warnings.js +16 -0
- package/dist/presentation/routes/warnings.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_lowly_ogun.sql +59 -0
- package/drizzle/meta/0000_snapshot.json +547 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +77 -0
- package/src/config.test.ts +41 -0
- package/src/config.ts +41 -0
- package/src/core/crypto.test.ts +115 -0
- package/src/core/crypto.ts +105 -0
- package/src/core/dek-manager.test.ts +120 -0
- package/src/core/dek-manager.ts +98 -0
- package/src/core/errors.ts +100 -0
- package/src/core/fingerprint.test.ts +27 -0
- package/src/core/fingerprint.ts +14 -0
- package/src/core/index.ts +153 -0
- package/src/core/key-provider.test.ts +44 -0
- package/src/core/key-provider.ts +70 -0
- package/src/core/local-secret-store.ts +49 -0
- package/src/core/management.test.ts +37 -0
- package/src/core/management.ts +503 -0
- package/src/core/rotate-kek.test.ts +183 -0
- package/src/core/rotate-kek.ts +100 -0
- package/src/core/secret-store.test.ts +301 -0
- package/src/core/secret-store.ts +251 -0
- package/src/core/store-resolver.test.ts +20 -0
- package/src/core/store-resolver.ts +28 -0
- package/src/core/store-validation.ts +43 -0
- package/src/core/value-cipher.ts +26 -0
- package/src/core/variable-store.test.ts +106 -0
- package/src/core/variable-store.ts +232 -0
- package/src/db/cap.ts +58 -0
- package/src/db/data-keys.ts +99 -0
- package/src/db/db.ts +26 -0
- package/src/db/index.ts +45 -0
- package/src/db/management.ts +252 -0
- package/src/db/schema/common.ts +8 -0
- package/src/db/schema/data-keys.ts +31 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/schema/values.ts +74 -0
- package/src/db/schema/variables.ts +71 -0
- package/src/db/scope.ts +42 -0
- package/src/db/values.ts +191 -0
- package/src/db/variables.ts +187 -0
- package/src/index.test.ts +11 -0
- package/src/index.ts +50 -0
- package/src/metrics/index.ts +19 -0
- package/src/metrics/instance.ts +219 -0
- package/src/metrics/service.ts +19 -0
- package/src/presentation/dto/index.ts +1 -0
- package/src/presentation/dto/management.ts +36 -0
- package/src/presentation/e2eRoutes/create-secret.ts +31 -0
- package/src/presentation/e2eRoutes/create-variable.ts +34 -0
- package/src/presentation/e2eRoutes/index.test.ts +94 -0
- package/src/presentation/e2eRoutes/index.ts +8 -0
- package/src/presentation/routes/auth.ts +50 -0
- package/src/presentation/routes/batch-secrets.ts +33 -0
- package/src/presentation/routes/batch-variables.ts +33 -0
- package/src/presentation/routes/cursor.ts +23 -0
- package/src/presentation/routes/delete-secret.ts +26 -0
- package/src/presentation/routes/delete-variable.ts +26 -0
- package/src/presentation/routes/errors.ts +63 -0
- package/src/presentation/routes/get-variable.ts +32 -0
- package/src/presentation/routes/index.ts +29 -0
- package/src/presentation/routes/list-secrets.ts +41 -0
- package/src/presentation/routes/list-variables.ts +41 -0
- package/src/presentation/routes/management.test.ts +452 -0
- package/src/presentation/routes/put-secret.ts +39 -0
- package/src/presentation/routes/put-variable.ts +39 -0
- package/src/presentation/routes/warnings.ts +23 -0
- package/test/env.ts +8 -0
- package/test/globalSetup.ts +18 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +7 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { countSecretValueRows, db, deleteSecretValueRows, lockWorkspaceEntries, upsertSecretValueRows } from '#db/index.js';
|
|
2
|
+
import { normalizedProjectId } from '#db/scope.js';
|
|
3
|
+
import { classifySecretsOperationError, operationScope, recordSecretsEntriesMutated, recordSecretsOperation } from '#metrics/instance.js';
|
|
4
|
+
import { fingerprintSecretValue } from './fingerprint.js';
|
|
5
|
+
import { assertWorkspaceCap, validateNamespace, validateSecretKeys, validateValueBytes } from './store-validation.js';
|
|
6
|
+
import { encryptSecretValue } from './value-cipher.js';
|
|
7
|
+
export function createSecretStoreApi(params) {
|
|
8
|
+
return {
|
|
9
|
+
async getSecret (input) {
|
|
10
|
+
const startedAt = Date.now();
|
|
11
|
+
const scope = operationScope(input);
|
|
12
|
+
try {
|
|
13
|
+
const namespace = input.namespace ?? '';
|
|
14
|
+
validateNamespace(namespace);
|
|
15
|
+
validateSecretKeys([
|
|
16
|
+
input.key
|
|
17
|
+
]);
|
|
18
|
+
const value = await params.resolveSecretStore(input.store).getSecret({
|
|
19
|
+
...input,
|
|
20
|
+
namespace
|
|
21
|
+
});
|
|
22
|
+
recordSecretsOperation({
|
|
23
|
+
resource: 'secret',
|
|
24
|
+
operation: 'get',
|
|
25
|
+
surface: 'internal',
|
|
26
|
+
scope,
|
|
27
|
+
outcome: value === null ? 'not_found' : 'success',
|
|
28
|
+
durationMs: Date.now() - startedAt
|
|
29
|
+
});
|
|
30
|
+
return value;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
recordSecretsOperation({
|
|
33
|
+
resource: 'secret',
|
|
34
|
+
operation: 'get',
|
|
35
|
+
surface: 'internal',
|
|
36
|
+
scope,
|
|
37
|
+
outcome: classifySecretsOperationError(error),
|
|
38
|
+
durationMs: Date.now() - startedAt
|
|
39
|
+
});
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
async getSecretsByNamespace (input) {
|
|
44
|
+
const startedAt = Date.now();
|
|
45
|
+
const scope = operationScope(input);
|
|
46
|
+
try {
|
|
47
|
+
const namespace = input.namespace ?? '';
|
|
48
|
+
validateNamespace(namespace);
|
|
49
|
+
const values = await params.resolveSecretStore(input.store).getSecretsByNamespace({
|
|
50
|
+
...input,
|
|
51
|
+
namespace
|
|
52
|
+
});
|
|
53
|
+
recordSecretsOperation({
|
|
54
|
+
resource: 'secret',
|
|
55
|
+
operation: 'get_namespace',
|
|
56
|
+
surface: 'internal',
|
|
57
|
+
scope,
|
|
58
|
+
outcome: 'success',
|
|
59
|
+
durationMs: Date.now() - startedAt
|
|
60
|
+
});
|
|
61
|
+
return values;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
recordSecretsOperation({
|
|
64
|
+
resource: 'secret',
|
|
65
|
+
operation: 'get_namespace',
|
|
66
|
+
surface: 'internal',
|
|
67
|
+
scope,
|
|
68
|
+
outcome: classifySecretsOperationError(error),
|
|
69
|
+
durationMs: Date.now() - startedAt
|
|
70
|
+
});
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
async setSecrets (input) {
|
|
75
|
+
const startedAt = Date.now();
|
|
76
|
+
const scope = operationScope(input);
|
|
77
|
+
try {
|
|
78
|
+
const namespace = input.namespace ?? '';
|
|
79
|
+
const entries = Object.entries(input.values);
|
|
80
|
+
validateNamespace(namespace);
|
|
81
|
+
validateSecretKeys(entries.map(([key])=>key));
|
|
82
|
+
validateValueBytes(entries.map(([, value])=>value));
|
|
83
|
+
if (entries.length === 0) {
|
|
84
|
+
recordSecretsOperation({
|
|
85
|
+
resource: 'secret',
|
|
86
|
+
operation: 'set',
|
|
87
|
+
surface: 'internal',
|
|
88
|
+
scope,
|
|
89
|
+
outcome: 'success',
|
|
90
|
+
durationMs: Date.now() - startedAt
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const dek = await params.dekManager.getPlaintextDek(input.workspaceId);
|
|
95
|
+
const projectId = normalizedProjectId(input);
|
|
96
|
+
const rows = entries.map(([key, value])=>({
|
|
97
|
+
workspaceId: input.workspaceId,
|
|
98
|
+
projectId,
|
|
99
|
+
namespace,
|
|
100
|
+
key,
|
|
101
|
+
ciphertext: encryptSecretValue({
|
|
102
|
+
dek,
|
|
103
|
+
workspaceId: input.workspaceId,
|
|
104
|
+
scope: {
|
|
105
|
+
projectId
|
|
106
|
+
},
|
|
107
|
+
namespace,
|
|
108
|
+
key,
|
|
109
|
+
value
|
|
110
|
+
}),
|
|
111
|
+
fingerprint: fingerprintSecretValue(value, dek),
|
|
112
|
+
lastEditedBy: input.editedBy ?? null
|
|
113
|
+
}));
|
|
114
|
+
let existingEntries = 0;
|
|
115
|
+
await db().transaction(async (tx)=>{
|
|
116
|
+
await lockWorkspaceEntries(input.workspaceId, tx);
|
|
117
|
+
existingEntries = await countSecretValueRows({
|
|
118
|
+
workspaceId: input.workspaceId,
|
|
119
|
+
projectId,
|
|
120
|
+
namespace,
|
|
121
|
+
keys: entries.map(([key])=>key)
|
|
122
|
+
}, tx);
|
|
123
|
+
await assertWorkspaceCap({
|
|
124
|
+
workspaceId: input.workspaceId,
|
|
125
|
+
namespace,
|
|
126
|
+
incomingEntries: entries.length - existingEntries,
|
|
127
|
+
tx
|
|
128
|
+
});
|
|
129
|
+
await upsertSecretValueRows(rows, tx);
|
|
130
|
+
});
|
|
131
|
+
recordSecretsEntriesMutated({
|
|
132
|
+
resource: 'secret',
|
|
133
|
+
operation: 'set',
|
|
134
|
+
effect: 'created',
|
|
135
|
+
surface: 'internal',
|
|
136
|
+
count: entries.length - existingEntries
|
|
137
|
+
});
|
|
138
|
+
recordSecretsEntriesMutated({
|
|
139
|
+
resource: 'secret',
|
|
140
|
+
operation: 'set',
|
|
141
|
+
effect: 'updated',
|
|
142
|
+
surface: 'internal',
|
|
143
|
+
count: existingEntries
|
|
144
|
+
});
|
|
145
|
+
recordSecretsOperation({
|
|
146
|
+
resource: 'secret',
|
|
147
|
+
operation: 'set',
|
|
148
|
+
surface: 'internal',
|
|
149
|
+
scope,
|
|
150
|
+
outcome: 'success',
|
|
151
|
+
durationMs: Date.now() - startedAt
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
recordSecretsOperation({
|
|
155
|
+
resource: 'secret',
|
|
156
|
+
operation: 'set',
|
|
157
|
+
surface: 'internal',
|
|
158
|
+
scope,
|
|
159
|
+
outcome: classifySecretsOperationError(error),
|
|
160
|
+
durationMs: Date.now() - startedAt
|
|
161
|
+
});
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
async deleteSecrets (input) {
|
|
166
|
+
const startedAt = Date.now();
|
|
167
|
+
const scope = operationScope(input);
|
|
168
|
+
try {
|
|
169
|
+
const namespace = input.namespace ?? '';
|
|
170
|
+
validateNamespace(namespace);
|
|
171
|
+
if (input.keys) validateSecretKeys(input.keys);
|
|
172
|
+
const deleted = await deleteSecretValueRows({
|
|
173
|
+
workspaceId: input.workspaceId,
|
|
174
|
+
projectId: normalizedProjectId(input),
|
|
175
|
+
namespace,
|
|
176
|
+
keys: input.keys
|
|
177
|
+
});
|
|
178
|
+
recordSecretsEntriesMutated({
|
|
179
|
+
resource: 'secret',
|
|
180
|
+
operation: 'delete',
|
|
181
|
+
effect: 'deleted',
|
|
182
|
+
surface: 'internal',
|
|
183
|
+
count: deleted
|
|
184
|
+
});
|
|
185
|
+
recordSecretsOperation({
|
|
186
|
+
resource: 'secret',
|
|
187
|
+
operation: 'delete',
|
|
188
|
+
surface: 'internal',
|
|
189
|
+
scope,
|
|
190
|
+
outcome: 'success',
|
|
191
|
+
durationMs: Date.now() - startedAt
|
|
192
|
+
});
|
|
193
|
+
return deleted;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
recordSecretsOperation({
|
|
196
|
+
resource: 'secret',
|
|
197
|
+
operation: 'delete',
|
|
198
|
+
surface: 'internal',
|
|
199
|
+
scope,
|
|
200
|
+
outcome: classifySecretsOperationError(error),
|
|
201
|
+
durationMs: Date.now() - startedAt
|
|
202
|
+
});
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
//# sourceMappingURL=secret-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/secret-store.ts"],"sourcesContent":["import {\n countSecretValueRows,\n db,\n deleteSecretValueRows,\n lockWorkspaceEntries,\n type StoreScope,\n upsertSecretValueRows,\n} from '#db/index.js';\nimport {normalizedProjectId} from '#db/scope.js';\nimport {\n classifySecretsOperationError,\n operationScope,\n recordSecretsEntriesMutated,\n recordSecretsOperation,\n} from '#metrics/instance.js';\nimport type {DekManager} from './dek-manager.js';\nimport {fingerprintSecretValue} from './fingerprint.js';\nimport type {SecretStoreProvider} from './store-resolver.js';\nimport {\n assertWorkspaceCap,\n validateNamespace,\n validateSecretKeys,\n validateValueBytes,\n} from './store-validation.js';\nimport {encryptSecretValue} from './value-cipher.js';\n\nexport interface SetSecretsParams extends StoreScope {\n workspaceId: string;\n namespace?: string | undefined;\n values: Record<string, string>;\n editedBy?: string | null | undefined;\n}\n\nexport interface DeleteSecretsParams extends StoreScope {\n workspaceId: string;\n namespace?: string | undefined;\n keys?: string[] | undefined;\n}\n\nexport function createSecretStoreApi(params: {\n dekManager: DekManager;\n resolveSecretStore: (name?: string | undefined) => SecretStoreProvider;\n}) {\n return {\n async getSecret(\n input: StoreScope & {\n workspaceId: string;\n namespace?: string | undefined;\n key: string;\n store?: string;\n },\n ) {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const namespace = input.namespace ?? '';\n validateNamespace(namespace);\n validateSecretKeys([input.key]);\n const value = await params.resolveSecretStore(input.store).getSecret({...input, namespace});\n recordSecretsOperation({\n resource: 'secret',\n operation: 'get',\n surface: 'internal',\n scope,\n outcome: value === null ? 'not_found' : 'success',\n durationMs: Date.now() - startedAt,\n });\n return value;\n } catch (error) {\n recordSecretsOperation({\n resource: 'secret',\n operation: 'get',\n surface: 'internal',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n async getSecretsByNamespace(\n input: StoreScope & {workspaceId: string; namespace?: string | undefined; store?: string},\n ) {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const namespace = input.namespace ?? '';\n validateNamespace(namespace);\n const values = await params\n .resolveSecretStore(input.store)\n .getSecretsByNamespace({...input, namespace});\n recordSecretsOperation({\n resource: 'secret',\n operation: 'get_namespace',\n surface: 'internal',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return values;\n } catch (error) {\n recordSecretsOperation({\n resource: 'secret',\n operation: 'get_namespace',\n surface: 'internal',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n async setSecrets(input: SetSecretsParams): Promise<void> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const namespace = input.namespace ?? '';\n const entries = Object.entries(input.values);\n validateNamespace(namespace);\n validateSecretKeys(entries.map(([key]) => key));\n validateValueBytes(entries.map(([, value]) => value));\n if (entries.length === 0) {\n recordSecretsOperation({\n resource: 'secret',\n operation: 'set',\n surface: 'internal',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return;\n }\n\n const dek = await params.dekManager.getPlaintextDek(input.workspaceId);\n const projectId = normalizedProjectId(input);\n const rows = entries.map(([key, value]) => ({\n workspaceId: input.workspaceId,\n projectId,\n namespace,\n key,\n ciphertext: encryptSecretValue({\n dek,\n workspaceId: input.workspaceId,\n scope: {projectId},\n namespace,\n key,\n value,\n }),\n fingerprint: fingerprintSecretValue(value, dek),\n lastEditedBy: input.editedBy ?? null,\n }));\n\n let existingEntries = 0;\n await db().transaction(async (tx) => {\n await lockWorkspaceEntries(input.workspaceId, tx);\n existingEntries = await countSecretValueRows(\n {\n workspaceId: input.workspaceId,\n projectId,\n namespace,\n keys: entries.map(([key]) => key),\n },\n tx,\n );\n await assertWorkspaceCap({\n workspaceId: input.workspaceId,\n namespace,\n incomingEntries: entries.length - existingEntries,\n tx,\n });\n await upsertSecretValueRows(rows, tx);\n });\n\n recordSecretsEntriesMutated({\n resource: 'secret',\n operation: 'set',\n effect: 'created',\n surface: 'internal',\n count: entries.length - existingEntries,\n });\n recordSecretsEntriesMutated({\n resource: 'secret',\n operation: 'set',\n effect: 'updated',\n surface: 'internal',\n count: existingEntries,\n });\n recordSecretsOperation({\n resource: 'secret',\n operation: 'set',\n surface: 'internal',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n } catch (error) {\n recordSecretsOperation({\n resource: 'secret',\n operation: 'set',\n surface: 'internal',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n async deleteSecrets(input: DeleteSecretsParams): Promise<number> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const namespace = input.namespace ?? '';\n validateNamespace(namespace);\n if (input.keys) validateSecretKeys(input.keys);\n\n const deleted = await deleteSecretValueRows({\n workspaceId: input.workspaceId,\n projectId: normalizedProjectId(input),\n namespace,\n keys: input.keys,\n });\n recordSecretsEntriesMutated({\n resource: 'secret',\n operation: 'delete',\n effect: 'deleted',\n surface: 'internal',\n count: deleted,\n });\n recordSecretsOperation({\n resource: 'secret',\n operation: 'delete',\n surface: 'internal',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return deleted;\n } catch (error) {\n recordSecretsOperation({\n resource: 'secret',\n operation: 'delete',\n surface: 'internal',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n },\n };\n}\n"],"names":["countSecretValueRows","db","deleteSecretValueRows","lockWorkspaceEntries","upsertSecretValueRows","normalizedProjectId","classifySecretsOperationError","operationScope","recordSecretsEntriesMutated","recordSecretsOperation","fingerprintSecretValue","assertWorkspaceCap","validateNamespace","validateSecretKeys","validateValueBytes","encryptSecretValue","createSecretStoreApi","params","getSecret","input","startedAt","Date","now","scope","namespace","key","value","resolveSecretStore","store","resource","operation","surface","outcome","durationMs","error","getSecretsByNamespace","values","setSecrets","entries","Object","map","length","dek","dekManager","getPlaintextDek","workspaceId","projectId","rows","ciphertext","fingerprint","lastEditedBy","editedBy","existingEntries","transaction","tx","keys","incomingEntries","effect","count","deleteSecrets","deleted"],"mappings":"AAAA,SACEA,oBAAoB,EACpBC,EAAE,EACFC,qBAAqB,EACrBC,oBAAoB,EAEpBC,qBAAqB,QAChB,eAAe;AACtB,SAAQC,mBAAmB,QAAO,eAAe;AACjD,SACEC,6BAA6B,EAC7BC,cAAc,EACdC,2BAA2B,EAC3BC,sBAAsB,QACjB,uBAAuB;AAE9B,SAAQC,sBAAsB,QAAO,mBAAmB;AAExD,SACEC,kBAAkB,EAClBC,iBAAiB,EACjBC,kBAAkB,EAClBC,kBAAkB,QACb,wBAAwB;AAC/B,SAAQC,kBAAkB,QAAO,oBAAoB;AAerD,OAAO,SAASC,qBAAqBC,MAGpC;IACC,OAAO;QACL,MAAMC,WACJC,KAKC;YAED,MAAMC,YAAYC,KAAKC,GAAG;YAC1B,MAAMC,QAAQhB,eAAeY;YAC7B,IAAI;gBACF,MAAMK,YAAYL,MAAMK,SAAS,IAAI;gBACrCZ,kBAAkBY;gBAClBX,mBAAmB;oBAACM,MAAMM,GAAG;iBAAC;gBAC9B,MAAMC,QAAQ,MAAMT,OAAOU,kBAAkB,CAACR,MAAMS,KAAK,EAAEV,SAAS,CAAC;oBAAC,GAAGC,KAAK;oBAAEK;gBAAS;gBACzFf,uBAAuB;oBACrBoB,UAAU;oBACVC,WAAW;oBACXC,SAAS;oBACTR;oBACAS,SAASN,UAAU,OAAO,cAAc;oBACxCO,YAAYZ,KAAKC,GAAG,KAAKF;gBAC3B;gBACA,OAAOM;YACT,EAAE,OAAOQ,OAAO;gBACdzB,uBAAuB;oBACrBoB,UAAU;oBACVC,WAAW;oBACXC,SAAS;oBACTR;oBACAS,SAAS1B,8BAA8B4B;oBACvCD,YAAYZ,KAAKC,GAAG,KAAKF;gBAC3B;gBACA,MAAMc;YACR;QACF;QACA,MAAMC,uBACJhB,KAAyF;YAEzF,MAAMC,YAAYC,KAAKC,GAAG;YAC1B,MAAMC,QAAQhB,eAAeY;YAC7B,IAAI;gBACF,MAAMK,YAAYL,MAAMK,SAAS,IAAI;gBACrCZ,kBAAkBY;gBAClB,MAAMY,SAAS,MAAMnB,OAClBU,kBAAkB,CAACR,MAAMS,KAAK,EAC9BO,qBAAqB,CAAC;oBAAC,GAAGhB,KAAK;oBAAEK;gBAAS;gBAC7Cf,uBAAuB;oBACrBoB,UAAU;oBACVC,WAAW;oBACXC,SAAS;oBACTR;oBACAS,SAAS;oBACTC,YAAYZ,KAAKC,GAAG,KAAKF;gBAC3B;gBACA,OAAOgB;YACT,EAAE,OAAOF,OAAO;gBACdzB,uBAAuB;oBACrBoB,UAAU;oBACVC,WAAW;oBACXC,SAAS;oBACTR;oBACAS,SAAS1B,8BAA8B4B;oBACvCD,YAAYZ,KAAKC,GAAG,KAAKF;gBAC3B;gBACA,MAAMc;YACR;QACF;QACA,MAAMG,YAAWlB,KAAuB;YACtC,MAAMC,YAAYC,KAAKC,GAAG;YAC1B,MAAMC,QAAQhB,eAAeY;YAC7B,IAAI;gBACF,MAAMK,YAAYL,MAAMK,SAAS,IAAI;gBACrC,MAAMc,UAAUC,OAAOD,OAAO,CAACnB,MAAMiB,MAAM;gBAC3CxB,kBAAkBY;gBAClBX,mBAAmByB,QAAQE,GAAG,CAAC,CAAC,CAACf,IAAI,GAAKA;gBAC1CX,mBAAmBwB,QAAQE,GAAG,CAAC,CAAC,GAAGd,MAAM,GAAKA;gBAC9C,IAAIY,QAAQG,MAAM,KAAK,GAAG;oBACxBhC,uBAAuB;wBACrBoB,UAAU;wBACVC,WAAW;wBACXC,SAAS;wBACTR;wBACAS,SAAS;wBACTC,YAAYZ,KAAKC,GAAG,KAAKF;oBAC3B;oBACA;gBACF;gBAEA,MAAMsB,MAAM,MAAMzB,OAAO0B,UAAU,CAACC,eAAe,CAACzB,MAAM0B,WAAW;gBACrE,MAAMC,YAAYzC,oBAAoBc;gBACtC,MAAM4B,OAAOT,QAAQE,GAAG,CAAC,CAAC,CAACf,KAAKC,MAAM,GAAM,CAAA;wBAC1CmB,aAAa1B,MAAM0B,WAAW;wBAC9BC;wBACAtB;wBACAC;wBACAuB,YAAYjC,mBAAmB;4BAC7B2B;4BACAG,aAAa1B,MAAM0B,WAAW;4BAC9BtB,OAAO;gCAACuB;4BAAS;4BACjBtB;4BACAC;4BACAC;wBACF;wBACAuB,aAAavC,uBAAuBgB,OAAOgB;wBAC3CQ,cAAc/B,MAAMgC,QAAQ,IAAI;oBAClC,CAAA;gBAEA,IAAIC,kBAAkB;gBACtB,MAAMnD,KAAKoD,WAAW,CAAC,OAAOC;oBAC5B,MAAMnD,qBAAqBgB,MAAM0B,WAAW,EAAES;oBAC9CF,kBAAkB,MAAMpD,qBACtB;wBACE6C,aAAa1B,MAAM0B,WAAW;wBAC9BC;wBACAtB;wBACA+B,MAAMjB,QAAQE,GAAG,CAAC,CAAC,CAACf,IAAI,GAAKA;oBAC/B,GACA6B;oBAEF,MAAM3C,mBAAmB;wBACvBkC,aAAa1B,MAAM0B,WAAW;wBAC9BrB;wBACAgC,iBAAiBlB,QAAQG,MAAM,GAAGW;wBAClCE;oBACF;oBACA,MAAMlD,sBAAsB2C,MAAMO;gBACpC;gBAEA9C,4BAA4B;oBAC1BqB,UAAU;oBACVC,WAAW;oBACX2B,QAAQ;oBACR1B,SAAS;oBACT2B,OAAOpB,QAAQG,MAAM,GAAGW;gBAC1B;gBACA5C,4BAA4B;oBAC1BqB,UAAU;oBACVC,WAAW;oBACX2B,QAAQ;oBACR1B,SAAS;oBACT2B,OAAON;gBACT;gBACA3C,uBAAuB;oBACrBoB,UAAU;oBACVC,WAAW;oBACXC,SAAS;oBACTR;oBACAS,SAAS;oBACTC,YAAYZ,KAAKC,GAAG,KAAKF;gBAC3B;YACF,EAAE,OAAOc,OAAO;gBACdzB,uBAAuB;oBACrBoB,UAAU;oBACVC,WAAW;oBACXC,SAAS;oBACTR;oBACAS,SAAS1B,8BAA8B4B;oBACvCD,YAAYZ,KAAKC,GAAG,KAAKF;gBAC3B;gBACA,MAAMc;YACR;QACF;QACA,MAAMyB,eAAcxC,KAA0B;YAC5C,MAAMC,YAAYC,KAAKC,GAAG;YAC1B,MAAMC,QAAQhB,eAAeY;YAC7B,IAAI;gBACF,MAAMK,YAAYL,MAAMK,SAAS,IAAI;gBACrCZ,kBAAkBY;gBAClB,IAAIL,MAAMoC,IAAI,EAAE1C,mBAAmBM,MAAMoC,IAAI;gBAE7C,MAAMK,UAAU,MAAM1D,sBAAsB;oBAC1C2C,aAAa1B,MAAM0B,WAAW;oBAC9BC,WAAWzC,oBAAoBc;oBAC/BK;oBACA+B,MAAMpC,MAAMoC,IAAI;gBAClB;gBACA/C,4BAA4B;oBAC1BqB,UAAU;oBACVC,WAAW;oBACX2B,QAAQ;oBACR1B,SAAS;oBACT2B,OAAOE;gBACT;gBACAnD,uBAAuB;oBACrBoB,UAAU;oBACVC,WAAW;oBACXC,SAAS;oBACTR;oBACAS,SAAS;oBACTC,YAAYZ,KAAKC,GAAG,KAAKF;gBAC3B;gBACA,OAAOwC;YACT,EAAE,OAAO1B,OAAO;gBACdzB,uBAAuB;oBACrBoB,UAAU;oBACVC,WAAW;oBACXC,SAAS;oBACTR;oBACAS,SAAS1B,8BAA8B4B;oBACvCD,YAAYZ,KAAKC,GAAG,KAAKF;gBAC3B;gBACA,MAAMc;YACR;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const BUILTIN_LOCAL_STORE = "local";
|
|
2
|
+
export interface GetSecretParams {
|
|
3
|
+
workspaceId: string;
|
|
4
|
+
projectId?: string | null | undefined;
|
|
5
|
+
namespace: string;
|
|
6
|
+
key: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GetSecretsByNamespaceParams {
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
projectId?: string | null | undefined;
|
|
11
|
+
namespace: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SecretStoreProvider {
|
|
14
|
+
getSecret(params: GetSecretParams): Promise<string | null>;
|
|
15
|
+
getSecretsByNamespace(params: GetSecretsByNamespaceParams): Promise<Record<string, string>>;
|
|
16
|
+
}
|
|
17
|
+
export declare function createSecretStoreResolver(localProvider: SecretStoreProvider): (name?: string) => SecretStoreProvider;
|
|
18
|
+
//# sourceMappingURL=store-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-resolver.d.ts","sourceRoot":"","sources":["../../src/core/store-resolver.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,qBAAqB,CAAC,MAAM,EAAE,2BAA2B,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC7F;AAED,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,mBAAmB,IACvC,aAA0B,KAAG,mBAAmB,CAIpF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { UnknownSecretStoreError } from './errors.js';
|
|
2
|
+
export const BUILTIN_LOCAL_STORE = 'local';
|
|
3
|
+
export function createSecretStoreResolver(localProvider) {
|
|
4
|
+
return function resolveSecretStore(name = BUILTIN_LOCAL_STORE) {
|
|
5
|
+
if (name === BUILTIN_LOCAL_STORE) return localProvider;
|
|
6
|
+
throw new UnknownSecretStoreError(name);
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=store-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/store-resolver.ts"],"sourcesContent":["import {UnknownSecretStoreError} from './errors.js';\n\nexport const BUILTIN_LOCAL_STORE = 'local';\n\nexport interface GetSecretParams {\n workspaceId: string;\n projectId?: string | null | undefined;\n namespace: string;\n key: string;\n}\n\nexport interface GetSecretsByNamespaceParams {\n workspaceId: string;\n projectId?: string | null | undefined;\n namespace: string;\n}\n\nexport interface SecretStoreProvider {\n getSecret(params: GetSecretParams): Promise<string | null>;\n getSecretsByNamespace(params: GetSecretsByNamespaceParams): Promise<Record<string, string>>;\n}\n\nexport function createSecretStoreResolver(localProvider: SecretStoreProvider) {\n return function resolveSecretStore(name = BUILTIN_LOCAL_STORE): SecretStoreProvider {\n if (name === BUILTIN_LOCAL_STORE) return localProvider;\n throw new UnknownSecretStoreError(name);\n };\n}\n"],"names":["UnknownSecretStoreError","BUILTIN_LOCAL_STORE","createSecretStoreResolver","localProvider","resolveSecretStore","name"],"mappings":"AAAA,SAAQA,uBAAuB,QAAO,cAAc;AAEpD,OAAO,MAAMC,sBAAsB,QAAQ;AAoB3C,OAAO,SAASC,0BAA0BC,aAAkC;IAC1E,OAAO,SAASC,mBAAmBC,OAAOJ,mBAAmB;QAC3D,IAAII,SAASJ,qBAAqB,OAAOE;QACzC,MAAM,IAAIH,wBAAwBK;IACpC;AACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Tx } from '#db/index.js';
|
|
2
|
+
export declare function validateNamespace(namespace: string): void;
|
|
3
|
+
export declare function validateSecretKeys(keys: Iterable<string>): void;
|
|
4
|
+
export declare function validateValueBytes(values: Iterable<string>): void;
|
|
5
|
+
export declare function assertWorkspaceCap(params: {
|
|
6
|
+
workspaceId: string;
|
|
7
|
+
namespace: string;
|
|
8
|
+
incomingEntries: number;
|
|
9
|
+
tx: Tx;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=store-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-validation.d.ts","sourceRoot":"","sources":["../../src/core/store-validation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,EAAE,EAAC,MAAM,cAAc,CAAC;AAQ5D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGzD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAI/D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAMjE;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,EAAE,EAAE,EAAE,CAAC;CACR,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isSystemNamespace, namespaceSchema, secretKeySchema } from '@shipfox/api-secrets-dto';
|
|
2
|
+
import { config, MAX_VALUE_BYTES } from '#config.js';
|
|
3
|
+
import { countWorkspaceEntries } from '#db/index.js';
|
|
4
|
+
import { NamespaceValidationError, SecretKeyValidationError, SecretValueTooLargeError, WorkspaceSecretCapExceededError } from './errors.js';
|
|
5
|
+
export function validateNamespace(namespace) {
|
|
6
|
+
if (namespaceSchema.safeParse(namespace).success) return;
|
|
7
|
+
throw new NamespaceValidationError(namespace);
|
|
8
|
+
}
|
|
9
|
+
export function validateSecretKeys(keys) {
|
|
10
|
+
for (const key of keys){
|
|
11
|
+
if (!secretKeySchema.safeParse(key).success) throw new SecretKeyValidationError(key);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function validateValueBytes(values) {
|
|
15
|
+
for (const value of values){
|
|
16
|
+
if (Buffer.byteLength(value, 'utf8') > MAX_VALUE_BYTES) {
|
|
17
|
+
throw new SecretValueTooLargeError(MAX_VALUE_BYTES);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function assertWorkspaceCap(params) {
|
|
22
|
+
// System namespaces are internal-only; public management routes do not accept a namespace.
|
|
23
|
+
if (isSystemNamespace(params.namespace)) return;
|
|
24
|
+
const count = await countWorkspaceEntries(params.workspaceId, params.tx);
|
|
25
|
+
if (count + params.incomingEntries > config.SECRETS_MAX_PER_WORKSPACE) {
|
|
26
|
+
throw new WorkspaceSecretCapExceededError(params.workspaceId, config.SECRETS_MAX_PER_WORKSPACE);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=store-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/store-validation.ts"],"sourcesContent":["import {isSystemNamespace, namespaceSchema, secretKeySchema} from '@shipfox/api-secrets-dto';\nimport {config, MAX_VALUE_BYTES} from '#config.js';\nimport {countWorkspaceEntries, type Tx} from '#db/index.js';\nimport {\n NamespaceValidationError,\n SecretKeyValidationError,\n SecretValueTooLargeError,\n WorkspaceSecretCapExceededError,\n} from './errors.js';\n\nexport function validateNamespace(namespace: string): void {\n if (namespaceSchema.safeParse(namespace).success) return;\n throw new NamespaceValidationError(namespace);\n}\n\nexport function validateSecretKeys(keys: Iterable<string>): void {\n for (const key of keys) {\n if (!secretKeySchema.safeParse(key).success) throw new SecretKeyValidationError(key);\n }\n}\n\nexport function validateValueBytes(values: Iterable<string>): void {\n for (const value of values) {\n if (Buffer.byteLength(value, 'utf8') > MAX_VALUE_BYTES) {\n throw new SecretValueTooLargeError(MAX_VALUE_BYTES);\n }\n }\n}\n\nexport async function assertWorkspaceCap(params: {\n workspaceId: string;\n namespace: string;\n incomingEntries: number;\n tx: Tx;\n}): Promise<void> {\n // System namespaces are internal-only; public management routes do not accept a namespace.\n if (isSystemNamespace(params.namespace)) return;\n\n const count = await countWorkspaceEntries(params.workspaceId, params.tx);\n if (count + params.incomingEntries > config.SECRETS_MAX_PER_WORKSPACE) {\n throw new WorkspaceSecretCapExceededError(params.workspaceId, config.SECRETS_MAX_PER_WORKSPACE);\n }\n}\n"],"names":["isSystemNamespace","namespaceSchema","secretKeySchema","config","MAX_VALUE_BYTES","countWorkspaceEntries","NamespaceValidationError","SecretKeyValidationError","SecretValueTooLargeError","WorkspaceSecretCapExceededError","validateNamespace","namespace","safeParse","success","validateSecretKeys","keys","key","validateValueBytes","values","value","Buffer","byteLength","assertWorkspaceCap","params","count","workspaceId","tx","incomingEntries","SECRETS_MAX_PER_WORKSPACE"],"mappings":"AAAA,SAAQA,iBAAiB,EAAEC,eAAe,EAAEC,eAAe,QAAO,2BAA2B;AAC7F,SAAQC,MAAM,EAAEC,eAAe,QAAO,aAAa;AACnD,SAAQC,qBAAqB,QAAgB,eAAe;AAC5D,SACEC,wBAAwB,EACxBC,wBAAwB,EACxBC,wBAAwB,EACxBC,+BAA+B,QAC1B,cAAc;AAErB,OAAO,SAASC,kBAAkBC,SAAiB;IACjD,IAAIV,gBAAgBW,SAAS,CAACD,WAAWE,OAAO,EAAE;IAClD,MAAM,IAAIP,yBAAyBK;AACrC;AAEA,OAAO,SAASG,mBAAmBC,IAAsB;IACvD,KAAK,MAAMC,OAAOD,KAAM;QACtB,IAAI,CAACb,gBAAgBU,SAAS,CAACI,KAAKH,OAAO,EAAE,MAAM,IAAIN,yBAAyBS;IAClF;AACF;AAEA,OAAO,SAASC,mBAAmBC,MAAwB;IACzD,KAAK,MAAMC,SAASD,OAAQ;QAC1B,IAAIE,OAAOC,UAAU,CAACF,OAAO,UAAUf,iBAAiB;YACtD,MAAM,IAAII,yBAAyBJ;QACrC;IACF;AACF;AAEA,OAAO,eAAekB,mBAAmBC,MAKxC;IACC,2FAA2F;IAC3F,IAAIvB,kBAAkBuB,OAAOZ,SAAS,GAAG;IAEzC,MAAMa,QAAQ,MAAMnB,sBAAsBkB,OAAOE,WAAW,EAAEF,OAAOG,EAAE;IACvE,IAAIF,QAAQD,OAAOI,eAAe,GAAGxB,OAAOyB,yBAAyB,EAAE;QACrE,MAAM,IAAInB,gCAAgCc,OAAOE,WAAW,EAAEtB,OAAOyB,yBAAyB;IAChG;AACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type SecretValueAadParams } from './crypto.js';
|
|
2
|
+
export declare function encryptSecretValue(params: SecretValueAadParams & {
|
|
3
|
+
dek: Buffer;
|
|
4
|
+
value: string;
|
|
5
|
+
}): string;
|
|
6
|
+
export declare function decryptSecretValue(params: SecretValueAadParams & {
|
|
7
|
+
dek: Buffer;
|
|
8
|
+
ciphertext: string;
|
|
9
|
+
}): string;
|
|
10
|
+
//# sourceMappingURL=value-cipher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value-cipher.d.ts","sourceRoot":"","sources":["../../src/core/value-cipher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,oBAAoB,EAAC,MAAM,aAAa,CAAC;AAG3F,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,oBAAoB,GAAG;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GAC1D,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,oBAAoB,GAAG;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAC,GAC/D,MAAM,CAUR"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { aadForValue, aesGcmOpen, aesGcmSeal } from './crypto.js';
|
|
2
|
+
import { SecretDecryptionError } from './errors.js';
|
|
3
|
+
export function encryptSecretValue(params) {
|
|
4
|
+
return aesGcmSeal({
|
|
5
|
+
key: params.dek,
|
|
6
|
+
plaintext: Buffer.from(params.value, 'utf8'),
|
|
7
|
+
aad: aadForValue(params)
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function decryptSecretValue(params) {
|
|
11
|
+
try {
|
|
12
|
+
return aesGcmOpen({
|
|
13
|
+
key: params.dek,
|
|
14
|
+
encoded: params.ciphertext,
|
|
15
|
+
aad: aadForValue(params)
|
|
16
|
+
}).toString('utf8');
|
|
17
|
+
} catch {
|
|
18
|
+
throw new SecretDecryptionError();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=value-cipher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/value-cipher.ts"],"sourcesContent":["import {aadForValue, aesGcmOpen, aesGcmSeal, type SecretValueAadParams} from './crypto.js';\nimport {SecretDecryptionError} from './errors.js';\n\nexport function encryptSecretValue(\n params: SecretValueAadParams & {dek: Buffer; value: string},\n): string {\n return aesGcmSeal({\n key: params.dek,\n plaintext: Buffer.from(params.value, 'utf8'),\n aad: aadForValue(params),\n });\n}\n\nexport function decryptSecretValue(\n params: SecretValueAadParams & {dek: Buffer; ciphertext: string},\n): string {\n try {\n return aesGcmOpen({\n key: params.dek,\n encoded: params.ciphertext,\n aad: aadForValue(params),\n }).toString('utf8');\n } catch {\n throw new SecretDecryptionError();\n }\n}\n"],"names":["aadForValue","aesGcmOpen","aesGcmSeal","SecretDecryptionError","encryptSecretValue","params","key","dek","plaintext","Buffer","from","value","aad","decryptSecretValue","encoded","ciphertext","toString"],"mappings":"AAAA,SAAQA,WAAW,EAAEC,UAAU,EAAEC,UAAU,QAAkC,cAAc;AAC3F,SAAQC,qBAAqB,QAAO,cAAc;AAElD,OAAO,SAASC,mBACdC,MAA2D;IAE3D,OAAOH,WAAW;QAChBI,KAAKD,OAAOE,GAAG;QACfC,WAAWC,OAAOC,IAAI,CAACL,OAAOM,KAAK,EAAE;QACrCC,KAAKZ,YAAYK;IACnB;AACF;AAEA,OAAO,SAASQ,mBACdR,MAAgE;IAEhE,IAAI;QACF,OAAOJ,WAAW;YAChBK,KAAKD,OAAOE,GAAG;YACfO,SAAST,OAAOU,UAAU;YAC1BH,KAAKZ,YAAYK;QACnB,GAAGW,QAAQ,CAAC;IACd,EAAE,OAAM;QACN,MAAM,IAAIb;IACZ;AACF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type StoreScope } from '#db/index.js';
|
|
2
|
+
export interface SetVariablesParams extends StoreScope {
|
|
3
|
+
workspaceId: string;
|
|
4
|
+
namespace?: string | undefined;
|
|
5
|
+
values: Record<string, string>;
|
|
6
|
+
editedBy?: string | null | undefined;
|
|
7
|
+
}
|
|
8
|
+
export interface DeleteVariablesParams extends StoreScope {
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
namespace?: string | undefined;
|
|
11
|
+
keys?: string[] | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare function getVariable(input: StoreScope & {
|
|
14
|
+
workspaceId: string;
|
|
15
|
+
namespace?: string | undefined;
|
|
16
|
+
key: string;
|
|
17
|
+
}): Promise<string | null>;
|
|
18
|
+
export declare function getVariablesByNamespace(input: StoreScope & {
|
|
19
|
+
workspaceId: string;
|
|
20
|
+
namespace?: string | undefined;
|
|
21
|
+
}): Promise<Record<string, string>>;
|
|
22
|
+
export declare function setVariables(input: SetVariablesParams): Promise<void>;
|
|
23
|
+
export declare function deleteVariables(input: DeleteVariablesParams): Promise<number>;
|
|
24
|
+
//# sourceMappingURL=variable-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variable-store.d.ts","sourceRoot":"","sources":["../../src/core/variable-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,UAAU,EAEhB,MAAM,cAAc,CAAC;AAetB,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,qBAAsB,SAAQ,UAAU;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAC7B;AAED,wBAAsB,WAAW,CAC/B,KAAK,EAAE,UAAU,GAAG;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,GACrF,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6BxB;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,UAAU,GAAG;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAC,GACxE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA4BjC;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsF3E;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyCnF"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { countSecretVariableRows, db, deleteSecretVariableRows, getSecretVariableRowWithPrecedence, listSecretVariableRowsByNamespace, lockWorkspaceEntries, upsertSecretVariableRows } from '#db/index.js';
|
|
2
|
+
import { normalizedProjectId } from '#db/scope.js';
|
|
3
|
+
import { classifySecretsOperationError, operationScope, recordSecretsEntriesMutated, recordSecretsOperation } from '#metrics/instance.js';
|
|
4
|
+
import { assertWorkspaceCap, validateNamespace, validateSecretKeys, validateValueBytes } from './store-validation.js';
|
|
5
|
+
export async function getVariable(input) {
|
|
6
|
+
const startedAt = Date.now();
|
|
7
|
+
const scope = operationScope(input);
|
|
8
|
+
try {
|
|
9
|
+
const namespace = input.namespace ?? '';
|
|
10
|
+
validateNamespace(namespace);
|
|
11
|
+
validateSecretKeys([
|
|
12
|
+
input.key
|
|
13
|
+
]);
|
|
14
|
+
const row = await getSecretVariableRowWithPrecedence({
|
|
15
|
+
...input,
|
|
16
|
+
namespace
|
|
17
|
+
});
|
|
18
|
+
recordSecretsOperation({
|
|
19
|
+
resource: 'variable',
|
|
20
|
+
operation: 'get',
|
|
21
|
+
surface: 'internal',
|
|
22
|
+
scope,
|
|
23
|
+
outcome: row ? 'success' : 'not_found',
|
|
24
|
+
durationMs: Date.now() - startedAt
|
|
25
|
+
});
|
|
26
|
+
return row?.value ?? null;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
recordSecretsOperation({
|
|
29
|
+
resource: 'variable',
|
|
30
|
+
operation: 'get',
|
|
31
|
+
surface: 'internal',
|
|
32
|
+
scope,
|
|
33
|
+
outcome: classifySecretsOperationError(error),
|
|
34
|
+
durationMs: Date.now() - startedAt
|
|
35
|
+
});
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function getVariablesByNamespace(input) {
|
|
40
|
+
const startedAt = Date.now();
|
|
41
|
+
const scope = operationScope(input);
|
|
42
|
+
try {
|
|
43
|
+
const namespace = input.namespace ?? '';
|
|
44
|
+
validateNamespace(namespace);
|
|
45
|
+
const rows = await listSecretVariableRowsByNamespace({
|
|
46
|
+
...input,
|
|
47
|
+
namespace
|
|
48
|
+
});
|
|
49
|
+
recordSecretsOperation({
|
|
50
|
+
resource: 'variable',
|
|
51
|
+
operation: 'get_namespace',
|
|
52
|
+
surface: 'internal',
|
|
53
|
+
scope,
|
|
54
|
+
outcome: 'success',
|
|
55
|
+
durationMs: Date.now() - startedAt
|
|
56
|
+
});
|
|
57
|
+
return Object.fromEntries(rows.map((row)=>[
|
|
58
|
+
row.key,
|
|
59
|
+
row.value
|
|
60
|
+
]));
|
|
61
|
+
} catch (error) {
|
|
62
|
+
recordSecretsOperation({
|
|
63
|
+
resource: 'variable',
|
|
64
|
+
operation: 'get_namespace',
|
|
65
|
+
surface: 'internal',
|
|
66
|
+
scope,
|
|
67
|
+
outcome: classifySecretsOperationError(error),
|
|
68
|
+
durationMs: Date.now() - startedAt
|
|
69
|
+
});
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function setVariables(input) {
|
|
74
|
+
const startedAt = Date.now();
|
|
75
|
+
const scope = operationScope(input);
|
|
76
|
+
try {
|
|
77
|
+
const namespace = input.namespace ?? '';
|
|
78
|
+
const entries = Object.entries(input.values);
|
|
79
|
+
validateNamespace(namespace);
|
|
80
|
+
validateSecretKeys(entries.map(([key])=>key));
|
|
81
|
+
validateValueBytes(entries.map(([, value])=>value));
|
|
82
|
+
if (entries.length === 0) {
|
|
83
|
+
recordSecretsOperation({
|
|
84
|
+
resource: 'variable',
|
|
85
|
+
operation: 'set',
|
|
86
|
+
surface: 'internal',
|
|
87
|
+
scope,
|
|
88
|
+
outcome: 'success',
|
|
89
|
+
durationMs: Date.now() - startedAt
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const projectId = normalizedProjectId(input);
|
|
94
|
+
let existingEntries = 0;
|
|
95
|
+
await db().transaction(async (tx)=>{
|
|
96
|
+
await lockWorkspaceEntries(input.workspaceId, tx);
|
|
97
|
+
existingEntries = await countSecretVariableRows({
|
|
98
|
+
workspaceId: input.workspaceId,
|
|
99
|
+
projectId,
|
|
100
|
+
namespace,
|
|
101
|
+
keys: entries.map(([key])=>key)
|
|
102
|
+
}, tx);
|
|
103
|
+
await assertWorkspaceCap({
|
|
104
|
+
workspaceId: input.workspaceId,
|
|
105
|
+
namespace,
|
|
106
|
+
incomingEntries: entries.length - existingEntries,
|
|
107
|
+
tx
|
|
108
|
+
});
|
|
109
|
+
await upsertSecretVariableRows(entries.map(([key, value])=>({
|
|
110
|
+
workspaceId: input.workspaceId,
|
|
111
|
+
projectId,
|
|
112
|
+
namespace,
|
|
113
|
+
key,
|
|
114
|
+
value,
|
|
115
|
+
lastEditedBy: input.editedBy ?? null
|
|
116
|
+
})), tx);
|
|
117
|
+
});
|
|
118
|
+
recordSecretsEntriesMutated({
|
|
119
|
+
resource: 'variable',
|
|
120
|
+
operation: 'set',
|
|
121
|
+
effect: 'created',
|
|
122
|
+
surface: 'internal',
|
|
123
|
+
count: entries.length - existingEntries
|
|
124
|
+
});
|
|
125
|
+
recordSecretsEntriesMutated({
|
|
126
|
+
resource: 'variable',
|
|
127
|
+
operation: 'set',
|
|
128
|
+
effect: 'updated',
|
|
129
|
+
surface: 'internal',
|
|
130
|
+
count: existingEntries
|
|
131
|
+
});
|
|
132
|
+
recordSecretsOperation({
|
|
133
|
+
resource: 'variable',
|
|
134
|
+
operation: 'set',
|
|
135
|
+
surface: 'internal',
|
|
136
|
+
scope,
|
|
137
|
+
outcome: 'success',
|
|
138
|
+
durationMs: Date.now() - startedAt
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
recordSecretsOperation({
|
|
142
|
+
resource: 'variable',
|
|
143
|
+
operation: 'set',
|
|
144
|
+
surface: 'internal',
|
|
145
|
+
scope,
|
|
146
|
+
outcome: classifySecretsOperationError(error),
|
|
147
|
+
durationMs: Date.now() - startedAt
|
|
148
|
+
});
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export async function deleteVariables(input) {
|
|
153
|
+
const startedAt = Date.now();
|
|
154
|
+
const scope = operationScope(input);
|
|
155
|
+
try {
|
|
156
|
+
const namespace = input.namespace ?? '';
|
|
157
|
+
validateNamespace(namespace);
|
|
158
|
+
if (input.keys) validateSecretKeys(input.keys);
|
|
159
|
+
const deleted = await deleteSecretVariableRows({
|
|
160
|
+
workspaceId: input.workspaceId,
|
|
161
|
+
projectId: normalizedProjectId(input),
|
|
162
|
+
namespace,
|
|
163
|
+
keys: input.keys
|
|
164
|
+
});
|
|
165
|
+
recordSecretsEntriesMutated({
|
|
166
|
+
resource: 'variable',
|
|
167
|
+
operation: 'delete',
|
|
168
|
+
effect: 'deleted',
|
|
169
|
+
surface: 'internal',
|
|
170
|
+
count: deleted
|
|
171
|
+
});
|
|
172
|
+
recordSecretsOperation({
|
|
173
|
+
resource: 'variable',
|
|
174
|
+
operation: 'delete',
|
|
175
|
+
surface: 'internal',
|
|
176
|
+
scope,
|
|
177
|
+
outcome: 'success',
|
|
178
|
+
durationMs: Date.now() - startedAt
|
|
179
|
+
});
|
|
180
|
+
return deleted;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
recordSecretsOperation({
|
|
183
|
+
resource: 'variable',
|
|
184
|
+
operation: 'delete',
|
|
185
|
+
surface: 'internal',
|
|
186
|
+
scope,
|
|
187
|
+
outcome: classifySecretsOperationError(error),
|
|
188
|
+
durationMs: Date.now() - startedAt
|
|
189
|
+
});
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//# sourceMappingURL=variable-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/variable-store.ts"],"sourcesContent":["import {\n countSecretVariableRows,\n db,\n deleteSecretVariableRows,\n getSecretVariableRowWithPrecedence,\n listSecretVariableRowsByNamespace,\n lockWorkspaceEntries,\n type StoreScope,\n upsertSecretVariableRows,\n} from '#db/index.js';\nimport {normalizedProjectId} from '#db/scope.js';\nimport {\n classifySecretsOperationError,\n operationScope,\n recordSecretsEntriesMutated,\n recordSecretsOperation,\n} from '#metrics/instance.js';\nimport {\n assertWorkspaceCap,\n validateNamespace,\n validateSecretKeys,\n validateValueBytes,\n} from './store-validation.js';\n\nexport interface SetVariablesParams extends StoreScope {\n workspaceId: string;\n namespace?: string | undefined;\n values: Record<string, string>;\n editedBy?: string | null | undefined;\n}\n\nexport interface DeleteVariablesParams extends StoreScope {\n workspaceId: string;\n namespace?: string | undefined;\n keys?: string[] | undefined;\n}\n\nexport async function getVariable(\n input: StoreScope & {workspaceId: string; namespace?: string | undefined; key: string},\n): Promise<string | null> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const namespace = input.namespace ?? '';\n validateNamespace(namespace);\n validateSecretKeys([input.key]);\n\n const row = await getSecretVariableRowWithPrecedence({...input, namespace});\n recordSecretsOperation({\n resource: 'variable',\n operation: 'get',\n surface: 'internal',\n scope,\n outcome: row ? 'success' : 'not_found',\n durationMs: Date.now() - startedAt,\n });\n return row?.value ?? null;\n } catch (error) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'get',\n surface: 'internal',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n}\n\nexport async function getVariablesByNamespace(\n input: StoreScope & {workspaceId: string; namespace?: string | undefined},\n): Promise<Record<string, string>> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const namespace = input.namespace ?? '';\n validateNamespace(namespace);\n\n const rows = await listSecretVariableRowsByNamespace({...input, namespace});\n recordSecretsOperation({\n resource: 'variable',\n operation: 'get_namespace',\n surface: 'internal',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return Object.fromEntries(rows.map((row) => [row.key, row.value]));\n } catch (error) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'get_namespace',\n surface: 'internal',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n}\n\nexport async function setVariables(input: SetVariablesParams): Promise<void> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const namespace = input.namespace ?? '';\n const entries = Object.entries(input.values);\n validateNamespace(namespace);\n validateSecretKeys(entries.map(([key]) => key));\n validateValueBytes(entries.map(([, value]) => value));\n if (entries.length === 0) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'set',\n surface: 'internal',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return;\n }\n\n const projectId = normalizedProjectId(input);\n let existingEntries = 0;\n await db().transaction(async (tx) => {\n await lockWorkspaceEntries(input.workspaceId, tx);\n existingEntries = await countSecretVariableRows(\n {\n workspaceId: input.workspaceId,\n projectId,\n namespace,\n keys: entries.map(([key]) => key),\n },\n tx,\n );\n await assertWorkspaceCap({\n workspaceId: input.workspaceId,\n namespace,\n incomingEntries: entries.length - existingEntries,\n tx,\n });\n await upsertSecretVariableRows(\n entries.map(([key, value]) => ({\n workspaceId: input.workspaceId,\n projectId,\n namespace,\n key,\n value,\n lastEditedBy: input.editedBy ?? null,\n })),\n tx,\n );\n });\n\n recordSecretsEntriesMutated({\n resource: 'variable',\n operation: 'set',\n effect: 'created',\n surface: 'internal',\n count: entries.length - existingEntries,\n });\n recordSecretsEntriesMutated({\n resource: 'variable',\n operation: 'set',\n effect: 'updated',\n surface: 'internal',\n count: existingEntries,\n });\n recordSecretsOperation({\n resource: 'variable',\n operation: 'set',\n surface: 'internal',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n } catch (error) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'set',\n surface: 'internal',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n}\n\nexport async function deleteVariables(input: DeleteVariablesParams): Promise<number> {\n const startedAt = Date.now();\n const scope = operationScope(input);\n try {\n const namespace = input.namespace ?? '';\n validateNamespace(namespace);\n if (input.keys) validateSecretKeys(input.keys);\n\n const deleted = await deleteSecretVariableRows({\n workspaceId: input.workspaceId,\n projectId: normalizedProjectId(input),\n namespace,\n keys: input.keys,\n });\n recordSecretsEntriesMutated({\n resource: 'variable',\n operation: 'delete',\n effect: 'deleted',\n surface: 'internal',\n count: deleted,\n });\n recordSecretsOperation({\n resource: 'variable',\n operation: 'delete',\n surface: 'internal',\n scope,\n outcome: 'success',\n durationMs: Date.now() - startedAt,\n });\n return deleted;\n } catch (error) {\n recordSecretsOperation({\n resource: 'variable',\n operation: 'delete',\n surface: 'internal',\n scope,\n outcome: classifySecretsOperationError(error),\n durationMs: Date.now() - startedAt,\n });\n throw error;\n }\n}\n"],"names":["countSecretVariableRows","db","deleteSecretVariableRows","getSecretVariableRowWithPrecedence","listSecretVariableRowsByNamespace","lockWorkspaceEntries","upsertSecretVariableRows","normalizedProjectId","classifySecretsOperationError","operationScope","recordSecretsEntriesMutated","recordSecretsOperation","assertWorkspaceCap","validateNamespace","validateSecretKeys","validateValueBytes","getVariable","input","startedAt","Date","now","scope","namespace","key","row","resource","operation","surface","outcome","durationMs","value","error","getVariablesByNamespace","rows","Object","fromEntries","map","setVariables","entries","values","length","projectId","existingEntries","transaction","tx","workspaceId","keys","incomingEntries","lastEditedBy","editedBy","effect","count","deleteVariables","deleted"],"mappings":"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"}
|
package/dist/db/cap.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Tx } from './db.js';
|
|
2
|
+
export declare function countWorkspaceEntries(workspaceId: string, tx?: Tx): Promise<number>;
|
|
3
|
+
export declare function countPublicEntriesByResource(tx?: Tx): Promise<{
|
|
4
|
+
secrets: number;
|
|
5
|
+
variables: number;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function lockWorkspaceEntries(workspaceId: string, tx: Tx): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=cap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cap.d.ts","sourceRoot":"","sources":["../../src/db/cap.ts"],"names":[],"mappings":"AACA,OAAO,EAAK,KAAK,EAAE,EAAC,MAAM,SAAS,CAAC;AAIpC,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBzF;AAED,wBAAsB,4BAA4B,CAChD,EAAE,CAAC,EAAE,EAAE,GACN,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,CAAC,CAiB/C;AAED,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIrF"}
|