@highstate/backend 0.9.16 → 0.9.19

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.
Files changed (302) hide show
  1. package/dist/chunk-5WVU2AK4.js +1535 -0
  2. package/dist/chunk-5WVU2AK4.js.map +1 -0
  3. package/dist/chunk-I7BWSAN6.js +49 -0
  4. package/dist/chunk-I7BWSAN6.js.map +1 -0
  5. package/dist/{chunk-RCB4AFGD.js → chunk-VB4YL327.js} +51 -71
  6. package/dist/chunk-VB4YL327.js.map +1 -0
  7. package/dist/database/local/prisma.config.js +26 -0
  8. package/dist/database/local/prisma.config.js.map +1 -0
  9. package/dist/highstate.manifest.json +5 -4
  10. package/dist/index.js +7676 -6634
  11. package/dist/index.js.map +1 -1
  12. package/dist/library/package-resolution-worker.js +8 -6
  13. package/dist/library/package-resolution-worker.js.map +1 -1
  14. package/dist/library/worker/main.js +63 -58
  15. package/dist/library/worker/main.js.map +1 -1
  16. package/dist/shared/index.js +3 -216
  17. package/dist/shared/index.js.map +1 -1
  18. package/package.json +23 -11
  19. package/prisma/backend/_schema/layout.prisma +7 -0
  20. package/prisma/backend/_schema/library.prisma +17 -0
  21. package/prisma/backend/_schema/project.prisma +101 -0
  22. package/prisma/backend/_schema/pulumi.prisma +17 -0
  23. package/prisma/backend/postgresql/main.prisma +17 -0
  24. package/prisma/backend/sqlite/main.prisma +17 -0
  25. package/prisma/backend/sqlite/migrations/20250817070609_initiial/migration.sql +34 -0
  26. package/prisma/backend/sqlite/migrations/20250817104948_add_fields/migration.sql +59 -0
  27. package/prisma/backend/sqlite/migrations/20250818082732_add_models/migration.sql +41 -0
  28. package/prisma/backend/sqlite/migrations/20250818083106_a/migration.sql +19 -0
  29. package/prisma/backend/sqlite/migrations/20250818101945_hi/migration.sql +1 -0
  30. package/prisma/backend/sqlite/migrations/20250819082315_a/migration.sql +5 -0
  31. package/prisma/backend/sqlite/migrations/migration_lock.toml +3 -0
  32. package/prisma/project/api-key.prisma +27 -0
  33. package/prisma/project/artifact.prisma +52 -0
  34. package/prisma/project/custom-status.prisma +46 -0
  35. package/prisma/project/evaluation.prisma +35 -0
  36. package/prisma/project/instance.prisma +160 -0
  37. package/prisma/project/layout.prisma +23 -0
  38. package/prisma/project/lock.prisma +18 -0
  39. package/prisma/project/main.prisma +17 -0
  40. package/prisma/project/migrations/20250816081310_initial/migration.sql +300 -0
  41. package/prisma/project/migrations/20250816082523_test/migration.sql +72 -0
  42. package/prisma/project/migrations/20250818065643_update/migration.sql +42 -0
  43. package/prisma/project/migrations/20250818070758_a/migration.sql +8 -0
  44. package/prisma/project/migrations/20250818070913_a/migration.sql +8 -0
  45. package/prisma/project/migrations/20250818082720_add_motels/migration.sql +11 -0
  46. package/prisma/project/migrations/20250818112523_hello/migration.sql +35 -0
  47. package/prisma/project/migrations/20250819082305_a/migration.sql +14 -0
  48. package/prisma/project/migrations/20250819165004_add_missing_fields/migration.sql +216 -0
  49. package/prisma/project/migrations/20250819171309_a/migration.sql +22 -0
  50. package/prisma/project/migrations/20250820113949_a/migration.sql +66 -0
  51. package/prisma/project/migrations/20250820144256_b/migration.sql +31 -0
  52. package/prisma/project/migrations/20250820145547_a/migration.sql +24 -0
  53. package/prisma/project/migrations/20250820182517_b/migration.sql +2 -0
  54. package/prisma/project/migrations/20250821172324_a/migration.sql +2 -0
  55. package/prisma/project/migrations/20250822081339_a/migration.sql +219 -0
  56. package/prisma/project/migrations/20250822083742_b/migration.sql +1 -0
  57. package/prisma/project/migrations/20250822105134_boom/migration.sql +1 -0
  58. package/prisma/project/migrations/20250822141028_b/migration.sql +1 -0
  59. package/prisma/project/migrations/20250822142342_b/migration.sql +16 -0
  60. package/prisma/project/migrations/20250824072720_a/migration.sql +1 -0
  61. package/prisma/project/migrations/20250824093656_b/migration.sql +21 -0
  62. package/prisma/project/migrations/20250825082518_a/migration.sql +1 -0
  63. package/prisma/project/migrations/20250825085343_b/migration.sql +1 -0
  64. package/prisma/project/migrations/20250825091312_a/migration.sql +1 -0
  65. package/prisma/project/migrations/20250903095431_hi/migration.sql +44 -0
  66. package/prisma/project/migrations/20250903174255_a/migration.sql +24 -0
  67. package/prisma/project/migrations/20250908095205_hi/migration.sql +18 -0
  68. package/prisma/project/migrations/20250909155857_hi/migration.sql +15 -0
  69. package/prisma/project/migrations/migration_lock.toml +3 -0
  70. package/prisma/project/model.prisma +37 -0
  71. package/prisma/project/operation.prisma +148 -0
  72. package/prisma/project/page.prisma +41 -0
  73. package/prisma/project/secret.prisma +42 -0
  74. package/prisma/project/service-account.prisma +36 -0
  75. package/prisma/project/terminal.prisma +90 -0
  76. package/prisma/project/trigger.prisma +31 -0
  77. package/prisma/project/unlock-method.prisma +32 -0
  78. package/prisma/project/worker.prisma +138 -0
  79. package/src/artifact/abstractions.ts +13 -13
  80. package/src/artifact/encryption.ts +31 -15
  81. package/src/artifact/factory.ts +7 -10
  82. package/src/artifact/local.ts +33 -50
  83. package/src/business/api-key.ts +24 -36
  84. package/src/business/artifact.test.ts +978 -0
  85. package/src/business/artifact.ts +136 -215
  86. package/src/business/evaluation.ts +328 -0
  87. package/src/business/index.ts +5 -1
  88. package/src/business/instance-lock.test.ts +1060 -0
  89. package/src/business/instance-lock.ts +387 -77
  90. package/src/business/instance-state.test.ts +735 -0
  91. package/src/business/instance-state.ts +604 -217
  92. package/src/business/operation.test.ts +439 -0
  93. package/src/business/operation.ts +174 -208
  94. package/src/business/project-model.ts +258 -0
  95. package/src/business/project-unlock.ts +172 -112
  96. package/src/business/project.ts +407 -0
  97. package/src/business/secret.test.ts +513 -0
  98. package/src/business/secret.ts +194 -131
  99. package/src/business/settings.test.ts +695 -0
  100. package/src/business/settings.ts +855 -0
  101. package/src/business/terminal-session.ts +90 -0
  102. package/src/business/unit-extra.test.ts +539 -0
  103. package/src/business/unit-extra.ts +160 -0
  104. package/src/business/worker.test.ts +391 -0
  105. package/src/business/worker.ts +250 -114
  106. package/src/common/codebase.ts +65 -0
  107. package/src/common/index.ts +3 -2
  108. package/src/common/logger.ts +5 -0
  109. package/src/common/utils.ts +4 -3
  110. package/src/config.ts +15 -12
  111. package/src/database/_generated/backend/postgresql/client.ts +72 -0
  112. package/src/database/_generated/backend/postgresql/commonInputTypes.ts +350 -0
  113. package/src/database/_generated/backend/postgresql/enums.ts +13 -0
  114. package/src/database/_generated/backend/postgresql/internal/class.ts +320 -0
  115. package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +1238 -0
  116. package/src/database/_generated/backend/postgresql/models/Library.ts +1263 -0
  117. package/src/database/_generated/backend/postgresql/models/Project.ts +2175 -0
  118. package/src/database/_generated/backend/postgresql/models/ProjectModelStorage.ts +1263 -0
  119. package/src/database/_generated/backend/postgresql/models/ProjectSpace.ts +1602 -0
  120. package/src/database/_generated/backend/postgresql/models/PulumiBackend.ts +1263 -0
  121. package/src/database/_generated/backend/postgresql/models/UserWorkspaseLayout.ts +1065 -0
  122. package/src/database/_generated/backend/postgresql/models.ts +16 -0
  123. package/src/database/_generated/backend/postgresql/pjtg.ts +182 -0
  124. package/src/database/_generated/backend/sqlite/client.ts +72 -0
  125. package/src/database/_generated/backend/sqlite/commonInputTypes.ts +331 -0
  126. package/src/database/_generated/backend/sqlite/enums.ts +13 -0
  127. package/src/database/_generated/backend/sqlite/internal/class.ts +318 -0
  128. package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +1207 -0
  129. package/src/database/_generated/backend/sqlite/models/Library.ts +1261 -0
  130. package/src/database/_generated/backend/sqlite/models/Project.ts +2169 -0
  131. package/src/database/_generated/backend/sqlite/models/ProjectModelStorage.ts +1261 -0
  132. package/src/database/_generated/backend/sqlite/models/ProjectSpace.ts +1599 -0
  133. package/src/database/_generated/backend/sqlite/models/PulumiBackend.ts +1261 -0
  134. package/src/database/_generated/backend/sqlite/models/UserWorkspaseLayout.ts +1063 -0
  135. package/src/database/_generated/backend/sqlite/models.ts +16 -0
  136. package/src/database/_generated/backend/sqlite/pjtg.ts +182 -0
  137. package/src/database/_generated/project/client.ts +204 -0
  138. package/src/database/_generated/project/commonInputTypes.ts +827 -0
  139. package/src/database/_generated/project/enums.ts +104 -0
  140. package/src/database/_generated/project/internal/class.ts +479 -0
  141. package/src/database/_generated/project/internal/prismaNamespace.ts +2974 -0
  142. package/src/database/_generated/project/models/ApiKey.ts +1506 -0
  143. package/src/database/_generated/project/models/Artifact.ts +2051 -0
  144. package/src/database/_generated/project/models/HubModel.ts +1125 -0
  145. package/src/database/_generated/project/models/InstanceCustomStatus.ts +1713 -0
  146. package/src/database/_generated/project/models/InstanceEvaluationState.ts +1312 -0
  147. package/src/database/_generated/project/models/InstanceLock.ts +1268 -0
  148. package/src/database/_generated/project/models/InstanceModel.ts +1125 -0
  149. package/src/database/_generated/project/models/InstanceOperationState.ts +1707 -0
  150. package/src/database/_generated/project/models/InstanceState.ts +4613 -0
  151. package/src/database/_generated/project/models/Operation.ts +1647 -0
  152. package/src/database/_generated/project/models/OperationLog.ts +1455 -0
  153. package/src/database/_generated/project/models/Page.ts +1838 -0
  154. package/src/database/_generated/project/models/Secret.ts +1692 -0
  155. package/src/database/_generated/project/models/ServiceAccount.ts +2165 -0
  156. package/src/database/_generated/project/models/Terminal.ts +2038 -0
  157. package/src/database/_generated/project/models/TerminalSession.ts +1454 -0
  158. package/src/database/_generated/project/models/TerminalSessionLog.ts +1280 -0
  159. package/src/database/_generated/project/models/Trigger.ts +1430 -0
  160. package/src/database/_generated/project/models/UnlockMethod.ts +1220 -0
  161. package/src/database/_generated/project/models/UserCompositeViewport.ts +1280 -0
  162. package/src/database/_generated/project/models/UserProjectViewport.ts +1059 -0
  163. package/src/database/_generated/project/models/Worker.ts +1459 -0
  164. package/src/database/_generated/project/models/WorkerUnitRegistration.ts +1524 -0
  165. package/src/database/_generated/project/models/WorkerVersion.ts +1974 -0
  166. package/src/database/_generated/project/models/WorkerVersionLog.ts +1318 -0
  167. package/src/database/_generated/project/models.ts +35 -0
  168. package/src/database/_generated/project/pjtg.ts +182 -0
  169. package/src/database/abstractions.ts +19 -0
  170. package/src/database/factory.ts +37 -0
  171. package/src/database/index.ts +6 -0
  172. package/src/database/local/backend.ts +134 -0
  173. package/src/database/local/index.ts +3 -0
  174. package/src/database/local/meta.ts +46 -0
  175. package/src/database/local/prisma.config.ts +25 -0
  176. package/src/database/local/project.ts +39 -0
  177. package/src/database/manager.ts +181 -0
  178. package/src/database/migrate.ts +35 -0
  179. package/src/database/prisma.ts +56 -0
  180. package/src/database/well-known.ts +38 -0
  181. package/src/index.ts +4 -4
  182. package/src/library/abstractions.ts +21 -14
  183. package/src/library/factory.ts +1 -1
  184. package/src/library/local.ts +86 -38
  185. package/src/library/package-resolution-worker.ts +1 -1
  186. package/src/library/worker/evaluator.ts +61 -48
  187. package/src/library/worker/loader.lite.ts +14 -1
  188. package/src/library/worker/main.ts +9 -16
  189. package/src/library/worker/protocol.ts +0 -12
  190. package/src/lock/manager.ts +12 -7
  191. package/src/orchestrator/manager.ts +198 -131
  192. package/src/orchestrator/operation-context.ts +357 -0
  193. package/src/orchestrator/operation-plan.destroy.test.md +357 -0
  194. package/src/orchestrator/operation-plan.destroy.test.ts +775 -0
  195. package/src/orchestrator/operation-plan.fixtures.ts +213 -0
  196. package/src/orchestrator/operation-plan.md +198 -0
  197. package/src/orchestrator/operation-plan.refresh.test.md +199 -0
  198. package/src/orchestrator/operation-plan.refresh.test.ts +367 -0
  199. package/src/orchestrator/operation-plan.ts +709 -0
  200. package/src/orchestrator/operation-plan.update.test.md +485 -0
  201. package/src/orchestrator/operation-plan.update.test.ts +1066 -0
  202. package/src/orchestrator/operation-workset.ts +235 -583
  203. package/src/orchestrator/operation.ts +446 -904
  204. package/src/orchestrator/plan-test-builder.ts +267 -0
  205. package/src/project-model/abstractions.ts +118 -0
  206. package/src/project-model/backends/codebase.ts +365 -0
  207. package/src/project-model/backends/database.ts +440 -0
  208. package/src/project-model/errors.ts +81 -0
  209. package/src/project-model/factory.ts +24 -0
  210. package/src/project-model/index.ts +4 -0
  211. package/src/project-model/utils.test.ts +544 -0
  212. package/src/project-model/utils.ts +242 -0
  213. package/src/pubsub/abstractions.ts +10 -1
  214. package/src/pubsub/factory.ts +4 -4
  215. package/src/pubsub/index.ts +1 -0
  216. package/src/pubsub/manager.ts +49 -25
  217. package/src/pubsub/memory.ts +31 -0
  218. package/src/runner/abstractions.ts +38 -26
  219. package/src/runner/artifact-env.ts +17 -6
  220. package/src/runner/factory.ts +6 -6
  221. package/src/runner/force-abort.ts +3 -6
  222. package/src/runner/local.ts +79 -72
  223. package/src/runner/pulumi.ts +26 -63
  224. package/src/services.ts +214 -103
  225. package/src/shared/models/backend/index.ts +3 -1
  226. package/src/shared/models/backend/library.ts +12 -4
  227. package/src/shared/models/backend/project.ts +43 -23
  228. package/src/shared/models/backend/pulumi.ts +14 -0
  229. package/src/shared/models/backend/unlock-method.ts +1 -1
  230. package/src/shared/models/backend/well-known.ts +58 -0
  231. package/src/shared/models/base.ts +40 -109
  232. package/src/shared/models/errors.ts +82 -1
  233. package/src/shared/models/index.ts +3 -2
  234. package/src/shared/models/prisma.ts +36 -0
  235. package/src/shared/models/project/api-key.ts +37 -56
  236. package/src/shared/models/project/artifact.ts +15 -105
  237. package/src/shared/models/project/custom-status.ts +12 -0
  238. package/src/shared/models/project/index.ts +9 -9
  239. package/src/shared/models/project/lock.ts +10 -78
  240. package/src/shared/models/project/model.ts +32 -0
  241. package/src/shared/models/project/operation.ts +222 -99
  242. package/src/shared/models/project/page.ts +37 -48
  243. package/src/shared/models/project/secret.ts +29 -103
  244. package/src/shared/models/project/service-account.ts +12 -17
  245. package/src/shared/models/project/state.ts +100 -390
  246. package/src/shared/models/project/terminal.ts +75 -89
  247. package/src/shared/models/project/trigger.ts +13 -49
  248. package/src/shared/models/project/unlock-method.ts +21 -20
  249. package/src/shared/models/project/worker.ts +89 -88
  250. package/src/shared/resolvers/graph-resolver.ts +62 -26
  251. package/src/shared/resolvers/index.ts +1 -1
  252. package/src/shared/resolvers/input-hash.ts +24 -14
  253. package/src/shared/resolvers/input.ts +48 -6
  254. package/src/shared/resolvers/registry.ts +5 -4
  255. package/src/shared/resolvers/state.ts +12 -1
  256. package/src/shared/resolvers/validation.ts +29 -9
  257. package/src/shared/utils/index.ts +1 -1
  258. package/src/shared/utils/promise-tracker.ts +30 -3
  259. package/src/terminal/abstractions.ts +1 -1
  260. package/src/terminal/docker.ts +3 -3
  261. package/src/terminal/manager.ts +102 -118
  262. package/src/test-utils/database.ts +119 -0
  263. package/src/test-utils/index.ts +2 -0
  264. package/src/test-utils/services.ts +134 -0
  265. package/src/unlock/abstractions.ts +31 -0
  266. package/src/unlock/index.ts +2 -0
  267. package/src/unlock/memory.ts +27 -0
  268. package/src/worker/abstractions.ts +7 -4
  269. package/src/worker/docker.ts +14 -19
  270. package/src/worker/manager.ts +376 -79
  271. package/dist/chunk-RCB4AFGD.js.map +0 -1
  272. package/dist/chunk-WHALQHEZ.js +0 -2017
  273. package/dist/chunk-WHALQHEZ.js.map +0 -1
  274. package/src/business/backend-unlock.ts +0 -10
  275. package/src/common/performance.ts +0 -44
  276. package/src/hotstate/abstractions.ts +0 -48
  277. package/src/hotstate/factory.ts +0 -17
  278. package/src/hotstate/index.ts +0 -3
  279. package/src/hotstate/manager.ts +0 -192
  280. package/src/hotstate/memory.ts +0 -100
  281. package/src/hotstate/validation.ts +0 -101
  282. package/src/project/abstractions.ts +0 -102
  283. package/src/project/factory.ts +0 -11
  284. package/src/project/index.ts +0 -3
  285. package/src/project/local.ts +0 -469
  286. package/src/project/manager.ts +0 -574
  287. package/src/pubsub/local.ts +0 -36
  288. package/src/pubsub/validation.ts +0 -33
  289. package/src/shared/models/project/component.ts +0 -45
  290. package/src/shared/models/project/instance.ts +0 -74
  291. package/src/state/abstractions.ts +0 -450
  292. package/src/state/encryption.ts +0 -59
  293. package/src/state/factory.ts +0 -20
  294. package/src/state/index.ts +0 -6
  295. package/src/state/local/backend.ts +0 -299
  296. package/src/state/local/collection.ts +0 -342
  297. package/src/state/local/index.ts +0 -2
  298. package/src/state/manager.ts +0 -819
  299. package/src/state/repository/index.ts +0 -2
  300. package/src/state/repository/repository.index.ts +0 -193
  301. package/src/state/repository/repository.ts +0 -458
  302. /package/src/{state → database/local}/keyring.ts +0 -0
