@selvajs/local-provider 0.11.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 (295) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/dist/auth/LocalAuthProvider.d.ts +28 -0
  4. package/dist/auth/LocalAuthProvider.d.ts.map +1 -0
  5. package/dist/auth/LocalAuthProvider.js +142 -0
  6. package/dist/auth/LocalAuthProvider.js.map +1 -0
  7. package/dist/auth/__tests__/conformance.test.d.ts +2 -0
  8. package/dist/auth/__tests__/conformance.test.d.ts.map +1 -0
  9. package/dist/auth/__tests__/conformance.test.js +36 -0
  10. package/dist/auth/__tests__/conformance.test.js.map +1 -0
  11. package/dist/auth/hmac.d.ts +18 -0
  12. package/dist/auth/hmac.d.ts.map +1 -0
  13. package/dist/auth/hmac.js +41 -0
  14. package/dist/auth/hmac.js.map +1 -0
  15. package/dist/auth/index.d.ts +6 -0
  16. package/dist/auth/index.d.ts.map +1 -0
  17. package/dist/auth/index.js +4 -0
  18. package/dist/auth/index.js.map +1 -0
  19. package/dist/auth/users.d.ts +38 -0
  20. package/dist/auth/users.d.ts.map +1 -0
  21. package/dist/auth/users.js +100 -0
  22. package/dist/auth/users.js.map +1 -0
  23. package/dist/compute/FilesystemComputeProvider.d.ts +16 -0
  24. package/dist/compute/FilesystemComputeProvider.d.ts.map +1 -0
  25. package/dist/compute/FilesystemComputeProvider.js +51 -0
  26. package/dist/compute/FilesystemComputeProvider.js.map +1 -0
  27. package/dist/compute/SingleComputeServerProvider.d.ts +15 -0
  28. package/dist/compute/SingleComputeServerProvider.d.ts.map +1 -0
  29. package/dist/compute/SingleComputeServerProvider.js +26 -0
  30. package/dist/compute/SingleComputeServerProvider.js.map +1 -0
  31. package/dist/compute/types.d.ts +30 -0
  32. package/dist/compute/types.d.ts.map +1 -0
  33. package/dist/compute/types.js +2 -0
  34. package/dist/compute/types.js.map +1 -0
  35. package/dist/computeServer/FilesystemComputeServerStore.d.ts +14 -0
  36. package/dist/computeServer/FilesystemComputeServerStore.d.ts.map +1 -0
  37. package/dist/computeServer/FilesystemComputeServerStore.js +35 -0
  38. package/dist/computeServer/FilesystemComputeServerStore.js.map +1 -0
  39. package/dist/computeServer/LocalComputeServerProvider.d.ts +18 -0
  40. package/dist/computeServer/LocalComputeServerProvider.d.ts.map +1 -0
  41. package/dist/computeServer/LocalComputeServerProvider.js +29 -0
  42. package/dist/computeServer/LocalComputeServerProvider.js.map +1 -0
  43. package/dist/computeServer/__tests__/conformance.test.d.ts +2 -0
  44. package/dist/computeServer/__tests__/conformance.test.d.ts.map +1 -0
  45. package/dist/computeServer/__tests__/conformance.test.js +20 -0
  46. package/dist/computeServer/__tests__/conformance.test.js.map +1 -0
  47. package/dist/computeServer/index.d.ts +2 -0
  48. package/dist/computeServer/index.d.ts.map +1 -0
  49. package/dist/computeServer/index.js +2 -0
  50. package/dist/computeServer/index.js.map +1 -0
  51. package/dist/data/LocalComputeServerStore.d.ts +72 -0
  52. package/dist/data/LocalComputeServerStore.d.ts.map +1 -0
  53. package/dist/data/LocalComputeServerStore.js +207 -0
  54. package/dist/data/LocalComputeServerStore.js.map +1 -0
  55. package/dist/data/LocalDataProvider.d.ts +47 -0
  56. package/dist/data/LocalDataProvider.d.ts.map +1 -0
  57. package/dist/data/LocalDataProvider.js +118 -0
  58. package/dist/data/LocalDataProvider.js.map +1 -0
  59. package/dist/data/LocalDefinitionMetaProvider.d.ts +22 -0
  60. package/dist/data/LocalDefinitionMetaProvider.d.ts.map +1 -0
  61. package/dist/data/LocalDefinitionMetaProvider.js +131 -0
  62. package/dist/data/LocalDefinitionMetaProvider.js.map +1 -0
  63. package/dist/data/LocalDefinitionStore.d.ts +33 -0
  64. package/dist/data/LocalDefinitionStore.d.ts.map +1 -0
  65. package/dist/data/LocalDefinitionStore.js +274 -0
  66. package/dist/data/LocalDefinitionStore.js.map +1 -0
  67. package/dist/data/LocalInviteStore.d.ts +23 -0
  68. package/dist/data/LocalInviteStore.d.ts.map +1 -0
  69. package/dist/data/LocalInviteStore.js +98 -0
  70. package/dist/data/LocalInviteStore.js.map +1 -0
  71. package/dist/data/LocalOrgStore.d.ts +67 -0
  72. package/dist/data/LocalOrgStore.d.ts.map +1 -0
  73. package/dist/data/LocalOrgStore.js +255 -0
  74. package/dist/data/LocalOrgStore.js.map +1 -0
  75. package/dist/data/LocalPlatformProjectGrantStore.d.ts +14 -0
  76. package/dist/data/LocalPlatformProjectGrantStore.d.ts.map +1 -0
  77. package/dist/data/LocalPlatformProjectGrantStore.js +62 -0
  78. package/dist/data/LocalPlatformProjectGrantStore.js.map +1 -0
  79. package/dist/data/LocalProjectStore.d.ts +30 -0
  80. package/dist/data/LocalProjectStore.d.ts.map +1 -0
  81. package/dist/data/LocalProjectStore.js +171 -0
  82. package/dist/data/LocalProjectStore.js.map +1 -0
  83. package/dist/data/LocalShareLinkStore.d.ts +39 -0
  84. package/dist/data/LocalShareLinkStore.d.ts.map +1 -0
  85. package/dist/data/LocalShareLinkStore.js +108 -0
  86. package/dist/data/LocalShareLinkStore.js.map +1 -0
  87. package/dist/data/__tests__/LocalDefinitionMetaProvider.test.d.ts +2 -0
  88. package/dist/data/__tests__/LocalDefinitionMetaProvider.test.d.ts.map +1 -0
  89. package/dist/data/__tests__/LocalDefinitionMetaProvider.test.js +21 -0
  90. package/dist/data/__tests__/LocalDefinitionMetaProvider.test.js.map +1 -0
  91. package/dist/data/__tests__/cascade.test.d.ts +2 -0
  92. package/dist/data/__tests__/cascade.test.d.ts.map +1 -0
  93. package/dist/data/__tests__/cascade.test.js +265 -0
  94. package/dist/data/__tests__/cascade.test.js.map +1 -0
  95. package/dist/data/__tests__/compute-server-conformance.test.d.ts +2 -0
  96. package/dist/data/__tests__/compute-server-conformance.test.d.ts.map +1 -0
  97. package/dist/data/__tests__/compute-server-conformance.test.js +21 -0
  98. package/dist/data/__tests__/compute-server-conformance.test.js.map +1 -0
  99. package/dist/data/__tests__/compute-server-encryption.test.d.ts +2 -0
  100. package/dist/data/__tests__/compute-server-encryption.test.d.ts.map +1 -0
  101. package/dist/data/__tests__/compute-server-encryption.test.js +131 -0
  102. package/dist/data/__tests__/compute-server-encryption.test.js.map +1 -0
  103. package/dist/data/__tests__/definition-conformance.test.d.ts +2 -0
  104. package/dist/data/__tests__/definition-conformance.test.d.ts.map +1 -0
  105. package/dist/data/__tests__/definition-conformance.test.js +20 -0
  106. package/dist/data/__tests__/definition-conformance.test.js.map +1 -0
  107. package/dist/data/__tests__/event-sink-conformance.test.d.ts +2 -0
  108. package/dist/data/__tests__/event-sink-conformance.test.d.ts.map +1 -0
  109. package/dist/data/__tests__/event-sink-conformance.test.js +24 -0
  110. package/dist/data/__tests__/event-sink-conformance.test.js.map +1 -0
  111. package/dist/data/__tests__/invite-conformance.test.d.ts +2 -0
  112. package/dist/data/__tests__/invite-conformance.test.d.ts.map +1 -0
  113. package/dist/data/__tests__/invite-conformance.test.js +21 -0
  114. package/dist/data/__tests__/invite-conformance.test.js.map +1 -0
  115. package/dist/data/__tests__/org-conformance.test.d.ts +2 -0
  116. package/dist/data/__tests__/org-conformance.test.d.ts.map +1 -0
  117. package/dist/data/__tests__/org-conformance.test.js +36 -0
  118. package/dist/data/__tests__/org-conformance.test.js.map +1 -0
  119. package/dist/data/__tests__/platform-project-grant-conformance.test.d.ts +2 -0
  120. package/dist/data/__tests__/platform-project-grant-conformance.test.d.ts.map +1 -0
  121. package/dist/data/__tests__/platform-project-grant-conformance.test.js +20 -0
  122. package/dist/data/__tests__/platform-project-grant-conformance.test.js.map +1 -0
  123. package/dist/data/__tests__/project-conformance.test.d.ts +2 -0
  124. package/dist/data/__tests__/project-conformance.test.d.ts.map +1 -0
  125. package/dist/data/__tests__/project-conformance.test.js +53 -0
  126. package/dist/data/__tests__/project-conformance.test.js.map +1 -0
  127. package/dist/data/__tests__/rules.test.d.ts +2 -0
  128. package/dist/data/__tests__/rules.test.d.ts.map +1 -0
  129. package/dist/data/__tests__/rules.test.js +484 -0
  130. package/dist/data/__tests__/rules.test.js.map +1 -0
  131. package/dist/data/__tests__/share-link-conformance.test.d.ts +2 -0
  132. package/dist/data/__tests__/share-link-conformance.test.d.ts.map +1 -0
  133. package/dist/data/__tests__/share-link-conformance.test.js +20 -0
  134. package/dist/data/__tests__/share-link-conformance.test.js.map +1 -0
  135. package/dist/data/fsJson.d.ts +12 -0
  136. package/dist/data/fsJson.d.ts.map +1 -0
  137. package/dist/data/fsJson.js +29 -0
  138. package/dist/data/fsJson.js.map +1 -0
  139. package/dist/data/index.d.ts +13 -0
  140. package/dist/data/index.d.ts.map +1 -0
  141. package/dist/data/index.js +9 -0
  142. package/dist/data/index.js.map +1 -0
  143. package/dist/data/pagination.d.ts +15 -0
  144. package/dist/data/pagination.d.ts.map +1 -0
  145. package/dist/data/pagination.js +36 -0
  146. package/dist/data/pagination.js.map +1 -0
  147. package/dist/data/secretCrypto.d.ts +23 -0
  148. package/dist/data/secretCrypto.d.ts.map +1 -0
  149. package/dist/data/secretCrypto.js +64 -0
  150. package/dist/data/secretCrypto.js.map +1 -0
  151. package/dist/data/userData.d.ts +40 -0
  152. package/dist/data/userData.d.ts.map +1 -0
  153. package/dist/data/userData.js +84 -0
  154. package/dist/data/userData.js.map +1 -0
  155. package/dist/definitions/LocalDefinitionMetaProvider.d.ts +27 -0
  156. package/dist/definitions/LocalDefinitionMetaProvider.d.ts.map +1 -0
  157. package/dist/definitions/LocalDefinitionMetaProvider.js +188 -0
  158. package/dist/definitions/LocalDefinitionMetaProvider.js.map +1 -0
  159. package/dist/definitions/__tests__/conformance.test.d.ts +2 -0
  160. package/dist/definitions/__tests__/conformance.test.d.ts.map +1 -0
  161. package/dist/definitions/__tests__/conformance.test.js +20 -0
  162. package/dist/definitions/__tests__/conformance.test.js.map +1 -0
  163. package/dist/definitions/index.d.ts +2 -0
  164. package/dist/definitions/index.d.ts.map +1 -0
  165. package/dist/definitions/index.js +2 -0
  166. package/dist/definitions/index.js.map +1 -0
  167. package/dist/definitions/providers/filesystem-files.d.ts +24 -0
  168. package/dist/definitions/providers/filesystem-files.d.ts.map +1 -0
  169. package/dist/definitions/providers/filesystem-files.js +170 -0
  170. package/dist/definitions/providers/filesystem-files.js.map +1 -0
  171. package/dist/definitions/providers/filesystem-meta.d.ts +17 -0
  172. package/dist/definitions/providers/filesystem-meta.d.ts.map +1 -0
  173. package/dist/definitions/providers/filesystem-meta.js +216 -0
  174. package/dist/definitions/providers/filesystem-meta.js.map +1 -0
  175. package/dist/fsJson.d.ts +12 -0
  176. package/dist/fsJson.d.ts.map +1 -0
  177. package/dist/fsJson.js +29 -0
  178. package/dist/fsJson.js.map +1 -0
  179. package/dist/index.d.ts +13 -0
  180. package/dist/index.d.ts.map +1 -0
  181. package/dist/index.js +16 -0
  182. package/dist/index.js.map +1 -0
  183. package/dist/invites/LocalInviteProvider.d.ts +24 -0
  184. package/dist/invites/LocalInviteProvider.d.ts.map +1 -0
  185. package/dist/invites/LocalInviteProvider.js +89 -0
  186. package/dist/invites/LocalInviteProvider.js.map +1 -0
  187. package/dist/invites/__tests__/conformance.test.d.ts +2 -0
  188. package/dist/invites/__tests__/conformance.test.d.ts.map +1 -0
  189. package/dist/invites/__tests__/conformance.test.js +21 -0
  190. package/dist/invites/__tests__/conformance.test.js.map +1 -0
  191. package/dist/organizations/LocalOrganizationProvider.d.ts +41 -0
  192. package/dist/organizations/LocalOrganizationProvider.d.ts.map +1 -0
  193. package/dist/organizations/LocalOrganizationProvider.js +198 -0
  194. package/dist/organizations/LocalOrganizationProvider.js.map +1 -0
  195. package/dist/organizations/__tests__/conformance.test.d.ts +2 -0
  196. package/dist/organizations/__tests__/conformance.test.d.ts.map +1 -0
  197. package/dist/organizations/__tests__/conformance.test.js +20 -0
  198. package/dist/organizations/__tests__/conformance.test.js.map +1 -0
  199. package/dist/organizations/index.d.ts +2 -0
  200. package/dist/organizations/index.d.ts.map +1 -0
  201. package/dist/organizations/index.js +2 -0
  202. package/dist/organizations/index.js.map +1 -0
  203. package/dist/pagination.d.ts +15 -0
  204. package/dist/pagination.d.ts.map +1 -0
  205. package/dist/pagination.js +36 -0
  206. package/dist/pagination.js.map +1 -0
  207. package/dist/permissions/LocalPlatformPermissionStore.d.ts +39 -0
  208. package/dist/permissions/LocalPlatformPermissionStore.d.ts.map +1 -0
  209. package/dist/permissions/LocalPlatformPermissionStore.js +117 -0
  210. package/dist/permissions/LocalPlatformPermissionStore.js.map +1 -0
  211. package/dist/permissions/__tests__/conformance.test.d.ts +2 -0
  212. package/dist/permissions/__tests__/conformance.test.d.ts.map +1 -0
  213. package/dist/permissions/__tests__/conformance.test.js +37 -0
  214. package/dist/permissions/__tests__/conformance.test.js.map +1 -0
  215. package/dist/permissions/index.d.ts +2 -0
  216. package/dist/permissions/index.d.ts.map +1 -0
  217. package/dist/permissions/index.js +2 -0
  218. package/dist/permissions/index.js.map +1 -0
  219. package/dist/projects/LocalProjectProvider.d.ts +21 -0
  220. package/dist/projects/LocalProjectProvider.d.ts.map +1 -0
  221. package/dist/projects/LocalProjectProvider.js +125 -0
  222. package/dist/projects/LocalProjectProvider.js.map +1 -0
  223. package/dist/projects/__tests__/conformance.test.d.ts +2 -0
  224. package/dist/projects/__tests__/conformance.test.d.ts.map +1 -0
  225. package/dist/projects/__tests__/conformance.test.js +44 -0
  226. package/dist/projects/__tests__/conformance.test.js.map +1 -0
  227. package/dist/projects/index.d.ts +2 -0
  228. package/dist/projects/index.d.ts.map +1 -0
  229. package/dist/projects/index.js +2 -0
  230. package/dist/projects/index.js.map +1 -0
  231. package/dist/storage/LocalStorageProvider.d.ts +27 -0
  232. package/dist/storage/LocalStorageProvider.d.ts.map +1 -0
  233. package/dist/storage/LocalStorageProvider.js +74 -0
  234. package/dist/storage/LocalStorageProvider.js.map +1 -0
  235. package/dist/storage/__tests__/conformance.test.d.ts +2 -0
  236. package/dist/storage/__tests__/conformance.test.d.ts.map +1 -0
  237. package/dist/storage/__tests__/conformance.test.js +20 -0
  238. package/dist/storage/__tests__/conformance.test.js.map +1 -0
  239. package/dist/storage/index.d.ts +2 -0
  240. package/dist/storage/index.d.ts.map +1 -0
  241. package/dist/storage/index.js +2 -0
  242. package/dist/storage/index.js.map +1 -0
  243. package/dist/userProfile/LocalUserProfileProvider.d.ts +25 -0
  244. package/dist/userProfile/LocalUserProfileProvider.d.ts.map +1 -0
  245. package/dist/userProfile/LocalUserProfileProvider.js +110 -0
  246. package/dist/userProfile/LocalUserProfileProvider.js.map +1 -0
  247. package/dist/userProfile/__tests__/conformance.test.d.ts +2 -0
  248. package/dist/userProfile/__tests__/conformance.test.d.ts.map +1 -0
  249. package/dist/userProfile/__tests__/conformance.test.js +40 -0
  250. package/dist/userProfile/__tests__/conformance.test.js.map +1 -0
  251. package/dist/userProfile/index.d.ts +2 -0
  252. package/dist/userProfile/index.d.ts.map +1 -0
  253. package/dist/userProfile/index.js +2 -0
  254. package/dist/userProfile/index.js.map +1 -0
  255. package/package.json +70 -0
  256. package/src/README.md +37 -0
  257. package/src/auth/LocalAuthProvider.ts +165 -0
  258. package/src/auth/__tests__/conformance.test.ts +40 -0
  259. package/src/auth/hmac.ts +53 -0
  260. package/src/auth/index.ts +5 -0
  261. package/src/auth/users.ts +151 -0
  262. package/src/data/LocalComputeServerStore.ts +290 -0
  263. package/src/data/LocalDataProvider.ts +148 -0
  264. package/src/data/LocalDefinitionStore.ts +369 -0
  265. package/src/data/LocalInviteStore.ts +117 -0
  266. package/src/data/LocalOrgStore.ts +356 -0
  267. package/src/data/LocalPlatformProjectGrantStore.ts +85 -0
  268. package/src/data/LocalProjectStore.ts +274 -0
  269. package/src/data/LocalShareLinkStore.ts +138 -0
  270. package/src/data/__tests__/cascade.test.ts +300 -0
  271. package/src/data/__tests__/compute-server-conformance.test.ts +26 -0
  272. package/src/data/__tests__/compute-server-encryption.test.ts +185 -0
  273. package/src/data/__tests__/definition-conformance.test.ts +23 -0
  274. package/src/data/__tests__/event-sink-conformance.test.ts +28 -0
  275. package/src/data/__tests__/invite-conformance.test.ts +24 -0
  276. package/src/data/__tests__/org-conformance.test.ts +43 -0
  277. package/src/data/__tests__/platform-project-grant-conformance.test.ts +24 -0
  278. package/src/data/__tests__/project-conformance.test.ts +64 -0
  279. package/src/data/__tests__/rules.test.ts +682 -0
  280. package/src/data/__tests__/share-link-conformance.test.ts +23 -0
  281. package/src/data/fsJson.ts +28 -0
  282. package/src/data/index.ts +16 -0
  283. package/src/data/pagination.ts +48 -0
  284. package/src/data/secretCrypto.ts +69 -0
  285. package/src/data/userData.ts +134 -0
  286. package/src/index.ts +42 -0
  287. package/src/permissions/LocalPlatformPermissionStore.ts +129 -0
  288. package/src/permissions/__tests__/conformance.test.ts +40 -0
  289. package/src/permissions/index.ts +1 -0
  290. package/src/storage/LocalStorageProvider.ts +78 -0
  291. package/src/storage/__tests__/conformance.test.ts +23 -0
  292. package/src/storage/index.ts +1 -0
  293. package/src/userProfile/LocalUserProfileProvider.ts +135 -0
  294. package/src/userProfile/__tests__/conformance.test.ts +43 -0
  295. package/src/userProfile/index.ts +1 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Selva FelixBrunold VektorNode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # @selvajs/local-provider
