libre-webui 0.26.0 → 0.27.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.
Files changed (249) hide show
  1. package/backend/dist/index.d.ts.map +1 -1
  2. package/backend/dist/index.js +7 -0
  3. package/backend/dist/index.js.map +1 -1
  4. package/backend/dist/persistence/postgresMfaPushRecoveryMigration.d.ts +9 -0
  5. package/backend/dist/persistence/postgresMfaPushRecoveryMigration.d.ts.map +1 -0
  6. package/backend/dist/persistence/postgresMfaPushRecoveryMigration.js +107 -0
  7. package/backend/dist/persistence/postgresMfaPushRecoveryMigration.js.map +1 -0
  8. package/backend/dist/persistence/postgresMigrationRegistry.d.ts.map +1 -1
  9. package/backend/dist/persistence/postgresMigrationRegistry.js +2 -0
  10. package/backend/dist/persistence/postgresMigrationRegistry.js.map +1 -1
  11. package/backend/dist/persistence/postgresResourceRepositories.d.ts.map +1 -1
  12. package/backend/dist/persistence/postgresResourceRepositories.js +162 -0
  13. package/backend/dist/persistence/postgresResourceRepositories.js.map +1 -1
  14. package/backend/dist/persistence/postgresSecurityRepositories.d.ts.map +1 -1
  15. package/backend/dist/persistence/postgresSecurityRepositories.js +150 -0
  16. package/backend/dist/persistence/postgresSecurityRepositories.js.map +1 -1
  17. package/backend/dist/persistence/resourceTypes.d.ts +59 -0
  18. package/backend/dist/persistence/resourceTypes.d.ts.map +1 -1
  19. package/backend/dist/persistence/resourceTypes.js.map +1 -1
  20. package/backend/dist/persistence/securityTypes.d.ts +59 -0
  21. package/backend/dist/persistence/securityTypes.d.ts.map +1 -1
  22. package/backend/dist/persistence/sqliteMigrations.d.ts +1 -0
  23. package/backend/dist/persistence/sqliteMigrations.d.ts.map +1 -1
  24. package/backend/dist/persistence/sqliteMigrations.js +194 -0
  25. package/backend/dist/persistence/sqliteMigrations.js.map +1 -1
  26. package/backend/dist/persistence/sqliteResourceRepositories.d.ts.map +1 -1
  27. package/backend/dist/persistence/sqliteResourceRepositories.js +134 -0
  28. package/backend/dist/persistence/sqliteResourceRepositories.js.map +1 -1
  29. package/backend/dist/persistence/sqliteSecurityRepositories.d.ts.map +1 -1
  30. package/backend/dist/persistence/sqliteSecurityRepositories.js +134 -0
  31. package/backend/dist/persistence/sqliteSecurityRepositories.js.map +1 -1
  32. package/backend/dist/persistence/sqliteToPostgresMigration.d.ts.map +1 -1
  33. package/backend/dist/persistence/sqliteToPostgresMigration.js +45 -0
  34. package/backend/dist/persistence/sqliteToPostgresMigration.js.map +1 -1
  35. package/backend/dist/platform/jobs/domainJobContracts.d.ts +2 -0
  36. package/backend/dist/platform/jobs/domainJobContracts.d.ts.map +1 -1
  37. package/backend/dist/platform/jobs/domainJobContracts.js +2 -0
  38. package/backend/dist/platform/jobs/domainJobContracts.js.map +1 -1
  39. package/backend/dist/platform/jobs/domainJobHandlers.d.ts.map +1 -1
  40. package/backend/dist/platform/jobs/domainJobHandlers.js +65 -2
  41. package/backend/dist/platform/jobs/domainJobHandlers.js.map +1 -1
  42. package/backend/dist/routes/auth.d.ts.map +1 -1
  43. package/backend/dist/routes/auth.js +419 -1
  44. package/backend/dist/routes/auth.js.map +1 -1
  45. package/backend/dist/routes/documents.d.ts.map +1 -1
  46. package/backend/dist/routes/documents.js +4 -0
  47. package/backend/dist/routes/documents.js.map +1 -1
  48. package/backend/dist/routes/push.d.ts +3 -0
  49. package/backend/dist/routes/push.d.ts.map +1 -0
  50. package/backend/dist/routes/push.js +105 -0
  51. package/backend/dist/routes/push.js.map +1 -0
  52. package/backend/dist/routes/recovery.d.ts +3 -0
  53. package/backend/dist/routes/recovery.d.ts.map +1 -0
  54. package/backend/dist/routes/recovery.js +83 -0
  55. package/backend/dist/routes/recovery.js.map +1 -0
  56. package/backend/dist/routes/users.d.ts.map +1 -1
  57. package/backend/dist/routes/users.js +35 -0
  58. package/backend/dist/routes/users.js.map +1 -1
  59. package/backend/dist/services/authService.d.ts +6 -0
  60. package/backend/dist/services/authService.d.ts.map +1 -1
  61. package/backend/dist/services/authService.js +9 -0
  62. package/backend/dist/services/authService.js.map +1 -1
  63. package/backend/dist/services/automationSchedulerService.d.ts.map +1 -1
  64. package/backend/dist/services/automationSchedulerService.js +7 -0
  65. package/backend/dist/services/automationSchedulerService.js.map +1 -1
  66. package/backend/dist/services/dataArchiveService.d.ts.map +1 -1
  67. package/backend/dist/services/dataArchiveService.js +3 -0
  68. package/backend/dist/services/dataArchiveService.js.map +1 -1
  69. package/backend/dist/services/documentMediaExtractionService.d.ts +40 -0
  70. package/backend/dist/services/documentMediaExtractionService.d.ts.map +1 -0
  71. package/backend/dist/services/documentMediaExtractionService.js +157 -0
  72. package/backend/dist/services/documentMediaExtractionService.js.map +1 -0
  73. package/backend/dist/services/documentService.d.ts +6 -0
  74. package/backend/dist/services/documentService.d.ts.map +1 -1
  75. package/backend/dist/services/documentService.js +58 -4
  76. package/backend/dist/services/documentService.js.map +1 -1
  77. package/backend/dist/services/encryptionService.d.ts +6 -0
  78. package/backend/dist/services/encryptionService.d.ts.map +1 -1
  79. package/backend/dist/services/encryptionService.js +12 -0
  80. package/backend/dist/services/encryptionService.js.map +1 -1
  81. package/backend/dist/services/mfaService.d.ts +64 -0
  82. package/backend/dist/services/mfaService.d.ts.map +1 -0
  83. package/backend/dist/services/mfaService.js +263 -0
  84. package/backend/dist/services/mfaService.js.map +1 -0
  85. package/backend/dist/services/notificationService.d.ts +5 -0
  86. package/backend/dist/services/notificationService.d.ts.map +1 -1
  87. package/backend/dist/services/notificationService.js +33 -1
  88. package/backend/dist/services/notificationService.js.map +1 -1
  89. package/backend/dist/services/preferencesService.d.ts +1 -1
  90. package/backend/dist/services/preferencesService.d.ts.map +1 -1
  91. package/backend/dist/services/preferencesService.js +1 -0
  92. package/backend/dist/services/preferencesService.js.map +1 -1
  93. package/backend/dist/services/recoveryDrillService.d.ts +57 -0
  94. package/backend/dist/services/recoveryDrillService.d.ts.map +1 -0
  95. package/backend/dist/services/recoveryDrillService.js +363 -0
  96. package/backend/dist/services/recoveryDrillService.js.map +1 -0
  97. package/backend/dist/services/webPushService.d.ts +71 -0
  98. package/backend/dist/services/webPushService.d.ts.map +1 -0
  99. package/backend/dist/services/webPushService.js +328 -0
  100. package/backend/dist/services/webPushService.js.map +1 -0
  101. package/backend/dist/services/webauthnService.d.ts +58 -0
  102. package/backend/dist/services/webauthnService.d.ts.map +1 -0
  103. package/backend/dist/services/webauthnService.js +300 -0
  104. package/backend/dist/services/webauthnService.js.map +1 -0
  105. package/backend/dist/types/index.d.ts +3 -2
  106. package/backend/dist/types/index.d.ts.map +1 -1
  107. package/backend/dist/types/index.js.map +1 -1
  108. package/backend/dist/utils/documentExtraction.d.ts +31 -3
  109. package/backend/dist/utils/documentExtraction.d.ts.map +1 -1
  110. package/backend/dist/utils/documentExtraction.js +106 -0
  111. package/backend/dist/utils/documentExtraction.js.map +1 -1
  112. package/backend/dist/utils/toolEgress.d.ts +2 -1
  113. package/backend/dist/utils/toolEgress.d.ts.map +1 -1
  114. package/backend/dist/utils/toolEgress.js +14 -2
  115. package/backend/dist/utils/toolEgress.js.map +1 -1
  116. package/backend/dist/utils/totp.d.ts +19 -0
  117. package/backend/dist/utils/totp.d.ts.map +1 -0
  118. package/backend/dist/utils/totp.js +117 -0
  119. package/backend/dist/utils/totp.js.map +1 -0
  120. package/backend/dist/utils/webauthn.d.ts +43 -0
  121. package/backend/dist/utils/webauthn.d.ts.map +1 -0
  122. package/backend/dist/utils/webauthn.js +294 -0
  123. package/backend/dist/utils/webauthn.js.map +1 -0
  124. package/backend/package.json +1 -1
  125. package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
  126. package/frontend/dist/assets/index-DBAf_VaS.js +47 -0
  127. package/frontend/dist/assets/index-kn_Axjts.css +1 -0
  128. package/frontend/dist/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  129. package/frontend/dist/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
  130. package/frontend/dist/fonts/jetbrains-mono.css +24 -0
  131. package/frontend/dist/index.html +20 -13
  132. package/frontend/dist/js/ArtifactContainer-C9zNtCay.js +1 -0
  133. package/frontend/dist/js/{ArtifactDemoPage-D0JEIAbZ.js → ArtifactDemoPage-C-EBFBCt.js} +1 -1
  134. package/frontend/dist/js/ArtifactSandboxFrame-CTA1j_d1.js +111 -0
  135. package/frontend/dist/js/ArtifactSlideOutPanel-BOENbOiv.js +1 -0
  136. package/frontend/dist/js/AutomationsPage-C58MQ4te.js +1 -0
  137. package/frontend/dist/js/CalendarPage-BcdAZ292.js +1 -0
  138. package/frontend/dist/js/ChannelsPage-DPr9Yajs.js +1 -0
  139. package/frontend/dist/js/ChatPage-DsbB748s.js +14 -0
  140. package/frontend/dist/js/{EvaluationsPage-Bm8FRoeX.js → EvaluationsPage-FWIJu2fI.js} +1 -1
  141. package/frontend/dist/js/FirstTimeSetup-Cy-w6xko.js +1 -0
  142. package/frontend/dist/js/GalleryPage-DDAtxM-x.js +1 -0
  143. package/frontend/dist/js/{HomePage-BqNy-FWX.js → HomePage-DLKwmE7n.js} +1 -1
  144. package/frontend/dist/js/LibreClawPage-vKOj-9w9.js +1 -0
  145. package/frontend/dist/js/{ModelManagerModals-DX9eT5r2.js → ModelManagerModals-BY5TK38_.js} +1 -1
  146. package/frontend/dist/js/ModelSelector-CoCS6MZ9.js +1 -0
  147. package/frontend/dist/js/NotesPage-BElIpXgA.js +3 -0
  148. package/frontend/dist/js/OptimizedSyntaxHighlighter-xgyE3dRA.js +1 -0
  149. package/frontend/dist/js/PersonasPage-P40jVbfU.js +13 -0
  150. package/frontend/dist/js/{PluginUsagePage-Ctoka6JZ.js → PluginUsagePage-CKUEPCJN.js} +1 -1
  151. package/frontend/dist/js/RichMessageContent-Bm2WaEGi.js +3 -0
  152. package/frontend/dist/js/SettingsModal-DxSGzx9A.js +19 -0
  153. package/frontend/dist/js/SystemPage-BT07dTMT.js +1 -0
  154. package/frontend/dist/js/UserManagementPage-B9Vz5cm3.js +1 -0
  155. package/frontend/dist/js/WorkPage-qoN_e_jK.js +6 -0
  156. package/frontend/dist/js/api-D7Ff5M2Q.js +14 -0
  157. package/frontend/dist/js/appStore-DmV5wqDz.js +2 -0
  158. package/frontend/dist/js/ar-YHHTVP8f.js +2 -0
  159. package/frontend/dist/js/artifactHtml-DDmfpcB0.js +41 -0
  160. package/frontend/dist/js/{authStore-DbqqrfZv.js → authStore-BYW8-kP6.js} +3 -3
  161. package/frontend/dist/js/bn-C8VTxBVl.js +2 -0
  162. package/frontend/dist/js/chatStore-D-NTlArO.js +3 -0
  163. package/frontend/dist/js/{config-8KdgjtEx.js → config-BqZ5304Q.js} +1 -1
  164. package/frontend/dist/js/cs-CCngB5fC.js +2 -0
  165. package/frontend/dist/js/da-CM2PP2ma.js +2 -0
  166. package/frontend/dist/js/de-BqVkD-dw.js +2 -0
  167. package/frontend/dist/js/es-BtWsWBjZ.js +2 -0
  168. package/frontend/dist/js/{estree-BU5-LV_1.js → estree-DPkpBuOU.js} +1 -1
  169. package/frontend/dist/js/{evaluationsApi-CwZJsgeG.js → evaluationsApi-Dnf2dTd-.js} +1 -1
  170. package/frontend/dist/js/fr-C68w9abT.js +2 -0
  171. package/frontend/dist/js/hi-DtBlcyXZ.js +2 -0
  172. package/frontend/dist/js/{html-DeoAy2bo.js → html-BpGUXG8E.js} +1 -1
  173. package/frontend/dist/js/id-BWzIu8tn.js +2 -0
  174. package/frontend/dist/js/is-yeFwu4dJ.js +2 -0
  175. package/frontend/dist/js/it-AxEMG8Jg.js +2 -0
  176. package/frontend/dist/js/ja-B-VyokB8.js +2 -0
  177. package/frontend/dist/js/ko-CvwzYr9k.js +2 -0
  178. package/frontend/dist/js/{markdown-DOLsYkBY.js → markdown-DCBRWJSK.js} +1 -1
  179. package/frontend/dist/js/{messageCodeStyles-DhVgizF-.js → messageCodeStyles-BQwf7sSz.js} +2 -2
  180. package/frontend/dist/js/ms-BhEwb4b7.js +2 -0
  181. package/frontend/dist/js/nl-Dq-QRx1L.js +2 -0
  182. package/frontend/dist/js/pl-BNEy8mQm.js +2 -0
  183. package/frontend/dist/js/pt-_3Jl3FdY.js +2 -0
  184. package/frontend/dist/js/ru-IhQiJ8ta.js +2 -0
  185. package/frontend/dist/js/{standalone-BO5pzI8_.js → standalone-BFrK0R2A.js} +1 -1
  186. package/frontend/dist/js/sv-DWunXUNz.js +2 -0
  187. package/frontend/dist/js/{syntax-highlight-DcR9Vhb7.js → syntax-highlight-P2zo9o06.js} +1 -1
  188. package/frontend/dist/js/th-DVnW2yyh.js +2 -0
  189. package/frontend/dist/js/tr-Do8CFmhR.js +2 -0
  190. package/frontend/dist/js/{typescript-6GJ8APmn.js → typescript-DYVyvmm_.js} +1 -1
  191. package/frontend/dist/js/ui-db9meqAB.js +2 -0
  192. package/frontend/dist/js/ui-vendor-C0DZ8-Cm.js +177 -0
  193. package/frontend/dist/js/uk-BNPKIZga.js +2 -0
  194. package/frontend/dist/js/vi-CZMFo9je.js +2 -0
  195. package/frontend/dist/js/{workApi-BruWTlHM.js → workApi-CcprMH_1.js} +2 -2
  196. package/frontend/dist/js/zh-Pg0881v0.js +2 -0
  197. package/frontend/dist/manifest.webmanifest +32 -0
  198. package/frontend/dist/sw.js +125 -0
  199. package/package.json +2 -2
  200. package/frontend/dist/assets/index-BPsH9FII.js +0 -80
  201. package/frontend/dist/assets/index-BtXF0Oxa.css +0 -1
  202. package/frontend/dist/js/ArtifactContainer-CIoFoMv8.js +0 -1
  203. package/frontend/dist/js/ArtifactSandboxFrame-REazyIrx.js +0 -151
  204. package/frontend/dist/js/ArtifactSlideOutPanel-A-i94ghF.js +0 -1
  205. package/frontend/dist/js/AutomationsPage-Dow_lbIf.js +0 -1
  206. package/frontend/dist/js/CalendarPage-DLjasZOd.js +0 -1
  207. package/frontend/dist/js/ChannelsPage-C8hvTD7J.js +0 -1
  208. package/frontend/dist/js/ChatPage-BAgBHUES.js +0 -16
  209. package/frontend/dist/js/FirstTimeSetup-BxJapNul.js +0 -1
  210. package/frontend/dist/js/GalleryPage-DHz9DkK7.js +0 -1
  211. package/frontend/dist/js/LibreClawPage-BDzDILjY.js +0 -1
  212. package/frontend/dist/js/ModelSelector-ol8mK2ja.js +0 -1
  213. package/frontend/dist/js/NotesPage-DzHEx2O4.js +0 -3
  214. package/frontend/dist/js/OptimizedSyntaxHighlighter-CZNPwN0i.js +0 -1
  215. package/frontend/dist/js/PersonasPage-M_WKH2a9.js +0 -13
  216. package/frontend/dist/js/RichMessageContent-BqgafH4p.js +0 -3
  217. package/frontend/dist/js/SettingsModal-R9EDadAV.js +0 -19
  218. package/frontend/dist/js/SystemPage-aOkrSuY7.js +0 -1
  219. package/frontend/dist/js/UserManagementPage-BOuaX3ay.js +0 -1
  220. package/frontend/dist/js/WorkPage-DxZISerQ.js +0 -6
  221. package/frontend/dist/js/api-6bSMtSNh.js +0 -14
  222. package/frontend/dist/js/appStore-CdObhQBb.js +0 -2
  223. package/frontend/dist/js/ar-DT_VLBO3.js +0 -2
  224. package/frontend/dist/js/bn-BrvEqhGA.js +0 -2
  225. package/frontend/dist/js/chatStore-DbZPJNsu.js +0 -3
  226. package/frontend/dist/js/cs-BthnM_YI.js +0 -2
  227. package/frontend/dist/js/da-DCth-6_j.js +0 -2
  228. package/frontend/dist/js/de-BBWt0gJ_.js +0 -2
  229. package/frontend/dist/js/es-DuslOPqM.js +0 -2
  230. package/frontend/dist/js/fr-DZAeg46S.js +0 -2
  231. package/frontend/dist/js/hi-ijDSiY77.js +0 -2
  232. package/frontend/dist/js/id-BS6qS0oq.js +0 -2
  233. package/frontend/dist/js/is-CO2PMDte.js +0 -2
  234. package/frontend/dist/js/it-ChhxT4hs.js +0 -2
  235. package/frontend/dist/js/ja-cyeqUeJN.js +0 -2
  236. package/frontend/dist/js/ko-CfqHTLXj.js +0 -2
  237. package/frontend/dist/js/ms-DZ5pACe1.js +0 -2
  238. package/frontend/dist/js/nl-BsRsnOVp.js +0 -2
  239. package/frontend/dist/js/pl-DXSTAW4M.js +0 -2
  240. package/frontend/dist/js/pt-DmDx0JxB.js +0 -2
  241. package/frontend/dist/js/ru-d2g8EWfY.js +0 -2
  242. package/frontend/dist/js/sv-CIhYUTVQ.js +0 -2
  243. package/frontend/dist/js/th-B3wSxanB.js +0 -2
  244. package/frontend/dist/js/tr-R4UGgPOY.js +0 -2
  245. package/frontend/dist/js/ui-CV0LYmkM.js +0 -2
  246. package/frontend/dist/js/ui-vendor-CWjIBW-z.js +0 -177
  247. package/frontend/dist/js/uk-BzUF4Vkt.js +0 -2
  248. package/frontend/dist/js/vi-CKc6Fg4h.js +0 -2
  249. package/frontend/dist/js/zh-CXL-kDe8.js +0 -2
