@wenathlan/saddle 2.0.154 → 2.0.159

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 (314) hide show
  1. package/Dockerfile +167 -1334
  2. package/README.md +59 -60
  3. package/biome.jsonc +3 -3
  4. package/browser.ts +482 -0
  5. package/cli.ts +274 -0
  6. package/distribution.ts +731 -0
  7. package/docker.config +29 -8
  8. package/docs/{architecture.1.8.19.mmd → architecture1819.md} +6 -0
  9. package/docs/artifactavailability.md +2 -2
  10. package/docs/browser.md +18 -1
  11. package/docs/{containerplatforms.2.1.0.md → containerplatforms210.md} +1 -1
  12. package/docs/{documentmap.1.8.12.md → documentmap1812.md} +1 -1
  13. package/docs/extension.md +13 -2
  14. package/docs/features.md +5 -0
  15. package/docs/libraryapi.md +1 -1
  16. package/docs/{ninety.modalities.md → ninetymodalities.md} +87 -4
  17. package/docs/{planning.ninety.md → planningninety.md} +6 -6
  18. package/docs/{releasenotes.1.8.13.md → releasenotes1813.md} +2 -2
  19. package/docs/{releasenotes.1.8.14.md → releasenotes1814.md} +1 -1
  20. package/docs/{repo.synthesis.1.8.16.md → reposynthesis1816.md} +1 -1
  21. package/docs/todo.md +2 -2
  22. package/docs/{todo.1.8.15.md → todo1815.md} +1 -1
  23. package/docs/{todo.1.8.16.md → todo1816.md} +5 -5
  24. package/execution.ts +677 -0
  25. package/foundation.ts +314 -0
  26. package/index.ts +36 -34
  27. package/isolation.ts +84 -0
  28. package/modalityregistry.ts +189 -145
  29. package/modes.ts +170 -0
  30. package/mttg.config +1 -1
  31. package/orchestrator.ts +49 -15
  32. package/package.json +105 -89
  33. package/qemu.config +2 -2
  34. package/sandbox.ts +3279 -0
  35. package/scheduler.ts +1 -1
  36. package/server.ts +52 -0
  37. package/tsconfig.json +12 -13
  38. package/virtual.ts +7528 -0
  39. package/workloadlaw.ts +293 -0
  40. package/dist/App.d.ts +0 -5
  41. package/dist/App.js +0 -52
  42. package/dist/Sol/Sol.d.ts +0 -3
  43. package/dist/Sol/Sol.js +0 -46
  44. package/dist/Sol/agentbrowser/agentbrowser.d.ts +0 -9
  45. package/dist/Sol/agentbrowser/agentbrowser.js +0 -121
  46. package/dist/Sol/architecture/architecture.d.ts +0 -9
  47. package/dist/Sol/architecture/architecture.js +0 -93
  48. package/dist/Sol/compute/compute.d.ts +0 -9
  49. package/dist/Sol/compute/compute.js +0 -22
  50. package/dist/Sol/console/console.d.ts +0 -15
  51. package/dist/Sol/console/console.js +0 -292
  52. package/dist/Sol/dashboard/dashboard.d.ts +0 -10
  53. package/dist/Sol/dashboard/dashboard.js +0 -181
  54. package/dist/Sol/docs/docs.d.ts +0 -9
  55. package/dist/Sol/docs/docs.js +0 -28
  56. package/dist/Sol/home/home.d.ts +0 -35
  57. package/dist/Sol/home/home.js +0 -239
  58. package/dist/Sol/integrations/integrations.d.ts +0 -9
  59. package/dist/Sol/integrations/integrations.js +0 -83
  60. package/dist/Sol/intro/intro.d.ts +0 -7
  61. package/dist/Sol/intro/intro.js +0 -102
  62. package/dist/Sol/login/login.d.ts +0 -16
  63. package/dist/Sol/login/login.js +0 -49
  64. package/dist/Sol/notfound/notfound.d.ts +0 -9
  65. package/dist/Sol/notfound/notfound.js +0 -15
  66. package/dist/Sol/onboarding/onboarding.d.ts +0 -25
  67. package/dist/Sol/onboarding/onboarding.js +0 -60
  68. package/dist/Sol/playground/playground.d.ts +0 -9
  69. package/dist/Sol/playground/playground.js +0 -127
  70. package/dist/Sol/register/register.d.ts +0 -16
  71. package/dist/Sol/register/register.js +0 -193
  72. package/dist/Sol/shell/Shell.d.ts +0 -124
  73. package/dist/Sol/shell/Shell.js +0 -335
  74. package/dist/Sol/wingets/ComputeBootladder.d.ts +0 -6
  75. package/dist/Sol/wingets/ComputeBootladder.js +0 -71
  76. package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +0 -7
  77. package/dist/Sol/wingets/ComputeModalitycatalog.js +0 -71
  78. package/dist/Sol/wingets/ConsoleApibadge.d.ts +0 -7
  79. package/dist/Sol/wingets/ConsoleApibadge.js +0 -5
  80. package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +0 -6
  81. package/dist/Sol/wingets/ConsoleEdgelaw.js +0 -36
  82. package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +0 -19
  83. package/dist/Sol/wingets/ConsoleEventstimeline.js +0 -12
  84. package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +0 -8
  85. package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +0 -8
  86. package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +0 -21
  87. package/dist/Sol/wingets/ConsoleSpecpanel.js +0 -75
  88. package/dist/Sol/wingets/ConsoleTerminal.d.ts +0 -22
  89. package/dist/Sol/wingets/ConsoleTerminal.js +0 -77
  90. package/dist/Sol/wingets/DashboardAccountpanel.d.ts +0 -18
  91. package/dist/Sol/wingets/DashboardAccountpanel.js +0 -65
  92. package/dist/Sol/wingets/DashboardAdminpanel.d.ts +0 -6
  93. package/dist/Sol/wingets/DashboardAdminpanel.js +0 -90
  94. package/dist/Sol/wingets/DashboardAuditlog.d.ts +0 -8
  95. package/dist/Sol/wingets/DashboardAuditlog.js +0 -50
  96. package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +0 -10
  97. package/dist/Sol/wingets/DashboardCreatesandboxform.js +0 -71
  98. package/dist/Sol/wingets/DashboardEventstimeline.d.ts +0 -8
  99. package/dist/Sol/wingets/DashboardEventstimeline.js +0 -83
  100. package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +0 -8
  101. package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +0 -57
  102. package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +0 -8
  103. package/dist/Sol/wingets/DashboardMeshnodestable.js +0 -103
  104. package/dist/Sol/wingets/DashboardSandboxlist.d.ts +0 -10
  105. package/dist/Sol/wingets/DashboardSandboxlist.js +0 -163
  106. package/dist/Sol/wingets/DashboardUserstable.d.ts +0 -8
  107. package/dist/Sol/wingets/DashboardUserstable.js +0 -78
  108. package/dist/Sol/wingets/HomeEntrygate.d.ts +0 -23
  109. package/dist/Sol/wingets/HomeEntrygate.js +0 -73
  110. package/dist/Sol/wingets/HomeMetricstrip.d.ts +0 -9
  111. package/dist/Sol/wingets/HomeMetricstrip.js +0 -4
  112. package/dist/Sol/wingets/HomeRuntimediagram.d.ts +0 -1
  113. package/dist/Sol/wingets/HomeRuntimediagram.js +0 -22
  114. package/dist/Sol/wingets/LoginForm.d.ts +0 -19
  115. package/dist/Sol/wingets/LoginForm.js +0 -141
  116. package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +0 -13
  117. package/dist/Sol/wingets/RegisterPasswordconfirm.js +0 -16
  118. package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +0 -19
  119. package/dist/Sol/wingets/RegisterPasswordmeter.js +0 -65
  120. package/dist/Sol/wingets/RegisterUsernamerules.d.ts +0 -11
  121. package/dist/Sol/wingets/RegisterUsernamerules.js +0 -16
  122. package/dist/Sol/wingets/index.d.ts +0 -39
  123. package/dist/Sol/wingets/index.js +0 -39
  124. package/dist/alternatives.d.ts +0 -508
  125. package/dist/alternatives.js +0 -950
  126. package/dist/api.d.ts +0 -154
  127. package/dist/api.js +0 -174
  128. package/dist/auth.d.ts +0 -268
  129. package/dist/auth.js +0 -476
  130. package/dist/automation.d.ts +0 -425
  131. package/dist/automation.js +0 -566
  132. package/dist/browser.d.ts +0 -561
  133. package/dist/browser.js +0 -1058
  134. package/dist/capacitor.config.d.ts +0 -45
  135. package/dist/capacitor.config.js +0 -48
  136. package/dist/catalog.d.ts +0 -43
  137. package/dist/catalog.js +0 -44
  138. package/dist/cli.d.ts +0 -16
  139. package/dist/cli.js +0 -168
  140. package/dist/communication.d.ts +0 -181
  141. package/dist/communication.js +0 -376
  142. package/dist/compute.d.ts +0 -1160
  143. package/dist/compute.js +0 -2272
  144. package/dist/dashboardlib.d.ts +0 -58
  145. package/dist/dashboardlib.js +0 -161
  146. package/dist/db.d.ts +0 -569
  147. package/dist/db.js +0 -1080
  148. package/dist/distribution.d.ts +0 -528
  149. package/dist/distribution.js +0 -810
  150. package/dist/drizzle.config.d.ts +0 -20
  151. package/dist/drizzle.config.js +0 -18
  152. package/dist/edgelandes.d.ts +0 -70
  153. package/dist/edgelandes.host.d.ts +0 -26
  154. package/dist/edgelandes.host.js +0 -1089
  155. package/dist/edgelandes.inproc.d.ts +0 -27
  156. package/dist/edgelandes.inproc.js +0 -952
  157. package/dist/edgelandes.js +0 -1133
  158. package/dist/edgelandes.ninety.d.ts +0 -31
  159. package/dist/edgelandes.ninety.js +0 -174
  160. package/dist/edgelandes.virtual.d.ts +0 -25
  161. package/dist/edgelandes.virtual.js +0 -629
  162. package/dist/edgeplane.d.ts +0 -121
  163. package/dist/edgeplane.js +0 -341
  164. package/dist/examplelocaljob.d.ts +0 -1
  165. package/dist/examplelocaljob.js +0 -14
  166. package/dist/execution.d.ts +0 -529
  167. package/dist/execution.js +0 -760
  168. package/dist/family.d.ts +0 -16
  169. package/dist/family.js +0 -42
  170. package/dist/familyurl.d.ts +0 -21
  171. package/dist/familyurl.js +0 -24
  172. package/dist/format.d.ts +0 -18
  173. package/dist/format.js +0 -59
  174. package/dist/foundation.d.ts +0 -172
  175. package/dist/foundation.js +0 -312
  176. package/dist/index.d.ts +0 -735
  177. package/dist/index.js +0 -1020
  178. package/dist/integration.d.ts +0 -235
  179. package/dist/integration.js +0 -228
  180. package/dist/intelligence.d.ts +0 -93
  181. package/dist/intelligence.js +0 -144
  182. package/dist/isolation.d.ts +0 -128
  183. package/dist/isolation.js +0 -95
  184. package/dist/labels.d.ts +0 -665
  185. package/dist/labels.js +0 -707
  186. package/dist/localauth.d.ts +0 -127
  187. package/dist/localauth.js +0 -413
  188. package/dist/media.d.ts +0 -343
  189. package/dist/media.js +0 -985
  190. package/dist/mesh.d.ts +0 -177
  191. package/dist/mesh.js +0 -468
  192. package/dist/modalityregistry.d.ts +0 -109
  193. package/dist/modalityregistry.js +0 -1386
  194. package/dist/modes.d.ts +0 -358
  195. package/dist/modes.js +0 -133
  196. package/dist/onboardingstate.d.ts +0 -79
  197. package/dist/onboardingstate.js +0 -282
  198. package/dist/orchestrator.d.ts +0 -1338
  199. package/dist/orchestrator.js +0 -4310
  200. package/dist/packager/targetcli.js +0 -4
  201. package/dist/paths.d.ts +0 -2
  202. package/dist/paths.js +0 -14
  203. package/dist/performance.d.ts +0 -415
  204. package/dist/performance.js +0 -826
  205. package/dist/quantum.d.ts +0 -809
  206. package/dist/quantum.js +0 -2072
  207. package/dist/render.d.ts +0 -380
  208. package/dist/render.js +0 -1072
  209. package/dist/runall.d.ts +0 -2
  210. package/dist/runall.js +0 -39
  211. package/dist/runtimeprobe.d.ts +0 -4
  212. package/dist/runtimeprobe.js +0 -8
  213. package/dist/sandbox.d.ts +0 -355
  214. package/dist/sandbox.js +0 -1721
  215. package/dist/sandboxenvprofile.d.ts +0 -29
  216. package/dist/sandboxenvprofile.js +0 -89
  217. package/dist/sandboxprofile.d.ts +0 -137
  218. package/dist/sandboxprofile.js +0 -241
  219. package/dist/sandboxqueue.d.ts +0 -108
  220. package/dist/sandboxqueue.js +0 -201
  221. package/dist/sandboxquota.d.ts +0 -121
  222. package/dist/sandboxquota.js +0 -236
  223. package/dist/scheduler.d.ts +0 -595
  224. package/dist/scheduler.js +0 -1258
  225. package/dist/scriptlanes.d.ts +0 -182
  226. package/dist/scriptlanes.js +0 -660
  227. package/dist/scripts.buildengine.d.ts +0 -1
  228. package/dist/scripts.buildengine.js +0 -39
  229. package/dist/scripts.securitygate.d.ts +0 -1
  230. package/dist/scripts.securitygate.js +0 -68
  231. package/dist/scripts.validateflatnative.d.ts +0 -1
  232. package/dist/scripts.validateflatnative.js +0 -72
  233. package/dist/security.d.ts +0 -500
  234. package/dist/security.js +0 -1169
  235. package/dist/seedcatalog.d.ts +0 -13
  236. package/dist/seedcatalog.js +0 -46
  237. package/dist/server.d.ts +0 -20
  238. package/dist/server.js +0 -53
  239. package/dist/speccatalog.d.ts +0 -31
  240. package/dist/speccatalog.js +0 -163
  241. package/dist/tiers.d.ts +0 -1001
  242. package/dist/tiers.js +0 -2169
  243. package/dist/utils.d.ts +0 -2
  244. package/dist/utils.js +0 -5
  245. package/dist/vaulthandoff.d.ts +0 -141
  246. package/dist/vaulthandoff.js +0 -444
  247. package/dist/virtual.d.ts +0 -640
  248. package/dist/virtual.js +0 -1284
  249. package/dist/virtualcpu.d.ts +0 -315
  250. package/dist/virtualcpu.js +0 -1180
  251. package/dist/virtualgpu.d.ts +0 -344
  252. package/dist/virtualgpu.js +0 -1000
  253. package/dist/virtualization.d.ts +0 -606
  254. package/dist/virtualization.js +0 -1186
  255. package/dist/virtualmemory.d.ts +0 -817
  256. package/dist/virtualmemory.js +0 -1795
  257. package/dist/vite.config.d.ts +0 -3
  258. package/dist/vite.config.js +0 -48
  259. package/dist/vitest.config.d.ts +0 -2
  260. package/dist/vitest.config.js +0 -25
  261. package/dist/webserver.d.ts +0 -166
  262. package/dist/webserver.js +0 -2226
  263. package/dist/workloadlaw.d.ts +0 -133
  264. package/dist/workloadlaw.js +0 -193
  265. package/docs/.gitkeep +0 -0
  266. package/docs/architecture.1.8.19.md +0 -76
  267. package/docs/logs/examplesession.json +0 -54
  268. package/docs/repo.analysis.1.8.21.json +0 -2617
  269. package/docs/repo.candidates.1.8.21.jsonl +0 -666
  270. package/docs/repo.selected.1.8.21.json +0 -786
  271. package/manifest.json +0 -20
  272. package/media.ts +0 -1337
  273. package/virtualcpu.ts +0 -1274
  274. package/virtualgpu.ts +0 -1182
  275. package/virtualization.ts +0 -1628
  276. package/virtualmemory.ts +0 -2303
  277. /package/docs/{artifactresearch.1.8.9.md → artifactresearch189.md} +0 -0
  278. /package/docs/{batches.1816.to.1820.md → batches1816to1820.md} +0 -0
  279. /package/docs/{brancharchive.2026.08.18.md → brancharchive20260818.md} +0 -0
  280. /package/docs/{containerplatforms.1.8.17.md → containerplatforms1817.md} +0 -0
  281. /package/docs/{e2ugh.engine.md → e2ughengine.md} +0 -0
  282. /package/docs/{flatbuildresearch.1.8.11.md → flatbuildresearch1811.md} +0 -0
  283. /package/docs/{licenseaudit.1.8.12.md → licenseaudit1812.md} +0 -0
  284. /package/docs/{mobileconversionresearch.1.8.10.md → mobileconversionresearch1810.md} +0 -0
  285. /package/docs/{nativeidentityresearch.1.8.11.md → nativeidentityresearch1811.md} +0 -0
  286. /package/docs/{native.wrappers.md → nativewrappers.md} +0 -0
  287. /package/docs/{planning.1.8.18.md → planning1818.md} +0 -0
  288. /package/docs/{planning.1.8.19.md → planning1819.md} +0 -0
  289. /package/docs/{planning.engine.md → planningengine.md} +0 -0
  290. /package/docs/{releasenotes.1.8.12.md → releasenotes1812.md} +0 -0
  291. /package/docs/{releasenotes.1.8.15.md → releasenotes1815.md} +0 -0
  292. /package/docs/{releasenotes.1.8.16.md → releasenotes1816.md} +0 -0
  293. /package/docs/{releasenotes.1.8.17.md → releasenotes1817.md} +0 -0
  294. /package/docs/{releasenotes.1.8.18.md → releasenotes1818.md} +0 -0
  295. /package/docs/{releasenotes.1.8.19.md → releasenotes1819.md} +0 -0
  296. /package/docs/{reorganization.1.8.8.md → reorganization188.md} +0 -0
  297. /package/docs/{reorganization.1.8.9.md → reorganization189.md} +0 -0
  298. /package/docs/{repo.research.1.8.16.md → reporesearch1816.md} +0 -0
  299. /package/docs/{repo.research.1.8.21.md → reporesearch1821.md} +0 -0
  300. /package/docs/{repo.synthesis.1.8.21.md → reposynthesis1821.md} +0 -0
  301. /package/docs/{research.1.8.18.isolation.md → research1818isolation.md} +0 -0
  302. /package/docs/{research.1.8.19.virtual.browser.md → research1819virtualbrowser.md} +0 -0
  303. /package/docs/{scanningresearch.1.8.11.md → scanningresearch1811.md} +0 -0
  304. /package/docs/{securityaudit.1.8.7.md → securityaudit187.md} +0 -0
  305. /package/docs/{securityreferences.1.8.11.md → securityreferences1811.md} +0 -0
  306. /package/docs/{securityresearch.1.8.11.md → securityresearch1811.md} +0 -0
  307. /package/docs/{signingresearch.1.8.12.md → signingresearch1812.md} +0 -0
  308. /package/docs/{tauri.glib.risk.1.8.11.md → tauriglibrisk1811.md} +0 -0
  309. /package/docs/{todo.1.8.18.md → todo1818.md} +0 -0
  310. /package/docs/{todo.1.8.19.md → todo1819.md} +0 -0
  311. /package/docs/{toolchainresearch.1.8.9.md → toolchainresearch189.md} +0 -0
  312. /package/docs/{web.duplicatecleanup.md → webduplicatecleanup.md} +0 -0
  313. /package/docs/{web.ideas.md → webideas.md} +0 -0
  314. /package/docs/{workflowimprovements.2026.08.19.md → workflowimprovements20260819.md} +0 -0