2
+
3
+ Filesystem + JSON + HMAC implementation of the `@selvajs/platform` interfaces.
4
+
5
+ The default provider for development and small single-instance deployments. All state — users, orgs, projects, definitions, compute config, uploaded `.gh` files — lives under one directory on disk. No database, no external services.
6
+
7
+ For production-scale or multi-instance deployments, use [`@selvajs/supabase-provider`](../supabase/README.md) instead.
8
+
9
+ ---
10
+
11
+ ## Table of contents
12
+
13
+ - [When to use this provider](#when-to-use-this-provider)
14
+ - [Environment variables](#environment-variables)
15
+ - [On-disk layout](#on-disk-layout)
16
+ - [Wiring into `selva.config.ts`](#wiring-into-selvaconfigts)
17
+ - [Architecture notes](#architecture-notes)
18
+
19
+ ---
20
+
21
+ ## When to use this provider
22
+
23
+ Pick local when:
24
+
25
+ - You're developing or evaluating Selva
26
+ - You're running a single-tenant, single-instance deployment (one VM, PM2)
27
+ - You want zero external dependencies — no DB, no S3
28
+ - You're OK with simple file-based backups (`tar` the data dir)
29
+
30
+ Pick [Supabase](../supabase/README.md) when:
31
+
32
+ - You need multiple selva app instances behind a load balancer
33
+ - You want managed auth (password reset, MFA, OAuth)
34
+ - You want managed Postgres + storage with backups + RLS
35
+ - You need atomic counters across processes
36
+
37
+ ---
38
+
39
+ ## Environment variables
40
+
41
+ All env vars are documented in [`packages/selva/.env.example`](../../selva/.env.example) — copy that file to `.env` and edit it. The local provider reads `DATA_PATH`, `SELVA_HMAC_KEY` (signs sessions + tokens), and `SELVA_AT_REST_KEY` (encrypts the Rhino.Compute API key on disk) from there.
42
+
43
+ The first admin user is created through the in-app setup page on first boot — there is no env-var fallback login.
44
+
45
+ Rhino.Compute server URL + API key are configured in `/admin/compute` and persisted to `compute.config.json` — not env vars.
46
+
47
+ ---
48
+
49
+ ## On-disk layout
50
+
51
+ ```
52
+ $DATA_PATH/
53
+ ├── users.json # users + hashed passwords + platform permissions
54
+ ├── local-org.json # organizations, projects, and their memberships
55
+ ├── definitions-config.json # definition metadata + version history
56
+ ├── share-links.json # per-definition share tokens (HMAC-hashed)
57
+ ├── invites.json # pending invite tokens
58
+ ├── compute.config.json # registered Rhino.Compute servers
59
+ └── definitions/ # uploaded .gh / .ghx + cover images
60
+ └── <definition-guid>/
61
+ ├── versions/v{n}.{ext}
62
+ └── cover.webp
63
+ ```
64
+
65
+ All JSON files are written atomically (temp file + rename) so a crash mid-write leaves either the old or new file — never a partial. Image uploads are transcoded to WebP (1200px max, quality 85) via `sharp`.
66
+
67
+ **Backups:** `tar -czf backup.tar.gz $DATA_PATH`. Restore is the reverse — no schema migrations, no DB to bring up.
68
+
69
+ **Caveats:**
70
+
71
+ - Not safe across **processes** — no file locking. One selva app instance per data dir.
72
+ - Read-modify-write on JSON files (`incrementRunCount`, etc.) can lose updates under concurrent solves on the same definition. Acceptable for typical single-user workloads; switch to Supabase if you need exact counts under contention.
73
+
74
+ ---
75
+
76
+ ## Wiring into `selva.config.ts`
77
+
78
+ This is the default config in [`selva.config.ts`](../../../selva.config.ts) at the repo root:
79
+
80
+ ```ts
81
+ import { defineConfig } from '@selvajs/platform';
82
+ import * as local from '@selvajs/local-provider';
83
+
84
+ export default defineConfig((env) => ({
85
+ tenancy: 'single' as const,
86
+ flags: {
87
+ ALLOW_CROSS_ORG_PUBLIC: false,
88
+ ALLOW_ORG_COMPUTE_OVERRIDE: false,
89
+ ALLOW_ORG_CREATION: false
90
+ },
91
+ auth: local.LocalAuthProvider.fromEnv(env),
92
+ data: local.LocalDataProvider.fromEnv(env),
93
+ storage: local.LocalStorageProvider.fromEnv(env)
94
+ }));
95
+ ```
96
+
97
+ `LocalDataProvider` internally wires every store — orgs, projects, definitions, share-links, invites, compute server, user profile, platform permissions.
98
+
99
+ To switch to Supabase, see [`@selvajs/supabase-provider`](../supabase/README.md#wiring-into-selvaconfigts).
100
+
101
+ ---
102
+
103
+ ## Architecture notes
104
+
105
+ ### Auth
106
+
107
+ `LocalAuthProvider` issues HMAC-signed session tokens (no JWT library; see [`auth/`](src/auth/)). Tokens carry `{ userId, expiresAt }` and are verified on every request.
108
+
109
+ Users live in `users.json` with `PBKDF2-SHA256` password hashes and platform permissions. The first admin is bootstrapped through the in-app setup page on a fresh install.
110
+
111
+ ### Data
112
+
113
+ Each store (`LocalOrgStore`, `LocalProjectStore`, `LocalDefinitionStore`, `LocalInviteStore`, `LocalComputeServerStore`) reads its JSON file fully into memory on each call, mutates, and writes back. Fine at config-scale; not for high-churn data.
114
+
115
+ Access control is enforced **in-process** by inspecting `RequestContext.adapterContext` — there's no RLS layer to lean on. Tests for these checks live alongside each store.
116
+
117
+ ### Storage
118
+
119
+ `LocalStorageProvider` writes blobs under `$DATA_PATH/<path>` (e.g. `$DATA_PATH/definitions/<guid>/versions/v1.gh`) — the caller's storage path is appended directly to the data root, with `..` rejected. `getPublicUrl` returns `/api/files/<path>`, which the selva app proxies after an auth check. Image uploads pass through the shared `transcodeImageIfNeeded` helper from `@selvajs/platform/storage` — same WebP output as Supabase.
120
+
121
+ ### Shared helpers
122
+
123
+ `src/fsJson.ts` centralizes the read/atomic-write pattern every store uses. See [src/README.md](src/README.md) for details on the helper API.
@@ -0,0 +1,28 @@
1
+ import type { IAuthProvider, IPasswordAuth, AuthUser, UserManagementResult, ListOptions, Page } from '@selvajs/platform';
2
+ export interface LocalAuthProviderConfig {
3
+ /** HMAC signing secret. Pass from env (SELVA_HMAC_KEY). */
4
+ hmacSecret: string;
5
+ /**
6
+ * Absolute path to auth-users.json — identity-only storage. Per-user app
7
+ * state (permissions, profile, starred defs, recent runs) lives in
8
+ * `user-data.json`, owned by `LocalDataProvider`. The two files key on
9
+ * the same user ID and are written independently.
10
+ */
11
+ usersFilePath?: string;
12
+ }
13
+ export declare class LocalAuthProvider implements IAuthProvider {
14
+ private readonly hmacSecret;
15
+ private readonly users?;
16
+ readonly name = "Local";
17
+ readonly passwordAuth: IPasswordAuth;
18
+ constructor(config: LocalAuthProviderConfig);
19
+ static fromEnv(env: Record<string, string | undefined>): LocalAuthProvider;
20
+ /** Verify an HMAC session token and return the live user record. */
21
+ verifyToken(token: string): Promise<AuthUser | null>;
22
+ touchLastLogin(id: string): Promise<void>;
23
+ getUser(id: string): Promise<AuthUser | null>;
24
+ listUsers(opts?: ListOptions): Promise<Page<AuthUser> | null>;
25
+ deleteUser(id: string): Promise<UserManagementResult>;
26
+ disableUser(id: string): Promise<UserManagementResult>;
27
+ }
28
+ //# sourceMappingURL=LocalAuthProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalAuthProvider.d.ts","sourceRoot":"","sources":["../../src/auth/LocalAuthProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EACb,aAAa,EACb,QAAQ,EAER,oBAAoB,EACpB,WAAW,EACX,IAAI,EACJ,MAAM,mBAAmB,CAAC;AAqB3B,MAAM,WAAW,uBAAuB;IACvC,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAuCD,qBAAa,iBAAkB,YAAW,aAAa;IACtD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAqB;IAE5C,QAAQ,CAAC,IAAI,WAAW;IACxB,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC;gBAEzB,MAAM,EAAE,uBAAuB;IAU3C,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,iBAAiB;IAS1E,oEAAoE;IAC9D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAUpD,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAM7C,SAAS,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IAM7D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAiBrD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAc5D"}
@@ -0,0 +1,142 @@
1
+ import * as path from 'node:path';
2
+ import { ProviderError } from '@selvajs/platform';
3
+ import { signHmacToken, verifyHmacToken } from './hmac.js';
4
+ import { verifyPasswordHash, createLocalAuthUserStore } from './users.js';
5
+ import { paginate } from '../data/pagination.js';
6
+ const SESSION_MAX_AGE_MS = 8 * 60 * 60 * 1000; // 8 hours
7
+ function toAuthUser(u) {
8
+ return {
9
+ id: u.id,
10
+ email: u.email,
11
+ createdAt: u.createdAt,
12
+ lastLoginAt: u.lastLoginAt,
13
+ disabled: u.disabled
14
+ };
15
+ }
16
+ class LocalPasswordAuth {
17
+ users;
18
+ mintToken;
19
+ constructor(users, mintToken) {
20
+ this.users = users;
21
+ this.mintToken = mintToken;
22
+ }
23
+ async verifyLogin(email, password) {
24
+ if (!this.users)
25
+ return { kind: 'failed', reason: 'invalid_credentials' };
26
+ const user = await this.users.findByEmail(email);
27
+ if (!user)
28
+ return { kind: 'failed', reason: 'invalid_credentials' };
29
+ if (user.disabled)
30
+ return { kind: 'failed', reason: 'disabled' };
31
+ if (!user.passwordHash || !(await verifyPasswordHash(password, user.passwordHash))) {
32
+ return { kind: 'failed', reason: 'invalid_credentials' };
33
+ }
34
+ await this.users.touchLastLogin(user.id).catch(() => { });
35
+ const auth = toAuthUser(user);
36
+ return { kind: 'success', user: auth, sessionToken: this.mintToken(auth) };
37
+ }
38
+ async createUserWithPassword(email, password) {
39
+ if (!this.users) {
40
+ throw new ProviderError('createUserWithPassword requires a users.json backend (DATA_PATH)', 500);
41
+ }
42
+ // Identity-only — platform permissions and the user-data row are seeded
43
+ // separately via `IDataProvider.ensureUser` + `IPlatformPermissionStore.set`.
44
+ return toAuthUser(await this.users.createUser(email, password));
45
+ }
46
+ async registerUser(email, password) {
47
+ if (!this.users)
48
+ return null;
49
+ return toAuthUser(await this.users.createUser(email, password));
50
+ }
51
+ }
52
+ export class LocalAuthProvider {
53
+ hmacSecret;
54
+ users;
55
+ name = 'Local';
56
+ passwordAuth;
57
+ constructor(config) {
58
+ this.hmacSecret = config.hmacSecret;
59
+ if (config.usersFilePath) {
60
+ this.users = createLocalAuthUserStore(config.usersFilePath);
61
+ }
62
+ this.passwordAuth = new LocalPasswordAuth(this.users, (user) => signHmacToken(this.hmacSecret, user.id, SESSION_MAX_AGE_MS));
63
+ }
64
+ static fromEnv(env) {
65
+ const hmacSecret = env.SELVA_HMAC_KEY;
66
+ if (!hmacSecret)
67
+ throw new Error('Missing required env var: SELVA_HMAC_KEY');
68
+ return new LocalAuthProvider({
69
+ hmacSecret,
70
+ usersFilePath: env.DATA_PATH ? path.join(env.DATA_PATH, 'auth-users.json') : undefined
71
+ });
72
+ }
73
+ /** Verify an HMAC session token and return the live user record. */
74
+ async verifyToken(token) {
75
+ const { valid, userId } = verifyHmacToken(token, this.hmacSecret);
76
+ if (!valid)
77
+ return null;
78
+ if (!this.users)
79
+ return null;
80
+ const u = await this.users.findById(userId);
81
+ if (!u || u.disabled)
82
+ return null;
83
+ await this.users.touchLastLogin(u.id).catch(() => { });
84
+ return toAuthUser(u);
85
+ }
86
+ async touchLastLogin(id) {
87
+ if (!this.users)
88
+ return;
89
+ await this.users.touchLastLogin(id);
90
+ }
91
+ async getUser(id) {
92
+ if (!this.users)
93
+ return null;
94
+ const u = await this.users.findById(id);
95
+ return u ? toAuthUser(u) : null;
96
+ }
97
+ async listUsers(opts) {
98
+ if (!this.users)
99
+ return null;
100
+ const users = await this.users.listUsers();
101
+ return paginate(users.map(toAuthUser), opts);
102
+ }
103
+ async deleteUser(id) {
104
+ // Identity-only delete. The §2 sole-`instance_admin` invariant is
105
+ // enforced by the caller via `IPlatformPermissionStore.countInstanceAdminsExcluding`
106
+ // before this method is called. The caller is also responsible for
107
+ // removing the user-data row via `LocalDataProvider`'s cascade hook.
108
+ if (!this.users)
109
+ return 'not_supported';
110
+ const target = await this.users.findById(id);
111
+ if (!target)
112
+ return 'not_found';
113
+ try {
114
+ await this.users.deleteUser(id);
115
+ return 'ok';
116
+ }
117
+ catch (err) {
118
+ if (err instanceof ProviderError && err.statusCode === 404)
119
+ return 'not_found';
120
+ throw err;
121
+ }
122
+ }
123
+ async disableUser(id) {
124
+ // Identity-only disable. The §2 sole-`instance_admin` invariant is
125
+ // enforced by the caller, same as `deleteUser`.
126
+ if (!this.users)
127
+ return 'not_supported';
128
+ const target = await this.users.findById(id);
129
+ if (!target)
130
+ return 'not_found';
131
+ try {
132
+ await this.users.setDisabled(id, true);
133
+ return 'ok';
134
+ }
135
+ catch (err) {
136
+ if (err instanceof ProviderError && err.statusCode === 404)
137
+ return 'not_found';
138
+ throw err;
139
+ }
140
+ }
141
+ }
142
+ //# sourceMappingURL=LocalAuthProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalAuthProvider.js","sourceRoot":"","sources":["../../src/auth/LocalAuthProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAUlC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE1E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,UAAU;AAEzD,SAAS,UAAU,CAClB,CAAkF;IAElF,OAAO;QACN,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,QAAQ,EAAE,CAAC,CAAC,QAAQ;KACpB,CAAC;AACH,CAAC;AAcD,MAAM,iBAAiB;IAEJ;IACA;IAFlB,YACkB,KAAqC,EACrC,SAAqC;QADrC,UAAK,GAAL,KAAK,CAAgC;QACrC,cAAS,GAAT,SAAS,CAA4B;IACpD,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,KAAa,EAAE,QAAgB;QAChD,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QAC1E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QACpE,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;QACjE,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,MAAM,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;YACpF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QAC1D,CAAC;QACD,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,KAAa,EAAE,QAAgB;QAC3D,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACjB,MAAM,IAAI,aAAa,CACtB,kEAAkE,EAClE,GAAG,CACH,CAAC;QACH,CAAC;QACD,wEAAwE;QACxE,8EAA8E;QAC9E,OAAO,UAAU,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAa,EAAE,QAAgB;QACjD,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAC7B,OAAO,UAAU,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE,CAAC;CACD;AAED,MAAM,OAAO,iBAAiB;IACZ,UAAU,CAAS;IACnB,KAAK,CAAsB;IAEnC,IAAI,GAAG,OAAO,CAAC;IACf,YAAY,CAAgB;IAErC,YAAY,MAA+B;QAC1C,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QACpC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,wBAAwB,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAC9D,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,kBAAkB,CAAC,CAC3D,CAAC;IACH,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,GAAuC;QACrD,MAAM,UAAU,GAAG,GAAG,CAAC,cAAc,CAAC;QACtC,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC7E,OAAO,IAAI,iBAAiB,CAAC;YAC5B,UAAU;YACV,aAAa,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS;SACtF,CAAC,CAAC;IACJ,CAAC;IAED,oEAAoE;IACpE,KAAK,CAAC,WAAW,CAAC,KAAa;QAC9B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAClE,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClC,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,EAAU;QAC9B,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO;QACxB,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU;QACvB,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACxC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAkB;QACjC,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QAC3C,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,EAAU;QAC1B,kEAAkE;QAClE,qFAAqF;QACrF,mEAAmE;QACnE,qEAAqE;QACrE,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,eAAe,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM;YAAE,OAAO,WAAW,CAAC;QAChC,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAChC,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,GAAG,YAAY,aAAa,IAAI,GAAG,CAAC,UAAU,KAAK,GAAG;gBAAE,OAAO,WAAW,CAAC;YAC/E,MAAM,GAAG,CAAC;QACX,CAAC;IACF,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,EAAU;QAC3B,mEAAmE;QACnE,gDAAgD;QAChD,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,eAAe,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM;YAAE,OAAO,WAAW,CAAC;QAChC,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACvC,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,GAAG,YAAY,aAAa,IAAI,GAAG,CAAC,UAAU,KAAK,GAAG;gBAAE,OAAO,WAAW,CAAC;YAC/E,MAAM,GAAG,CAAC;QACX,CAAC;IACF,CAAC;CACD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=conformance.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance.test.d.ts","sourceRoot":"","sources":["../../../src/auth/__tests__/conformance.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ import { describe, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'node:fs/promises';
3
+ import * as path from 'node:path';
4
+ import * as os from 'node:os';
5
+ import { runAuthProviderConformance } from '@selvajs/platform/testing';
6
+ import { LocalAuthProvider } from '../LocalAuthProvider.js';
7
+ const TEST_SECRET = 'test-hmac-secret-for-conformance';
8
+ const ADMIN_EMAIL = 'conformance-admin@example.com';
9
+ const ADMIN_PASSWORD = 'test-admin-password';
10
+ describe('LocalAuthProvider — auth-users.json mode', () => {
11
+ let tempDir;
12
+ beforeEach(async () => {
13
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'selva-auth-test-'));
14
+ });
15
+ afterEach(async () => {
16
+ await fs.rm(tempDir, { recursive: true, force: true });
17
+ });
18
+ runAuthProviderConformance({
19
+ name: 'LocalAuthProvider/auth-users-json',
20
+ createProvider: async () => {
21
+ const provider = new LocalAuthProvider({
22
+ hmacSecret: TEST_SECRET,
23
+ usersFilePath: path.join(tempDir, 'auth-users.json')
24
+ });
25
+ // Idempotent seed — the conformance suite calls createProvider per test,
26
+ // but a single test may invoke it more than once.
27
+ const existing = await provider.passwordAuth.verifyLogin(ADMIN_EMAIL, ADMIN_PASSWORD);
28
+ if (existing.kind !== 'success') {
29
+ await provider.passwordAuth.createUserWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
30
+ }
31
+ return { provider, adminEmail: ADMIN_EMAIL, adminPassword: ADMIN_PASSWORD };
32
+ },
33
+ userManagement: true
34
+ });
35
+ });
36
+ //# sourceMappingURL=conformance.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance.test.js","sourceRoot":"","sources":["../../../src/auth/__tests__/conformance.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,MAAM,WAAW,GAAG,kCAAkC,CAAC;AACvD,MAAM,WAAW,GAAG,+BAA+B,CAAC;AACpD,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAE7C,QAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACzD,IAAI,OAAe,CAAC;IAEpB,UAAU,CAAC,KAAK,IAAI,EAAE;QACrB,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,KAAK,IAAI,EAAE;QACpB,MAAM,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,0BAA0B,CAAC;QAC1B,IAAI,EAAE,mCAAmC;QACzC,cAAc,EAAE,KAAK,IAAI,EAAE;YAC1B,MAAM,QAAQ,GAAG,IAAI,iBAAiB,CAAC;gBACtC,UAAU,EAAE,WAAW;gBACvB,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC;aACpD,CAAC,CAAC;YACH,yEAAyE;YACzE,kDAAkD;YAClD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,WAAW,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;YACtF,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACjC,MAAM,QAAQ,CAAC,YAAY,CAAC,sBAAsB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;YACjF,CAAC;YACD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC;QAC7E,CAAC;QACD,cAAc,EAAE,IAAI;KACpB,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Token format: base64url(userId + ':' + expiry) + '.' + hmac(payload)
3
+ *
4
+ * The userId is embedded so verifyToken can look up the live user record
5
+ * (and therefore always reflect the current permissions, not stale ones
6
+ * baked into the token at login time).
7
+ */
8
+ export declare function signHmacToken(secret: string, userId: string, maxAgeMs?: number): string;
9
+ export interface HmacTokenPayload {
10
+ userId: string;
11
+ valid: boolean;
12
+ }
13
+ /**
14
+ * Verify an HMAC token. Returns the userId if valid and unexpired, or
15
+ * { valid: false } if the signature is wrong or the token has expired.
16
+ */
17
+ export declare function verifyHmacToken(token: string, secret: string): HmacTokenPayload;
18
+ //# sourceMappingURL=hmac.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hmac.d.ts","sourceRoot":"","sources":["../../src/auth/hmac.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC5B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,SAAqB,GAC3B,MAAM,CAKR;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAqB/E"}
@@ -0,0 +1,41 @@
1
+ import { timingSafeEqual, createHmac } from 'node:crypto';
2
+ const DEFAULT_MAX_AGE_MS = 8 * 60 * 60 * 1000; // 8 hours
3
+ /**
4
+ * Token format: base64url(userId + ':' + expiry) + '.' + hmac(payload)
5
+ *
6
+ * The userId is embedded so verifyToken can look up the live user record
7
+ * (and therefore always reflect the current permissions, not stale ones
8
+ * baked into the token at login time).
9
+ */
10
+ export function signHmacToken(secret, userId, maxAgeMs = DEFAULT_MAX_AGE_MS) {
11
+ const expiry = Date.now() + maxAgeMs;
12
+ const payload = Buffer.from(`${userId}:${expiry}`).toString('base64url');
13
+ const sig = createHmac('sha256', secret).update(payload).digest('base64url');
14
+ return `${payload}.${sig}`;
15
+ }
16
+ /**
17
+ * Verify an HMAC token. Returns the userId if valid and unexpired, or
18
+ * { valid: false } if the signature is wrong or the token has expired.
19
+ */
20
+ export function verifyHmacToken(token, secret) {
21
+ const dot = token.lastIndexOf('.');
22
+ if (dot === -1)
23
+ return { userId: '', valid: false };
24
+ const payload = token.slice(0, dot);
25
+ const sig = token.slice(dot + 1);
26
+ const expectedSig = createHmac('sha256', secret).update(payload).digest('base64url');
27
+ const a = Buffer.from(sig);
28
+ const b = Buffer.from(expectedSig);
29
+ if (a.length !== b.length || !timingSafeEqual(a, b))
30
+ return { userId: '', valid: false };
31
+ const decoded = Buffer.from(payload, 'base64url').toString();
32
+ const colon = decoded.lastIndexOf(':');
33
+ if (colon === -1)
34
+ return { userId: '', valid: false };
35
+ const userId = decoded.slice(0, colon);
36
+ const expiry = parseInt(decoded.slice(colon + 1), 10);
37
+ if (!Number.isFinite(expiry) || Date.now() >= expiry)
38
+ return { userId: '', valid: false };
39
+ return { userId, valid: true };
40
+ }
41
+ //# sourceMappingURL=hmac.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hmac.js","sourceRoot":"","sources":["../../src/auth/hmac.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,UAAU;AAEzD;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC5B,MAAc,EACd,MAAc,EACd,QAAQ,GAAG,kBAAkB;IAE7B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC;IACrC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACzE,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC7E,OAAO,GAAG,OAAO,IAAI,GAAG,EAAE,CAAC;AAC5B,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,MAAc;IAC5D,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAEpD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IACjC,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAErF,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACnC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAEzF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAEtD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,MAAM;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAE1F,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { LocalAuthProvider } from './LocalAuthProvider.js';
2
+ export type { LocalAuthProviderConfig } from './LocalAuthProvider.js';
3
+ export { signHmacToken, verifyHmacToken } from './hmac.js';
4
+ export { hashPassword, verifyPasswordHash, createLocalAuthUserStore } from './users.js';
5
+ export type { StoredAuthUser, AuthUsersFile, LocalAuthUserStore } from './users.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AACxF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { LocalAuthProvider } from './LocalAuthProvider.js';
2
+ export { signHmacToken, verifyHmacToken } from './hmac.js';
3
+ export { hashPassword, verifyPasswordHash, createLocalAuthUserStore } from './users.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Identity-only on-disk shape. Per-user app state (permissions, profile,
3
+ * starred definitions, recent runs) lives in `user-data.json`, owned by
4
+ * `LocalDataProvider`. This split lets `LocalAuthProvider` be paired with any
5
+ * data provider, and any auth provider be paired with `LocalDataProvider`,
6
+ * without one stepping on the other.
7
+ */
8
+ export interface StoredAuthUser {
9
+ id: string;
10
+ email: string;
11
+ /**
12
+ * "pbkdf2:sha256:<iterations>:<salt>:<hash>" — all binary values base64url encoded.
13
+ * Null for OAuth-only users (allowlisted email, no password stored).
14
+ */
15
+ passwordHash: string | null;
16
+ createdAt: string;
17
+ /** ISO 8601 — most recent successful credential login or token verification. */
18
+ lastLoginAt?: string;
19
+ /** When true, the provider MUST refuse to authenticate this user. */
20
+ disabled?: boolean;
21
+ }
22
+ export interface AuthUsersFile {
23
+ users: StoredAuthUser[];
24
+ }
25
+ export declare function hashPassword(password: string): Promise<string>;
26
+ export declare function verifyPasswordHash(password: string, storedHash: string): Promise<boolean>;
27
+ export interface LocalAuthUserStore {
28
+ findByEmail(email: string): Promise<StoredAuthUser | null>;
29
+ findById(id: string): Promise<StoredAuthUser | null>;
30
+ listUsers(): Promise<Omit<StoredAuthUser, 'passwordHash'>[]>;
31
+ /** password null = OAuth allowlist entry (no password stored) */
32
+ createUser(email: string, password: string | null): Promise<StoredAuthUser>;
33
+ setDisabled(id: string, disabled: boolean): Promise<void>;
34
+ touchLastLogin(id: string): Promise<void>;
35
+ deleteUser(id: string): Promise<void>;
36
+ }
37
+ export declare function createLocalAuthUserStore(usersFilePath: string): LocalAuthUserStore;
38
+ //# sourceMappingURL=users.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/auth/users.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAKD,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,cAAc,EAAE,CAAC;CACxB;AASD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQpE;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB/F;AAUD,MAAM,WAAW,kBAAkB;IAClC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC3D,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACrD,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC;IAC7D,iEAAiE;IACjE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC5E,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAED,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,MAAM,GAAG,kBAAkB,CA8DlF"}
@@ -0,0 +1,100 @@
1
+ import * as crypto from 'node:crypto';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { ProviderError } from '@selvajs/platform';
4
+ import { readJsonFile, writeJsonFile } from '../data/fsJson.js';
5
+ /** Debounce window for lastLoginAt writes — skip if prior stamp is newer than this. */
6
+ const LAST_LOGIN_DEBOUNCE_MS = 60_000;
7
+ // ============================================================================
8
+ // PBKDF2 password hashing
9
+ // ============================================================================
10
+ const PBKDF2_ITERATIONS = 100_000;
11
+ const PBKDF2_KEYLEN = 32;
12
+ const PBKDF2_DIGEST = 'sha256';
13
+ export async function hashPassword(password) {
14
+ const salt = crypto.randomBytes(16).toString('base64url');
15
+ const hash = await new Promise((resolve, reject) => crypto.pbkdf2(password, salt, PBKDF2_ITERATIONS, PBKDF2_KEYLEN, PBKDF2_DIGEST, (err, key) => err ? reject(err) : resolve(key)));
16
+ return `pbkdf2:${PBKDF2_DIGEST}:${PBKDF2_ITERATIONS}:${salt}:${hash.toString('base64url')}`;
17
+ }
18
+ export async function verifyPasswordHash(password, storedHash) {
19
+ const parts = storedHash.split(':');
20
+ if (parts.length !== 5 || parts[0] !== 'pbkdf2')
21
+ return false;
22
+ const [, digest, iterStr, salt, expectedHashB64] = parts;
23
+ const iterations = parseInt(iterStr, 10);
24
+ if (!Number.isFinite(iterations) || iterations <= 0)
25
+ return false;
26
+ const expected = Buffer.from(expectedHashB64, 'base64url');
27
+ const actual = await new Promise((resolve, reject) => crypto.pbkdf2(password, salt, iterations, PBKDF2_KEYLEN, digest, (err, key) => err ? reject(err) : resolve(key)));
28
+ if (actual.length !== expected.length)
29
+ return false;
30
+ return crypto.timingSafeEqual(actual, expected);
31
+ }
32
+ // ============================================================================
33
+ // CRUD
34
+ // ============================================================================
35
+ // Fresh object per call — `readJsonFile` returns its fallback by reference
36
+ // when the file is missing, so a shared singleton would let one test (or
37
+ // one process write) mutate state visible to the next read.
38
+ const empty = () => ({ users: [] });
39
+ export function createLocalAuthUserStore(usersFilePath) {
40
+ return {
41
+ async findByEmail(email) {
42
+ const { users } = await readJsonFile(usersFilePath, empty());
43
+ return users.find((u) => u.email.toLowerCase() === email.toLowerCase()) ?? null;
44
+ },
45
+ async findById(id) {
46
+ const { users } = await readJsonFile(usersFilePath, empty());
47
+ return users.find((u) => u.id === id) ?? null;
48
+ },
49
+ async listUsers() {
50
+ const { users } = await readJsonFile(usersFilePath, empty());
51
+ return users.map(({ passwordHash: _ph, ...rest }) => rest);
52
+ },
53
+ async createUser(email, password) {
54
+ const file = await readJsonFile(usersFilePath, empty());
55
+ if (file.users.some((u) => u.email.toLowerCase() === email.toLowerCase())) {
56
+ throw new ProviderError(`User with email "${email}" already exists`, 409);
57
+ }
58
+ const user = {
59
+ id: randomUUID(),
60
+ email,
61
+ passwordHash: password !== null ? await hashPassword(password) : null,
62
+ createdAt: new Date().toISOString()
63
+ };
64
+ file.users.push(user);
65
+ await writeJsonFile(usersFilePath, file);
66
+ return user;
67
+ },
68
+ async setDisabled(id, disabled) {
69
+ const file = await readJsonFile(usersFilePath, empty());
70
+ const user = file.users.find((u) => u.id === id);
71
+ if (!user)
72
+ throw new ProviderError(`User "${id}" not found`, 404);
73
+ user.disabled = disabled;
74
+ await writeJsonFile(usersFilePath, file);
75
+ },
76
+ async touchLastLogin(id) {
77
+ const file = await readJsonFile(usersFilePath, empty());
78
+ const user = file.users.find((u) => u.id === id);
79
+ if (!user)
80
+ return;
81
+ const now = Date.now();
82
+ if (user.lastLoginAt) {
83
+ const prev = Date.parse(user.lastLoginAt);
84
+ if (Number.isFinite(prev) && now - prev < LAST_LOGIN_DEBOUNCE_MS)
85
+ return;
86
+ }
87
+ user.lastLoginAt = new Date(now).toISOString();
88
+ await writeJsonFile(usersFilePath, file);
89
+ },
90
+ async deleteUser(id) {
91
+ const file = await readJsonFile(usersFilePath, empty());
92
+ const before = file.users.length;
93
+ file.users = file.users.filter((u) => u.id !== id);
94
+ if (file.users.length === before)
95
+ throw new ProviderError(`User "${id}" not found`, 404);
96
+ await writeJsonFile(usersFilePath, file);
97
+ }
98
+ };
99
+ }
100
+ //# sourceMappingURL=users.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/auth/users.ts"],"names":[],"mappings":"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"}