@highstate/backend 0.9.14 → 0.9.16
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/dist/chunk-RCB4AFGD.js +159 -0
- package/dist/chunk-RCB4AFGD.js.map +1 -0
- package/dist/chunk-WHALQHEZ.js +2017 -0
- package/dist/chunk-WHALQHEZ.js.map +1 -0
- package/dist/highstate.manifest.json +3 -3
- package/dist/index.js +6146 -2174
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +51 -159
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +159 -43
- package/package.json +25 -7
- package/src/artifact/abstractions.ts +46 -0
- package/src/artifact/encryption.ts +69 -0
- package/src/artifact/factory.ts +36 -0
- package/src/artifact/index.ts +3 -0
- package/src/artifact/local.ts +142 -0
- package/src/business/api-key.ts +65 -0
- package/src/business/artifact.ts +288 -0
- package/src/business/backend-unlock.ts +10 -0
- package/src/business/index.ts +9 -0
- package/src/business/instance-lock.ts +124 -0
- package/src/business/instance-state.ts +292 -0
- package/src/business/operation.ts +251 -0
- package/src/business/project-unlock.ts +242 -0
- package/src/business/secret.ts +187 -0
- package/src/business/worker.ts +161 -0
- package/src/common/index.ts +2 -1
- package/src/common/performance.ts +44 -0
- package/src/common/tree.ts +33 -0
- package/src/common/utils.ts +40 -1
- package/src/config.ts +14 -10
- package/src/hotstate/abstractions.ts +48 -0
- package/src/hotstate/factory.ts +17 -0
- package/src/{secret → hotstate}/index.ts +1 -0
- package/src/hotstate/manager.ts +192 -0
- package/src/hotstate/memory.ts +100 -0
- package/src/hotstate/validation.ts +101 -0
- package/src/index.ts +2 -1
- package/src/library/abstractions.ts +10 -23
- package/src/library/factory.ts +2 -2
- package/src/library/local.ts +89 -102
- package/src/library/worker/evaluator.ts +14 -47
- package/src/library/worker/loader.lite.ts +41 -0
- package/src/library/worker/main.ts +14 -65
- package/src/library/worker/protocol.ts +8 -24
- package/src/lock/abstractions.ts +6 -0
- package/src/lock/factory.ts +15 -0
- package/src/{workspace → lock}/index.ts +1 -0
- package/src/lock/manager.ts +82 -0
- package/src/lock/memory.ts +19 -0
- package/src/orchestrator/manager.ts +131 -82
- package/src/orchestrator/operation-workset.ts +188 -77
- package/src/orchestrator/operation.ts +975 -284
- package/src/project/abstractions.ts +20 -7
- package/src/project/factory.ts +1 -1
- package/src/project/index.ts +0 -1
- package/src/project/local.ts +73 -17
- package/src/project/manager.ts +272 -131
- package/src/pubsub/abstractions.ts +13 -0
- package/src/pubsub/factory.ts +19 -0
- package/src/pubsub/index.ts +3 -0
- package/src/pubsub/local.ts +36 -0
- package/src/pubsub/manager.ts +100 -0
- package/src/pubsub/validation.ts +33 -0
- package/src/runner/abstractions.ts +135 -68
- package/src/runner/artifact-env.ts +160 -0
- package/src/runner/factory.ts +20 -5
- package/src/runner/force-abort.ts +117 -0
- package/src/runner/local.ts +281 -372
- package/src/{common → runner}/pulumi.ts +86 -37
- package/src/services.ts +193 -35
- package/src/shared/index.ts +3 -11
- package/src/shared/models/backend/index.ts +3 -0
- package/src/shared/models/backend/project.ts +63 -0
- package/src/shared/models/backend/unlock-method.ts +20 -0
- package/src/shared/models/base.ts +151 -0
- package/src/shared/models/errors.ts +5 -0
- package/src/shared/models/index.ts +4 -0
- package/src/shared/models/project/api-key.ts +62 -0
- package/src/shared/models/project/artifact.ts +113 -0
- package/src/shared/models/project/component.ts +45 -0
- package/src/shared/models/project/index.ts +14 -0
- package/src/shared/{project.ts → models/project/instance.ts} +12 -0
- package/src/shared/models/project/lock.ts +91 -0
- package/src/shared/{operation.ts → models/project/operation.ts} +43 -8
- package/src/shared/models/project/page.ts +57 -0
- package/src/shared/models/project/secret.ts +112 -0
- package/src/shared/models/project/service-account.ts +22 -0
- package/src/shared/models/project/state.ts +432 -0
- package/src/shared/models/project/terminal.ts +99 -0
- package/src/shared/models/project/trigger.ts +56 -0
- package/src/shared/models/project/unlock-method.ts +31 -0
- package/src/shared/models/project/worker.ts +105 -0
- package/src/shared/resolvers/graph-resolver.ts +74 -13
- package/src/shared/resolvers/index.ts +5 -0
- package/src/shared/resolvers/input-hash.ts +53 -15
- package/src/shared/resolvers/input.ts +1 -9
- package/src/shared/resolvers/registry.ts +7 -2
- package/src/shared/resolvers/state.ts +12 -0
- package/src/shared/resolvers/validation.ts +61 -20
- package/src/shared/{async-batcher.ts → utils/async-batcher.ts} +13 -1
- package/src/shared/utils/hash.ts +6 -0
- package/src/shared/utils/index.ts +3 -0
- package/src/shared/utils/promise-tracker.ts +23 -0
- package/src/state/abstractions.ts +330 -101
- package/src/state/encryption.ts +59 -0
- package/src/state/factory.ts +3 -5
- package/src/state/index.ts +3 -0
- package/src/state/keyring.ts +22 -0
- package/src/state/local/backend.ts +299 -0
- package/src/state/local/collection.ts +342 -0
- package/src/state/local/index.ts +2 -0
- package/src/state/manager.ts +804 -18
- package/src/state/repository/index.ts +2 -0
- package/src/state/repository/repository.index.ts +193 -0
- package/src/state/repository/repository.ts +458 -0
- package/src/terminal/{shared.ts → abstractions.ts} +3 -3
- package/src/terminal/docker.ts +18 -14
- package/src/terminal/factory.ts +3 -3
- package/src/terminal/index.ts +1 -1
- package/src/terminal/manager.ts +134 -80
- package/src/terminal/run.sh.ts +22 -10
- package/src/worker/abstractions.ts +42 -0
- package/src/worker/docker.ts +83 -0
- package/src/worker/factory.ts +20 -0
- package/src/worker/index.ts +3 -0
- package/src/worker/manager.ts +139 -0
- package/dist/chunk-C2TJAQAD.js +0 -937
- package/dist/chunk-C2TJAQAD.js.map +0 -1
- package/dist/chunk-WXDYCRTT.js +0 -234
- package/dist/chunk-WXDYCRTT.js.map +0 -1
- package/src/library/worker/loader.ts +0 -114
- package/src/preferences/shared.ts +0 -1
- package/src/project/lock.ts +0 -39
- package/src/secret/abstractions.ts +0 -59
- package/src/secret/factory.ts +0 -22
- package/src/secret/local.ts +0 -152
- package/src/shared/state.ts +0 -270
- package/src/shared/terminal.ts +0 -13
- package/src/state/local.ts +0 -612
- package/src/workspace/abstractions.ts +0 -41
- package/src/workspace/factory.ts +0 -14
- package/src/workspace/local.ts +0 -54
- /package/src/shared/{library.ts → models/backend/library.ts} +0 -0
package/src/state/manager.ts
CHANGED
|
@@ -1,33 +1,819 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
1
|
+
import type { Logger } from "pino"
|
|
2
|
+
import type { HotStateManager } from "../hotstate"
|
|
3
|
+
import { randomBytes } from "node:crypto"
|
|
4
|
+
import { readFile } from "node:fs/promises"
|
|
5
|
+
import { z } from "zod"
|
|
6
|
+
import { BetterLock } from "better-lock"
|
|
7
|
+
import { v4 as uuidv4, v5 as uuidv5 } from "uuid"
|
|
8
|
+
import { armor, Decrypter, Encrypter, identityToRecipient } from "age-encryption"
|
|
9
|
+
import { LRUCache } from "lru-cache"
|
|
10
|
+
import {
|
|
11
|
+
terminalSessionSchema,
|
|
12
|
+
triggerSchema,
|
|
13
|
+
unlockMethodSchema,
|
|
14
|
+
terminalSchema,
|
|
15
|
+
terminalSpecSchema,
|
|
16
|
+
pageSchema,
|
|
17
|
+
artifactSchema,
|
|
18
|
+
secretSchema,
|
|
19
|
+
operationSchema,
|
|
20
|
+
instanceStateSchema,
|
|
21
|
+
serviceAccountSchema,
|
|
22
|
+
projectApiKeySchema,
|
|
23
|
+
workerSchema,
|
|
24
|
+
workerUnitRegistrationSchema,
|
|
25
|
+
instanceLockSchema,
|
|
26
|
+
compositeInstanceSchema,
|
|
27
|
+
pageBlockSchema,
|
|
28
|
+
projectSchema,
|
|
29
|
+
projectUnlockSuiteSchema,
|
|
30
|
+
} from "../shared"
|
|
31
|
+
import {
|
|
32
|
+
ProjectLockedError,
|
|
33
|
+
type StateBackend,
|
|
34
|
+
type StateBatch,
|
|
35
|
+
type StateSnapshot,
|
|
36
|
+
} from "./abstractions"
|
|
37
|
+
import { MasterKeyEncryptionBackend, type EncryptionBackend } from "./encryption"
|
|
38
|
+
import { StateIndexRepository, StateRepository, NO_KEY_HASHING } from "./repository"
|
|
39
|
+
import { getOrCreateBackendIdentity } from "./keyring"
|
|
3
40
|
|
|
4
|
-
export
|
|
41
|
+
export const stateManagerConfig = z.object({
|
|
42
|
+
HIGHSTATE_BACKEND_MASTER_KEY: z.string().optional(),
|
|
43
|
+
HIGHSTATE_BACKEND_MASTER_KEY_PATH: z.string().optional(),
|
|
44
|
+
})
|
|
5
45
|
|
|
46
|
+
/**
|
|
47
|
+
* StateManager handles the creation of repositories and provides a unified interface
|
|
48
|
+
* for accessing typed repositories with proper configuration.
|
|
49
|
+
*/
|
|
6
50
|
export class StateManager {
|
|
7
|
-
|
|
51
|
+
constructor(
|
|
52
|
+
private readonly backendNamespace: string,
|
|
53
|
+
private readonly encryptionBackend: EncryptionBackend,
|
|
54
|
+
private readonly backend: StateBackend,
|
|
55
|
+
private readonly hotStateManager: HotStateManager,
|
|
56
|
+
private readonly logger: Logger,
|
|
57
|
+
) {}
|
|
8
58
|
|
|
9
59
|
/**
|
|
10
|
-
*
|
|
60
|
+
* Creates a new batch for atomic operations.
|
|
61
|
+
* This allows multiple operations to be executed atomically.
|
|
62
|
+
*/
|
|
63
|
+
batch(): StateBatch {
|
|
64
|
+
return this.backend.createStateBatch()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates a snapshot of the current state.
|
|
69
|
+
* This allows for consistent reads without affecting ongoing writes.
|
|
70
|
+
*/
|
|
71
|
+
snapshot(): StateSnapshot {
|
|
72
|
+
return this.backend.createStateSnapshot()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Gets a repository with projects.
|
|
77
|
+
*/
|
|
78
|
+
getProjectRepository(): StateRepository<typeof projectSchema> {
|
|
79
|
+
return new StateRepository(
|
|
80
|
+
this.backend.createProjectCollection(),
|
|
81
|
+
projectSchema,
|
|
82
|
+
this.encryptionBackend,
|
|
83
|
+
this.configureBackendKeyHashing(), // to hide project IDs
|
|
84
|
+
this.logger,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Gets a repository for encrypted project master keys armored in AGE's format.
|
|
90
|
+
* These project keys are not ready to be used for encryption/decryption
|
|
91
|
+
* and must be first decrypted using one of the project unlock methods.
|
|
92
|
+
*/
|
|
93
|
+
getProjectMasterKeyRepository(): StateRepository<z.ZodString> {
|
|
94
|
+
return new StateRepository(
|
|
95
|
+
this.backend.createProjectMasterKeyCollection(),
|
|
96
|
+
z.string(),
|
|
97
|
+
this.encryptionBackend,
|
|
98
|
+
this.configureBackendKeyHashing(), // to hide project IDs
|
|
99
|
+
this.logger,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Creates a repository for project unlock suites.
|
|
105
|
+
* They contain encrypted AGE identities which can be used to unlock project master keys.
|
|
106
|
+
*/
|
|
107
|
+
getProjectUnlockSuiteRepository(): StateRepository<typeof projectUnlockSuiteSchema> {
|
|
108
|
+
return new StateRepository(
|
|
109
|
+
this.backend.createProjectUnlockSuiteCollection(),
|
|
110
|
+
projectUnlockSuiteSchema,
|
|
111
|
+
this.encryptionBackend,
|
|
112
|
+
this.configureBackendKeyHashing(), // to hide project IDs
|
|
113
|
+
this.logger,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Gets a repository for terminal sessions.
|
|
119
|
+
*
|
|
120
|
+
* @param projectId The ID of the project owning the terminal sessions.
|
|
121
|
+
*/
|
|
122
|
+
getTerminalSessionRepository(projectId: string): StateRepository<typeof terminalSessionSchema> {
|
|
123
|
+
return new StateRepository(
|
|
124
|
+
this.backend.createTerminalSessionCollection(projectId),
|
|
125
|
+
terminalSessionSchema,
|
|
126
|
+
this.getEncryptionBackend(projectId),
|
|
127
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
128
|
+
this.logger,
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Creates index repository which keeps the list of terminal sessions for a specific instance.
|
|
11
134
|
*
|
|
12
|
-
* @param projectId The project
|
|
13
|
-
* @param
|
|
135
|
+
* @param projectId The ID of the project owning the terminal sessions.
|
|
136
|
+
* @param instanceId The ID of the instance to which the terminal sessions belong.
|
|
14
137
|
*/
|
|
15
|
-
|
|
138
|
+
getInstanceTerminalSessionIndexRepository(
|
|
16
139
|
projectId: string,
|
|
17
|
-
|
|
18
|
-
):
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
140
|
+
instanceId: string,
|
|
141
|
+
): StateIndexRepository<typeof terminalSessionSchema> {
|
|
142
|
+
return new StateIndexRepository(
|
|
143
|
+
this,
|
|
144
|
+
this.getTerminalSessionRepository(projectId),
|
|
145
|
+
new StateRepository(
|
|
146
|
+
this.backend.createInstanceTerminalSessionIndexCollection(projectId, instanceId),
|
|
147
|
+
z.string(),
|
|
148
|
+
this.getEncryptionBackend(projectId),
|
|
149
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
150
|
+
this.logger,
|
|
151
|
+
),
|
|
152
|
+
this.logger,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Creates index repository which keeps the list of active terminal sessions.
|
|
158
|
+
*
|
|
159
|
+
* @param projectId The ID of the project owning the terminal sessions.
|
|
160
|
+
*/
|
|
161
|
+
getActiveTerminalSessionIndexRepository(
|
|
162
|
+
projectId: string,
|
|
163
|
+
): StateIndexRepository<typeof terminalSessionSchema> {
|
|
164
|
+
return new StateIndexRepository(
|
|
165
|
+
this,
|
|
166
|
+
this.getTerminalSessionRepository(projectId),
|
|
167
|
+
new StateRepository(
|
|
168
|
+
this.backend.createActiveTerminalSessionIndexCollection(projectId),
|
|
169
|
+
z.string(),
|
|
170
|
+
this.getEncryptionBackend(projectId),
|
|
171
|
+
this.configureKeyHashing(projectId), // to hide which terminal sessions are active
|
|
172
|
+
this.logger,
|
|
173
|
+
),
|
|
174
|
+
this.logger,
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Creates a repository for terminal session lines.
|
|
180
|
+
*
|
|
181
|
+
* @param projectId The ID of the project owning the terminal session.
|
|
182
|
+
* @param sessionId The ID of the terminal session to which the lines belong.
|
|
183
|
+
*/
|
|
184
|
+
getTerminalSessionLineRepository(
|
|
185
|
+
projectId: string,
|
|
186
|
+
sessionId: string,
|
|
187
|
+
): StateRepository<z.ZodString> {
|
|
188
|
+
return new StateRepository(
|
|
189
|
+
this.backend.createTerminalSessionLineCollection(projectId, sessionId),
|
|
190
|
+
z.string(),
|
|
191
|
+
this.getEncryptionBackend(projectId),
|
|
192
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
193
|
+
this.logger,
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Creates a repository for project terminals.
|
|
199
|
+
*
|
|
200
|
+
* @param projectId The ID of the project owning the terminals.
|
|
201
|
+
*/
|
|
202
|
+
getTerminalRepository(projectId: string): StateRepository<typeof terminalSchema> {
|
|
203
|
+
return new StateRepository(
|
|
204
|
+
this.backend.createTerminalCollection(projectId),
|
|
205
|
+
terminalSchema,
|
|
206
|
+
this.getEncryptionBackend(projectId),
|
|
207
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
208
|
+
this.logger,
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Creates a repository for specifications of project terminals.
|
|
214
|
+
*
|
|
215
|
+
* @param projectId The ID of the project owning the terminals.
|
|
216
|
+
*/
|
|
217
|
+
getTerminalSpecRepository(projectId: string): StateRepository<typeof terminalSpecSchema> {
|
|
218
|
+
return new StateRepository(
|
|
219
|
+
this.backend.createTerminalSpecCollection(projectId),
|
|
220
|
+
terminalSpecSchema,
|
|
221
|
+
this.getEncryptionBackend(projectId),
|
|
222
|
+
NO_KEY_HASHING, // nothing to hide
|
|
223
|
+
this.logger,
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Creates a repository for pages.
|
|
229
|
+
*
|
|
230
|
+
* @param projectId The ID of the project owning the pages.
|
|
231
|
+
*/
|
|
232
|
+
getPageRepository(projectId: string): StateRepository<typeof pageSchema> {
|
|
233
|
+
return new StateRepository(
|
|
234
|
+
this.backend.createPageCollection(projectId),
|
|
235
|
+
pageSchema,
|
|
236
|
+
this.getEncryptionBackend(projectId),
|
|
237
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
238
|
+
this.logger,
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Creates a repository for page content.
|
|
244
|
+
*
|
|
245
|
+
* @param projectId The ID of the project owning the page content.
|
|
246
|
+
*/
|
|
247
|
+
getPageContentRepository(projectId: string): StateRepository<z.ZodArray<typeof pageBlockSchema>> {
|
|
248
|
+
return new StateRepository(
|
|
249
|
+
this.backend.createPageContentCollection(projectId),
|
|
250
|
+
pageBlockSchema.array(),
|
|
251
|
+
this.getEncryptionBackend(projectId),
|
|
252
|
+
NO_KEY_HASHING, // nothing to hide
|
|
253
|
+
this.logger,
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Creates a repository for service accounts.
|
|
259
|
+
*
|
|
260
|
+
* @param projectId The ID of the project owning the service accounts.
|
|
261
|
+
*/
|
|
262
|
+
getServiceAccountRepository(projectId: string): StateRepository<typeof serviceAccountSchema> {
|
|
263
|
+
return new StateRepository(
|
|
264
|
+
this.backend.createServiceAccountCollection(projectId),
|
|
265
|
+
serviceAccountSchema,
|
|
266
|
+
this.getEncryptionBackend(projectId),
|
|
267
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
268
|
+
this.logger,
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Creates a repository for API keys.
|
|
274
|
+
*
|
|
275
|
+
* @param projectId The ID of the project owning the API keys.
|
|
276
|
+
*/
|
|
277
|
+
getApiKeyRepository(projectId: string): StateRepository<typeof projectApiKeySchema> {
|
|
278
|
+
return new StateRepository(
|
|
279
|
+
this.backend.createApiKeyCollection(projectId),
|
|
280
|
+
projectApiKeySchema,
|
|
281
|
+
this.getEncryptionBackend(projectId),
|
|
282
|
+
NO_KEY_HASHING, // we want to sort by ULID
|
|
283
|
+
this.logger,
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Creates an index repository which maps API key tokens to API key IDs.
|
|
289
|
+
* The tokens are hashed to prevent leaking sensitive information.
|
|
290
|
+
*
|
|
291
|
+
* @param projectId The ID of the project owning the API keys.
|
|
292
|
+
*/
|
|
293
|
+
getApiKeyTokenIndexRepository(
|
|
294
|
+
projectId: string,
|
|
295
|
+
): StateIndexRepository<typeof projectApiKeySchema> {
|
|
296
|
+
return new StateIndexRepository(
|
|
297
|
+
this,
|
|
298
|
+
this.getApiKeyRepository(projectId),
|
|
299
|
+
new StateRepository(
|
|
300
|
+
this.backend.createApiKeyTokenIndexCollection(projectId),
|
|
301
|
+
z.string(),
|
|
302
|
+
this.getEncryptionBackend(projectId),
|
|
303
|
+
this.configureKeyHashing(projectId), // to hide the tokens
|
|
304
|
+
this.logger,
|
|
305
|
+
),
|
|
306
|
+
this.logger,
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Creates a repository for workers.
|
|
312
|
+
*
|
|
313
|
+
* @param projectId The ID of the project owning the workers.
|
|
314
|
+
*/
|
|
315
|
+
getWorkerRepository(projectId: string): StateRepository<typeof workerSchema> {
|
|
316
|
+
return new StateRepository(
|
|
317
|
+
this.backend.createWorkerCollection(projectId),
|
|
318
|
+
workerSchema,
|
|
319
|
+
this.getEncryptionBackend(projectId),
|
|
320
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
321
|
+
this.logger,
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Creates a repository for worker logs.
|
|
327
|
+
*
|
|
328
|
+
* @param projectId The ID of the project owning the worker logs.
|
|
329
|
+
* @param workerId The ID of the worker to which the logs belong.
|
|
330
|
+
*/
|
|
331
|
+
getWorkerLogRepository(projectId: string, workerId: string): StateRepository<z.ZodString> {
|
|
332
|
+
return new StateRepository(
|
|
333
|
+
this.backend.createWorkerLogCollection(projectId, workerId),
|
|
334
|
+
z.string(),
|
|
335
|
+
this.getEncryptionBackend(projectId),
|
|
336
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
337
|
+
this.logger,
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Creates a repository for worker registrations.
|
|
343
|
+
*
|
|
344
|
+
* @param projectId The ID of the project owning the worker registrations.
|
|
345
|
+
*/
|
|
346
|
+
getWorkerRegistrationRepository(
|
|
347
|
+
projectId: string,
|
|
348
|
+
): StateRepository<typeof workerUnitRegistrationSchema> {
|
|
349
|
+
return new StateRepository(
|
|
350
|
+
this.backend.createWorkerRegistrationCollection(projectId),
|
|
351
|
+
workerUnitRegistrationSchema,
|
|
352
|
+
this.getEncryptionBackend(projectId),
|
|
353
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
354
|
+
this.logger,
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Creates an index repository which holds the list of worker registrations.
|
|
360
|
+
*
|
|
361
|
+
* @param projectId The ID of the project owning the workers.
|
|
362
|
+
* @param workerId The ID of the worker.
|
|
363
|
+
*/
|
|
364
|
+
getWorkerRegistrationIndexRepository(
|
|
365
|
+
projectId: string,
|
|
366
|
+
workerId: string,
|
|
367
|
+
): StateIndexRepository<typeof workerUnitRegistrationSchema> {
|
|
368
|
+
return new StateIndexRepository(
|
|
369
|
+
this,
|
|
370
|
+
this.getWorkerRegistrationRepository(projectId),
|
|
371
|
+
new StateRepository(
|
|
372
|
+
this.backend.createWorkerRegistrationIndexCollection(projectId, workerId),
|
|
373
|
+
z.string(),
|
|
374
|
+
this.getEncryptionBackend(projectId),
|
|
375
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
376
|
+
this.logger,
|
|
377
|
+
),
|
|
378
|
+
this.logger,
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Gets a repository for artifacts.
|
|
384
|
+
*
|
|
385
|
+
* @param projectId The ID of the project owning the artifacts.
|
|
386
|
+
*/
|
|
387
|
+
getArtifactRepository(projectId: string): StateRepository<typeof artifactSchema> {
|
|
388
|
+
return new StateRepository(
|
|
389
|
+
this.backend.createArtifactCollection(projectId),
|
|
390
|
+
artifactSchema,
|
|
391
|
+
this.getEncryptionBackend(projectId),
|
|
392
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
393
|
+
this.logger,
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Creates an index repository which maps artifact hashes to artifact IDs.
|
|
399
|
+
*
|
|
400
|
+
* @param projectId The ID of the project owning the artifacts.
|
|
401
|
+
*/
|
|
402
|
+
getArtifactHashIndexRepository(projectId: string): StateIndexRepository<typeof artifactSchema> {
|
|
403
|
+
return new StateIndexRepository(
|
|
404
|
+
this,
|
|
405
|
+
this.getArtifactRepository(projectId),
|
|
406
|
+
new StateRepository(
|
|
407
|
+
this.backend.createArtifactHashIndexCollection(projectId),
|
|
408
|
+
z.string(),
|
|
409
|
+
this.getEncryptionBackend(projectId),
|
|
410
|
+
this.configureKeyHashing(projectId), // to hide the original sha256 hash
|
|
411
|
+
this.logger,
|
|
412
|
+
),
|
|
413
|
+
this.logger,
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Creates a repository for trigger info.
|
|
419
|
+
*
|
|
420
|
+
* @param projectId The ID of the project owning the triggers.
|
|
421
|
+
*/
|
|
422
|
+
getTriggerRepository(projectId: string): StateRepository<typeof triggerSchema> {
|
|
423
|
+
return new StateRepository(
|
|
424
|
+
this.backend.createTriggerCollection(projectId),
|
|
425
|
+
triggerSchema,
|
|
426
|
+
this.getEncryptionBackend(projectId),
|
|
427
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
428
|
+
this.logger,
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Creates a repository for unlock methods.
|
|
434
|
+
*
|
|
435
|
+
* @param projectId The ID of the project owning the unlock methods.
|
|
436
|
+
*/
|
|
437
|
+
getUnlockMethodRepository(projectId: string): StateRepository<typeof unlockMethodSchema> {
|
|
438
|
+
return new StateRepository(
|
|
439
|
+
this.backend.createUnlockMethodCollection(projectId),
|
|
440
|
+
unlockMethodSchema,
|
|
441
|
+
this.getEncryptionBackend(projectId),
|
|
442
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
443
|
+
this.logger,
|
|
444
|
+
)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Creates a repository for secret info.
|
|
449
|
+
*
|
|
450
|
+
* @param projectId The ID of the project owning the secrets.
|
|
451
|
+
*/
|
|
452
|
+
getSecretRepository(projectId: string): StateRepository<typeof secretSchema> {
|
|
453
|
+
const collection = this.backend.createSecretCollection(projectId)
|
|
454
|
+
|
|
455
|
+
return new StateRepository(
|
|
456
|
+
//
|
|
457
|
+
collection,
|
|
458
|
+
secretSchema,
|
|
459
|
+
this.getEncryptionBackend(projectId),
|
|
460
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
461
|
+
this.logger,
|
|
462
|
+
)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Creates a repository for secret content.
|
|
467
|
+
*
|
|
468
|
+
* The ID is secret ID, not UUIDv7.
|
|
469
|
+
*
|
|
470
|
+
* @param projectId The ID of the project owning the secret content.
|
|
471
|
+
*/
|
|
472
|
+
getSecretContentRepository(projectId: string): StateRepository<z.ZodUnknown> {
|
|
473
|
+
return new StateRepository(
|
|
474
|
+
this.backend.createSecretContentCollection(projectId),
|
|
475
|
+
z.unknown(),
|
|
476
|
+
this.getEncryptionBackend(projectId),
|
|
477
|
+
NO_KEY_HASHING, // nothing to hide
|
|
478
|
+
this.logger,
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Creates an index repository to map descriptors like `instance:{instanceId}:{secretName}` or `system:{systemSecretName}`
|
|
484
|
+
* to UUIDv7 IDs of these secrets.
|
|
485
|
+
*
|
|
486
|
+
* @param projectId The ID of the project owning the secrets.
|
|
487
|
+
*/
|
|
488
|
+
createSecretIndexRepository(projectId: string): StateIndexRepository<typeof secretSchema> {
|
|
489
|
+
return new StateIndexRepository(
|
|
490
|
+
this,
|
|
491
|
+
this.getSecretRepository(projectId),
|
|
492
|
+
new StateRepository(
|
|
493
|
+
this.backend.createSecretIndexCollection(projectId),
|
|
494
|
+
z.string(),
|
|
495
|
+
this.getEncryptionBackend(projectId),
|
|
496
|
+
this.configureKeyHashing(projectId), // to hide the descriptor
|
|
497
|
+
this.logger,
|
|
498
|
+
),
|
|
499
|
+
this.logger,
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Creates a repository for operation logs.
|
|
505
|
+
*
|
|
506
|
+
* @param projectId The ID of the project owning the operation logs.
|
|
507
|
+
* @param operationId The ID of the operation to which the logs belong.
|
|
508
|
+
*/
|
|
509
|
+
getOperationLogRepository(projectId: string, operationId: string): StateRepository<z.ZodString> {
|
|
510
|
+
return new StateRepository(
|
|
511
|
+
this.backend.createOperationLogCollection(projectId, operationId),
|
|
512
|
+
z.string(),
|
|
513
|
+
this.getEncryptionBackend(projectId),
|
|
514
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
515
|
+
this.logger,
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Creates an index repository for instance logs.
|
|
521
|
+
* This repository allows to retrieve operation logs by instance ID.
|
|
522
|
+
*
|
|
523
|
+
* @param projectId The ID of the project owning the operation logs.
|
|
524
|
+
* @param operationId The ID of the operation to which the logs belong.
|
|
525
|
+
* @param instanceId The ID of the instance to which the logs belong.
|
|
526
|
+
*/
|
|
527
|
+
getInstanceLogIndexRepository(
|
|
528
|
+
projectId: string,
|
|
529
|
+
operationId: string,
|
|
530
|
+
instanceId: string,
|
|
531
|
+
): StateIndexRepository<z.ZodString> {
|
|
532
|
+
return new StateIndexRepository(
|
|
533
|
+
this,
|
|
534
|
+
this.getOperationLogRepository(projectId, operationId),
|
|
535
|
+
new StateRepository(
|
|
536
|
+
this.backend.createInstanceLogIndexCollection(projectId, operationId, instanceId),
|
|
537
|
+
z.string(),
|
|
538
|
+
this.getEncryptionBackend(projectId),
|
|
539
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
540
|
+
this.logger,
|
|
541
|
+
),
|
|
542
|
+
this.logger,
|
|
543
|
+
)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Creates a repository for operation info.
|
|
548
|
+
*
|
|
549
|
+
* @param projectId The ID of the project owning the operations.
|
|
550
|
+
*/
|
|
551
|
+
getOperationRepository(projectId: string): StateRepository<typeof operationSchema> {
|
|
552
|
+
return new StateRepository(
|
|
553
|
+
this.backend.createOperationCollection(projectId),
|
|
554
|
+
operationSchema,
|
|
555
|
+
this.getEncryptionBackend(projectId),
|
|
556
|
+
NO_KEY_HASHING, // we want to sort by UUIDv7
|
|
557
|
+
this.logger,
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Creates an index repository for active operations.
|
|
563
|
+
*
|
|
564
|
+
* @param projectId The ID of the project owning the operations.
|
|
565
|
+
*/
|
|
566
|
+
getActiveOperationIndexRepository(
|
|
567
|
+
projectId: string,
|
|
568
|
+
): StateIndexRepository<typeof operationSchema> {
|
|
569
|
+
return new StateIndexRepository(
|
|
570
|
+
this,
|
|
571
|
+
this.getOperationRepository(projectId),
|
|
572
|
+
new StateRepository(
|
|
573
|
+
this.backend.createActiveOperationIndexCollection(projectId),
|
|
574
|
+
z.string(),
|
|
575
|
+
this.getEncryptionBackend(projectId),
|
|
576
|
+
this.configureKeyHashing(projectId), // to hide which operations are active
|
|
577
|
+
this.logger,
|
|
578
|
+
),
|
|
579
|
+
this.logger,
|
|
580
|
+
)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Creates a repository for instance states with sensitive ID hashing.
|
|
585
|
+
*
|
|
586
|
+
* @param projectId The ID of the project owning the instance states.
|
|
587
|
+
*/
|
|
588
|
+
getInstanceStateRepository(projectId: string): StateRepository<typeof instanceStateSchema> {
|
|
589
|
+
return new StateRepository(
|
|
590
|
+
this.backend.createInstanceStateCollection(projectId),
|
|
591
|
+
instanceStateSchema,
|
|
592
|
+
this.getEncryptionBackend(projectId),
|
|
593
|
+
this.configureKeyHashing(projectId), // to hide the instance ID
|
|
594
|
+
this.logger,
|
|
595
|
+
)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Creates a repository for instance locks.
|
|
600
|
+
*
|
|
601
|
+
* @param projectId The ID of the project owning the instance locks.
|
|
602
|
+
*/
|
|
603
|
+
getInstanceLockRepository(projectId: string): StateRepository<typeof instanceLockSchema> {
|
|
604
|
+
return new StateRepository(
|
|
605
|
+
this.backend.createInstanceLockCollection(projectId),
|
|
606
|
+
instanceLockSchema,
|
|
607
|
+
this.getEncryptionBackend(projectId),
|
|
608
|
+
this.configureKeyHashing(projectId), // to hide the instance ID
|
|
609
|
+
this.logger,
|
|
610
|
+
)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Creates a repository for composite instances with sensitive ID hashing.
|
|
615
|
+
*/
|
|
616
|
+
getCompositeInstanceRepository(
|
|
617
|
+
projectId: string,
|
|
618
|
+
): StateRepository<typeof compositeInstanceSchema> {
|
|
619
|
+
return new StateRepository(
|
|
620
|
+
this.backend.createCompositeInstanceCollection(projectId),
|
|
621
|
+
compositeInstanceSchema,
|
|
622
|
+
this.getEncryptionBackend(projectId),
|
|
623
|
+
this.configureKeyHashing(projectId), // to hide the composite instance ID
|
|
624
|
+
this.logger,
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Creates a repository for user layouts.
|
|
630
|
+
*/
|
|
631
|
+
getUserLayoutRepository(): StateRepository<z.ZodUnknown> {
|
|
632
|
+
return new StateRepository(
|
|
633
|
+
this.backend.createUserLayoutCollection(),
|
|
634
|
+
z.unknown(),
|
|
635
|
+
this.encryptionBackend,
|
|
636
|
+
this.configureBackendKeyHashing(), // to hide user IDs
|
|
637
|
+
this.logger,
|
|
638
|
+
)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Creates a repository for project viewports.
|
|
643
|
+
*
|
|
644
|
+
* @param projectId The ID of the project owning the viewports.
|
|
645
|
+
*/
|
|
646
|
+
getProjectViewportRepository(projectId: string): StateRepository<z.ZodUnknown> {
|
|
647
|
+
return new StateRepository(
|
|
648
|
+
this.backend.createProjectViewportCollection(projectId),
|
|
649
|
+
z.unknown(),
|
|
650
|
+
this.getEncryptionBackend(projectId),
|
|
651
|
+
this.configureKeyHashing(projectId), // to hide user IDs
|
|
652
|
+
this.logger,
|
|
653
|
+
)
|
|
22
654
|
}
|
|
23
655
|
|
|
24
656
|
/**
|
|
25
|
-
*
|
|
657
|
+
* Creates a repository for instance viewports.
|
|
26
658
|
*
|
|
27
|
-
* @param projectId The
|
|
28
|
-
* @param
|
|
659
|
+
* @param projectId The ID of the project owning the viewports.
|
|
660
|
+
* @param instanceId The ID of the instance to which the viewports belong.
|
|
29
661
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
662
|
+
getInstanceViewportRepository(
|
|
663
|
+
projectId: string,
|
|
664
|
+
instanceId: string,
|
|
665
|
+
): StateRepository<z.ZodUnknown> {
|
|
666
|
+
return new StateRepository(
|
|
667
|
+
this.backend.createInstanceViewportCollection(projectId, instanceId),
|
|
668
|
+
z.unknown(),
|
|
669
|
+
this.getEncryptionBackend(projectId),
|
|
670
|
+
this.configureKeyHashing(projectId), // to hide user IDs
|
|
671
|
+
this.logger,
|
|
672
|
+
)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
private idNamespaceHashCache = new Map<string, string>()
|
|
676
|
+
private idNamespaceHashLock = new BetterLock()
|
|
677
|
+
|
|
678
|
+
private getProjectIdHashNamespaceRepository(): StateRepository<z.ZodString> {
|
|
679
|
+
return new StateRepository(
|
|
680
|
+
this.backend.createProjectIdHashNamespaceCollection(),
|
|
681
|
+
z.string(),
|
|
682
|
+
this.encryptionBackend,
|
|
683
|
+
() => Promise.resolve(this.backendNamespace),
|
|
684
|
+
this.logger,
|
|
685
|
+
)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private configureBackendKeyHashing(): () => Promise<string> {
|
|
689
|
+
return () => Promise.resolve(this.backendNamespace)
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
public configureKeyHashing(projectId: string): () => Promise<string> {
|
|
693
|
+
return async () => {
|
|
694
|
+
const existing = this.idNamespaceHashCache.get(projectId)
|
|
695
|
+
if (existing) {
|
|
696
|
+
return existing
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return await this.idNamespaceHashLock.acquire(projectId, async () => {
|
|
700
|
+
let stored = await this.getProjectIdHashNamespaceRepository().get(projectId)
|
|
701
|
+
|
|
702
|
+
if (!stored) {
|
|
703
|
+
stored = uuidv4()
|
|
704
|
+
await this.getProjectIdHashNamespaceRepository().put(projectId, stored)
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
this.idNamespaceHashCache.set(projectId, stored)
|
|
708
|
+
return stored
|
|
709
|
+
})
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// store the master keys in memory cache for 30 seconds
|
|
714
|
+
private readonly masterKeyCache = new LRUCache<string, Uint8Array>({
|
|
715
|
+
ttl: 30_000,
|
|
716
|
+
ttlAutopurge: true,
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
async getProjectMasterKey(projectId: string): Promise<Uint8Array> {
|
|
720
|
+
const cachedKey = this.masterKeyCache.get(projectId)
|
|
721
|
+
if (cachedKey) {
|
|
722
|
+
return cachedKey
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const masterKey = await this.hotStateManager.get(["project-master-key", projectId])
|
|
726
|
+
if (!masterKey) {
|
|
727
|
+
throw new ProjectLockedError(projectId)
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
this.masterKeyCache.set(projectId, masterKey)
|
|
731
|
+
return masterKey
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
public getEncryptionBackend(projectId: string): EncryptionBackend {
|
|
735
|
+
return new MasterKeyEncryptionBackend(() => this.getProjectMasterKey(projectId))
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
public getHashedProjectId(projectId: string): string {
|
|
739
|
+
return uuidv5(projectId, this.backendNamespace)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
static async create(
|
|
743
|
+
config: z.infer<typeof stateManagerConfig>,
|
|
744
|
+
backend: StateBackend,
|
|
745
|
+
hotStateManager: HotStateManager,
|
|
746
|
+
logger: Logger,
|
|
747
|
+
): Promise<StateManager> {
|
|
748
|
+
let backendId = await backend.getStaticBackendId()
|
|
749
|
+
if (!backendId) {
|
|
750
|
+
backendId = uuidv4()
|
|
751
|
+
await backend.setStaticBackendId(backendId)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const masterKey = await StateManager.loadBackendMasterKey(config, backend, logger)
|
|
755
|
+
const encryptionBackend = new MasterKeyEncryptionBackend(() => Promise.resolve(masterKey))
|
|
756
|
+
|
|
757
|
+
return new StateManager(backendId, encryptionBackend, backend, hotStateManager, logger)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
static async loadBackendMasterKey(
|
|
761
|
+
config: z.infer<typeof stateManagerConfig>,
|
|
762
|
+
backend: StateBackend,
|
|
763
|
+
logger: Logger,
|
|
764
|
+
): Promise<Uint8Array> {
|
|
765
|
+
// 1. try to load the master key from HIGHSTATE_BACKEND_MASTER_KEY
|
|
766
|
+
const configMasterKey = config.HIGHSTATE_BACKEND_MASTER_KEY
|
|
767
|
+
if (configMasterKey) {
|
|
768
|
+
logger.info("loaded backend master key from HIGHSTATE_BACKEND_MASTER_KEY")
|
|
769
|
+
|
|
770
|
+
return Buffer.from(configMasterKey, "hex")
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// 2. try to load the master key from file path provided in HIGHSTATE_BACKEND_MASTER_KEY_PATH
|
|
774
|
+
const configMasterKeyPath = config.HIGHSTATE_BACKEND_MASTER_KEY_PATH
|
|
775
|
+
if (configMasterKeyPath) {
|
|
776
|
+
try {
|
|
777
|
+
const content = await readFile(configMasterKeyPath, "utf-8")
|
|
778
|
+
|
|
779
|
+
return Buffer.from(content.trim(), "hex")
|
|
780
|
+
} catch (error) {
|
|
781
|
+
throw new Error(`Failed to read backend master key from path: ${configMasterKeyPath}`, {
|
|
782
|
+
cause: error,
|
|
783
|
+
})
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// 3. try to decrypt the master key using the keyring
|
|
788
|
+
try {
|
|
789
|
+
const identity = await getOrCreateBackendIdentity(logger)
|
|
790
|
+
|
|
791
|
+
let armoredMasterKey = await backend.getEncryptedBackendMasterKey()
|
|
792
|
+
if (!armoredMasterKey) {
|
|
793
|
+
const masterKey = randomBytes(32)
|
|
794
|
+
const encrypter = new Encrypter()
|
|
795
|
+
|
|
796
|
+
const recipient = await identityToRecipient(identity)
|
|
797
|
+
encrypter.addRecipient(recipient)
|
|
798
|
+
|
|
799
|
+
const encryptedMasterKey = await encrypter.encrypt(masterKey)
|
|
800
|
+
armoredMasterKey = armor.encode(encryptedMasterKey)
|
|
801
|
+
await backend.setEncryptedBackendMasterKey(armoredMasterKey)
|
|
802
|
+
|
|
803
|
+
logger.info("generated and stored new backend master key")
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const decrypter = new Decrypter()
|
|
807
|
+
decrypter.addIdentity(identity)
|
|
808
|
+
|
|
809
|
+
const encryptedMasterKey = armor.decode(armoredMasterKey)
|
|
810
|
+
const masterKey = await decrypter.decrypt(encryptedMasterKey)
|
|
811
|
+
|
|
812
|
+
logger.info("loaded backend master key using OS keyring")
|
|
813
|
+
|
|
814
|
+
return masterKey
|
|
815
|
+
} catch (error) {
|
|
816
|
+
throw new Error("Failed to load master key using OS keyring", { cause: error })
|
|
817
|
+
}
|
|
32
818
|
}
|
|
33
819
|
}
|