@@ -1,127 +0,0 @@
1
- /**
2
- * browser-local account fallback for the static edge (github pages,
3
- * netlify, vercel clones, file://): when no api backend answers at the
4
- * resolved base, the auth pages switch to local browser accounts so the
5
- * full interface still works on hosting without a server. local
6
- * accounts live ONLY in this browser (localstorage, pbkdf2-hashed
7
- * passwords through webcrypto); they never sync to the main node and
8
- * never hold real authority - the self-hosted node keeps the real
9
- * scrypt/cookie sessions (see the saddle readme, static-first design).
10
- *
11
- * typed ES module conversion of web/localauth.js (the window-global
12
- * IIFE): same storage keys, same seed, same keyfile format, same
13
- * returns. the module is shared by the Login, Register and Dashboard
14
- * pages and exposes:
15
- * probe(base) - true when the api answers
16
- * available() - local accounts exist
17
- * register(u, p) - creates a local account (throws on conflict)
18
- * login(u, p) - verifies and opens a local session
19
- * session() - the open local session or null
20
- * logout() - closes the local session
21
- * exportaccounts() - keyfile backup download
22
- * importaccounts(file) - keyfile restore (merge)
23
- * roleof(u) - "admin" only for the CODEOWNERS names
24
- *
25
- * built-in admins: the CODEOWNERS accounts (iakadion, inathlan,
26
- * aasblor, nasblor) are seeded as local admin accounts when the
27
- * bootstrap seed rides the build env (VITE_SADDLE_ADMIN_SEED_PASSWORD).
28
- * the public repository carries no hardcoded credential — the seed
29
- * password is a pipeline secret injected at build time, and when the
30
- * env is absent the seed is skipped (locally created accounts still
31
- * work; only the CODEOWNERS accounts carry the admin role anywhere).
32
- * clearing the browser storage never locks the interface out: the seed
33
- * is re-applied whenever the env is present and the store is missing
34
- * the admin entries, so "the account never comes back" cannot happen
35
- * on the static edge (vercel / netlify / github pages).
36
- */
37
- /** role of a local account: "admin" only for the CODEOWNERS names. */
38
- export type LocalAccountRole = 'admin' | 'user';
39
- /** one stored local account (salt + hash, never the plaintext). */
40
- export type LocalAccountRecord = {
41
- salt: string;
42
- hash: string;
43
- iterations: number;
44
- algorithm: 'pbkdf2-sha256';
45
- createdat: string;
46
- role: LocalAccountRole;
47
- seeded?: boolean;
48
- };
49
- /** the open local session (12h lifetime, localstorage only). */
50
- export type LocalSession = {
51
- user: string;
52
- mode: 'local';
53
- role: LocalAccountRole;
54
- issuedat: number;
55
- expiresat: number;
56
- };
57
- /** the backup keyfile payload produced by exportaccounts. */
58
- export type AccountsBackupPayload = {
59
- format: 'saddle-local-accounts/1';
60
- exportedat: string;
61
- accounts: Record<string, LocalAccountRecord>;
62
- };
63
- /** the exported admin allowlist (a defensive copy). */
64
- export declare const adminusers: readonly string[];
65
- /**
66
- * exports every account (salt + hash only, never plaintext passwords)
67
- * as a downloadable backup keyfile.
68
- *
69
- * @returns the number of exported accounts.
70
- */
71
- export declare function exportaccounts(): Promise<number>;
72
- /**
73
- * imports accounts from a backup keyfile (merges; the seed and
74
- * operator-owned entries are never overwritten).
75
- *
76
- * @param file the keyfile chosen by the operator.
77
- * @returns the number of imported accounts.
78
- * @throws when the file is not a saddle accounts backup.
79
- */
80
- export declare function importaccounts(file: File): Promise<number>;
81
- /**
82
- * probes the api health endpoint with a short timeout.
83
- *
84
- * @param base the api base to probe ("" means same-origin).
85
- * @returns true when the api answers ok.
86
- */
87
- export declare function probe(base: string): Promise<boolean>;
88
- /**
89
- * creates a local browser account and opens the local session.
90
- *
91
- * @param username the account name (validated by the caller).
92
- * @param password the plaintext password (hashed before storage).
93
- * @returns true on success.
94
- * @throws when the username already exists on this browser.
95
- */
96
- export declare function register(username: string, password: string): Promise<boolean>;
97
- /**
98
- * verifies the password against the stored account and opens the
99
- * local session.
100
- *
101
- * @param username the account name.
102
- * @param password the plaintext password.
103
- * @returns true on success.
104
- * @throws when the account is missing or the password is wrong.
105
- */
106
- export declare function login(username: string, password: string): Promise<boolean>;
107
- /**
108
- * reads the open local session.
109
- *
110
- * @returns the session when valid and unexpired, null otherwise.
111
- */
112
- export declare function session(): LocalSession | null;
113
- /** closes the local session. */
114
- export declare function logout(): void;
115
- /**
116
- * checks whether local accounts exist on this browser.
117
- *
118
- * @returns true when at least one account is stored.
119
- */
120
- export declare function available(): boolean;
121
- /**
122
- * the role of an account: "admin" only for the CODEOWNERS names.
123
- *
124
- * @param username the account name.
125
- * @returns "admin" or "user".
126
- */
127
- export declare function roleof(username: string): LocalAccountRole;
package/dist/localauth.js DELETED
@@ -1,413 +0,0 @@
1
- /**
2
- * browser-local account fallback for the static edge (github pages,
3
- * netlify, vercel clones, file://): when no api backend answers at the
4
- * resolved base, the auth pages switch to local browser accounts so the
5
- * full interface still works on hosting without a server. local
6
- * accounts live ONLY in this browser (localstorage, pbkdf2-hashed
7
- * passwords through webcrypto); they never sync to the main node and
8
- * never hold real authority - the self-hosted node keeps the real
9
- * scrypt/cookie sessions (see the saddle readme, static-first design).
10
- *
11
- * typed ES module conversion of web/localauth.js (the window-global
12
- * IIFE): same storage keys, same seed, same keyfile format, same
13
- * returns. the module is shared by the Login, Register and Dashboard
14
- * pages and exposes:
15
- * probe(base) - true when the api answers
16
- * available() - local accounts exist
17
- * register(u, p) - creates a local account (throws on conflict)
18
- * login(u, p) - verifies and opens a local session
19
- * session() - the open local session or null
20
- * logout() - closes the local session
21
- * exportaccounts() - keyfile backup download
22
- * importaccounts(file) - keyfile restore (merge)
23
- * roleof(u) - "admin" only for the CODEOWNERS names
24
- *
25
- * built-in admins: the CODEOWNERS accounts (iakadion, inathlan,
26
- * aasblor, nasblor) are seeded as local admin accounts when the
27
- * bootstrap seed rides the build env (VITE_SADDLE_ADMIN_SEED_PASSWORD).
28
- * the public repository carries no hardcoded credential — the seed
29
- * password is a pipeline secret injected at build time, and when the
30
- * env is absent the seed is skipped (locally created accounts still
31
- * work; only the CODEOWNERS accounts carry the admin role anywhere).
32
- * clearing the browser storage never locks the interface out: the seed
33
- * is re-applied whenever the env is present and the store is missing
34
- * the admin entries, so "the account never comes back" cannot happen
35
- * on the static edge (vercel / netlify / github pages).
36
- */
37
- /** localstorage key carrying the local account map. */
38
- const userskey = 'saddle_local_users';
39
- /** localstorage key carrying the open local session. */
40
- const sessionkey = 'saddle_local_session';
41
- /** pbkdf2 iteration count, kept in lockstep with the stored records. */
42
- const iterations = 150000;
43
- /** IndexedDB mirror coordinates (database / object store names). */
44
- const idbname = 'saddle-auth';
45
- const idbstore = 'accounts';
46
- /** the ONLY admin usernames: the CODEOWNERS list. mirror changes to
47
- * .github CODEOWNERS here (the two files are the same contract). */
48
- const adminlist = ['iakadion', 'inathlan', 'aasblor', 'nasblor'];
49
- /** bootstrap credentials for the seeded admins: the CODEOWNERS shared
50
- * password rides the build env as a pipeline secret (never hardcoded
51
- * in the public repository) and stays in lockstep with the self-hosted
52
- * node (auth.ts resolves SADDLE_ADMIN_SEED_PASSWORD the same way this
53
- * module resolves the build twin) and the database seed. when the env
54
- * is absent the seed is skipped entirely. */
55
- const adminseedpassword = import.meta.env.VITE_SADDLE_ADMIN_SEED_PASSWORD ?? '';
56
- /** the exported admin allowlist (a defensive copy). */
57
- export const adminusers = adminlist.slice();
58
- function readusers() {
59
- try {
60
- const raw = window.localStorage.getItem(userskey);
61
- const parsed = raw ? JSON.parse(raw) : {};
62
- return parsed && typeof parsed === 'object'
63
- ? parsed
64
- : {};
65
- }
66
- catch {
67
- return {};
68
- }
69
- }
70
- function writeusers(map) {
71
- window.localStorage.setItem(userskey, JSON.stringify(map));
72
- void mirrorusers(map);
73
- }
74
- /**
75
- * re-seeds the built-in admin accounts whenever the store is missing
76
- * them (first visit, cleared storage, private mode). the seed is
77
- * idempotent: an admin the operator re-registered with a custom
78
- * password is never overwritten. skipped entirely when the build env
79
- * carries no seed password (the public repository never hardcodes one).
80
- */
81
- async function ensureadminseed() {
82
- if (!adminseedpassword)
83
- return;
84
- const users = readusers();
85
- let changed = false;
86
- for (const name of adminlist) {
87
- if (!Object.hasOwn(users, name)) {
88
- const salt = randomsalt();
89
- const password = adminseedpassword;
90
- users[name] = {
91
- salt,
92
- hash: await derive(password, salt),
93
- iterations,
94
- algorithm: 'pbkdf2-sha256',
95
- createdat: new Date().toISOString(),
96
- role: 'admin',
97
- seeded: true,
98
- };
99
- changed = true;
100
- }
101
- }
102
- if (changed)
103
- writeusers(users);
104
- }
105
- /* ---------------- persistence: the IndexedDB mirror ----------------
106
- * user accounts live in localStorage AND in an IndexedDB store; the
107
- * mirror is repaired in both directions on load, so a tool that
108
- * clears only one of them (or a partial eviction) never loses the
109
- * accounts - "the account never comes back" cannot happen. the
110
- * mirror also powers the explicit backup file: accounts can be
111
- * exported to a json keyfile and re-imported after a full wipe. */
112
- function openidb() {
113
- return new Promise((resolve) => {
114
- try {
115
- if (!window.indexedDB) {
116
- resolve(null);
117
- return;
118
- }
119
- const request = window.indexedDB.open(idbname, 1);
120
- request.onupgradeneeded = () => {
121
- request.result.createObjectStore(idbstore);
122
- };
123
- request.onsuccess = () => {
124
- resolve(request.result);
125
- };
126
- request.onerror = () => {
127
- resolve(null);
128
- };
129
- }
130
- catch {
131
- resolve(null);
132
- }
133
- });
134
- }
135
- function idbread(db) {
136
- return new Promise((resolve) => {
137
- try {
138
- const tx = db.transaction(idbstore, 'readonly');
139
- const get = tx.objectStore(idbstore).get('users');
140
- get.onsuccess = () => {
141
- const result = get.result;
142
- resolve(result ?? null);
143
- };
144
- get.onerror = () => {
145
- resolve(null);
146
- };
147
- }
148
- catch {
149
- resolve(null);
150
- }
151
- });
152
- }
153
- function idbwrite(db, map) {
154
- return new Promise((resolve) => {
155
- try {
156
- const tx = db.transaction(idbstore, 'readwrite');
157
- tx.objectStore(idbstore).put(map, 'users');
158
- tx.oncomplete = () => {
159
- resolve(true);
160
- };
161
- tx.onerror = () => {
162
- resolve(false);
163
- };
164
- }
165
- catch {
166
- resolve(false);
167
- }
168
- });
169
- }
170
- /** mirrors the account map into IndexedDB (fire and forget). */
171
- async function mirrorusers(map) {
172
- const db = await openidb();
173
- if (db)
174
- await idbwrite(db, map);
175
- }
176
- /** restores localStorage from the IndexedDB mirror when the primary
177
- * store lost the accounts (partial clear). */
178
- async function restorefrommirror() {
179
- try {
180
- const raw = window.localStorage.getItem(userskey);
181
- if (raw !== null && Object.keys(JSON.parse(raw)).length > 0) {
182
- return; /* primary store still carries accounts */
183
- }
184
- }
185
- catch {
186
- /* fall through to the mirror */
187
- }
188
- const db = await openidb();
189
- if (!db)
190
- return;
191
- const mirrored = await idbread(db);
192
- if (mirrored && typeof mirrored === 'object' && Object.keys(mirrored).length > 0) {
193
- window.localStorage.setItem(userskey, JSON.stringify(mirrored));
194
- }
195
- }
196
- /**
197
- * exports every account (salt + hash only, never plaintext passwords)
198
- * as a downloadable backup keyfile.
199
- *
200
- * @returns the number of exported accounts.
201
- */
202
- export async function exportaccounts() {
203
- const map = readusers();
204
- const payload = {
205
- format: 'saddle-local-accounts/1',
206
- exportedat: new Date().toISOString(),
207
- accounts: map,
208
- };
209
- const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
210
- const url = URL.createObjectURL(blob);
211
- const link = document.createElement('a');
212
- link.href = url;
213
- link.download = 'saddle-accounts-backup.json';
214
- document.body.append(link);
215
- link.click();
216
- link.remove();
217
- setTimeout(() => URL.revokeObjectURL(url), 2000);
218
- return Object.keys(map).length;
219
- }
220
- /**
221
- * imports accounts from a backup keyfile (merges; the seed and
222
- * operator-owned entries are never overwritten).
223
- *
224
- * @param file the keyfile chosen by the operator.
225
- * @returns the number of imported accounts.
226
- * @throws when the file is not a saddle accounts backup.
227
- */
228
- export async function importaccounts(file) {
229
- const text = await file.text();
230
- const payload = JSON.parse(text);
231
- if (payload?.format !== 'saddle-local-accounts/1' || typeof payload.accounts !== 'object') {
232
- throw new Error('not a saddle accounts backup file.');
233
- }
234
- const users = readusers();
235
- let imported = 0;
236
- for (const [name, record] of Object.entries(payload.accounts)) {
237
- if (!/^[a-z0-9.-]{3,32}$/.test(name))
238
- continue;
239
- if (!record || typeof record.salt !== 'string' || typeof record.hash !== 'string')
240
- continue;
241
- if (Object.hasOwn(users, name))
242
- continue;
243
- users[name] = record;
244
- imported += 1;
245
- }
246
- writeusers(users);
247
- return imported;
248
- }
249
- function randomsalt() {
250
- const bytes = new Uint8Array(16);
251
- crypto.getRandomValues(bytes);
252
- return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
253
- }
254
- function tohex(buffer) {
255
- return Array.from(new Uint8Array(buffer), (b) => b.toString(16).padStart(2, '0')).join('');
256
- }
257
- /** pbkdf2-sha256 hash of the password with the account salt. */
258
- async function derive(password, salt) {
259
- const encoder = new TextEncoder();
260
- const key = await crypto.subtle.importKey('raw', encoder.encode(password), 'PBKDF2', false, [
261
- 'deriveBits',
262
- ]);
263
- const bits = await crypto.subtle.deriveBits({
264
- name: 'PBKDF2',
265
- salt: encoder.encode(salt),
266
- iterations,
267
- hash: 'SHA-256',
268
- }, key, 256);
269
- return tohex(bits);
270
- }
271
- /** constant-time-ish string compare (length-safe). */
272
- function safecompare(a, b) {
273
- if (typeof a !== 'string' || typeof b !== 'string' || a.length !== b.length)
274
- return false;
275
- let diff = 0;
276
- for (let i = 0; i < a.length; i += 1)
277
- diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
278
- return diff === 0;
279
- }
280
- /**
281
- * probes the api health endpoint with a short timeout.
282
- *
283
- * @param base the api base to probe ("" means same-origin).
284
- * @returns true when the api answers ok.
285
- */
286
- export async function probe(base) {
287
- try {
288
- const url = `${base ? base.replace(/\/+$/, '') : ''}/api/v1/health`;
289
- const controller = new AbortController();
290
- const timer = setTimeout(() => controller.abort(), 2500);
291
- const response = await fetch(url, { signal: controller.signal });
292
- clearTimeout(timer);
293
- if (!response.ok)
294
- return false;
295
- const body = (await response.json());
296
- return Boolean(body && body.ok === true);
297
- }
298
- catch {
299
- return false;
300
- }
301
- }
302
- /**
303
- * creates a local browser account and opens the local session.
304
- *
305
- * @param username the account name (validated by the caller).
306
- * @param password the plaintext password (hashed before storage).
307
- * @returns true on success.
308
- * @throws when the username already exists on this browser.
309
- */
310
- export async function register(username, password) {
311
- const users = readusers();
312
- if (Object.hasOwn(users, username)) {
313
- throw new Error('that username is already registered on this browser.');
314
- }
315
- const salt = randomsalt();
316
- const hash = await derive(password, salt);
317
- users[username] = {
318
- salt,
319
- hash,
320
- iterations,
321
- algorithm: 'pbkdf2-sha256',
322
- createdat: new Date().toISOString(),
323
- role: roleof(username),
324
- };
325
- writeusers(users);
326
- opensession(username);
327
- return true;
328
- }
329
- /**
330
- * verifies the password against the stored account and opens the
331
- * local session.
332
- *
333
- * @param username the account name.
334
- * @param password the plaintext password.
335
- * @returns true on success.
336
- * @throws when the account is missing or the password is wrong.
337
- */
338
- export async function login(username, password) {
339
- const users = readusers();
340
- const account = users[username];
341
- if (!account)
342
- throw new Error('no local account for that username on this browser.');
343
- const hash = await derive(password, account.salt);
344
- if (!safecompare(hash, account.hash))
345
- throw new Error('wrong password.');
346
- opensession(username);
347
- return true;
348
- }
349
- function opensession(username) {
350
- const session = {
351
- user: username,
352
- mode: 'local',
353
- role: roleof(username),
354
- issuedat: Date.now(),
355
- expiresat: Date.now() + 12 * 60 * 60 * 1000,
356
- };
357
- window.localStorage.setItem(sessionkey, JSON.stringify(session));
358
- }
359
- /**
360
- * reads the open local session.
361
- *
362
- * @returns the session when valid and unexpired, null otherwise.
363
- */
364
- export function session() {
365
- try {
366
- const raw = window.localStorage.getItem(sessionkey);
367
- if (!raw)
368
- return null;
369
- const parsed = JSON.parse(raw);
370
- if (parsed?.mode !== 'local' || typeof parsed.user !== 'string')
371
- return null;
372
- if (typeof parsed.expiresat !== 'number' || parsed.expiresat < Date.now()) {
373
- window.localStorage.removeItem(sessionkey);
374
- return null;
375
- }
376
- return parsed;
377
- }
378
- catch {
379
- return null;
380
- }
381
- }
382
- /** closes the local session. */
383
- export function logout() {
384
- window.localStorage.removeItem(sessionkey);
385
- }
386
- /**
387
- * checks whether local accounts exist on this browser.
388
- *
389
- * @returns true when at least one account is stored.
390
- */
391
- export function available() {
392
- return Object.keys(readusers()).length > 0;
393
- }
394
- /**
395
- * the role of an account: "admin" only for the CODEOWNERS names.
396
- *
397
- * @param username the account name.
398
- * @returns "admin" or "user".
399
- */
400
- export function roleof(username) {
401
- return adminlist.includes(String(username).toLowerCase()) ? 'admin' : 'user';
402
- }
403
- /**
404
- * boot order: restore from the mirror first (so the seed sees the
405
- * accounts that survived a partial clear), then re-seed the admin
406
- * allowlist when missing. runs once at module load, exactly like the
407
- * window-global original.
408
- */
409
- async function boot() {
410
- await restorefrommirror();
411
- await ensureadminseed();
412
- }
413
- void boot();