@@ -1,37 +1,43 @@
1
- import type { StateBatch, StateManager } from "../state"
2
1
  import type { Logger } from "pino"
3
- import type { HotStateManager } from "../hotstate"
2
+ import type { DatabaseManager } from "../database"
4
3
  import type { PubSubManager } from "../pubsub"
4
+ import type { ProjectUnlockBackend } from "../unlock"
5
5
  import { randomBytes } from "node:crypto"
6
6
  import { armor, Decrypter, Encrypter } from "age-encryption"
7
- import { type ProjectUnlockState, type UnlockMethod } from "../shared"
7
+ import { z } from "zod"
8
+ import { createProjectLogger } from "../common"
9
+ import {
10
+ CannotDeleteLastUnlockMethodError,
11
+ ProjectNotFoundError,
12
+ type ProjectUnlockState,
13
+ type ProjectUnlockSuite,
14
+ type UnlockMethodInput,
15
+ } from "../shared"
8
16
 
9
17
  type UnlockTask = {
10
18
  name: string
11
19
  handler: (projectId: string) => Promise<void> | void
12
20
  }
13
21
 
22
+ export const projectUnlockServiceConfig = z.object({
23
+ HIGHSTATE_ENCRYPTION_ENABLED: z.stringbool().default(true),
24
+ HIGHSTATE_DEV_AUTO_UNLOCK_PROJECT_IDS: z
25
+ .string()
26
+ .transform(val => val?.split(",") ?? [])
27
+ .default([]),
28
+ })
29
+
14
30
  export class ProjectUnlockService {
15
31
  constructor(
16
- private readonly stateManager: StateManager,
17
- private readonly hotStateManager: HotStateManager,
32
+ private readonly database: DatabaseManager,
18
33
  private readonly pubsubManager: PubSubManager,
34
+ private readonly projectUnlockBackend: ProjectUnlockBackend,
35
+ private readonly config: z.infer<typeof projectUnlockServiceConfig>,
19
36
  private readonly logger: Logger,
20
37
  ) {}
21
38
 
22
39
  private readonly unlockTasks: UnlockTask[] = []
23
40
 
24
- /**
25
- * Checks if the project is unlocked.
26
- * A project is considered locked if it has no master key set in memory.
27
- *
28
- * @param projectId The ID of the project to check.
29
- * @returns True if the project is locked, false otherwise.
30
- */
31
- async isProjectUnlocked(projectId: string): Promise<boolean> {
32
- return await this.hotStateManager.exists(["project-master-key", projectId])
33
- }
34
-
35
41
  /**
36
42
  * Gets the current unlock state of the project.
37
43
  * If the project is unlocked, it returns an object with type "unlocked".
@@ -41,41 +47,59 @@ export class ProjectUnlockService {
41
47
  * @returns The unlock state of the project.
42
48
  */
43
49
  async getProjectUnlockState(projectId: string): Promise<ProjectUnlockState> {
44
- const isUnlocked = await this.isProjectUnlocked(projectId)
50
+ const isUnlocked = await this.projectUnlockBackend.checkProjectUnlocked(projectId)
45
51
  if (isUnlocked) {
46
52
  return { type: "unlocked" }
47
53
  }
48
54
 
49
- const unlockSuite = await this.stateManager.getProjectUnlockSuiteRepository().get(projectId)
55
+ const project = await this.database.backend.project.findUnique({
56
+ where: { id: projectId },
57
+ select: { unlockSuite: true },
58
+ })
59
+
60
+ if (!project) {
61
+ throw new ProjectNotFoundError(projectId)
62
+ }
63
+
50
64
  return {
51
65
  type: "locked",
52
- unlockSuite,
66
+ unlockSuite: project.unlockSuite,
53
67
  }
54
68
  }
55
69
 
56
70
  /**
57
- * Creates a new project state and generates a master key for it.
71
+ * Sets up the project database by creating a master key and unlock suite with the provided unlock method.
72
+ *
73
+ * Creates databases, configures encryption and persists the unlock method inside the project database.
74
+ *
75
+ * Then returns the encrypted master key and the unlock suite for further persisting in the backend database.
58
76
  *
59
77
  * @param projectId The ID of the project to create the state for.
60
78
  * @param unlockMethod The unlock method to use to encrypt the master key. Should be provided by the frontend.
61
79
  */
62
- async createProjectState(projectId: string, unlockMethod: UnlockMethod): Promise<void> {
80
+ async setupProjectDatabase(
81
+ projectId: string,
82
+ unlockMethodInput: UnlockMethodInput,
83
+ ): Promise<[encryptedMasterKey: string, unlockSuite: ProjectUnlockSuite]> {
84
+ // generate a new master key for the project
63
85
  const masterKey = randomBytes(32)
64
- await using batch = this.stateManager.batch()
65
86
 
66
- // unlock the project by storing the master key in memory
67
- await this.hotStateManager.set(["project-master-key", projectId], masterKey)
87
+ // set the master key to setup the database encryption
88
+ await this.projectUnlockBackend.unlockProject(projectId, masterKey)
89
+
90
+ const encryptedMasterKey = await this.encryptProjectMasterKey(projectId, [unlockMethodInput])
68
91
 
69
- // store the master key in the backend
70
- await this.updateUnlockSuite(projectId, [unlockMethod], batch)
71
- await this.updateEncryptedMasterKey(projectId, [unlockMethod], batch, masterKey)
92
+ const database = await this.database.setupDatabase(projectId)
72
93
 
73
- // persist the unlock method as any other project data
74
- await this.stateManager
75
- .getUnlockMethodRepository(projectId)
76
- .put(unlockMethod.id, unlockMethod, batch)
94
+ // persist unlock method (now we can do it since the database is set up and unlocked)
95
+ await database.unlockMethod.create({ data: unlockMethodInput })
77
96
 
78
- await batch.write()
97
+ const unlockSuite: ProjectUnlockSuite = {
98
+ encryptedIdentities: [unlockMethodInput.encryptedIdentity],
99
+ hasPasskey: unlockMethodInput.type === "passkey",
100
+ }
101
+
102
+ return [encryptedMasterKey, unlockSuite]
79
103
  }
80
104
 
81
105
  /**
@@ -85,7 +109,7 @@ export class ProjectUnlockService {
85
109
  * @param decryptedIdentity The decrypted identity to use for unlocking the project. Should be provided by the frontend.
86
110
  */
87
111
  async unlockProject(projectId: string, decryptedIdentity: string): Promise<void> {
88
- if (await this.isProjectUnlocked(projectId)) {
112
+ if (await this.projectUnlockBackend.checkProjectUnlocked(projectId)) {
89
113
  this.logger.warn(
90
114
  { projectId },
91
115
  `project "%s" is already unlocked, skipping unlock operation`,
@@ -95,12 +119,24 @@ export class ProjectUnlockService {
95
119
  }
96
120
 
97
121
  // load the encrypted master key for the project
98
- const armoredMasterKey = await this.stateManager.getProjectMasterKeyRepository().get(projectId)
99
- if (!armoredMasterKey) {
100
- throw new Error(`Project ${projectId} does not have a master key set.`)
122
+ const project = await this.database.backend.project.findUnique({
123
+ where: { id: projectId },
124
+ select: { encryptedMasterKey: true },
125
+ })
126
+
127
+ if (!project) {
128
+ throw new ProjectNotFoundError(projectId)
101
129
  }
102
130
 
103
- const encryptedMasterKey = armor.decode(armoredMasterKey)
131
+ if (!this.config.HIGHSTATE_ENCRYPTION_ENABLED) {
132
+ // no cryptography, just unlock with an empty master key
133
+ await this.projectUnlockBackend.unlockProject(projectId, Buffer.alloc(0))
134
+ await this.pubsubManager.publish(["project-unlock-state", projectId], { type: "unlocked" })
135
+ await this.runUnlockTasks(projectId)
136
+ return
137
+ }
138
+
139
+ const encryptedMasterKey = armor.decode(project.encryptedMasterKey)
104
140
 
105
141
  const decrypter = new Decrypter()
106
142
  decrypter.addIdentity(decryptedIdentity)
@@ -108,20 +144,8 @@ export class ProjectUnlockService {
108
144
  // decrypt the master key using the provided identity
109
145
  const masterKey = await decrypter.decrypt(encryptedMasterKey)
110
146
 
111
- // store the master key in memory to unlock the project
112
- await this.hotStateManager.set(["project-master-key", projectId], masterKey)
113
-
114
- // load instance states to the hot state
115
- // TODO: this should be done by something else, ideally lazy-loaded when needed
116
- const instanceStates = await this.stateManager
117
- .getInstanceStateRepository(projectId)
118
- .getAllItems()
119
-
120
- await this.hotStateManager.hmset(
121
- ["instance-states", projectId],
122
- instanceStates.map(state => [state.id, state]),
123
- )
124
-
147
+ // unlock the project in the backend
148
+ await this.projectUnlockBackend.unlockProject(projectId, Buffer.from(masterKey))
125
149
  await this.pubsubManager.publish(["project-unlock-state", projectId], { type: "unlocked" })
126
150
 
127
151
  // run unlock tasks
@@ -143,24 +167,37 @@ export class ProjectUnlockService {
143
167
  * The project must be unlocked.
144
168
  *
145
169
  * @param projectId The ID of the project to add the unlock method to.
146
- * @param unlockMethod The unlock method to add. Should be provided by the frontend.
170
+ * @param inputUnlockMethod The unlock method to add. Should be provided by the frontend.
147
171
  */
148
- async addProjectUnlockMethod(projectId: string, unlockMethod: UnlockMethod): Promise<void> {
149
- await using batch = this.stateManager.batch()
150
-
151
- // add the unlock method to the repository
152
- await this.stateManager
153
- .getUnlockMethodRepository(projectId)
154
- .put(unlockMethod.id, unlockMethod, batch)
155
-
156
- // get all existing unlock methods
157
- const existingMethods = await this.stateManager
158
- .getUnlockMethodRepository(projectId)
159
- .getAllItems()
160
-
161
- // update the encrypted master identity set and master key
162
- await this.updateUnlockSuite(projectId, existingMethods, batch)
163
- await this.updateEncryptedMasterKey(projectId, existingMethods, batch)
172
+ async addProjectUnlockMethod(
173
+ projectId: string,
174
+ inputUnlockMethod: UnlockMethodInput,
175
+ ): Promise<void> {
176
+ const database = await this.database.forProject(projectId)
177
+
178
+ await database.$transaction(async tx => {
179
+ // 1. fetch all unlock method recipients for the project
180
+ const unlockMethods = await tx.unlockMethod.findMany({
181
+ select: { type: true, recipient: true, encryptedIdentity: true },
182
+ })
183
+
184
+ const allUnlockMethods = [...unlockMethods, inputUnlockMethod]
185
+
186
+ // 2. encrypt the project data for all recipients + the new recipient
187
+ const encryptedMasterKey = await this.encryptProjectMasterKey(projectId, allUnlockMethods)
188
+
189
+ // 3. persist the new unlock method
190
+ await tx.unlockMethod.create({ data: inputUnlockMethod })
191
+
192
+ // 4. update the project with the new master key and unlock suite
193
+ await this.database.backend.project.update({
194
+ where: { id: projectId },
195
+ data: {
196
+ encryptedMasterKey,
197
+ unlockSuite: ProjectUnlockService.createUnlockSuite(allUnlockMethods),
198
+ },
199
+ })
200
+ })
164
201
  }
165
202
 
166
203
  /**
@@ -171,25 +208,34 @@ export class ProjectUnlockService {
171
208
  * @param unlockMethodId The ID of the unlock method to remove.
172
209
  */
173
210
  async removeProjectUnlockMethod(projectId: string, unlockMethodId: string): Promise<void> {
174
- await using batch = this.stateManager.batch()
175
-
176
- // get all existing unlock methods
177
- let existingMethods = await this.stateManager.getUnlockMethodRepository(projectId).getAllItems()
178
-
179
- // do not allow removing the last unlock method under any circumstances
180
- if (existingMethods.length === 1) {
181
- throw new Error("Rejected removing the last unlock method!")
182
- }
211
+ const database = await this.database.forProject(projectId)
183
212
 
184
- // remove the unlock method from the repository
185
- await this.stateManager.getUnlockMethodRepository(projectId).delete(unlockMethodId, batch)
186
- existingMethods = existingMethods.filter(method => method.id !== unlockMethodId)
213
+ await database.$transaction(async tx => {
214
+ // 1. fetch all unlock methods except the one to remove
215
+ const unlockMethods = await tx.unlockMethod.findMany({
216
+ where: { id: { not: unlockMethodId } },
217
+ select: { type: true, recipient: true, encryptedIdentity: true },
218
+ })
187
219
 
188
- // update the encrypted master identity set and master key
189
- await this.updateUnlockSuite(projectId, existingMethods, batch)
190
- await this.updateEncryptedMasterKey(projectId, existingMethods, batch)
220
+ if (unlockMethods.length === 0) {
221
+ throw new CannotDeleteLastUnlockMethodError(projectId)
222
+ }
191
223
 
192
- await batch.write()
224
+ // 2. encrypt the project data for remaining recipients
225
+ const encryptedMasterKey = await this.encryptProjectMasterKey(projectId, unlockMethods)
226
+
227
+ // 3. delete the unlock method
228
+ await tx.unlockMethod.delete({ where: { id: unlockMethodId } })
229
+
230
+ // 4. update the project with the new master key and unlock suite
231
+ await this.database.backend.project.update({
232
+ where: { id: projectId },
233
+ data: {
234
+ encryptedMasterKey,
235
+ unlockSuite: ProjectUnlockService.createUnlockSuite(unlockMethods),
236
+ },
237
+ })
238
+ })
193
239
  }
194
240
 
195
241
  /**
@@ -203,40 +249,54 @@ export class ProjectUnlockService {
203
249
  this.unlockTasks.push({ name, handler })
204
250
  }
205
251
 
206
- private async updateUnlockSuite(
252
+ private async encryptProjectMasterKey(
207
253
  projectId: string,
208
- unlockMethods: UnlockMethod[],
209
- batch: StateBatch,
210
- ): Promise<void> {
211
- // write new encrypted master identity set
212
- await this.stateManager.getProjectUnlockSuiteRepository().put(
213
- projectId,
214
- {
215
- encryptedIdentities: unlockMethods.map(method => method.encryptedIdentity),
216
- hasPasskey: unlockMethods.some(method => method.type === "passkey"),
217
- },
218
- batch,
219
- )
220
- }
221
-
222
- private async updateEncryptedMasterKey(
223
- projectId: string,
224
- unlockMethods: UnlockMethod[],
225
- batch: StateBatch,
226
- masterKey?: Uint8Array,
227
- ): Promise<void> {
228
- masterKey ??= await this.stateManager.getProjectMasterKey(projectId)
254
+ unlockMethods: { recipient: string }[],
255
+ ): Promise<string> {
256
+ const masterKey = await this.database.getProjectMasterKey(projectId)
257
+ if (!masterKey) {
258
+ // окак
259
+ return "encryption disabled"
260
+ }
229
261
 
262
+ // encrypt the master key for all unlock methods
230
263
  const encrypter = new Encrypter()
231
- for (const method of unlockMethods) {
232
- encrypter.addRecipient(method.recipient)
264
+ for (const unlockMethod of unlockMethods) {
265
+ encrypter.addRecipient(unlockMethod.recipient)
233
266
  }
234
267
 
235
- // encrypt the master key for all unlock methods
236
268
  const encryptedMasterKey = await encrypter.encrypt(masterKey)
237
-
238
- // set the encrypted master key in the backend
239
269
  const armoredMasterKey = armor.encode(encryptedMasterKey)
240
- await this.stateManager.getProjectMasterKeyRepository().put(projectId, armoredMasterKey, batch)
270
+
271
+ return armoredMasterKey
272
+ }
273
+
274
+ /**
275
+ * Auto-unlocks the projects for the development environment.
276
+ */
277
+ async autoUnlockProjects(): Promise<void> {
278
+ // just mark the projects as unlocked with an empty master key and run unlock tasks
279
+ for (const projectId of this.config.HIGHSTATE_DEV_AUTO_UNLOCK_PROJECT_IDS) {
280
+ const logger = createProjectLogger(this.logger, projectId)
281
+
282
+ try {
283
+ logger.info("auto-unlocking project (dev mode)")
284
+
285
+ await this.projectUnlockBackend.unlockProject(projectId, Buffer.alloc(0))
286
+ await this.pubsubManager.publish(["project-unlock-state", projectId], { type: "unlocked" })
287
+ await this.runUnlockTasks(projectId)
288
+ } catch (error) {
289
+ logger.error({ error }, "failed to auto-unlock project")
290
+ }
291
+ }
292
+ }
293
+
294
+ private static createUnlockSuite(
295
+ unlockMethods: { type: string; encryptedIdentity: string }[],
296
+ ): ProjectUnlockSuite {
297
+ return {
298
+ encryptedIdentities: unlockMethods.map(method => method.encryptedIdentity),
299
+ hasPasskey: unlockMethods.some(method => method.type === "passkey"),
300
+ }
241
301
  }
242
302
  }