@@ -0,0 +1,363 @@
1
+ /*
2
+ * Libre WebUI
3
+ * Copyright (C) 2025 Kroonen AI, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at:
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Scheduled verified recovery drills (RECOVERY-01).
19
+ *
20
+ * A drill proves the instance is actually recoverable, end to end, with the
21
+ * same signed/encrypted pipeline operators use for real backups:
22
+ *
23
+ * 1. Stage a quiescent copy of the data directory (SQLite through the
24
+ * online backup API, blobs and files by physical copy).
25
+ * 2. Create a signed, encrypted archive from the staged copy with
26
+ * ephemeral drill keys, running the full recovery inventory.
27
+ * 3. Verify the archive, restore it into an isolated temporary target,
28
+ * and verify the restored environment again.
29
+ * 4. Record measured timings — restore duration is the demonstrated RTO,
30
+ * and the spacing between successful drills bounds the achievable RPO
31
+ * of the current schedule — then delete every artifact.
32
+ *
33
+ * Drills are verification, not backups: nothing is retained. They run only
34
+ * on the solo (SQLite) profile, under a coordinator lease so replicas and
35
+ * overlapping ticks cannot double-run, and only while no other durable job
36
+ * is mid-flight — the same quiescence rule the recovery inventory enforces.
37
+ */
38
+ import fs from 'node:fs';
39
+ import os from 'node:os';
40
+ import path from 'node:path';
41
+ import { randomUUID } from 'node:crypto';
42
+ import { getPersistence, getSQLiteAdapterDatabase, } from '../persistence/index.js';
43
+ import { encryptionService } from './encryptionService.js';
44
+ import { getCoordinator } from '../platform/coordination/service.js';
45
+ import { resolveDataDirectory } from '../utils/dataDirectory.js';
46
+ import { createBackupArchive, generateBackupKeys, restoreBackupArchive, verifyBackupArchive, verifyRestoredBackup, } from '../platform/recovery/index.js';
47
+ import { createLogger } from '../utils/logger.js';
48
+ const logger = createLogger('services:recovery-drill');
49
+ const LEASE_KEY = 'recovery-drill';
50
+ const LEASE_TTL_MS = 30 * 60 * 1000;
51
+ /** A running drill older than this was interrupted (crash/restart). */
52
+ const STALE_RUNNING_MS = 60 * 60 * 1000;
53
+ const DEFAULT_HISTORY_LIMIT = 60;
54
+ const LIST_LIMIT = 50;
55
+ const repositories = () => getPersistence(encryptionService).repositories.resources;
56
+ export class RecoveryDrillError extends Error {
57
+ constructor(message, statusCode = 400) {
58
+ super(message);
59
+ this.statusCode = statusCode;
60
+ }
61
+ }
62
+ const historyLimit = () => {
63
+ const parsed = Number.parseInt(process.env.RECOVERY_DRILL_HISTORY || '', 10);
64
+ return Number.isInteger(parsed) && parsed >= 5 && parsed <= 1000
65
+ ? parsed
66
+ : DEFAULT_HISTORY_LIMIT;
67
+ };
68
+ export const drillIntervalHours = () => {
69
+ const parsed = Number.parseFloat(process.env.RECOVERY_DRILL_INTERVAL_HOURS || '');
70
+ return Number.isFinite(parsed) && parsed >= 1 ? parsed : null;
71
+ };
72
+ /** Drills stage a filesystem snapshot; the team profile has its own CLI. */
73
+ export const drillsSupported = () => {
74
+ const env = process.env;
75
+ return !(env.LIBRE_PLATFORM_MODE?.trim().toLowerCase() === 'team' ||
76
+ env.DATABASE_BACKEND?.trim().toLowerCase() === 'postgres' ||
77
+ env.BLOB_STORE_BACKEND?.trim().toLowerCase() === 's3' ||
78
+ env.VECTOR_STORE_BACKEND?.trim().toLowerCase() === 'pgvector');
79
+ };
80
+ const decryptReport = (value) => {
81
+ if (!value)
82
+ return null;
83
+ try {
84
+ return JSON.parse(encryptionService.decryptAuthenticated(value));
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ };
90
+ const toPublic = (record) => ({
91
+ id: record.id,
92
+ status: record.status,
93
+ origin: record.origin,
94
+ startedAt: record.started_at,
95
+ finishedAt: record.finished_at,
96
+ snapshotBytes: record.snapshot_bytes,
97
+ rpoSeconds: record.rpo_seconds,
98
+ restoreMs: record.restore_ms,
99
+ error: record.error,
100
+ report: decryptReport(record.report),
101
+ });
102
+ export const listDrills = async () => (await repositories().recoveryDrills.list(LIST_LIMIT)).map(toPublic);
103
+ const notifyAdminsOfFailure = async (drillId, message) => {
104
+ try {
105
+ const [{ userModel }, { notificationService }] = await Promise.all([
106
+ import('../models/userModel.js'),
107
+ import('./notificationService.js'),
108
+ ]);
109
+ const admins = (await userModel.getAllUsers()).filter(user => user.role === 'admin' && user.status === 'active');
110
+ for (const admin of admins) {
111
+ await notificationService.publish({
112
+ userId: admin.id,
113
+ type: 'system',
114
+ title: 'Recovery drill failed',
115
+ body: message,
116
+ href: '/system',
117
+ sourceKey: `recovery-drill-failed:${drillId}`,
118
+ });
119
+ }
120
+ }
121
+ catch (error) {
122
+ logger.warn('Recovery drill failure notification did not send', { error });
123
+ }
124
+ };
125
+ /** Copy the data directory, leaving the live SQLite files for the backup API. */
126
+ const stageDataDirectory = async (dataDir, staging) => {
127
+ fs.mkdirSync(staging, { recursive: true, mode: 0o700 });
128
+ const skip = new Set(['data.sqlite', 'data.sqlite-wal', 'data.sqlite-shm']);
129
+ for (const entry of fs.readdirSync(dataDir)) {
130
+ if (skip.has(entry))
131
+ continue;
132
+ fs.cpSync(path.join(dataDir, entry), path.join(staging, entry), {
133
+ recursive: true,
134
+ errorOnExist: true,
135
+ force: false,
136
+ });
137
+ }
138
+ // The online backup API yields a consistent snapshot of a live database
139
+ // without stopping writers — the reason drills need no downtime.
140
+ await getSQLiteAdapterDatabase().backup(path.join(staging, 'data.sqlite'));
141
+ };
142
+ const runningDurableJobs = () => {
143
+ // The drill runs outside the durable-job system precisely so its own
144
+ // execution never appears here: any running row is real in-flight work
145
+ // that would land mid-write in the snapshot. The archive's recovery
146
+ // inventory re-checks this on the staged copy and remains authoritative.
147
+ try {
148
+ const row = getSQLiteAdapterDatabase()
149
+ .prepare("SELECT COUNT(*) AS count FROM platform_jobs WHERE state = 'running'")
150
+ .get();
151
+ return row.count;
152
+ }
153
+ catch {
154
+ return 0;
155
+ }
156
+ };
157
+ /**
158
+ * Run one full drill. Returns the recorded drill, or null when it was
159
+ * skipped without recording (lease held elsewhere, or busy on a scheduled
160
+ * run).
161
+ */
162
+ export const runDrill = async (options) => {
163
+ if (!drillsSupported()) {
164
+ throw new RecoveryDrillError('Recovery drills run on the solo (SQLite) profile; the team profile uses the coordinated backup CLI', 501);
165
+ }
166
+ const lease = await getCoordinator().acquireLease(LEASE_KEY, LEASE_TTL_MS);
167
+ if (!lease) {
168
+ if (options.origin === 'manual') {
169
+ throw new RecoveryDrillError('A recovery drill is already running', 409);
170
+ }
171
+ return null;
172
+ }
173
+ const now = Date.now();
174
+ const drills = repositories().recoveryDrills;
175
+ try {
176
+ const running = await drills.findRunning();
177
+ if (running) {
178
+ if (now - running.started_at > STALE_RUNNING_MS) {
179
+ await drills.update(running.id, {
180
+ status: 'failed',
181
+ finished_at: now,
182
+ error: 'The drill was interrupted by a restart',
183
+ });
184
+ }
185
+ else if (options.origin === 'manual') {
186
+ throw new RecoveryDrillError('A recovery drill is already running', 409);
187
+ }
188
+ else {
189
+ return null;
190
+ }
191
+ }
192
+ const previous = await drills.findLatestFinished();
193
+ const record = {
194
+ id: randomUUID(),
195
+ status: 'running',
196
+ origin: options.origin,
197
+ started_at: now,
198
+ finished_at: null,
199
+ snapshot_bytes: null,
200
+ rpo_seconds: null,
201
+ restore_ms: null,
202
+ error: null,
203
+ report: null,
204
+ created_by: options.createdBy ?? null,
205
+ created_at: now,
206
+ };
207
+ await drills.insert(record);
208
+ const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'libre-drill-'));
209
+ try {
210
+ const busy = runningDurableJobs();
211
+ if (busy > 0) {
212
+ const message = `${busy} durable job(s) were running; the drill needs a quiet moment and will retry`;
213
+ if (options.origin === 'scheduled' && !options.recordBusyAsFailure) {
214
+ await drills.update(record.id, {
215
+ status: 'failed',
216
+ finished_at: Date.now(),
217
+ error: message,
218
+ });
219
+ // Scheduled busy-skips are unremarkable: no admin alert, and the
220
+ // interval clock restarts so the next tick can try again.
221
+ return toPublic({
222
+ ...record,
223
+ status: 'failed',
224
+ finished_at: Date.now(),
225
+ error: message,
226
+ });
227
+ }
228
+ throw new RecoveryDrillError(message, 409);
229
+ }
230
+ const dataDir = resolveDataDirectory();
231
+ const staging = path.join(workDir, 'staging');
232
+ const snapshotStarted = Date.now();
233
+ await stageDataDirectory(dataDir, staging);
234
+ const keys = generateBackupKeys(path.join(workDir, 'keys'));
235
+ const archivePath = path.join(workDir, 'drill.lwbackup');
236
+ await createBackupArchive({
237
+ dataDir: staging,
238
+ outputPath: archivePath,
239
+ encryptionKeyPath: keys.encryptionKeyPath,
240
+ signingPrivateKeyPath: keys.signingPrivateKeyPath,
241
+ offline: true,
242
+ });
243
+ const snapshotMs = Date.now() - snapshotStarted;
244
+ const archiveBytes = fs.statSync(archivePath).size;
245
+ const verifyStarted = Date.now();
246
+ const verification = verifyBackupArchive({
247
+ archivePath,
248
+ signingPublicKeyPath: keys.signingPublicKeyPath,
249
+ encryptionKeyPath: keys.encryptionKeyPath,
250
+ });
251
+ if (!verification.signatureVerified || !verification.payloadVerified) {
252
+ throw new Error('The drill archive failed verification');
253
+ }
254
+ const verifyMs = Date.now() - verifyStarted;
255
+ const restoreStarted = Date.now();
256
+ const restoreTarget = path.join(workDir, 'restore');
257
+ await restoreBackupArchive({
258
+ archivePath,
259
+ signingPublicKeyPath: keys.signingPublicKeyPath,
260
+ encryptionKeyPath: keys.encryptionKeyPath,
261
+ targetDirectory: restoreTarget,
262
+ apply: true,
263
+ });
264
+ const restoreMs = Date.now() - restoreStarted;
265
+ const restoreVerifyStarted = Date.now();
266
+ await verifyRestoredBackup(restoreTarget);
267
+ const restoreVerifyMs = Date.now() - restoreVerifyStarted;
268
+ const report = {
269
+ snapshotMs,
270
+ archiveBytes,
271
+ verifyMs,
272
+ restoreMs,
273
+ restoreVerifyMs,
274
+ };
275
+ const finishedAt = Date.now();
276
+ const rpoSeconds = previous?.status === 'passed'
277
+ ? Math.round((now - previous.started_at) / 1000)
278
+ : null;
279
+ await drills.update(record.id, {
280
+ status: 'passed',
281
+ finished_at: finishedAt,
282
+ snapshot_bytes: archiveBytes,
283
+ rpo_seconds: rpoSeconds,
284
+ restore_ms: restoreMs,
285
+ report: encryptionService.encrypt(JSON.stringify(report)),
286
+ });
287
+ await drills.pruneToLimit(historyLimit());
288
+ logger.info('Recovery drill passed', {
289
+ drillId: record.id,
290
+ restoreMs,
291
+ archiveBytes,
292
+ });
293
+ return toPublic({
294
+ ...record,
295
+ status: 'passed',
296
+ finished_at: finishedAt,
297
+ snapshot_bytes: archiveBytes,
298
+ rpo_seconds: rpoSeconds,
299
+ restore_ms: restoreMs,
300
+ report: encryptionService.encrypt(JSON.stringify(report)),
301
+ });
302
+ }
303
+ catch (error) {
304
+ const message = error instanceof Error ? error.message : 'Recovery drill failed';
305
+ await drills.update(record.id, {
306
+ status: 'failed',
307
+ finished_at: Date.now(),
308
+ error: message.slice(0, 500),
309
+ });
310
+ // Interactive refusals (busy, already running) answered the caller
311
+ // directly; the bell alert is for drills failing unattended.
312
+ if (!(error instanceof RecoveryDrillError)) {
313
+ await notifyAdminsOfFailure(record.id, message.slice(0, 500));
314
+ }
315
+ logger.warn('Recovery drill failed', { drillId: record.id, message });
316
+ if (error instanceof RecoveryDrillError)
317
+ throw error;
318
+ return toPublic({
319
+ ...(await drills.findById(record.id)),
320
+ });
321
+ }
322
+ finally {
323
+ fs.rmSync(workDir, { recursive: true, force: true });
324
+ }
325
+ }
326
+ finally {
327
+ await lease.release().catch(() => false);
328
+ }
329
+ };
330
+ let sweepInFlight = false;
331
+ /**
332
+ * Scheduler-lease sweep: start a drill when the configured interval has
333
+ * elapsed since the last one (any outcome, so a failing instance does not
334
+ * hammer itself). Fire-and-forget; the drill lease serializes execution.
335
+ */
336
+ export const sweepDrills = async (now) => {
337
+ const interval = drillIntervalHours();
338
+ if (!interval || !drillsSupported() || sweepInFlight)
339
+ return false;
340
+ const drills = repositories().recoveryDrills;
341
+ const latest = await drills.list(1);
342
+ const last = latest[0];
343
+ if (last && now - last.started_at < interval * 60 * 60 * 1000) {
344
+ return false;
345
+ }
346
+ sweepInFlight = true;
347
+ void runDrill({ origin: 'scheduled' })
348
+ .catch(error => {
349
+ logger.warn('Scheduled recovery drill errored', { error });
350
+ })
351
+ .finally(() => {
352
+ sweepInFlight = false;
353
+ });
354
+ return true;
355
+ };
356
+ export default {
357
+ runDrill,
358
+ sweepDrills,
359
+ listDrills,
360
+ drillsSupported,
361
+ drillIntervalHours,
362
+ };
363
+ //# sourceMappingURL=recoveryDrillService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recoveryDrillService.js","sourceRoot":"","sources":["../../src/services/recoveryDrillService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import type { StoredPushSubscriptionRecord } from '../persistence/resourceTypes.js';
2
+ export declare class WebPushError extends Error {
3
+ readonly statusCode: number;
4
+ constructor(message: string, statusCode?: number);
5
+ }
6
+ /** The endpoint acknowledged but the subscription no longer exists. */
7
+ export declare class PushSubscriptionGoneError extends Error {
8
+ }
9
+ export interface PushSubscriptionKeys {
10
+ endpoint: string;
11
+ keys: {
12
+ p256dh: string;
13
+ auth: string;
14
+ };
15
+ }
16
+ interface VapidKeys {
17
+ /** base64url uncompressed P-256 point (65 bytes). */
18
+ publicKey: string;
19
+ /** base64url raw private scalar (32 bytes). */
20
+ privateKey: string;
21
+ }
22
+ export declare const getVapidKeys: () => Promise<VapidKeys>;
23
+ export declare const getVapidPublicKey: () => Promise<string>;
24
+ /** RFC 8292 VAPID JWT for one push-service origin. */
25
+ export declare const buildVapidAuthorization: (endpointOrigin: string) => Promise<string>;
26
+ export interface EncryptParams {
27
+ /** Test hook: fixed ephemeral private key (32-byte scalar). */
28
+ ephemeralPrivateKey?: Buffer;
29
+ /** Test hook: fixed 16-byte salt. */
30
+ salt?: Buffer;
31
+ }
32
+ /**
33
+ * Encrypt one push message with aes128gcm content encoding. Returns the
34
+ * complete request body (header block + single record).
35
+ */
36
+ export declare const encryptPushPayload: (userAgentPublicKey: Buffer, authSecret: Buffer, plaintext: Buffer, params?: EncryptParams) => Buffer;
37
+ export interface PublicPushSubscription {
38
+ id: string;
39
+ createdAt: number;
40
+ lastUsedAt: number | null;
41
+ current: boolean;
42
+ }
43
+ export declare const subscribe: (userId: string, sessionId: string | null, input: {
44
+ endpoint?: unknown;
45
+ keys?: {
46
+ p256dh?: unknown;
47
+ auth?: unknown;
48
+ };
49
+ }, userAgent: string | null) => Promise<{
50
+ id: string;
51
+ }>;
52
+ export declare const unsubscribe: (userId: string, endpoint: string) => Promise<boolean>;
53
+ export declare const listSubscriptionsForUser: (userId: string) => Promise<StoredPushSubscriptionRecord[]>;
54
+ export interface PushMessage {
55
+ title: string;
56
+ body?: string;
57
+ href?: string;
58
+ type?: string;
59
+ }
60
+ /**
61
+ * Deliver one message to one stored subscription. Returns false (and
62
+ * removes the row) when the push service reports the subscription gone.
63
+ */
64
+ export declare const deliverToSubscription: (subscriptionId: string, message: PushMessage, signal?: AbortSignal) => Promise<{
65
+ delivered: boolean;
66
+ status?: number;
67
+ }>;
68
+ /** Remove device subscriptions when their auth session is revoked. */
69
+ export declare const registerPushSessionCleanup: () => void;
70
+ export {};
71
+ //# sourceMappingURL=webPushService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webPushService.d.ts","sourceRoot":"","sources":["../../src/services/webPushService.ts"],"names":[],"mappings":"AAuDA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAYpF,qBAAa,YAAa,SAAQ,KAAK;aAGnB,UAAU;IAF5B,YACE,OAAO,EAAE,MAAM,EACC,UAAU,SAAM,EAGjC;CACF;AAED,uEAAuE;AACvE,qBAAa,yBAA0B,SAAQ,KAAK;CAAG;AAEvD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC;AAID,UAAU,SAAS;IACjB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;CACpB;AA2BD,eAAO,MAAM,YAAY,QAAa,OAAO,CAAC,SAAS,CAqCtD,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAa,OAAO,CAAC,MAAM,CACvB,CAAC;AAoBnC,sDAAsD;AACtD,eAAO,MAAM,uBAAuB,mBAClB,MAAM,KACrB,OAAO,CAAC,MAAM,CAchB,CAAC;AAIF,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,uBACT,MAAM,cACd,MAAM,aACP,MAAM,WACT,aAAa,KACpB,MAkEF,CAAC;AA6CF,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,SAAS,WACZ,MAAM,aACH,MAAM,GAAG,IAAI,SACjB;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,aAC/D,MAAM,GAAG,IAAI,KACvB,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAqCxB,CAAC;AAEF,eAAO,MAAM,WAAW,WACd,MAAM,YACJ,MAAM,KACf,OAAO,CAAC,OAAO,CAIf,CAAC;AAEJ,eAAO,MAAM,wBAAwB,WAC3B,MAAM,KACb,OAAO,CAAC,4BAA4B,EAAE,CACY,CAAC;AAwBtD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,mBAChB,MAAM,WACb,WAAW,WACX,WAAW,KACnB,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA6CjD,CAAC;AAMF,sEAAsE;AACtE,eAAO,MAAM,0BAA0B,QAAO,IAgB7C,CAAC"}