@wenathlan/saddle 2.0.153 → 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.
- package/Dockerfile +139 -55
- package/README.md +58 -59
- package/browser.ts +482 -0
- package/cli.ts +274 -0
- package/distribution.ts +731 -0
- package/docker.config +29 -8
- package/docs/artifactavailability.md +2 -2
- package/docs/browser.md +18 -1
- package/docs/{containerplatforms.2.1.0.md → containerplatforms210.md} +1 -1
- package/docs/{documentmap.1.8.12.md → documentmap1812.md} +1 -1
- package/docs/extension.md +13 -2
- package/docs/libraryapi.md +1 -1
- package/docs/{ninety.modalities.md → ninetymodalities.md} +87 -4
- package/docs/{planning.ninety.md → planningninety.md} +6 -6
- package/docs/{releasenotes.1.8.13.md → releasenotes1813.md} +2 -2
- package/docs/{releasenotes.1.8.14.md → releasenotes1814.md} +1 -1
- package/docs/{repo.synthesis.1.8.16.md → reposynthesis1816.md} +1 -1
- package/docs/todo.md +2 -2
- package/docs/{todo.1.8.15.md → todo1815.md} +1 -1
- package/docs/{todo.1.8.16.md → todo1816.md} +5 -5
- package/execution.ts +677 -0
- package/foundation.ts +314 -0
- package/index.ts +35 -27
- package/isolation.ts +84 -0
- package/modes.ts +170 -0
- package/mttg.config +1 -1
- package/orchestrator.ts +49 -15
- package/package.json +90 -75
- package/qemu.config +2 -2
- package/sandbox.ts +2094 -0
- package/scheduler.ts +1 -1
- package/server.ts +52 -0
- package/tsconfig.json +11 -12
- package/virtual.ts +1105 -0
- package/virtualgpu.ts +1 -1
- package/virtualization.ts +1 -1
- package/workloadlaw.ts +293 -0
- package/dist/App.d.ts +0 -5
- package/dist/App.js +0 -52
- package/dist/Sol/Sol.d.ts +0 -3
- package/dist/Sol/Sol.js +0 -46
- package/dist/Sol/agentbrowser/agentbrowser.d.ts +0 -9
- package/dist/Sol/agentbrowser/agentbrowser.js +0 -121
- package/dist/Sol/architecture/architecture.d.ts +0 -9
- package/dist/Sol/architecture/architecture.js +0 -93
- package/dist/Sol/compute/compute.d.ts +0 -9
- package/dist/Sol/compute/compute.js +0 -22
- package/dist/Sol/console/console.d.ts +0 -15
- package/dist/Sol/console/console.js +0 -292
- package/dist/Sol/dashboard/dashboard.d.ts +0 -10
- package/dist/Sol/dashboard/dashboard.js +0 -181
- package/dist/Sol/docs/docs.d.ts +0 -9
- package/dist/Sol/docs/docs.js +0 -28
- package/dist/Sol/home/home.d.ts +0 -35
- package/dist/Sol/home/home.js +0 -239
- package/dist/Sol/integrations/integrations.d.ts +0 -9
- package/dist/Sol/integrations/integrations.js +0 -83
- package/dist/Sol/intro/intro.d.ts +0 -7
- package/dist/Sol/intro/intro.js +0 -102
- package/dist/Sol/login/login.d.ts +0 -16
- package/dist/Sol/login/login.js +0 -49
- package/dist/Sol/notfound/notfound.d.ts +0 -9
- package/dist/Sol/notfound/notfound.js +0 -15
- package/dist/Sol/onboarding/onboarding.d.ts +0 -25
- package/dist/Sol/onboarding/onboarding.js +0 -60
- package/dist/Sol/playground/playground.d.ts +0 -9
- package/dist/Sol/playground/playground.js +0 -127
- package/dist/Sol/register/register.d.ts +0 -16
- package/dist/Sol/register/register.js +0 -193
- package/dist/Sol/shell/Shell.d.ts +0 -124
- package/dist/Sol/shell/Shell.js +0 -335
- package/dist/Sol/wingets/ComputeBootladder.d.ts +0 -6
- package/dist/Sol/wingets/ComputeBootladder.js +0 -71
- package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +0 -7
- package/dist/Sol/wingets/ComputeModalitycatalog.js +0 -71
- package/dist/Sol/wingets/ConsoleApibadge.d.ts +0 -7
- package/dist/Sol/wingets/ConsoleApibadge.js +0 -5
- package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +0 -6
- package/dist/Sol/wingets/ConsoleEdgelaw.js +0 -36
- package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +0 -19
- package/dist/Sol/wingets/ConsoleEventstimeline.js +0 -12
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +0 -8
- package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +0 -8
- package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +0 -21
- package/dist/Sol/wingets/ConsoleSpecpanel.js +0 -75
- package/dist/Sol/wingets/ConsoleTerminal.d.ts +0 -22
- package/dist/Sol/wingets/ConsoleTerminal.js +0 -77
- package/dist/Sol/wingets/DashboardAccountpanel.d.ts +0 -18
- package/dist/Sol/wingets/DashboardAccountpanel.js +0 -65
- package/dist/Sol/wingets/DashboardAdminpanel.d.ts +0 -6
- package/dist/Sol/wingets/DashboardAdminpanel.js +0 -90
- package/dist/Sol/wingets/DashboardAuditlog.d.ts +0 -8
- package/dist/Sol/wingets/DashboardAuditlog.js +0 -50
- package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +0 -10
- package/dist/Sol/wingets/DashboardCreatesandboxform.js +0 -71
- package/dist/Sol/wingets/DashboardEventstimeline.d.ts +0 -8
- package/dist/Sol/wingets/DashboardEventstimeline.js +0 -83
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +0 -8
- package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +0 -57
- package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +0 -8
- package/dist/Sol/wingets/DashboardMeshnodestable.js +0 -103
- package/dist/Sol/wingets/DashboardSandboxlist.d.ts +0 -10
- package/dist/Sol/wingets/DashboardSandboxlist.js +0 -163
- package/dist/Sol/wingets/DashboardUserstable.d.ts +0 -8
- package/dist/Sol/wingets/DashboardUserstable.js +0 -78
- package/dist/Sol/wingets/HomeEntrygate.d.ts +0 -23
- package/dist/Sol/wingets/HomeEntrygate.js +0 -73
- package/dist/Sol/wingets/HomeMetricstrip.d.ts +0 -9
- package/dist/Sol/wingets/HomeMetricstrip.js +0 -4
- package/dist/Sol/wingets/HomeRuntimediagram.d.ts +0 -1
- package/dist/Sol/wingets/HomeRuntimediagram.js +0 -22
- package/dist/Sol/wingets/LoginForm.d.ts +0 -19
- package/dist/Sol/wingets/LoginForm.js +0 -141
- package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +0 -13
- package/dist/Sol/wingets/RegisterPasswordconfirm.js +0 -16
- package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +0 -19
- package/dist/Sol/wingets/RegisterPasswordmeter.js +0 -65
- package/dist/Sol/wingets/RegisterUsernamerules.d.ts +0 -11
- package/dist/Sol/wingets/RegisterUsernamerules.js +0 -16
- package/dist/Sol/wingets/index.d.ts +0 -39
- package/dist/Sol/wingets/index.js +0 -39
- package/dist/alternatives.d.ts +0 -508
- package/dist/alternatives.js +0 -950
- package/dist/api.d.ts +0 -154
- package/dist/api.js +0 -174
- package/dist/auth.d.ts +0 -268
- package/dist/auth.js +0 -476
- package/dist/automation.d.ts +0 -425
- package/dist/automation.js +0 -566
- package/dist/browser.d.ts +0 -561
- package/dist/browser.js +0 -1058
- package/dist/capacitor.config.d.ts +0 -45
- package/dist/capacitor.config.js +0 -48
- package/dist/catalog.d.ts +0 -43
- package/dist/catalog.js +0 -44
- package/dist/cli.d.ts +0 -16
- package/dist/cli.js +0 -168
- package/dist/communication.d.ts +0 -181
- package/dist/communication.js +0 -376
- package/dist/compute.d.ts +0 -1160
- package/dist/compute.js +0 -2272
- package/dist/dashboardlib.d.ts +0 -58
- package/dist/dashboardlib.js +0 -161
- package/dist/db.d.ts +0 -569
- package/dist/db.js +0 -1080
- package/dist/distribution.d.ts +0 -528
- package/dist/distribution.js +0 -810
- package/dist/drizzle.config.d.ts +0 -20
- package/dist/drizzle.config.js +0 -18
- package/dist/edgelandes.d.ts +0 -70
- package/dist/edgelandes.host.d.ts +0 -26
- package/dist/edgelandes.host.js +0 -1089
- package/dist/edgelandes.inproc.d.ts +0 -27
- package/dist/edgelandes.inproc.js +0 -952
- package/dist/edgelandes.js +0 -1133
- package/dist/edgelandes.ninety.d.ts +0 -31
- package/dist/edgelandes.ninety.js +0 -174
- package/dist/edgelandes.virtual.d.ts +0 -25
- package/dist/edgelandes.virtual.js +0 -629
- package/dist/edgeplane.d.ts +0 -114
- package/dist/edgeplane.js +0 -305
- package/dist/examplelocaljob.d.ts +0 -1
- package/dist/examplelocaljob.js +0 -14
- package/dist/execution.d.ts +0 -529
- package/dist/execution.js +0 -760
- package/dist/family.d.ts +0 -16
- package/dist/family.js +0 -42
- package/dist/familyurl.d.ts +0 -21
- package/dist/familyurl.js +0 -24
- package/dist/format.d.ts +0 -18
- package/dist/format.js +0 -59
- package/dist/foundation.d.ts +0 -172
- package/dist/foundation.js +0 -312
- package/dist/index.d.ts +0 -735
- package/dist/index.js +0 -1020
- package/dist/integration.d.ts +0 -235
- package/dist/integration.js +0 -228
- package/dist/intelligence.d.ts +0 -93
- package/dist/intelligence.js +0 -144
- package/dist/isolation.d.ts +0 -128
- package/dist/isolation.js +0 -95
- package/dist/labels.d.ts +0 -665
- package/dist/labels.js +0 -751
- package/dist/localauth.d.ts +0 -127
- package/dist/localauth.js +0 -413
- package/dist/media.d.ts +0 -343
- package/dist/media.js +0 -985
- package/dist/mesh.d.ts +0 -177
- package/dist/mesh.js +0 -468
- package/dist/modalityregistry.d.ts +0 -109
- package/dist/modalityregistry.js +0 -1386
- package/dist/modes.d.ts +0 -358
- package/dist/modes.js +0 -133
- package/dist/onboardingstate.d.ts +0 -79
- package/dist/onboardingstate.js +0 -282
- package/dist/orchestrator.d.ts +0 -1338
- package/dist/orchestrator.js +0 -4310
- package/dist/packager/targetcli.js +0 -4
- package/dist/paths.d.ts +0 -2
- package/dist/paths.js +0 -14
- package/dist/performance.d.ts +0 -415
- package/dist/performance.js +0 -826
- package/dist/quantum.d.ts +0 -809
- package/dist/quantum.js +0 -2072
- package/dist/render.d.ts +0 -380
- package/dist/render.js +0 -1072
- package/dist/runall.d.ts +0 -2
- package/dist/runall.js +0 -39
- package/dist/runtimeprobe.d.ts +0 -4
- package/dist/runtimeprobe.js +0 -8
- package/dist/sandbox.d.ts +0 -355
- package/dist/sandbox.js +0 -1721
- package/dist/sandboxenvprofile.d.ts +0 -29
- package/dist/sandboxenvprofile.js +0 -89
- package/dist/sandboxprofile.d.ts +0 -137
- package/dist/sandboxprofile.js +0 -241
- package/dist/sandboxqueue.d.ts +0 -108
- package/dist/sandboxqueue.js +0 -201
- package/dist/sandboxquota.d.ts +0 -121
- package/dist/sandboxquota.js +0 -236
- package/dist/scheduler.d.ts +0 -595
- package/dist/scheduler.js +0 -1258
- package/dist/scriptlanes.d.ts +0 -182
- package/dist/scriptlanes.js +0 -660
- package/dist/scripts.buildengine.d.ts +0 -1
- package/dist/scripts.buildengine.js +0 -39
- package/dist/scripts.securitygate.d.ts +0 -1
- package/dist/scripts.securitygate.js +0 -68
- package/dist/scripts.validateflatnative.d.ts +0 -1
- package/dist/scripts.validateflatnative.js +0 -72
- package/dist/security.d.ts +0 -500
- package/dist/security.js +0 -1169
- package/dist/seedcatalog.d.ts +0 -13
- package/dist/seedcatalog.js +0 -46
- package/dist/server.d.ts +0 -20
- package/dist/server.js +0 -53
- package/dist/speccatalog.d.ts +0 -31
- package/dist/speccatalog.js +0 -163
- package/dist/tiers.d.ts +0 -1001
- package/dist/tiers.js +0 -2169
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +0 -5
- package/dist/vaulthandoff.d.ts +0 -141
- package/dist/vaulthandoff.js +0 -444
- package/dist/virtual.d.ts +0 -640
- package/dist/virtual.js +0 -1284
- package/dist/virtualcpu.d.ts +0 -315
- package/dist/virtualcpu.js +0 -1180
- package/dist/virtualgpu.d.ts +0 -344
- package/dist/virtualgpu.js +0 -1000
- package/dist/virtualization.d.ts +0 -606
- package/dist/virtualization.js +0 -1186
- package/dist/virtualmemory.d.ts +0 -817
- package/dist/virtualmemory.js +0 -1795
- package/dist/vite.config.d.ts +0 -3
- package/dist/vite.config.js +0 -43
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -25
- package/dist/webserver.d.ts +0 -166
- package/dist/webserver.js +0 -2226
- package/dist/workloadlaw.d.ts +0 -133
- package/dist/workloadlaw.js +0 -193
- package/manifest.json +0 -20
- package/media.ts +0 -1337
- /package/docs/{architecture.1.8.19.md → architecture1819.md} +0 -0
- /package/docs/{architecture.1.8.19.mmd → architecture1819.mmd} +0 -0
- /package/docs/{artifactresearch.1.8.9.md → artifactresearch189.md} +0 -0
- /package/docs/{batches.1816.to.1820.md → batches1816to1820.md} +0 -0
- /package/docs/{brancharchive.2026.08.18.md → brancharchive20260818.md} +0 -0
- /package/docs/{containerplatforms.1.8.17.md → containerplatforms1817.md} +0 -0
- /package/docs/{e2ugh.engine.md → e2ughengine.md} +0 -0
- /package/docs/{flatbuildresearch.1.8.11.md → flatbuildresearch1811.md} +0 -0
- /package/docs/{licenseaudit.1.8.12.md → licenseaudit1812.md} +0 -0
- /package/docs/{mobileconversionresearch.1.8.10.md → mobileconversionresearch1810.md} +0 -0
- /package/docs/{nativeidentityresearch.1.8.11.md → nativeidentityresearch1811.md} +0 -0
- /package/docs/{native.wrappers.md → nativewrappers.md} +0 -0
- /package/docs/{planning.1.8.18.md → planning1818.md} +0 -0
- /package/docs/{planning.1.8.19.md → planning1819.md} +0 -0
- /package/docs/{planning.engine.md → planningengine.md} +0 -0
- /package/docs/{releasenotes.1.8.12.md → releasenotes1812.md} +0 -0
- /package/docs/{releasenotes.1.8.15.md → releasenotes1815.md} +0 -0
- /package/docs/{releasenotes.1.8.16.md → releasenotes1816.md} +0 -0
- /package/docs/{releasenotes.1.8.17.md → releasenotes1817.md} +0 -0
- /package/docs/{releasenotes.1.8.18.md → releasenotes1818.md} +0 -0
- /package/docs/{releasenotes.1.8.19.md → releasenotes1819.md} +0 -0
- /package/docs/{reorganization.1.8.8.md → reorganization188.md} +0 -0
- /package/docs/{reorganization.1.8.9.md → reorganization189.md} +0 -0
- /package/docs/{repo.analysis.1.8.21.json → repoanalysis1821.json} +0 -0
- /package/docs/{repo.candidates.1.8.21.jsonl → repocandidates1821.jsonl} +0 -0
- /package/docs/{repo.research.1.8.16.md → reporesearch1816.md} +0 -0
- /package/docs/{repo.research.1.8.21.md → reporesearch1821.md} +0 -0
- /package/docs/{repo.selected.1.8.21.json → reposelected1821.json} +0 -0
- /package/docs/{repo.synthesis.1.8.21.md → reposynthesis1821.md} +0 -0
- /package/docs/{research.1.8.18.isolation.md → research1818isolation.md} +0 -0
- /package/docs/{research.1.8.19.virtual.browser.md → research1819virtualbrowser.md} +0 -0
- /package/docs/{scanningresearch.1.8.11.md → scanningresearch1811.md} +0 -0
- /package/docs/{securityaudit.1.8.7.md → securityaudit187.md} +0 -0
- /package/docs/{securityreferences.1.8.11.md → securityreferences1811.md} +0 -0
- /package/docs/{securityresearch.1.8.11.md → securityresearch1811.md} +0 -0
- /package/docs/{signingresearch.1.8.12.md → signingresearch1812.md} +0 -0
- /package/docs/{tauri.glib.risk.1.8.11.md → tauriglibrisk1811.md} +0 -0
- /package/docs/{todo.1.8.18.md → todo1818.md} +0 -0
- /package/docs/{todo.1.8.19.md → todo1819.md} +0 -0
- /package/docs/{toolchainresearch.1.8.9.md → toolchainresearch189.md} +0 -0
- /package/docs/{web.duplicatecleanup.md → webduplicatecleanup.md} +0 -0
- /package/docs/{web.ideas.md → webideas.md} +0 -0
- /package/docs/{workflowimprovements.2026.08.19.md → workflowimprovements20260819.md} +0 -0
package/dist/localauth.d.ts
DELETED
|
@@ -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();
|