@lamalibre/create-lamaste 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/LICENSE.md +118 -0
  2. package/README.md +114 -0
  3. package/bin/create-lamaste.js +15 -0
  4. package/package.json +54 -0
  5. package/scripts/bundle-vendor.js +110 -0
  6. package/scripts/lamaste-cert-info +62 -0
  7. package/scripts/lamaste-certbot +96 -0
  8. package/scripts/lamaste-priv +693 -0
  9. package/src/index.js +646 -0
  10. package/src/lib/accounts.js +86 -0
  11. package/src/lib/binaries.js +137 -0
  12. package/src/lib/cert-help-page.js +160 -0
  13. package/src/lib/env.js +132 -0
  14. package/src/lib/http-redirect.js +38 -0
  15. package/src/lib/ownership.js +231 -0
  16. package/src/lib/relay-services.js +103 -0
  17. package/src/lib/secrets.js +19 -0
  18. package/src/lib/service-config.js +311 -0
  19. package/src/lib/summary.js +131 -0
  20. package/src/lib/wrappers.js +63 -0
  21. package/src/tasks/gatekeeper.js +233 -0
  22. package/src/tasks/harden.js +293 -0
  23. package/src/tasks/mtls.js +193 -0
  24. package/src/tasks/nginx.js +265 -0
  25. package/src/tasks/node.js +110 -0
  26. package/src/tasks/panel.js +408 -0
  27. package/src/tasks/redeploy.js +438 -0
  28. package/vendor/docs/00-introduction/how-it-works.md +447 -0
  29. package/vendor/docs/00-introduction/quickstart.md +472 -0
  30. package/vendor/docs/00-introduction/what-is-lamaste.md +112 -0
  31. package/vendor/docs/01-concepts/authentication.md +464 -0
  32. package/vendor/docs/01-concepts/authorization.md +242 -0
  33. package/vendor/docs/01-concepts/certificates.md +452 -0
  34. package/vendor/docs/01-concepts/dns-and-domains.md +343 -0
  35. package/vendor/docs/01-concepts/mtls.md +490 -0
  36. package/vendor/docs/01-concepts/nginx-reverse-proxy.md +540 -0
  37. package/vendor/docs/01-concepts/security-model.md +600 -0
  38. package/vendor/docs/01-concepts/tickets.md +414 -0
  39. package/vendor/docs/01-concepts/tunneling.md +367 -0
  40. package/vendor/docs/02-guides/agent-setup.md +194 -0
  41. package/vendor/docs/02-guides/certificate-management.md +341 -0
  42. package/vendor/docs/02-guides/cloud-provisioning.md +406 -0
  43. package/vendor/docs/02-guides/desktop-app-setup.md +226 -0
  44. package/vendor/docs/02-guides/disaster-recovery.md +464 -0
  45. package/vendor/docs/02-guides/first-tunnel.md +230 -0
  46. package/vendor/docs/02-guides/installation.md +393 -0
  47. package/vendor/docs/02-guides/managing-users.md +255 -0
  48. package/vendor/docs/02-guides/onboarding.md +244 -0
  49. package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +375 -0
  50. package/vendor/docs/02-guides/static-sites.md +364 -0
  51. package/vendor/docs/03-architecture/e2e-three-vm-sequences.md +457 -0
  52. package/vendor/docs/03-architecture/installer-flow.md +151 -0
  53. package/vendor/docs/03-architecture/installer.md +445 -0
  54. package/vendor/docs/03-architecture/management-flow.md +288 -0
  55. package/vendor/docs/03-architecture/nginx-configuration.md +830 -0
  56. package/vendor/docs/03-architecture/onboarding-flow.md +164 -0
  57. package/vendor/docs/03-architecture/overview.md +541 -0
  58. package/vendor/docs/03-architecture/panel-client.md +416 -0
  59. package/vendor/docs/03-architecture/panel-server.md +695 -0
  60. package/vendor/docs/03-architecture/state-management.md +529 -0
  61. package/vendor/docs/03-architecture/system-overview.md +167 -0
  62. package/vendor/docs/04-api-reference/certificates.md +1141 -0
  63. package/vendor/docs/04-api-reference/gatekeeper.md +592 -0
  64. package/vendor/docs/04-api-reference/onboarding.md +473 -0
  65. package/vendor/docs/04-api-reference/overview.md +404 -0
  66. package/vendor/docs/04-api-reference/services.md +267 -0
  67. package/vendor/docs/04-api-reference/settings.md +343 -0
  68. package/vendor/docs/04-api-reference/sites.md +716 -0
  69. package/vendor/docs/04-api-reference/system.md +268 -0
  70. package/vendor/docs/04-api-reference/tickets.md +618 -0
  71. package/vendor/docs/04-api-reference/tunnels.md +417 -0
  72. package/vendor/docs/04-api-reference/users.md +333 -0
  73. package/vendor/docs/05-operations/backup-and-restore.md +342 -0
  74. package/vendor/docs/05-operations/monitoring.md +285 -0
  75. package/vendor/docs/05-operations/uninstalling.md +344 -0
  76. package/vendor/docs/05-operations/upgrades.md +270 -0
  77. package/vendor/docs/06-reference/config-files.md +1080 -0
  78. package/vendor/docs/06-reference/glossary.md +528 -0
  79. package/vendor/docs/06-reference/installer-flags.md +260 -0
  80. package/vendor/docs/06-reference/ports-and-services.md +244 -0
  81. package/vendor/docs/06-reference/troubleshooting.md +665 -0
  82. package/vendor/docs/_index.json +233 -0
  83. package/vendor/docs/gatekeeper-design.md +953 -0
  84. package/vendor/docs/package.json +15 -0
  85. package/vendor/docs/todo/lib-cli-desktop-refactor.md +641 -0
  86. package/vendor/gatekeeper/dist/boot.d.ts +1 -0
  87. package/vendor/gatekeeper/dist/boot.d.ts.map +1 -0
  88. package/vendor/gatekeeper/dist/boot.js +17 -0
  89. package/vendor/gatekeeper/dist/boot.js.map +1 -0
  90. package/vendor/gatekeeper/dist/cli/commands/access.d.ts +2 -0
  91. package/vendor/gatekeeper/dist/cli/commands/access.d.ts.map +1 -0
  92. package/vendor/gatekeeper/dist/cli/commands/access.js +33 -0
  93. package/vendor/gatekeeper/dist/cli/commands/access.js.map +1 -0
  94. package/vendor/gatekeeper/dist/cli/commands/grants.d.ts +2 -0
  95. package/vendor/gatekeeper/dist/cli/commands/grants.d.ts.map +1 -0
  96. package/vendor/gatekeeper/dist/cli/commands/grants.js +110 -0
  97. package/vendor/gatekeeper/dist/cli/commands/grants.js.map +1 -0
  98. package/vendor/gatekeeper/dist/cli/commands/groups.d.ts +2 -0
  99. package/vendor/gatekeeper/dist/cli/commands/groups.d.ts.map +1 -0
  100. package/vendor/gatekeeper/dist/cli/commands/groups.js +98 -0
  101. package/vendor/gatekeeper/dist/cli/commands/groups.js.map +1 -0
  102. package/vendor/gatekeeper/dist/cli/index.d.ts +3 -0
  103. package/vendor/gatekeeper/dist/cli/index.d.ts.map +1 -0
  104. package/vendor/gatekeeper/dist/cli/index.js +45 -0
  105. package/vendor/gatekeeper/dist/cli/index.js.map +1 -0
  106. package/vendor/gatekeeper/dist/index.d.ts +9 -0
  107. package/vendor/gatekeeper/dist/index.d.ts.map +1 -0
  108. package/vendor/gatekeeper/dist/index.js +13 -0
  109. package/vendor/gatekeeper/dist/index.js.map +1 -0
  110. package/vendor/gatekeeper/dist/lib/authz.d.ts +16 -0
  111. package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -0
  112. package/vendor/gatekeeper/dist/lib/authz.js +42 -0
  113. package/vendor/gatekeeper/dist/lib/authz.js.map +1 -0
  114. package/vendor/gatekeeper/dist/lib/cache-bust.d.ts +15 -0
  115. package/vendor/gatekeeper/dist/lib/cache-bust.d.ts.map +1 -0
  116. package/vendor/gatekeeper/dist/lib/cache-bust.js +19 -0
  117. package/vendor/gatekeeper/dist/lib/cache-bust.js.map +1 -0
  118. package/vendor/gatekeeper/dist/lib/constants.d.ts +82 -0
  119. package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -0
  120. package/vendor/gatekeeper/dist/lib/constants.js +79 -0
  121. package/vendor/gatekeeper/dist/lib/constants.js.map +1 -0
  122. package/vendor/gatekeeper/dist/lib/grants.d.ts +32 -0
  123. package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -0
  124. package/vendor/gatekeeper/dist/lib/grants.js +314 -0
  125. package/vendor/gatekeeper/dist/lib/grants.js.map +1 -0
  126. package/vendor/gatekeeper/dist/lib/groups.d.ts +10 -0
  127. package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -0
  128. package/vendor/gatekeeper/dist/lib/groups.js +247 -0
  129. package/vendor/gatekeeper/dist/lib/groups.js.map +1 -0
  130. package/vendor/gatekeeper/dist/lib/migration.d.ts +17 -0
  131. package/vendor/gatekeeper/dist/lib/migration.d.ts.map +1 -0
  132. package/vendor/gatekeeper/dist/lib/migration.js +79 -0
  133. package/vendor/gatekeeper/dist/lib/migration.js.map +1 -0
  134. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0001_groups.sql +7 -0
  135. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0002_access_grants.sql +14 -0
  136. package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0003_access_request_log.sql +9 -0
  137. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0001_groups.sql +7 -0
  138. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0002_access_grants.sql +14 -0
  139. package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0003_access_request_log.sql +9 -0
  140. package/vendor/gatekeeper/dist/lib/state-db.d.ts +16 -0
  141. package/vendor/gatekeeper/dist/lib/state-db.d.ts.map +1 -0
  142. package/vendor/gatekeeper/dist/lib/state-db.js +154 -0
  143. package/vendor/gatekeeper/dist/lib/state-db.js.map +1 -0
  144. package/vendor/gatekeeper/dist/lib/templates.d.ts +21 -0
  145. package/vendor/gatekeeper/dist/lib/templates.d.ts.map +1 -0
  146. package/vendor/gatekeeper/dist/lib/templates.js +151 -0
  147. package/vendor/gatekeeper/dist/lib/templates.js.map +1 -0
  148. package/vendor/gatekeeper/dist/lib/types.d.ts +178 -0
  149. package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -0
  150. package/vendor/gatekeeper/dist/lib/types.js +2 -0
  151. package/vendor/gatekeeper/dist/lib/types.js.map +1 -0
  152. package/vendor/gatekeeper/dist/server/index.d.ts +15 -0
  153. package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -0
  154. package/vendor/gatekeeper/dist/server/index.js +251 -0
  155. package/vendor/gatekeeper/dist/server/index.js.map +1 -0
  156. package/vendor/gatekeeper/dist/server/routes/access-request.d.ts +7 -0
  157. package/vendor/gatekeeper/dist/server/routes/access-request.d.ts.map +1 -0
  158. package/vendor/gatekeeper/dist/server/routes/access-request.js +17 -0
  159. package/vendor/gatekeeper/dist/server/routes/access-request.js.map +1 -0
  160. package/vendor/gatekeeper/dist/server/routes/authz.d.ts +3 -0
  161. package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -0
  162. package/vendor/gatekeeper/dist/server/routes/authz.js +279 -0
  163. package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -0
  164. package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts +13 -0
  165. package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -0
  166. package/vendor/gatekeeper/dist/server/routes/diagnostic.js +249 -0
  167. package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -0
  168. package/vendor/gatekeeper/dist/server/routes/grants.d.ts +3 -0
  169. package/vendor/gatekeeper/dist/server/routes/grants.d.ts.map +1 -0
  170. package/vendor/gatekeeper/dist/server/routes/grants.js +112 -0
  171. package/vendor/gatekeeper/dist/server/routes/grants.js.map +1 -0
  172. package/vendor/gatekeeper/dist/server/routes/groups.d.ts +3 -0
  173. package/vendor/gatekeeper/dist/server/routes/groups.d.ts.map +1 -0
  174. package/vendor/gatekeeper/dist/server/routes/groups.js +139 -0
  175. package/vendor/gatekeeper/dist/server/routes/groups.js.map +1 -0
  176. package/vendor/gatekeeper/package.json +49 -0
  177. package/vendor/server/bin/lamaste-server.js +204 -0
  178. package/vendor/server/package.json +31 -0
  179. package/vendor/server/src/commands/certs.js +224 -0
  180. package/vendor/server/src/commands/chisel.js +132 -0
  181. package/vendor/server/src/commands/logs.js +49 -0
  182. package/vendor/server/src/commands/plugins.js +321 -0
  183. package/vendor/server/src/commands/reset-admin.js +478 -0
  184. package/vendor/server/src/commands/restart.js +94 -0
  185. package/vendor/server/src/commands/sites.js +257 -0
  186. package/vendor/server/src/commands/status.js +158 -0
  187. package/vendor/server/src/commands/tunnels.js +365 -0
  188. package/vendor/server/src/commands/uninstall.js +326 -0
  189. package/vendor/server/src/config.js +38 -0
  190. package/vendor/server/src/exec.js +26 -0
  191. package/vendor/server/src/logger.js +39 -0
  192. package/vendor/server/src/ndjson.js +56 -0
  193. package/vendor/server/src/panel-api.js +85 -0
  194. package/vendor/server/src/state.js +45 -0
  195. package/vendor/server-ui/dist/assets/index-B0nWuUyL.js +257 -0
  196. package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +1 -0
  197. package/vendor/server-ui/dist/index.html +13 -0
  198. package/vendor/serverd/package.json +44 -0
  199. package/vendor/serverd/src/boot.js +13 -0
  200. package/vendor/serverd/src/index.js +191 -0
  201. package/vendor/serverd/src/lib/app-error.js +14 -0
  202. package/vendor/serverd/src/lib/authelia.js +85 -0
  203. package/vendor/serverd/src/lib/certbot.js +74 -0
  204. package/vendor/serverd/src/lib/chisel-args.js +6 -0
  205. package/vendor/serverd/src/lib/chisel-reconcile.js +232 -0
  206. package/vendor/serverd/src/lib/chisel-runtime.js +46 -0
  207. package/vendor/serverd/src/lib/chisel-users.js +109 -0
  208. package/vendor/serverd/src/lib/chisel.js +81 -0
  209. package/vendor/serverd/src/lib/config.js +190 -0
  210. package/vendor/serverd/src/lib/constants.js +8 -0
  211. package/vendor/serverd/src/lib/csr-signing.js +331 -0
  212. package/vendor/serverd/src/lib/enrollment.js +302 -0
  213. package/vendor/serverd/src/lib/files.js +19 -0
  214. package/vendor/serverd/src/lib/gatekeeper-client.js +135 -0
  215. package/vendor/serverd/src/lib/invite-page.js +281 -0
  216. package/vendor/serverd/src/lib/ip-utils.js +79 -0
  217. package/vendor/serverd/src/lib/migrations/state/0001_enrollment_tokens.sql +17 -0
  218. package/vendor/serverd/src/lib/migrations/state/0002_tickets.sql +70 -0
  219. package/vendor/serverd/src/lib/migrations/state/0003_plugins.sql +15 -0
  220. package/vendor/serverd/src/lib/migrations/state/0004_agents.sql +19 -0
  221. package/vendor/serverd/src/lib/mtls.js +795 -0
  222. package/vendor/serverd/src/lib/nginx.js +1090 -0
  223. package/vendor/serverd/src/lib/pki-paths.js +41 -0
  224. package/vendor/serverd/src/lib/pki-sign.js +103 -0
  225. package/vendor/serverd/src/lib/plugins.js +538 -0
  226. package/vendor/serverd/src/lib/push-install.js +390 -0
  227. package/vendor/serverd/src/lib/revocation.js +112 -0
  228. package/vendor/serverd/src/lib/services.js +131 -0
  229. package/vendor/serverd/src/lib/session.js +151 -0
  230. package/vendor/serverd/src/lib/state-db.js +130 -0
  231. package/vendor/serverd/src/lib/state.js +140 -0
  232. package/vendor/serverd/src/lib/storage.js +63 -0
  233. package/vendor/serverd/src/lib/system-stats.js +58 -0
  234. package/vendor/serverd/src/lib/tickets.js +1556 -0
  235. package/vendor/serverd/src/lib/totp.js +76 -0
  236. package/vendor/serverd/src/lib/tunnel-deps.js +41 -0
  237. package/vendor/serverd/src/lib/user-access-session.js +142 -0
  238. package/vendor/serverd/src/lib/user-access.js +52 -0
  239. package/vendor/serverd/src/middleware/errors.js +63 -0
  240. package/vendor/serverd/src/middleware/mtls.js +120 -0
  241. package/vendor/serverd/src/middleware/onboarding-guard.js +30 -0
  242. package/vendor/serverd/src/middleware/role-guard.js +58 -0
  243. package/vendor/serverd/src/middleware/twofa-session.js +65 -0
  244. package/vendor/serverd/src/middleware/user-access-session.js +52 -0
  245. package/vendor/serverd/src/routes/enrollment.js +120 -0
  246. package/vendor/serverd/src/routes/health.js +22 -0
  247. package/vendor/serverd/src/routes/invite.js +163 -0
  248. package/vendor/serverd/src/routes/management/agents.js +182 -0
  249. package/vendor/serverd/src/routes/management/certs.js +911 -0
  250. package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +160 -0
  251. package/vendor/serverd/src/routes/management/identity.js +107 -0
  252. package/vendor/serverd/src/routes/management/invitations.js +166 -0
  253. package/vendor/serverd/src/routes/management/logs.js +218 -0
  254. package/vendor/serverd/src/routes/management/plugins.js +560 -0
  255. package/vendor/serverd/src/routes/management/services.js +63 -0
  256. package/vendor/serverd/src/routes/management/settings.js +325 -0
  257. package/vendor/serverd/src/routes/management/sites.js +522 -0
  258. package/vendor/serverd/src/routes/management/storage.js +192 -0
  259. package/vendor/serverd/src/routes/management/system.js +201 -0
  260. package/vendor/serverd/src/routes/management/tickets.js +457 -0
  261. package/vendor/serverd/src/routes/management/tunnels.js +709 -0
  262. package/vendor/serverd/src/routes/management/users.js +377 -0
  263. package/vendor/serverd/src/routes/management.js +36 -0
  264. package/vendor/serverd/src/routes/onboarding/dns.js +101 -0
  265. package/vendor/serverd/src/routes/onboarding/domain.js +42 -0
  266. package/vendor/serverd/src/routes/onboarding/index.js +18 -0
  267. package/vendor/serverd/src/routes/onboarding/provision.js +327 -0
  268. package/vendor/serverd/src/routes/onboarding/status.js +12 -0
  269. package/vendor/serverd/src/routes/plugin-router.js +79 -0
  270. package/vendor/serverd/src/routes/user-access.js +389 -0
