@wenathlan/saddle 2.0.154 → 2.0.155

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 (307) hide show
  1. package/Dockerfile +112 -41
  2. package/README.md +58 -59
  3. package/browser.ts +482 -0
  4. package/cli.ts +274 -0
  5. package/distribution.ts +731 -0
  6. package/docker.config +29 -8
  7. package/docs/artifactavailability.md +2 -2
  8. package/docs/browser.md +18 -1
  9. package/docs/{containerplatforms.2.1.0.md → containerplatforms210.md} +1 -1
  10. package/docs/{documentmap.1.8.12.md → documentmap1812.md} +1 -1
  11. package/docs/extension.md +13 -2
  12. package/docs/libraryapi.md +1 -1
  13. package/docs/{ninety.modalities.md → ninetymodalities.md} +87 -4
  14. package/docs/{planning.ninety.md → planningninety.md} +6 -6
  15. package/docs/{releasenotes.1.8.13.md → releasenotes1813.md} +2 -2
  16. package/docs/{releasenotes.1.8.14.md → releasenotes1814.md} +1 -1
  17. package/docs/{repo.synthesis.1.8.16.md → reposynthesis1816.md} +1 -1
  18. package/docs/todo.md +2 -2
  19. package/docs/{todo.1.8.15.md → todo1815.md} +1 -1
  20. package/docs/{todo.1.8.16.md → todo1816.md} +5 -5
  21. package/execution.ts +677 -0
  22. package/foundation.ts +314 -0
  23. package/index.ts +35 -27
  24. package/isolation.ts +84 -0
  25. package/modes.ts +170 -0
  26. package/mttg.config +1 -1
  27. package/orchestrator.ts +49 -15
  28. package/package.json +90 -75
  29. package/qemu.config +2 -2
  30. package/sandbox.ts +2094 -0
  31. package/scheduler.ts +1 -1
  32. package/server.ts +52 -0
  33. package/tsconfig.json +11 -12
  34. package/virtual.ts +1105 -0
  35. package/virtualgpu.ts +1 -1
  36. package/virtualization.ts +1 -1
  37. package/workloadlaw.ts +293 -0
  38. package/dist/App.d.ts +0 -5
  39. package/dist/App.js +0 -52
  40. package/dist/Sol/Sol.d.ts +0 -3
  41. package/dist/Sol/Sol.js +0 -46
  42. package/dist/Sol/agentbrowser/agentbrowser.d.ts +0 -9
  43. package/dist/Sol/agentbrowser/agentbrowser.js +0 -121
  44. package/dist/Sol/architecture/architecture.d.ts +0 -9
  45. package/dist/Sol/architecture/architecture.js +0 -93
  46. package/dist/Sol/compute/compute.d.ts +0 -9
  47. package/dist/Sol/compute/compute.js +0 -22
  48. package/dist/Sol/console/console.d.ts +0 -15
  49. package/dist/Sol/console/console.js +0 -292
  50. package/dist/Sol/dashboard/dashboard.d.ts +0 -10
  51. package/dist/Sol/dashboard/dashboard.js +0 -181
  52. package/dist/Sol/docs/docs.d.ts +0 -9
  53. package/dist/Sol/docs/docs.js +0 -28
  54. package/dist/Sol/home/home.d.ts +0 -35
  55. package/dist/Sol/home/home.js +0 -239
  56. package/dist/Sol/integrations/integrations.d.ts +0 -9
  57. package/dist/Sol/integrations/integrations.js +0 -83
  58. package/dist/Sol/intro/intro.d.ts +0 -7
  59. package/dist/Sol/intro/intro.js +0 -102
  60. package/dist/Sol/login/login.d.ts +0 -16
  61. package/dist/Sol/login/login.js +0 -49
  62. package/dist/Sol/notfound/notfound.d.ts +0 -9
  63. package/dist/Sol/notfound/notfound.js +0 -15
  64. package/dist/Sol/onboarding/onboarding.d.ts +0 -25
  65. package/dist/Sol/onboarding/onboarding.js +0 -60
  66. package/dist/Sol/playground/playground.d.ts +0 -9
  67. package/dist/Sol/playground/playground.js +0 -127
  68. package/dist/Sol/register/register.d.ts +0 -16
  69. package/dist/Sol/register/register.js +0 -193
  70. package/dist/Sol/shell/Shell.d.ts +0 -124
  71. package/dist/Sol/shell/Shell.js +0 -335
  72. package/dist/Sol/wingets/ComputeBootladder.d.ts +0 -6
  73. package/dist/Sol/wingets/ComputeBootladder.js +0 -71
  74. package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +0 -7
  75. package/dist/Sol/wingets/ComputeModalitycatalog.js +0 -71
  76. package/dist/Sol/wingets/ConsoleApibadge.d.ts +0 -7
  77. package/dist/Sol/wingets/ConsoleApibadge.js +0 -5
  78. package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +0 -6
  79. package/dist/Sol/wingets/ConsoleEdgelaw.js +0 -36
  80. package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +0 -19
  81. package/dist/Sol/wingets/ConsoleEventstimeline.js +0 -12
  82. package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +0 -8
  83. package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +0 -8
  84. package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +0 -21
  85. package/dist/Sol/wingets/ConsoleSpecpanel.js +0 -75
  86. package/dist/Sol/wingets/ConsoleTerminal.d.ts +0 -22
  87. package/dist/Sol/wingets/ConsoleTerminal.js +0 -77
  88. package/dist/Sol/wingets/DashboardAccountpanel.d.ts +0 -18
  89. package/dist/Sol/wingets/DashboardAccountpanel.js +0 -65
  90. package/dist/Sol/wingets/DashboardAdminpanel.d.ts +0 -6
  91. package/dist/Sol/wingets/DashboardAdminpanel.js +0 -90
  92. package/dist/Sol/wingets/DashboardAuditlog.d.ts +0 -8
  93. package/dist/Sol/wingets/DashboardAuditlog.js +0 -50
  94. package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +0 -10
  95. package/dist/Sol/wingets/DashboardCreatesandboxform.js +0 -71
  96. package/dist/Sol/wingets/DashboardEventstimeline.d.ts +0 -8
  97. package/dist/Sol/wingets/DashboardEventstimeline.js +0 -83
  98. package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +0 -8
  99. package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +0 -57
  100. package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +0 -8
  101. package/dist/Sol/wingets/DashboardMeshnodestable.js +0 -103
  102. package/dist/Sol/wingets/DashboardSandboxlist.d.ts +0 -10
  103. package/dist/Sol/wingets/DashboardSandboxlist.js +0 -163
  104. package/dist/Sol/wingets/DashboardUserstable.d.ts +0 -8
  105. package/dist/Sol/wingets/DashboardUserstable.js +0 -78
  106. package/dist/Sol/wingets/HomeEntrygate.d.ts +0 -23
  107. package/dist/Sol/wingets/HomeEntrygate.js +0 -73
  108. package/dist/Sol/wingets/HomeMetricstrip.d.ts +0 -9
  109. package/dist/Sol/wingets/HomeMetricstrip.js +0 -4
  110. package/dist/Sol/wingets/HomeRuntimediagram.d.ts +0 -1
  111. package/dist/Sol/wingets/HomeRuntimediagram.js +0 -22
  112. package/dist/Sol/wingets/LoginForm.d.ts +0 -19
  113. package/dist/Sol/wingets/LoginForm.js +0 -141
  114. package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +0 -13
  115. package/dist/Sol/wingets/RegisterPasswordconfirm.js +0 -16
  116. package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +0 -19
  117. package/dist/Sol/wingets/RegisterPasswordmeter.js +0 -65
  118. package/dist/Sol/wingets/RegisterUsernamerules.d.ts +0 -11
  119. package/dist/Sol/wingets/RegisterUsernamerules.js +0 -16
  120. package/dist/Sol/wingets/index.d.ts +0 -39
  121. package/dist/Sol/wingets/index.js +0 -39
  122. package/dist/alternatives.d.ts +0 -508
  123. package/dist/alternatives.js +0 -950
  124. package/dist/api.d.ts +0 -154
  125. package/dist/api.js +0 -174
  126. package/dist/auth.d.ts +0 -268
  127. package/dist/auth.js +0 -476
  128. package/dist/automation.d.ts +0 -425
  129. package/dist/automation.js +0 -566
  130. package/dist/browser.d.ts +0 -561
  131. package/dist/browser.js +0 -1058
  132. package/dist/capacitor.config.d.ts +0 -45
  133. package/dist/capacitor.config.js +0 -48
  134. package/dist/catalog.d.ts +0 -43
  135. package/dist/catalog.js +0 -44
  136. package/dist/cli.d.ts +0 -16
  137. package/dist/cli.js +0 -168
  138. package/dist/communication.d.ts +0 -181
  139. package/dist/communication.js +0 -376
  140. package/dist/compute.d.ts +0 -1160
  141. package/dist/compute.js +0 -2272
  142. package/dist/dashboardlib.d.ts +0 -58
  143. package/dist/dashboardlib.js +0 -161
  144. package/dist/db.d.ts +0 -569
  145. package/dist/db.js +0 -1080
  146. package/dist/distribution.d.ts +0 -528
  147. package/dist/distribution.js +0 -810
  148. package/dist/drizzle.config.d.ts +0 -20
  149. package/dist/drizzle.config.js +0 -18
  150. package/dist/edgelandes.d.ts +0 -70
  151. package/dist/edgelandes.host.d.ts +0 -26
  152. package/dist/edgelandes.host.js +0 -1089
  153. package/dist/edgelandes.inproc.d.ts +0 -27
  154. package/dist/edgelandes.inproc.js +0 -952
  155. package/dist/edgelandes.js +0 -1133
  156. package/dist/edgelandes.ninety.d.ts +0 -31
  157. package/dist/edgelandes.ninety.js +0 -174
  158. package/dist/edgelandes.virtual.d.ts +0 -25
  159. package/dist/edgelandes.virtual.js +0 -629
  160. package/dist/edgeplane.d.ts +0 -121
  161. package/dist/edgeplane.js +0 -341
  162. package/dist/examplelocaljob.d.ts +0 -1
  163. package/dist/examplelocaljob.js +0 -14
  164. package/dist/execution.d.ts +0 -529
  165. package/dist/execution.js +0 -760
  166. package/dist/family.d.ts +0 -16
  167. package/dist/family.js +0 -42
  168. package/dist/familyurl.d.ts +0 -21
  169. package/dist/familyurl.js +0 -24
  170. package/dist/format.d.ts +0 -18
  171. package/dist/format.js +0 -59
  172. package/dist/foundation.d.ts +0 -172
  173. package/dist/foundation.js +0 -312
  174. package/dist/index.d.ts +0 -735
  175. package/dist/index.js +0 -1020
  176. package/dist/integration.d.ts +0 -235
  177. package/dist/integration.js +0 -228
  178. package/dist/intelligence.d.ts +0 -93
  179. package/dist/intelligence.js +0 -144
  180. package/dist/isolation.d.ts +0 -128
  181. package/dist/isolation.js +0 -95
  182. package/dist/labels.d.ts +0 -665
  183. package/dist/labels.js +0 -707
  184. package/dist/localauth.d.ts +0 -127
  185. package/dist/localauth.js +0 -413
  186. package/dist/media.d.ts +0 -343
  187. package/dist/media.js +0 -985
  188. package/dist/mesh.d.ts +0 -177
  189. package/dist/mesh.js +0 -468
  190. package/dist/modalityregistry.d.ts +0 -109
  191. package/dist/modalityregistry.js +0 -1386
  192. package/dist/modes.d.ts +0 -358
  193. package/dist/modes.js +0 -133
  194. package/dist/onboardingstate.d.ts +0 -79
  195. package/dist/onboardingstate.js +0 -282
  196. package/dist/orchestrator.d.ts +0 -1338
  197. package/dist/orchestrator.js +0 -4310
  198. package/dist/packager/targetcli.js +0 -4
  199. package/dist/paths.d.ts +0 -2
  200. package/dist/paths.js +0 -14
  201. package/dist/performance.d.ts +0 -415
  202. package/dist/performance.js +0 -826
  203. package/dist/quantum.d.ts +0 -809
  204. package/dist/quantum.js +0 -2072
  205. package/dist/render.d.ts +0 -380
  206. package/dist/render.js +0 -1072
  207. package/dist/runall.d.ts +0 -2
  208. package/dist/runall.js +0 -39
  209. package/dist/runtimeprobe.d.ts +0 -4
  210. package/dist/runtimeprobe.js +0 -8
  211. package/dist/sandbox.d.ts +0 -355
  212. package/dist/sandbox.js +0 -1721
  213. package/dist/sandboxenvprofile.d.ts +0 -29
  214. package/dist/sandboxenvprofile.js +0 -89
  215. package/dist/sandboxprofile.d.ts +0 -137
  216. package/dist/sandboxprofile.js +0 -241
  217. package/dist/sandboxqueue.d.ts +0 -108
  218. package/dist/sandboxqueue.js +0 -201
  219. package/dist/sandboxquota.d.ts +0 -121
  220. package/dist/sandboxquota.js +0 -236
  221. package/dist/scheduler.d.ts +0 -595
  222. package/dist/scheduler.js +0 -1258
  223. package/dist/scriptlanes.d.ts +0 -182
  224. package/dist/scriptlanes.js +0 -660
  225. package/dist/scripts.buildengine.d.ts +0 -1
  226. package/dist/scripts.buildengine.js +0 -39
  227. package/dist/scripts.securitygate.d.ts +0 -1
  228. package/dist/scripts.securitygate.js +0 -68
  229. package/dist/scripts.validateflatnative.d.ts +0 -1
  230. package/dist/scripts.validateflatnative.js +0 -72
  231. package/dist/security.d.ts +0 -500
  232. package/dist/security.js +0 -1169
  233. package/dist/seedcatalog.d.ts +0 -13
  234. package/dist/seedcatalog.js +0 -46
  235. package/dist/server.d.ts +0 -20
  236. package/dist/server.js +0 -53
  237. package/dist/speccatalog.d.ts +0 -31
  238. package/dist/speccatalog.js +0 -163
  239. package/dist/tiers.d.ts +0 -1001
  240. package/dist/tiers.js +0 -2169
  241. package/dist/utils.d.ts +0 -2
  242. package/dist/utils.js +0 -5
  243. package/dist/vaulthandoff.d.ts +0 -141
  244. package/dist/vaulthandoff.js +0 -444
  245. package/dist/virtual.d.ts +0 -640
  246. package/dist/virtual.js +0 -1284
  247. package/dist/virtualcpu.d.ts +0 -315
  248. package/dist/virtualcpu.js +0 -1180
  249. package/dist/virtualgpu.d.ts +0 -344
  250. package/dist/virtualgpu.js +0 -1000
  251. package/dist/virtualization.d.ts +0 -606
  252. package/dist/virtualization.js +0 -1186
  253. package/dist/virtualmemory.d.ts +0 -817
  254. package/dist/virtualmemory.js +0 -1795
  255. package/dist/vite.config.d.ts +0 -3
  256. package/dist/vite.config.js +0 -48
  257. package/dist/vitest.config.d.ts +0 -2
  258. package/dist/vitest.config.js +0 -25
  259. package/dist/webserver.d.ts +0 -166
  260. package/dist/webserver.js +0 -2226
  261. package/dist/workloadlaw.d.ts +0 -133
  262. package/dist/workloadlaw.js +0 -193
  263. package/manifest.json +0 -20
  264. package/media.ts +0 -1337
  265. /package/docs/{architecture.1.8.19.md → architecture1819.md} +0 -0
  266. /package/docs/{architecture.1.8.19.mmd → architecture1819.mmd} +0 -0
  267. /package/docs/{artifactresearch.1.8.9.md → artifactresearch189.md} +0 -0
  268. /package/docs/{batches.1816.to.1820.md → batches1816to1820.md} +0 -0
  269. /package/docs/{brancharchive.2026.08.18.md → brancharchive20260818.md} +0 -0
  270. /package/docs/{containerplatforms.1.8.17.md → containerplatforms1817.md} +0 -0
  271. /package/docs/{e2ugh.engine.md → e2ughengine.md} +0 -0
  272. /package/docs/{flatbuildresearch.1.8.11.md → flatbuildresearch1811.md} +0 -0
  273. /package/docs/{licenseaudit.1.8.12.md → licenseaudit1812.md} +0 -0
  274. /package/docs/{mobileconversionresearch.1.8.10.md → mobileconversionresearch1810.md} +0 -0
  275. /package/docs/{nativeidentityresearch.1.8.11.md → nativeidentityresearch1811.md} +0 -0
  276. /package/docs/{native.wrappers.md → nativewrappers.md} +0 -0
  277. /package/docs/{planning.1.8.18.md → planning1818.md} +0 -0
  278. /package/docs/{planning.1.8.19.md → planning1819.md} +0 -0
  279. /package/docs/{planning.engine.md → planningengine.md} +0 -0
  280. /package/docs/{releasenotes.1.8.12.md → releasenotes1812.md} +0 -0
  281. /package/docs/{releasenotes.1.8.15.md → releasenotes1815.md} +0 -0
  282. /package/docs/{releasenotes.1.8.16.md → releasenotes1816.md} +0 -0
  283. /package/docs/{releasenotes.1.8.17.md → releasenotes1817.md} +0 -0
  284. /package/docs/{releasenotes.1.8.18.md → releasenotes1818.md} +0 -0
  285. /package/docs/{releasenotes.1.8.19.md → releasenotes1819.md} +0 -0
  286. /package/docs/{reorganization.1.8.8.md → reorganization188.md} +0 -0
  287. /package/docs/{reorganization.1.8.9.md → reorganization189.md} +0 -0
  288. /package/docs/{repo.analysis.1.8.21.json → repoanalysis1821.json} +0 -0
  289. /package/docs/{repo.candidates.1.8.21.jsonl → repocandidates1821.jsonl} +0 -0
  290. /package/docs/{repo.research.1.8.16.md → reporesearch1816.md} +0 -0
  291. /package/docs/{repo.research.1.8.21.md → reporesearch1821.md} +0 -0
  292. /package/docs/{repo.selected.1.8.21.json → reposelected1821.json} +0 -0
  293. /package/docs/{repo.synthesis.1.8.21.md → reposynthesis1821.md} +0 -0
  294. /package/docs/{research.1.8.18.isolation.md → research1818isolation.md} +0 -0
  295. /package/docs/{research.1.8.19.virtual.browser.md → research1819virtualbrowser.md} +0 -0
  296. /package/docs/{scanningresearch.1.8.11.md → scanningresearch1811.md} +0 -0
  297. /package/docs/{securityaudit.1.8.7.md → securityaudit187.md} +0 -0
  298. /package/docs/{securityreferences.1.8.11.md → securityreferences1811.md} +0 -0
  299. /package/docs/{securityresearch.1.8.11.md → securityresearch1811.md} +0 -0
  300. /package/docs/{signingresearch.1.8.12.md → signingresearch1812.md} +0 -0
  301. /package/docs/{tauri.glib.risk.1.8.11.md → tauriglibrisk1811.md} +0 -0
  302. /package/docs/{todo.1.8.18.md → todo1818.md} +0 -0
  303. /package/docs/{todo.1.8.19.md → todo1819.md} +0 -0
  304. /package/docs/{toolchainresearch.1.8.9.md → toolchainresearch189.md} +0 -0
  305. /package/docs/{web.duplicatecleanup.md → webduplicatecleanup.md} +0 -0
  306. /package/docs/{web.ideas.md → webideas.md} +0 -0
  307. /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();