@@ -0,0 +1,151 @@
1
+ import crypto from 'node:crypto';
2
+ import { getConfig, updateConfig } from './config.js';
3
+
4
+ const SESSION_MAX_AGE = 12 * 60 * 60; // 12 hours in seconds
5
+ const INACTIVITY_TIMEOUT = 2 * 60 * 60; // 2 hours in seconds
6
+ const COOKIE_NAME = 'lamaste_2fa_session';
7
+ const REFRESH_THRESHOLD = 60; // Only re-sign if lastActivity is older than 60s
8
+
9
+ /**
10
+ * Standard cookie attributes for the 2FA session cookie.
11
+ * Used by the middleware (refresh), routes (set/clear), and exported for consistency.
12
+ */
13
+ const COOKIE_OPTIONS = Object.freeze({
14
+ httpOnly: true,
15
+ secure: true,
16
+ sameSite: 'strict',
17
+ path: '/',
18
+ });
19
+
20
+ export { COOKIE_NAME, SESSION_MAX_AGE, COOKIE_OPTIONS, REFRESH_THRESHOLD };
21
+
22
+ /**
23
+ * HMAC-sign a base64url payload string.
24
+ *
25
+ * @param {string} payloadB64 - Base64url-encoded payload
26
+ * @param {string} secretHex - Hex-encoded secret
27
+ * @returns {string} Base64url-encoded HMAC signature
28
+ */
29
+ function signPayload(payloadB64, secretHex) {
30
+ return crypto
31
+ .createHmac('sha256', Buffer.from(secretHex, 'hex'))
32
+ .update(payloadB64)
33
+ .digest('base64url');
34
+ }
35
+
36
+ /**
37
+ * Create a signed session cookie value.
38
+ *
39
+ * @param {string} sessionSecret - Hex-encoded session secret
40
+ * @param {string} [certSerial] - Certificate serial number to bind session to
41
+ * @returns {{ value: string, maxAge: number }}
42
+ */
43
+ export function createSessionCookie(sessionSecret, certSerial) {
44
+ const now = Math.floor(Date.now() / 1000);
45
+ const payload = {
46
+ iat: now,
47
+ exp: now + SESSION_MAX_AGE,
48
+ lastActivity: now,
49
+ ...(certSerial && { certSerial }),
50
+ };
51
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
52
+ const sig = signPayload(payloadB64, sessionSecret);
53
+
54
+ return {
55
+ value: `${payloadB64}.${sig}`,
56
+ maxAge: SESSION_MAX_AGE,
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Validate a session cookie value.
62
+ *
63
+ * @param {string} cookieValue - The cookie value (payload.sig)
64
+ * @param {string} sessionSecret - Hex-encoded session secret
65
+ * @param {string} [certSerial] - Certificate serial to verify against (if bound)
66
+ * @returns {{ valid: boolean, payload?: object, reason?: string }}
67
+ */
68
+ export function validateSession(cookieValue, sessionSecret, certSerial) {
69
+ if (!cookieValue || typeof cookieValue !== 'string') {
70
+ return { valid: false, reason: 'missing' };
71
+ }
72
+
73
+ const dotIndex = cookieValue.indexOf('.');
74
+ if (dotIndex === -1) {
75
+ return { valid: false, reason: 'malformed' };
76
+ }
77
+
78
+ const payloadB64 = cookieValue.slice(0, dotIndex);
79
+ const sig = cookieValue.slice(dotIndex + 1);
80
+
81
+ const expectedSig = signPayload(payloadB64, sessionSecret);
82
+
83
+ // Timing-safe comparison
84
+ const sigBuf = Buffer.from(sig);
85
+ const expectedBuf = Buffer.from(expectedSig);
86
+ if (sigBuf.length !== expectedBuf.length || !crypto.timingSafeEqual(sigBuf, expectedBuf)) {
87
+ return { valid: false, reason: 'invalid_signature' };
88
+ }
89
+
90
+ let payload;
91
+ try {
92
+ payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
93
+ } catch {
94
+ return { valid: false, reason: 'parse_error' };
95
+ }
96
+
97
+ const now = Math.floor(Date.now() / 1000);
98
+
99
+ if (payload.exp && now > payload.exp) {
100
+ return { valid: false, reason: 'expired' };
101
+ }
102
+
103
+ if (payload.lastActivity && now - payload.lastActivity > INACTIVITY_TIMEOUT) {
104
+ return { valid: false, reason: 'inactive' };
105
+ }
106
+
107
+ // Verify certificate binding if present in session
108
+ if (payload.certSerial && certSerial && payload.certSerial !== certSerial) {
109
+ return { valid: false, reason: 'cert_mismatch' };
110
+ }
111
+
112
+ return { valid: true, payload };
113
+ }
114
+
115
+ /**
116
+ * Refresh a session by updating lastActivity and re-signing.
117
+ *
118
+ * @param {object} payload - The existing session payload
119
+ * @param {string} sessionSecret - Hex-encoded session secret
120
+ * @returns {{ value: string, maxAge: number }}
121
+ */
122
+ export function refreshSession(payload, sessionSecret) {
123
+ const now = Math.floor(Date.now() / 1000);
124
+ const updated = { ...payload, lastActivity: now };
125
+ const payloadB64 = Buffer.from(JSON.stringify(updated)).toString('base64url');
126
+ const sig = signPayload(payloadB64, sessionSecret);
127
+
128
+ // Remaining time until absolute expiry
129
+ const remaining = Math.max(0, updated.exp - now);
130
+
131
+ return {
132
+ value: `${payloadB64}.${sig}`,
133
+ maxAge: remaining,
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Ensure a sessionSecret exists in the config. If not, generate and persist one.
139
+ *
140
+ * @returns {Promise<string>} The session secret (hex)
141
+ */
142
+ export async function ensureSessionSecret() {
143
+ const config = getConfig();
144
+ if (config.sessionSecret) {
145
+ return config.sessionSecret;
146
+ }
147
+
148
+ const secret = crypto.randomBytes(32).toString('hex');
149
+ await updateConfig({ sessionSecret: secret });
150
+ return secret;
151
+ }
@@ -0,0 +1,130 @@
1
+ // Foundation for SQLite-backed state in lamaste-serverd. Owns the singleton
2
+ // DB handle, PRAGMA tuning, file-permission tightening, and the forward-only
3
+ // numbered-migrations runner. Domain modules consume this via `getStateDb()`
4
+ // and never instantiate `DatabaseSync` themselves.
5
+
6
+ import { chmod, readdir, readFile } from 'node:fs/promises';
7
+ import { existsSync } from 'node:fs';
8
+ import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ const DATA_DIR_ENV = 'LAMALIBRE_LAMASTE_DATA_DIR';
14
+ const DEFAULT_DATA_DIR = '/etc/lamalibre/lamaste';
15
+ const STATE_DB_FILENAME = 'state.db';
16
+ const STATE_MIGRATIONS_DIR = path.join(__dirname, 'migrations', 'state');
17
+
18
+ const MIGRATION_FILENAME_RE = /^\d{4}_[a-z0-9_]+\.sql$/;
19
+
20
+ let dbPromise = null;
21
+
22
+ /**
23
+ * Returns the singleton `state.db` handle for lamaste-serverd. Lazy-opens on
24
+ * first call; subsequent calls return the cached handle. Safe to call
25
+ * concurrently from multiple modules at startup — the open is wrapped in a
26
+ * promise singleton so only one open ever races.
27
+ */
28
+ export async function getStateDb() {
29
+ if (!dbPromise) {
30
+ const dataDir = process.env[DATA_DIR_ENV] || DEFAULT_DATA_DIR;
31
+ dbPromise = openManagedDb({
32
+ path: path.join(dataDir, STATE_DB_FILENAME),
33
+ migrationsDir: STATE_MIGRATIONS_DIR,
34
+ });
35
+ }
36
+ return dbPromise;
37
+ }
38
+
39
+ /**
40
+ * Apply every pending migration in `migrationsDir` against `db`. Idempotent —
41
+ * the second call is a no-op once `schema_migrations` has caught up. Each
42
+ * migration runs inside `BEGIN IMMEDIATE` … `COMMIT`; a failure rolls the
43
+ * statement back and rethrows with the migration filename in the message.
44
+ */
45
+ export async function ensureMigrations(db, migrationsDir) {
46
+ db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
47
+ id INTEGER PRIMARY KEY,
48
+ name TEXT NOT NULL UNIQUE,
49
+ applied_at TEXT NOT NULL
50
+ )`);
51
+
52
+ const applied = new Set(
53
+ db
54
+ .prepare('SELECT name FROM schema_migrations')
55
+ .all()
56
+ .map((r) => r.name),
57
+ );
58
+
59
+ let entries;
60
+ try {
61
+ entries = await readdir(migrationsDir);
62
+ } catch (err) {
63
+ if (err.code === 'ENOENT') return;
64
+ throw err;
65
+ }
66
+
67
+ const files = entries.filter((f) => MIGRATION_FILENAME_RE.test(f)).sort();
68
+
69
+ for (const file of files) {
70
+ if (applied.has(file)) continue;
71
+ const sql = await readFile(path.join(migrationsDir, file), 'utf-8');
72
+ const id = Number(file.slice(0, 4));
73
+
74
+ db.exec('BEGIN IMMEDIATE');
75
+ try {
76
+ db.exec(sql);
77
+ db.prepare('INSERT INTO schema_migrations (id, name, applied_at) VALUES (?, ?, ?)').run(
78
+ id,
79
+ file,
80
+ new Date().toISOString(),
81
+ );
82
+ db.exec('COMMIT');
83
+ } catch (err) {
84
+ db.exec('ROLLBACK');
85
+ throw new Error(`Migration ${file} failed: ${err.message}`);
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Open `path`, apply PRAGMAs, tighten file modes, and run any pending
92
+ * migrations from `migrationsDir`. Single source of truth for DB lifecycle —
93
+ * `getStateDb()` is a thin wrapper that supplies serverd's path/migrations.
94
+ *
95
+ * `node:sqlite` is dynamically imported so `boot.js`'s `process.on('warning')`
96
+ * filter (installed during user-code evaluation) catches the
97
+ * ExperimentalWarning. A static import would emit it during ESM link.
98
+ */
99
+ async function openManagedDb({ path: dbPath, migrationsDir }) {
100
+ const { DatabaseSync } = await import('node:sqlite');
101
+ const db = new DatabaseSync(dbPath);
102
+
103
+ // PRAGMA order matters: journal_mode must be set before any write that
104
+ // would lock the rollback journal. cache_size = -2048 caps the page cache
105
+ // at 2 MiB (negative = KiB). synchronous = NORMAL is safe with WAL and
106
+ // skips the per-commit fsync.
107
+ db.exec(`
108
+ PRAGMA journal_mode = WAL;
109
+ PRAGMA synchronous = NORMAL;
110
+ PRAGMA foreign_keys = ON;
111
+ PRAGMA cache_size = -2048;
112
+ PRAGMA temp_store = MEMORY;
113
+ PRAGMA busy_timeout = 5000;
114
+ `);
115
+
116
+ // 0o600 on the DB plus its WAL/SHM sidecars (which appear lazily on first
117
+ // write — guard with existsSync). Best-effort: a failure here is logged by
118
+ // the caller's surrounding context, not fatal.
119
+ try {
120
+ await chmod(dbPath, 0o600);
121
+ if (existsSync(`${dbPath}-wal`)) await chmod(`${dbPath}-wal`, 0o600);
122
+ if (existsSync(`${dbPath}-shm`)) await chmod(`${dbPath}-shm`, 0o600);
123
+ } catch {
124
+ // best-effort
125
+ }
126
+
127
+ await ensureMigrations(db, migrationsDir);
128
+
129
+ return db;
130
+ }
@@ -0,0 +1,140 @@
1
+ import { readFile, writeFile, rename, open } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ const STATE_DIR = process.env.LAMALIBRE_LAMASTE_STATE_DIR || '/etc/lamalibre/lamaste';
5
+
6
+ function tunnelsPath() {
7
+ return path.join(STATE_DIR, 'tunnels.json');
8
+ }
9
+
10
+ /**
11
+ * Read the tunnels array from tunnels.json.
12
+ * Returns an empty array if the file does not exist.
13
+ */
14
+ export async function readTunnels() {
15
+ try {
16
+ const raw = await readFile(tunnelsPath(), 'utf-8');
17
+ const parsed = JSON.parse(raw);
18
+ if (!Array.isArray(parsed)) {
19
+ return [];
20
+ }
21
+ return parsed;
22
+ } catch (err) {
23
+ if (err.code === 'ENOENT') {
24
+ return [];
25
+ }
26
+ throw new Error(`Failed to read tunnels state: ${err.message}`);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Write the tunnels array to tunnels.json atomically.
32
+ *
33
+ * 1. Write to a temp file in the same directory.
34
+ * 2. fsync the temp file.
35
+ * 3. Rename temp → tunnels.json (atomic on POSIX).
36
+ */
37
+ export async function writeTunnels(tunnels) {
38
+ const filePath = tunnelsPath();
39
+ const tmpPath = `${filePath}.tmp`;
40
+
41
+ const content = JSON.stringify(tunnels, null, 2) + '\n';
42
+ await writeFile(tmpPath, content, { encoding: 'utf-8', mode: 0o600 });
43
+
44
+ // fsync the temp file to ensure data is flushed to disk
45
+ const fd = await open(tmpPath, 'r');
46
+ await fd.sync();
47
+ await fd.close();
48
+
49
+ await rename(tmpPath, filePath);
50
+ }
51
+
52
+ function sitesPath() {
53
+ return path.join(STATE_DIR, 'sites.json');
54
+ }
55
+
56
+ /**
57
+ * Read the sites array from sites.json.
58
+ * Returns an empty array if the file does not exist.
59
+ */
60
+ export async function readSites() {
61
+ try {
62
+ const raw = await readFile(sitesPath(), 'utf-8');
63
+ const parsed = JSON.parse(raw);
64
+ if (!Array.isArray(parsed)) {
65
+ return [];
66
+ }
67
+ return parsed;
68
+ } catch (err) {
69
+ if (err.code === 'ENOENT') {
70
+ return [];
71
+ }
72
+ throw new Error(`Failed to read sites state: ${err.message}`);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Write the sites array to sites.json atomically.
78
+ *
79
+ * 1. Write to a temp file in the same directory.
80
+ * 2. fsync the temp file.
81
+ * 3. Rename temp → sites.json (atomic on POSIX).
82
+ */
83
+ export async function writeSites(sites) {
84
+ const filePath = sitesPath();
85
+ const tmpPath = `${filePath}.tmp`;
86
+
87
+ const content = JSON.stringify(sites, null, 2) + '\n';
88
+ await writeFile(tmpPath, content, { encoding: 'utf-8', mode: 0o600 });
89
+
90
+ const fd = await open(tmpPath, 'r');
91
+ await fd.sync();
92
+ await fd.close();
93
+
94
+ await rename(tmpPath, filePath);
95
+ }
96
+
97
+ function invitationsPath() {
98
+ return path.join(STATE_DIR, 'invitations.json');
99
+ }
100
+
101
+ /**
102
+ * Read the invitations array from invitations.json.
103
+ * Returns an empty array if the file does not exist.
104
+ */
105
+ export async function readInvitations() {
106
+ try {
107
+ const raw = await readFile(invitationsPath(), 'utf-8');
108
+ const parsed = JSON.parse(raw);
109
+ if (!Array.isArray(parsed)) {
110
+ return [];
111
+ }
112
+ return parsed;
113
+ } catch (err) {
114
+ if (err.code === 'ENOENT') {
115
+ return [];
116
+ }
117
+ throw new Error(`Failed to read invitations state: ${err.message}`);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Write the invitations array to invitations.json atomically.
123
+ *
124
+ * 1. Write to a temp file in the same directory.
125
+ * 2. fsync the temp file.
126
+ * 3. Rename temp → invitations.json (atomic on POSIX).
127
+ */
128
+ export async function writeInvitations(invitations) {
129
+ const filePath = invitationsPath();
130
+ const tmpPath = `${filePath}.tmp`;
131
+
132
+ const content = JSON.stringify(invitations, null, 2) + '\n';
133
+ await writeFile(tmpPath, content, { encoding: 'utf-8', mode: 0o600 });
134
+
135
+ const fd = await open(tmpPath, 'r');
136
+ await fd.sync();
137
+ await fd.close();
138
+
139
+ await rename(tmpPath, filePath);
140
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shim — storage registry + credential encryption now lives in
3
+ * `@lamalibre/lamaste/server`. This file preserves the daemon-local API
4
+ * (no dataDir parameter) by resolving the data dir from env/config and
5
+ * forwarding to the core library.
6
+ */
7
+
8
+ import {
9
+ encryptCredential as encryptCredentialCore,
10
+ decryptCredential as decryptCredentialCore,
11
+ registerStorageServer as registerStorageServerCore,
12
+ removeStorageServer as removeStorageServerCore,
13
+ listStorageServers as listStorageServersCore,
14
+ bindPluginStorage as bindPluginStorageCore,
15
+ unbindPluginStorage as unbindPluginStorageCore,
16
+ listBindings as listBindingsCore,
17
+ getBinding as getBindingCore,
18
+ getPluginStorageConfig as getPluginStorageConfigCore,
19
+ } from '@lamalibre/lamaste/server';
20
+
21
+ function dataDir() {
22
+ return process.env.LAMALIBRE_LAMASTE_STATE_DIR || '/etc/lamalibre/lamaste';
23
+ }
24
+
25
+ export function encryptCredential(plaintext) {
26
+ return encryptCredentialCore(plaintext, dataDir());
27
+ }
28
+
29
+ export function decryptCredential(packed) {
30
+ return decryptCredentialCore(packed, dataDir());
31
+ }
32
+
33
+ export function registerStorageServer(input) {
34
+ return registerStorageServerCore(dataDir(), input);
35
+ }
36
+
37
+ export function removeStorageServer(id) {
38
+ return removeStorageServerCore(dataDir(), id);
39
+ }
40
+
41
+ export function listStorageServers() {
42
+ return listStorageServersCore(dataDir());
43
+ }
44
+
45
+ export function bindPluginStorage(pluginName, storageServerId) {
46
+ return bindPluginStorageCore(dataDir(), pluginName, storageServerId);
47
+ }
48
+
49
+ export function unbindPluginStorage(pluginName) {
50
+ return unbindPluginStorageCore(dataDir(), pluginName);
51
+ }
52
+
53
+ export function listBindings() {
54
+ return listBindingsCore(dataDir());
55
+ }
56
+
57
+ export function getBinding(pluginName) {
58
+ return getBindingCore(dataDir(), pluginName);
59
+ }
60
+
61
+ export function getPluginStorageConfig(pluginName) {
62
+ return getPluginStorageConfigCore(dataDir(), pluginName);
63
+ }
@@ -0,0 +1,58 @@
1
+ import si from 'systeminformation';
2
+
3
+ let cachedStats = null;
4
+ let cacheTimestamp = 0;
5
+ const CACHE_TTL_MS = 2000;
6
+
7
+ /**
8
+ * Retrieve system stats with a 2-second cache to avoid
9
+ * hammering the system when multiple clients poll simultaneously.
10
+ */
11
+ export async function getSystemStats(logger) {
12
+ const now = Date.now();
13
+
14
+ if (cachedStats && now - cacheTimestamp < CACHE_TTL_MS) {
15
+ return cachedStats;
16
+ }
17
+
18
+ try {
19
+ const [currentLoad, cpu, mem, fsSizes, time] = await Promise.all([
20
+ si.currentLoad(),
21
+ si.cpu(),
22
+ si.mem(),
23
+ si.fsSize(),
24
+ Promise.resolve(si.time()),
25
+ ]);
26
+
27
+ // Find root filesystem (or first available)
28
+ const rootFs = fsSizes.find((fs) => fs.mount === '/') || fsSizes[0] || {};
29
+
30
+ const stats = {
31
+ cpu: {
32
+ usage: Math.round(currentLoad.currentLoad * 10) / 10,
33
+ cores: cpu.cores,
34
+ },
35
+ memory: {
36
+ total: mem.total,
37
+ used: mem.active,
38
+ free: mem.available,
39
+ },
40
+ disk: {
41
+ total: rootFs.size || 0,
42
+ used: rootFs.used || 0,
43
+ free: (rootFs.size || 0) - (rootFs.used || 0),
44
+ },
45
+ uptime: time.uptime,
46
+ };
47
+
48
+ cachedStats = stats;
49
+ cacheTimestamp = now;
50
+
51
+ return stats;
52
+ } catch (err) {
53
+ if (logger) {
54
+ logger.error({ err }, 'Failed to retrieve system stats');
55
+ }
56
+ throw new Error('Failed to retrieve system stats');
57
+ }
58
+ }