@camstack/system 1.2.375 → 1.2.377
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/dist/addon-runner.js +9 -4
- package/dist/addon-runner.mjs +10 -5
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/auth/external-identity-manager.d.ts +43 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-config-secrets.d.ts +21 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +2 -0
- package/dist/builtins/device-manager/device-manager.addon.js +119 -6
- package/dist/builtins/device-manager/device-manager.addon.mjs +119 -6
- package/dist/builtins/device-manager/device-projection.d.ts +2 -1
- package/dist/builtins/device-manager/device-provider-context.d.ts +3 -0
- package/dist/builtins/device-manager/secret-config-paths.d.ts +24 -0
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.d.ts +1 -0
- package/dist/builtins/keychain/index.js +1416 -0
- package/dist/builtins/keychain/index.mjs +1409 -0
- package/dist/builtins/keychain/key-sources.d.ts +37 -0
- package/dist/builtins/keychain/keychain-crypto.d.ts +28 -0
- package/dist/builtins/keychain/keychain-service.d.ts +103 -0
- package/dist/builtins/keychain/keychain-store.d.ts +82 -0
- package/dist/builtins/keychain/keychain-usage.d.ts +6 -0
- package/dist/builtins/keychain/keychain.addon.d.ts +11 -0
- package/dist/builtins/keychain/platform-key-source.d.ts +24 -0
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/auth-schema.d.ts +14 -0
- package/dist/builtins/local-auth/local-auth.addon.d.ts +1 -0
- package/dist/builtins/local-auth/local-auth.addon.js +1358 -1129
- package/dist/builtins/local-auth/local-auth.addon.mjs +1358 -1129
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/location-store.d.ts +6 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +15 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +74 -14
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +74 -14
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-B9kziY4L.mjs → child-cap-dispatch-CgmOo0yl.mjs} +184 -29
- package/dist/{child-cap-dispatch-Bfdgkbq3.js → child-cap-dispatch-DlDSoqm1.js} +212 -27
- package/dist/{composition-sources-DYlXX2qW.js → composition-sources-BNyXPSs_.js} +1 -1
- package/dist/{composition-sources-BfZgiTYD.mjs → composition-sources-D39bl0nP.mjs} +1 -1
- package/dist/{dist-ZdSg09GJ.mjs → dist-BWNoS-1S.mjs} +2454 -108
- package/dist/{dist-CNbnikIh.js → dist-D_a9mUiM.js} +2525 -107
- package/dist/{event-loop-stall-monitor-DKcYBHdH.mjs → event-loop-stall-monitor-ClPhfx7Y.mjs} +1 -1
- package/dist/{event-loop-stall-monitor-CpEH3CoV.js → event-loop-stall-monitor-DuOn8H1n.js} +1 -1
- package/dist/{hub-cap-forward-BdT320dq.mjs → hub-cap-forward-CH6_VWVO.mjs} +13 -4
- package/dist/{hub-cap-forward-DmHNjbB0.js → hub-cap-forward-COCmYaup.js} +13 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20 -6
- package/dist/index.mjs +13 -7
- package/dist/kernel/config-schema.d.ts +4 -0
- package/dist/kernel/moleculer/child-addon-call-dispatch.d.ts +6 -0
- package/dist/kernel/transport/cap-route-resolver.d.ts +19 -0
- package/dist/kernel/transport/hub-cap-forward.d.ts +22 -1
- package/dist/kernel/transport/index.d.ts +2 -2
- package/dist/kernel/transport/local-child-registry.d.ts +4 -3
- package/dist/logging/log-redactor.d.ts +9 -0
- package/dist/platform-key-source-BWlq0jjn.mjs +23 -0
- package/dist/platform-key-source-Di7HrlGR.js +40 -0
- package/dist/{retired-settings-keys-CcWvdy6u.js → retired-settings-keys-CDyOGAje.js} +1 -1
- package/dist/{retired-settings-keys-B4ZeKrbR.mjs → retired-settings-keys-iGcmzwLV.mjs} +1 -1
- package/package.json +17 -1
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D_a9mUiM.js");
|
|
7
7
|
let zod = require("zod");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
@@ -6334,7 +6334,9 @@ var AuthManager = class {
|
|
|
6334
6334
|
...payload.integrationId !== void 0 ? { integrationId: payload.integrationId } : {},
|
|
6335
6335
|
...payload.jti !== void 0 ? { jti: payload.jti } : {},
|
|
6336
6336
|
...payload.sessionId !== void 0 ? { sessionId: payload.sessionId } : {},
|
|
6337
|
-
...payload.refreshTtl !== void 0 ? { refreshTtl: payload.refreshTtl } : {}
|
|
6337
|
+
...payload.refreshTtl !== void 0 ? { refreshTtl: payload.refreshTtl } : {},
|
|
6338
|
+
...payload.linkIntent !== void 0 ? { linkIntent: payload.linkIntent } : {},
|
|
6339
|
+
...payload.issuer !== void 0 ? { issuer: payload.issuer } : {}
|
|
6338
6340
|
};
|
|
6339
6341
|
/**
|
|
6340
6342
|
* `'never'` mints with NO `exp` claim, and the option must be OMITTED to do
|
|
@@ -6376,6 +6378,8 @@ var AuthManager = class {
|
|
|
6376
6378
|
const integrationId = typeof decoded["integrationId"] === "string" ? decoded["integrationId"] : void 0;
|
|
6377
6379
|
const jti = typeof decoded["jti"] === "string" ? decoded["jti"] : void 0;
|
|
6378
6380
|
const sessionId = typeof decoded["sessionId"] === "string" ? decoded["sessionId"] : void 0;
|
|
6381
|
+
const linkIntent = typeof decoded["linkIntent"] === "string" ? decoded["linkIntent"] : void 0;
|
|
6382
|
+
const issuer = typeof decoded["issuer"] === "string" ? decoded["issuer"] : void 0;
|
|
6379
6383
|
const rawRefreshTtl = decoded["refreshTtl"];
|
|
6380
6384
|
const refreshTtl = rawRefreshTtl === "never" || typeof rawRefreshTtl === "number" && rawRefreshTtl > 0 ? rawRefreshTtl : void 0;
|
|
6381
6385
|
return {
|
|
@@ -6391,7 +6395,9 @@ var AuthManager = class {
|
|
|
6391
6395
|
...integrationId !== void 0 ? { integrationId } : {},
|
|
6392
6396
|
...jti !== void 0 ? { jti } : {},
|
|
6393
6397
|
...sessionId !== void 0 ? { sessionId } : {},
|
|
6394
|
-
...refreshTtl !== void 0 ? { refreshTtl } : {}
|
|
6398
|
+
...refreshTtl !== void 0 ? { refreshTtl } : {},
|
|
6399
|
+
...linkIntent !== void 0 ? { linkIntent } : {},
|
|
6400
|
+
...issuer !== void 0 ? { issuer } : {}
|
|
6395
6401
|
};
|
|
6396
6402
|
} catch {
|
|
6397
6403
|
return null;
|
|
@@ -6772,168 +6778,652 @@ var UserManager = class {
|
|
|
6772
6778
|
if (!user) return false;
|
|
6773
6779
|
return this.auth.comparePassword(password, user.passwordHash);
|
|
6774
6780
|
}
|
|
6775
|
-
}, i$2 = class extends Error {
|
|
6776
|
-
constructor(e, t) {
|
|
6777
|
-
super(e, t), this.name = "OTPError";
|
|
6778
|
-
}
|
|
6779
|
-
}, G$3 = class extends i$2 {
|
|
6780
|
-
constructor(e) {
|
|
6781
|
-
super(e), this.name = "PluginError";
|
|
6782
|
-
}
|
|
6783
|
-
}, Y$3 = class extends G$3 {
|
|
6784
|
-
constructor() {
|
|
6785
|
-
super("Crypto plugin is required."), this.name = "CryptoPluginMissingError";
|
|
6786
|
-
}
|
|
6787
|
-
}, L$2 = class extends G$3 {
|
|
6788
|
-
constructor() {
|
|
6789
|
-
super("Base32 plugin is required."), this.name = "Base32PluginMissingError";
|
|
6790
|
-
}
|
|
6791
|
-
}, a$2 = class extends i$2 {
|
|
6792
|
-
constructor(e) {
|
|
6793
|
-
super(e), this.name = "ConfigurationError";
|
|
6794
|
-
}
|
|
6795
6781
|
};
|
|
6796
|
-
new TextEncoder();
|
|
6797
|
-
new TextDecoder();
|
|
6798
|
-
var ur$3 = 3600, pr$3 = Number.MAX_SAFE_INTEGER, er$3 = Symbol("otplib.guardrails.override");
|
|
6799
|
-
function y$2(r, e, t) {
|
|
6800
|
-
if (typeof e != "number" || !Number.isSafeInteger(e)) throw new a$2(`Guardrail '${r}' must be a safe integer`);
|
|
6801
|
-
if (e < t) throw new a$2(`Guardrail '${r}' must be >= ${t}`);
|
|
6802
|
-
}
|
|
6803
|
-
var d$5 = Object.freeze({
|
|
6804
|
-
MIN_SECRET_BYTES: 16,
|
|
6805
|
-
MAX_SECRET_BYTES: 64,
|
|
6806
|
-
MIN_PERIOD: 1,
|
|
6807
|
-
MAX_PERIOD: ur$3,
|
|
6808
|
-
MAX_COUNTER: pr$3,
|
|
6809
|
-
MAX_WINDOW: 99,
|
|
6810
|
-
[er$3]: !1
|
|
6811
|
-
});
|
|
6812
|
-
function hr$2(r) {
|
|
6813
|
-
if (!r) return d$5;
|
|
6814
|
-
r.MIN_SECRET_BYTES !== void 0 && y$2("MIN_SECRET_BYTES", r.MIN_SECRET_BYTES, 1), r.MAX_SECRET_BYTES !== void 0 && y$2("MAX_SECRET_BYTES", r.MAX_SECRET_BYTES, 1), r.MIN_PERIOD !== void 0 && y$2("MIN_PERIOD", r.MIN_PERIOD, 1), r.MAX_PERIOD !== void 0 && y$2("MAX_PERIOD", r.MAX_PERIOD, 1), r.MAX_COUNTER !== void 0 && y$2("MAX_COUNTER", r.MAX_COUNTER, 0), r.MAX_WINDOW !== void 0 && y$2("MAX_WINDOW", r.MAX_WINDOW, 1);
|
|
6815
|
-
let e = {
|
|
6816
|
-
...d$5,
|
|
6817
|
-
...r
|
|
6818
|
-
};
|
|
6819
|
-
if (e.MIN_SECRET_BYTES > e.MAX_SECRET_BYTES) throw new a$2("Guardrail 'MIN_SECRET_BYTES' must be <= 'MAX_SECRET_BYTES'");
|
|
6820
|
-
if (e.MIN_PERIOD > e.MAX_PERIOD) throw new a$2("Guardrail 'MIN_PERIOD' must be <= 'MAX_PERIOD'");
|
|
6821
|
-
return Object.freeze({
|
|
6822
|
-
...e,
|
|
6823
|
-
[er$3]: !0
|
|
6824
|
-
});
|
|
6825
|
-
}
|
|
6826
|
-
function Dr(r) {
|
|
6827
|
-
let { crypto: e, base32: t, length: o = 20 } = r;
|
|
6828
|
-
dr$2(e), nr$2(t);
|
|
6829
|
-
let n = e.randomBytes(o);
|
|
6830
|
-
return t.encode(n, { padding: !1 });
|
|
6831
|
-
}
|
|
6832
|
-
function dr$2(r) {
|
|
6833
|
-
if (!r) throw new Y$3();
|
|
6834
|
-
}
|
|
6835
|
-
function nr$2(r) {
|
|
6836
|
-
if (!r) throw new L$2();
|
|
6837
|
-
}
|
|
6838
6782
|
//#endregion
|
|
6839
|
-
//#region
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6783
|
+
//#region src/builtins/local-auth/auth-schema.ts
|
|
6784
|
+
/**
|
|
6785
|
+
* @durable class=config owner=local-auth
|
|
6786
|
+
* write="an operator creates a user, or `ensureAdminExists` seeds the configured
|
|
6787
|
+
* admin on first boot (or resets its password on the operator's one-shot
|
|
6788
|
+
* RESET_ADMIN_PASSWORD trigger); updated on scope, isAdmin and password edits"
|
|
6789
|
+
* retention="none — a row goes only when an operator deletes the user. A
|
|
6790
|
+
* `declareCollection` that adds a PK/UNIQUE/NOT-NULL-without-default column
|
|
6791
|
+
* REBUILDS the table: every account is wiped and only the configured admin
|
|
6792
|
+
* comes back on the next boot."
|
|
6793
|
+
*/
|
|
6794
|
+
var USERS_COLLECTION = "users";
|
|
6795
|
+
/**
|
|
6796
|
+
* @durable class=config owner=local-auth
|
|
6797
|
+
* write="an operator mints an API key; `validateToken` rewrites `lastUsedAt` on
|
|
6798
|
+
* every request the key authorises"
|
|
6799
|
+
* retention="none — an explicit `revoke` is the only delete. API keys have no
|
|
6800
|
+
* expiry column and no sweep, so a key lives until someone removes it (or the
|
|
6801
|
+
* destructive `declareCollection` rebuild takes the table)."
|
|
6802
|
+
*/
|
|
6803
|
+
var API_KEYS_COLLECTION = "api_keys";
|
|
6804
|
+
/**
|
|
6805
|
+
* @durable class=config owner=local-auth
|
|
6806
|
+
* write="an operator or the CLI mints a scoped token; `validate` fires
|
|
6807
|
+
* `updateLastUsed` on every accepted call"
|
|
6808
|
+
* retention="`revoke` deletes one. The daily sweep `reapExpired` drops rows past
|
|
6809
|
+
* `expiresAt` — a NULL expiry is a deliberate never-expires token and BETWEEN
|
|
6810
|
+
* never matches it. `cleanupOrphans` drops tokens owned by the legacy 'system'
|
|
6811
|
+
* id or by a user that no longer exists."
|
|
6812
|
+
*/
|
|
6813
|
+
var SCOPED_TOKENS_COLLECTION = "scoped_tokens";
|
|
6814
|
+
/**
|
|
6815
|
+
* @durable class=ledger owner=local-auth
|
|
6816
|
+
* write="one row per redeemed OAuth code — every completed account link through
|
|
6817
|
+
* `oauthExchangeCode`; `touch` rewrites `lastUsedAt` on each refresh"
|
|
6818
|
+
* retention="ONLY revoked rows are pruned: the daily sweep deletes sessions
|
|
6819
|
+
* revoked more than OAUTH_REVOKED_RETENTION_MS (30 d) ago. An ACTIVE session
|
|
6820
|
+
* has `revokedAt` NULL, never expires and is never swept — the table grows one
|
|
6821
|
+
* row per login until an operator revokes the link."
|
|
6822
|
+
*/
|
|
6823
|
+
var OAUTH_SESSIONS_COLLECTION = "oauth_sessions";
|
|
6824
|
+
/**
|
|
6825
|
+
* TOTP enrollment table — split off from `users` to avoid the
|
|
6826
|
+
* destructive drop-+-recreate migration `ensureTable` performs when a
|
|
6827
|
+
* new column is added to a typed schema (see `sqlite-settings-backend.ts`).
|
|
6828
|
+
*
|
|
6829
|
+
* Lifecycle:
|
|
6830
|
+
* - Setup: row inserted with `confirmedAt = null` and a fresh secret.
|
|
6831
|
+
* - Confirm: a valid code from the secret flips `confirmedAt` to now().
|
|
6832
|
+
* - Disable: the row is deleted (next setup starts fresh).
|
|
6833
|
+
*
|
|
6834
|
+
* "Enabled" means `confirmedAt != null`. A half-enrolled row (`confirmedAt
|
|
6835
|
+
* = null`) is INACTIVE — `verifyTotp` returns false until confirmation.
|
|
6836
|
+
*
|
|
6837
|
+
* @durable class=config owner=local-auth
|
|
6838
|
+
* write="`setup` inserts a fresh secret for the user (deleting any pending row
|
|
6839
|
+
* first); `confirm` flips `confirmedAt` once a valid code proves possession"
|
|
6840
|
+
* retention="`disable` deletes the row, and `setup` deletes before re-inserting.
|
|
6841
|
+
* Nothing else prunes — deleting a USER does not cascade here, so their
|
|
6842
|
+
* enrollment row outlives the account."
|
|
6843
|
+
*/
|
|
6844
|
+
var USER_TOTP_COLLECTION = "user_totp";
|
|
6845
|
+
/**
|
|
6846
|
+
* External sign-in identities linked to local users (D782): one row per
|
|
6847
|
+
* `(provider, subject)` — the row id IS the identity, so one identity maps to
|
|
6848
|
+
* one user by primary key. Written only by the identity's owner completing a
|
|
6849
|
+
* "Link" flow from their profile (bound to their session).
|
|
6850
|
+
*
|
|
6851
|
+
* @durable class=config owner=local-auth
|
|
6852
|
+
* write="`ExternalIdentityManager.link` when a signed-in user completes a
|
|
6853
|
+
* link flow; `unlink` from the profile or the Users page"
|
|
6854
|
+
* retention="`unlink` deletes one row; deleting the user deletes all of its
|
|
6855
|
+
* rows (`deleteUser` → `removeAllForUser`). A row whose provider instance
|
|
6856
|
+
* was renamed or removed stays, inert, until someone unlinks it."
|
|
6857
|
+
*/
|
|
6858
|
+
var USER_EXTERNAL_IDENTITIES_COLLECTION = "user_external_identities";
|
|
6859
|
+
var USERS_COLUMNS = [
|
|
6860
|
+
{
|
|
6861
|
+
name: "id",
|
|
6862
|
+
type: "TEXT",
|
|
6863
|
+
primaryKey: true,
|
|
6864
|
+
notNull: true
|
|
6865
|
+
},
|
|
6866
|
+
{
|
|
6867
|
+
name: "username",
|
|
6868
|
+
type: "TEXT",
|
|
6869
|
+
notNull: true,
|
|
6870
|
+
unique: true
|
|
6871
|
+
},
|
|
6872
|
+
{
|
|
6873
|
+
name: "passwordHash",
|
|
6874
|
+
type: "TEXT",
|
|
6875
|
+
notNull: true
|
|
6876
|
+
},
|
|
6877
|
+
{
|
|
6878
|
+
name: "isAdmin",
|
|
6879
|
+
type: "BOOLEAN",
|
|
6880
|
+
notNull: true
|
|
6881
|
+
},
|
|
6882
|
+
{
|
|
6883
|
+
name: "allowedProviders",
|
|
6884
|
+
type: "JSON"
|
|
6885
|
+
},
|
|
6886
|
+
{
|
|
6887
|
+
name: "allowedDevices",
|
|
6888
|
+
type: "JSON"
|
|
6889
|
+
},
|
|
6890
|
+
{
|
|
6891
|
+
name: "scopes",
|
|
6892
|
+
type: "JSON"
|
|
6893
|
+
},
|
|
6894
|
+
{
|
|
6895
|
+
name: "createdAt",
|
|
6896
|
+
type: "INTEGER",
|
|
6897
|
+
notNull: true
|
|
6898
|
+
},
|
|
6899
|
+
{
|
|
6900
|
+
name: "updatedAt",
|
|
6901
|
+
type: "INTEGER",
|
|
6902
|
+
notNull: true
|
|
6851
6903
|
}
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6904
|
+
];
|
|
6905
|
+
var API_KEYS_COLUMNS = [
|
|
6906
|
+
{
|
|
6907
|
+
name: "id",
|
|
6908
|
+
type: "TEXT",
|
|
6909
|
+
primaryKey: true,
|
|
6910
|
+
notNull: true
|
|
6911
|
+
},
|
|
6912
|
+
{
|
|
6913
|
+
name: "label",
|
|
6914
|
+
type: "TEXT",
|
|
6915
|
+
notNull: true
|
|
6916
|
+
},
|
|
6917
|
+
{
|
|
6918
|
+
name: "isAdmin",
|
|
6919
|
+
type: "BOOLEAN",
|
|
6920
|
+
notNull: true
|
|
6921
|
+
},
|
|
6922
|
+
{
|
|
6923
|
+
name: "tokenHash",
|
|
6924
|
+
type: "TEXT",
|
|
6925
|
+
notNull: true,
|
|
6926
|
+
unique: true
|
|
6927
|
+
},
|
|
6928
|
+
{
|
|
6929
|
+
name: "tokenPrefix",
|
|
6930
|
+
type: "TEXT",
|
|
6931
|
+
notNull: true
|
|
6932
|
+
},
|
|
6933
|
+
{
|
|
6934
|
+
name: "allowedProviders",
|
|
6935
|
+
type: "JSON"
|
|
6936
|
+
},
|
|
6937
|
+
{
|
|
6938
|
+
name: "allowedDevices",
|
|
6939
|
+
type: "JSON"
|
|
6940
|
+
},
|
|
6941
|
+
{
|
|
6942
|
+
name: "createdAt",
|
|
6943
|
+
type: "INTEGER",
|
|
6944
|
+
notNull: true
|
|
6945
|
+
},
|
|
6946
|
+
{
|
|
6947
|
+
name: "lastUsedAt",
|
|
6948
|
+
type: "INTEGER"
|
|
6855
6949
|
}
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6950
|
+
];
|
|
6951
|
+
var USER_TOTP_COLUMNS = [
|
|
6952
|
+
{
|
|
6953
|
+
name: "userId",
|
|
6954
|
+
type: "TEXT",
|
|
6955
|
+
primaryKey: true,
|
|
6956
|
+
notNull: true
|
|
6957
|
+
},
|
|
6958
|
+
{
|
|
6959
|
+
name: "secret",
|
|
6960
|
+
type: "TEXT",
|
|
6961
|
+
notNull: true
|
|
6962
|
+
},
|
|
6963
|
+
{
|
|
6964
|
+
name: "confirmedAt",
|
|
6965
|
+
type: "INTEGER"
|
|
6966
|
+
},
|
|
6967
|
+
{
|
|
6968
|
+
name: "createdAt",
|
|
6969
|
+
type: "INTEGER",
|
|
6970
|
+
notNull: true
|
|
6859
6971
|
}
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6972
|
+
];
|
|
6973
|
+
var USER_EXTERNAL_IDENTITIES_COLUMNS = [
|
|
6974
|
+
{
|
|
6975
|
+
name: "id",
|
|
6976
|
+
type: "TEXT",
|
|
6977
|
+
primaryKey: true,
|
|
6978
|
+
notNull: true
|
|
6979
|
+
},
|
|
6980
|
+
{
|
|
6981
|
+
name: "provider",
|
|
6982
|
+
type: "TEXT",
|
|
6983
|
+
notNull: true
|
|
6984
|
+
},
|
|
6985
|
+
{
|
|
6986
|
+
name: "subject",
|
|
6987
|
+
type: "TEXT",
|
|
6988
|
+
notNull: true
|
|
6989
|
+
},
|
|
6990
|
+
{
|
|
6991
|
+
name: "issuer",
|
|
6992
|
+
type: "TEXT",
|
|
6993
|
+
notNull: true
|
|
6994
|
+
},
|
|
6995
|
+
{
|
|
6996
|
+
name: "userId",
|
|
6997
|
+
type: "TEXT",
|
|
6998
|
+
notNull: true
|
|
6999
|
+
},
|
|
7000
|
+
{
|
|
7001
|
+
name: "label",
|
|
7002
|
+
type: "TEXT"
|
|
7003
|
+
},
|
|
7004
|
+
{
|
|
7005
|
+
name: "linkedAt",
|
|
7006
|
+
type: "INTEGER",
|
|
7007
|
+
notNull: true
|
|
6863
7008
|
}
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
7009
|
+
];
|
|
7010
|
+
var SCOPED_TOKENS_COLUMNS = [
|
|
7011
|
+
{
|
|
7012
|
+
name: "id",
|
|
7013
|
+
type: "TEXT",
|
|
7014
|
+
primaryKey: true,
|
|
7015
|
+
notNull: true
|
|
7016
|
+
},
|
|
7017
|
+
{
|
|
7018
|
+
name: "userId",
|
|
7019
|
+
type: "TEXT",
|
|
7020
|
+
notNull: true
|
|
7021
|
+
},
|
|
7022
|
+
{
|
|
7023
|
+
name: "name",
|
|
7024
|
+
type: "TEXT",
|
|
7025
|
+
notNull: true
|
|
7026
|
+
},
|
|
7027
|
+
{
|
|
7028
|
+
name: "tokenHash",
|
|
7029
|
+
type: "TEXT",
|
|
7030
|
+
notNull: true,
|
|
7031
|
+
unique: true
|
|
7032
|
+
},
|
|
7033
|
+
{
|
|
7034
|
+
name: "tokenPrefix",
|
|
7035
|
+
type: "TEXT",
|
|
7036
|
+
notNull: true
|
|
7037
|
+
},
|
|
7038
|
+
{
|
|
7039
|
+
name: "scopes",
|
|
7040
|
+
type: "JSON"
|
|
7041
|
+
},
|
|
7042
|
+
{
|
|
7043
|
+
name: "expiresAt",
|
|
7044
|
+
type: "INTEGER"
|
|
7045
|
+
},
|
|
7046
|
+
{
|
|
7047
|
+
name: "lastUsedAt",
|
|
7048
|
+
type: "INTEGER"
|
|
7049
|
+
},
|
|
7050
|
+
{
|
|
7051
|
+
name: "createdAt",
|
|
7052
|
+
type: "INTEGER",
|
|
7053
|
+
notNull: true
|
|
6867
7054
|
}
|
|
6868
|
-
|
|
7055
|
+
];
|
|
7056
|
+
var OAUTH_SESSIONS_COLUMNS = [
|
|
7057
|
+
{
|
|
7058
|
+
name: "id",
|
|
7059
|
+
type: "TEXT",
|
|
7060
|
+
primaryKey: true,
|
|
7061
|
+
notNull: true
|
|
7062
|
+
},
|
|
7063
|
+
{
|
|
7064
|
+
name: "userId",
|
|
7065
|
+
type: "TEXT",
|
|
7066
|
+
notNull: true
|
|
7067
|
+
},
|
|
7068
|
+
{
|
|
7069
|
+
name: "username",
|
|
7070
|
+
type: "TEXT",
|
|
7071
|
+
notNull: true
|
|
7072
|
+
},
|
|
7073
|
+
{
|
|
7074
|
+
name: "integrationId",
|
|
7075
|
+
type: "TEXT",
|
|
7076
|
+
notNull: true
|
|
7077
|
+
},
|
|
7078
|
+
{
|
|
7079
|
+
name: "scopes",
|
|
7080
|
+
type: "JSON"
|
|
7081
|
+
},
|
|
7082
|
+
{
|
|
7083
|
+
name: "createdAt",
|
|
7084
|
+
type: "INTEGER",
|
|
7085
|
+
notNull: true
|
|
7086
|
+
},
|
|
7087
|
+
{
|
|
7088
|
+
name: "lastUsedAt",
|
|
7089
|
+
type: "INTEGER",
|
|
7090
|
+
notNull: true
|
|
7091
|
+
},
|
|
7092
|
+
{
|
|
7093
|
+
name: "revokedAt",
|
|
7094
|
+
type: "INTEGER"
|
|
7095
|
+
}
|
|
7096
|
+
];
|
|
7097
|
+
/**
|
|
7098
|
+
* Declare every auth collection with the typed schema. Called by
|
|
7099
|
+
* `LocalAuthAddon.onInitialize` before constructing the per-collection
|
|
7100
|
+
* managers. Idempotent — `declareCollection` is a no-op when the
|
|
7101
|
+
* collection has already been declared (same shape).
|
|
7102
|
+
*/
|
|
7103
|
+
async function declareAuthSchema(store) {
|
|
7104
|
+
await store.declareCollection.mutate({
|
|
7105
|
+
collection: USERS_COLLECTION,
|
|
7106
|
+
columns: USERS_COLUMNS
|
|
7107
|
+
});
|
|
7108
|
+
await store.declareCollection.mutate({
|
|
7109
|
+
collection: API_KEYS_COLLECTION,
|
|
7110
|
+
columns: API_KEYS_COLUMNS,
|
|
7111
|
+
indexes: [{
|
|
7112
|
+
name: "idx_api_keys_token_prefix",
|
|
7113
|
+
columns: ["tokenPrefix"]
|
|
7114
|
+
}]
|
|
7115
|
+
});
|
|
7116
|
+
await store.declareCollection.mutate({
|
|
7117
|
+
collection: SCOPED_TOKENS_COLLECTION,
|
|
7118
|
+
columns: SCOPED_TOKENS_COLUMNS,
|
|
7119
|
+
indexes: [{
|
|
7120
|
+
name: "idx_scoped_tokens_user_id",
|
|
7121
|
+
columns: ["userId"]
|
|
7122
|
+
}]
|
|
7123
|
+
});
|
|
7124
|
+
await store.declareCollection.mutate({
|
|
7125
|
+
collection: USER_TOTP_COLLECTION,
|
|
7126
|
+
columns: USER_TOTP_COLUMNS
|
|
7127
|
+
});
|
|
7128
|
+
await store.declareCollection.mutate({
|
|
7129
|
+
collection: USER_EXTERNAL_IDENTITIES_COLLECTION,
|
|
7130
|
+
columns: USER_EXTERNAL_IDENTITIES_COLUMNS,
|
|
7131
|
+
indexes: [{
|
|
7132
|
+
name: "idx_user_external_identities_user_id",
|
|
7133
|
+
columns: ["userId"]
|
|
7134
|
+
}]
|
|
7135
|
+
});
|
|
7136
|
+
await store.declareCollection.mutate({
|
|
7137
|
+
collection: OAUTH_SESSIONS_COLLECTION,
|
|
7138
|
+
columns: OAUTH_SESSIONS_COLUMNS,
|
|
7139
|
+
indexes: [{
|
|
7140
|
+
name: "idx_oauth_sessions_user_id",
|
|
7141
|
+
columns: ["userId"]
|
|
7142
|
+
}]
|
|
7143
|
+
});
|
|
7144
|
+
}
|
|
7145
|
+
//#endregion
|
|
7146
|
+
//#region src/auth/external-identity-manager.ts
|
|
7147
|
+
/** The identity is already linked to a different user. */
|
|
7148
|
+
var ExternalIdentityConflictError = class extends Error {
|
|
6869
7149
|
constructor() {
|
|
6870
|
-
super("
|
|
7150
|
+
super("This sign-in account is already linked to another CamStack user");
|
|
7151
|
+
this.name = "ExternalIdentityConflictError";
|
|
6871
7152
|
}
|
|
6872
|
-
}
|
|
6873
|
-
|
|
6874
|
-
|
|
7153
|
+
};
|
|
7154
|
+
function rowId(key) {
|
|
7155
|
+
return JSON.stringify([key.provider, key.subject]);
|
|
7156
|
+
}
|
|
7157
|
+
function parseRow$1(data) {
|
|
7158
|
+
const { provider, subject, issuer, userId, label, linkedAt } = data;
|
|
7159
|
+
if (typeof provider !== "string" || typeof subject !== "string") return null;
|
|
7160
|
+
if (typeof issuer !== "string") return null;
|
|
7161
|
+
if (typeof userId !== "string" || typeof linkedAt !== "number") return null;
|
|
7162
|
+
return {
|
|
7163
|
+
provider,
|
|
7164
|
+
subject,
|
|
7165
|
+
issuer,
|
|
7166
|
+
userId,
|
|
7167
|
+
linkedAt,
|
|
7168
|
+
...typeof label === "string" && label.length > 0 ? { label } : {}
|
|
7169
|
+
};
|
|
7170
|
+
}
|
|
7171
|
+
var ExternalIdentityManager = class {
|
|
7172
|
+
store;
|
|
7173
|
+
constructor(store) {
|
|
7174
|
+
this.store = store;
|
|
6875
7175
|
}
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
7176
|
+
async find(key) {
|
|
7177
|
+
const rows = await this.store.query.query({
|
|
7178
|
+
collection: USER_EXTERNAL_IDENTITIES_COLLECTION,
|
|
7179
|
+
filter: { where: {
|
|
7180
|
+
provider: key.provider,
|
|
7181
|
+
subject: key.subject
|
|
7182
|
+
} }
|
|
7183
|
+
});
|
|
7184
|
+
for (const row of rows) {
|
|
7185
|
+
const parsed = parseRow$1(row.data);
|
|
7186
|
+
if (parsed !== null) return parsed;
|
|
7187
|
+
}
|
|
7188
|
+
return null;
|
|
6879
7189
|
}
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
7190
|
+
/** The link of `(provider, subject)`, or `null`. */
|
|
7191
|
+
async findLink(provider, subject) {
|
|
7192
|
+
return this.find({
|
|
7193
|
+
provider,
|
|
7194
|
+
subject
|
|
7195
|
+
});
|
|
6883
7196
|
}
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
7197
|
+
/** The user `(provider, subject)` is linked to, or `null`. */
|
|
7198
|
+
async findUser(provider, subject) {
|
|
7199
|
+
return (await this.find({
|
|
7200
|
+
provider,
|
|
7201
|
+
subject
|
|
7202
|
+
}))?.userId ?? null;
|
|
6887
7203
|
}
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
7204
|
+
async listForUser(userId) {
|
|
7205
|
+
return (await this.store.query.query({
|
|
7206
|
+
collection: USER_EXTERNAL_IDENTITIES_COLLECTION,
|
|
7207
|
+
filter: { where: { userId } }
|
|
7208
|
+
})).flatMap((row) => {
|
|
7209
|
+
const parsed = parseRow$1(row.data);
|
|
7210
|
+
return parsed === null ? [] : [parsed];
|
|
7211
|
+
});
|
|
6891
7212
|
}
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
7213
|
+
/**
|
|
7214
|
+
* Link `(provider, subject)` to `userId`. Idempotent for the same user;
|
|
7215
|
+
* {@link ExternalIdentityConflictError} when another user holds it.
|
|
7216
|
+
*/
|
|
7217
|
+
async link(input) {
|
|
7218
|
+
const existing = await this.find(input);
|
|
7219
|
+
if (existing !== null) {
|
|
7220
|
+
if (existing.userId === input.userId) return;
|
|
7221
|
+
throw new ExternalIdentityConflictError();
|
|
7222
|
+
}
|
|
7223
|
+
const record = {
|
|
7224
|
+
provider: input.provider,
|
|
7225
|
+
subject: input.subject,
|
|
7226
|
+
issuer: input.issuer,
|
|
7227
|
+
userId: input.userId,
|
|
7228
|
+
linkedAt: Date.now(),
|
|
7229
|
+
...input.label !== void 0 && input.label.length > 0 ? { label: input.label } : {}
|
|
7230
|
+
};
|
|
7231
|
+
try {
|
|
7232
|
+
await this.store.insert.mutate({
|
|
7233
|
+
collection: USER_EXTERNAL_IDENTITIES_COLLECTION,
|
|
7234
|
+
record: {
|
|
7235
|
+
id: rowId(input),
|
|
7236
|
+
data: { ...record }
|
|
7237
|
+
}
|
|
7238
|
+
});
|
|
7239
|
+
} catch (err) {
|
|
7240
|
+
const winner = await this.find(input);
|
|
7241
|
+
if (winner?.userId === input.userId) return;
|
|
7242
|
+
if (winner !== null) throw new ExternalIdentityConflictError();
|
|
7243
|
+
throw err;
|
|
7244
|
+
}
|
|
6895
7245
|
}
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
7246
|
+
/** Remove one link of `userId`; `false` when that user did not hold it. */
|
|
7247
|
+
async unlink(input) {
|
|
7248
|
+
const existing = await this.find(input);
|
|
7249
|
+
if (existing === null || existing.userId !== input.userId) return false;
|
|
7250
|
+
await this.store.delete.mutate({
|
|
7251
|
+
collection: USER_EXTERNAL_IDENTITIES_COLLECTION,
|
|
7252
|
+
key: rowId(input)
|
|
7253
|
+
});
|
|
7254
|
+
return true;
|
|
6899
7255
|
}
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
7256
|
+
/** Delete every link of a user being deleted. Returns how many were removed. */
|
|
7257
|
+
async removeAllForUser(userId) {
|
|
7258
|
+
const links = await this.listForUser(userId);
|
|
7259
|
+
for (const link of links) await this.store.delete.mutate({
|
|
7260
|
+
collection: USER_EXTERNAL_IDENTITIES_COLLECTION,
|
|
7261
|
+
key: rowId(link)
|
|
7262
|
+
});
|
|
7263
|
+
return links.length;
|
|
6903
7264
|
}
|
|
6904
|
-
},
|
|
6905
|
-
constructor() {
|
|
6906
|
-
super(
|
|
7265
|
+
}, i$2 = class extends Error {
|
|
7266
|
+
constructor(e, t) {
|
|
7267
|
+
super(e, t), this.name = "OTPError";
|
|
6907
7268
|
}
|
|
6908
|
-
}, G$
|
|
7269
|
+
}, G$3 = class extends i$2 {
|
|
6909
7270
|
constructor(e) {
|
|
6910
7271
|
super(e), this.name = "PluginError";
|
|
6911
7272
|
}
|
|
6912
|
-
}, Y$
|
|
7273
|
+
}, Y$3 = class extends G$3 {
|
|
6913
7274
|
constructor() {
|
|
6914
7275
|
super("Crypto plugin is required."), this.name = "CryptoPluginMissingError";
|
|
6915
7276
|
}
|
|
6916
|
-
}, L$
|
|
7277
|
+
}, L$2 = class extends G$3 {
|
|
6917
7278
|
constructor() {
|
|
6918
7279
|
super("Base32 plugin is required."), this.name = "Base32PluginMissingError";
|
|
6919
7280
|
}
|
|
6920
|
-
}, a$
|
|
7281
|
+
}, a$2 = class extends i$2 {
|
|
6921
7282
|
constructor(e) {
|
|
6922
7283
|
super(e), this.name = "ConfigurationError";
|
|
6923
7284
|
}
|
|
6924
|
-
}, W$1 = class extends a$1 {
|
|
6925
|
-
constructor() {
|
|
6926
|
-
super("Secret is required. Use generateSecret() to create one, or provide via { secret: 'YOUR_BASE32_SECRET' }"), this.name = "SecretMissingError";
|
|
6927
|
-
}
|
|
6928
7285
|
};
|
|
6929
7286
|
new TextEncoder();
|
|
6930
7287
|
new TextDecoder();
|
|
6931
|
-
var ur$
|
|
6932
|
-
function y$
|
|
6933
|
-
if (typeof e != "number" || !Number.isSafeInteger(e)) throw new a$
|
|
6934
|
-
if (e < t) throw new a$
|
|
7288
|
+
var ur$3 = 3600, pr$3 = Number.MAX_SAFE_INTEGER, er$3 = Symbol("otplib.guardrails.override");
|
|
7289
|
+
function y$2(r, e, t) {
|
|
7290
|
+
if (typeof e != "number" || !Number.isSafeInteger(e)) throw new a$2(`Guardrail '${r}' must be a safe integer`);
|
|
7291
|
+
if (e < t) throw new a$2(`Guardrail '${r}' must be >= ${t}`);
|
|
6935
7292
|
}
|
|
6936
|
-
var d$
|
|
7293
|
+
var d$5 = Object.freeze({
|
|
7294
|
+
MIN_SECRET_BYTES: 16,
|
|
7295
|
+
MAX_SECRET_BYTES: 64,
|
|
7296
|
+
MIN_PERIOD: 1,
|
|
7297
|
+
MAX_PERIOD: ur$3,
|
|
7298
|
+
MAX_COUNTER: pr$3,
|
|
7299
|
+
MAX_WINDOW: 99,
|
|
7300
|
+
[er$3]: !1
|
|
7301
|
+
});
|
|
7302
|
+
function hr$2(r) {
|
|
7303
|
+
if (!r) return d$5;
|
|
7304
|
+
r.MIN_SECRET_BYTES !== void 0 && y$2("MIN_SECRET_BYTES", r.MIN_SECRET_BYTES, 1), r.MAX_SECRET_BYTES !== void 0 && y$2("MAX_SECRET_BYTES", r.MAX_SECRET_BYTES, 1), r.MIN_PERIOD !== void 0 && y$2("MIN_PERIOD", r.MIN_PERIOD, 1), r.MAX_PERIOD !== void 0 && y$2("MAX_PERIOD", r.MAX_PERIOD, 1), r.MAX_COUNTER !== void 0 && y$2("MAX_COUNTER", r.MAX_COUNTER, 0), r.MAX_WINDOW !== void 0 && y$2("MAX_WINDOW", r.MAX_WINDOW, 1);
|
|
7305
|
+
let e = {
|
|
7306
|
+
...d$5,
|
|
7307
|
+
...r
|
|
7308
|
+
};
|
|
7309
|
+
if (e.MIN_SECRET_BYTES > e.MAX_SECRET_BYTES) throw new a$2("Guardrail 'MIN_SECRET_BYTES' must be <= 'MAX_SECRET_BYTES'");
|
|
7310
|
+
if (e.MIN_PERIOD > e.MAX_PERIOD) throw new a$2("Guardrail 'MIN_PERIOD' must be <= 'MAX_PERIOD'");
|
|
7311
|
+
return Object.freeze({
|
|
7312
|
+
...e,
|
|
7313
|
+
[er$3]: !0
|
|
7314
|
+
});
|
|
7315
|
+
}
|
|
7316
|
+
function Dr(r) {
|
|
7317
|
+
let { crypto: e, base32: t, length: o = 20 } = r;
|
|
7318
|
+
dr$2(e), nr$2(t);
|
|
7319
|
+
let n = e.randomBytes(o);
|
|
7320
|
+
return t.encode(n, { padding: !1 });
|
|
7321
|
+
}
|
|
7322
|
+
function dr$2(r) {
|
|
7323
|
+
if (!r) throw new Y$3();
|
|
7324
|
+
}
|
|
7325
|
+
function nr$2(r) {
|
|
7326
|
+
if (!r) throw new L$2();
|
|
7327
|
+
}
|
|
7328
|
+
//#endregion
|
|
7329
|
+
//#region ../../node_modules/@otplib/hotp/node_modules/@otplib/core/dist/index.js
|
|
7330
|
+
var i$1 = class extends Error {
|
|
7331
|
+
constructor(e, t) {
|
|
7332
|
+
super(e, t), this.name = "OTPError";
|
|
7333
|
+
}
|
|
7334
|
+
}, x$2 = class extends i$1 {
|
|
7335
|
+
constructor(e) {
|
|
7336
|
+
super(e), this.name = "SecretError";
|
|
7337
|
+
}
|
|
7338
|
+
}, h$1 = class extends x$2 {
|
|
7339
|
+
constructor(e, t) {
|
|
7340
|
+
super(`Secret must be at least ${e} bytes (${e * 8} bits), got ${t} bytes`), this.name = "SecretTooShortError";
|
|
7341
|
+
}
|
|
7342
|
+
}, P$1 = class extends x$2 {
|
|
7343
|
+
constructor(e, t) {
|
|
7344
|
+
super(`Secret must not exceed ${e} bytes, got ${t} bytes`), this.name = "SecretTooLongError";
|
|
7345
|
+
}
|
|
7346
|
+
}, m$2 = class extends i$1 {
|
|
7347
|
+
constructor(e) {
|
|
7348
|
+
super(e), this.name = "CounterError";
|
|
7349
|
+
}
|
|
7350
|
+
}, O$2 = class extends m$2 {
|
|
7351
|
+
constructor() {
|
|
7352
|
+
super("Counter must be non-negative"), this.name = "CounterNegativeError";
|
|
7353
|
+
}
|
|
7354
|
+
}, T$2 = class extends m$2 {
|
|
7355
|
+
constructor() {
|
|
7356
|
+
super("Counter exceeds maximum safe integer value"), this.name = "CounterOverflowError";
|
|
7357
|
+
}
|
|
7358
|
+
}, b$2 = class extends m$2 {
|
|
7359
|
+
constructor() {
|
|
7360
|
+
super("Counter must be a finite integer"), this.name = "CounterNotIntegerError";
|
|
7361
|
+
}
|
|
7362
|
+
}, R$2 = class extends i$1 {
|
|
7363
|
+
constructor(e) {
|
|
7364
|
+
super(e), this.name = "TokenError";
|
|
7365
|
+
}
|
|
7366
|
+
}, I$2 = class extends R$2 {
|
|
7367
|
+
constructor(e, t) {
|
|
7368
|
+
super(`Token must be ${e} digits, got ${t}`), this.name = "TokenLengthError";
|
|
7369
|
+
}
|
|
7370
|
+
}, M$1 = class extends R$2 {
|
|
7371
|
+
constructor() {
|
|
7372
|
+
super("Token must contain only digits"), this.name = "TokenFormatError";
|
|
7373
|
+
}
|
|
7374
|
+
}, N$1 = class extends i$1 {
|
|
7375
|
+
constructor(e, t) {
|
|
7376
|
+
super(e, t), this.name = "CryptoError";
|
|
7377
|
+
}
|
|
7378
|
+
}, c$2 = class extends N$1 {
|
|
7379
|
+
constructor(e, t) {
|
|
7380
|
+
super(`HMAC computation failed: ${e}`, t), this.name = "HMACError";
|
|
7381
|
+
}
|
|
7382
|
+
}, v$2 = class extends N$1 {
|
|
7383
|
+
constructor(e, t) {
|
|
7384
|
+
super(`Random byte generation failed: ${e}`, t), this.name = "RandomBytesError";
|
|
7385
|
+
}
|
|
7386
|
+
}, g$1 = class extends i$1 {
|
|
7387
|
+
constructor(e) {
|
|
7388
|
+
super(e), this.name = "CounterToleranceError";
|
|
7389
|
+
}
|
|
7390
|
+
}, U$2 = class extends g$1 {
|
|
7391
|
+
constructor(e, t) {
|
|
7392
|
+
super(`Counter tolerance validation failed: total checks (${t}) exceeds MAX_WINDOW (${e})`), this.name = "CounterToleranceTooLargeError";
|
|
7393
|
+
}
|
|
7394
|
+
}, X$1 = class extends g$1 {
|
|
7395
|
+
constructor() {
|
|
7396
|
+
super("Counter tolerance cannot contain negative values"), this.name = "CounterToleranceNegativeError";
|
|
7397
|
+
}
|
|
7398
|
+
}, G$2 = class extends i$1 {
|
|
7399
|
+
constructor(e) {
|
|
7400
|
+
super(e), this.name = "PluginError";
|
|
7401
|
+
}
|
|
7402
|
+
}, Y$2 = class extends G$2 {
|
|
7403
|
+
constructor() {
|
|
7404
|
+
super("Crypto plugin is required."), this.name = "CryptoPluginMissingError";
|
|
7405
|
+
}
|
|
7406
|
+
}, L$1 = class extends G$2 {
|
|
7407
|
+
constructor() {
|
|
7408
|
+
super("Base32 plugin is required."), this.name = "Base32PluginMissingError";
|
|
7409
|
+
}
|
|
7410
|
+
}, a$1 = class extends i$1 {
|
|
7411
|
+
constructor(e) {
|
|
7412
|
+
super(e), this.name = "ConfigurationError";
|
|
7413
|
+
}
|
|
7414
|
+
}, W$1 = class extends a$1 {
|
|
7415
|
+
constructor() {
|
|
7416
|
+
super("Secret is required. Use generateSecret() to create one, or provide via { secret: 'YOUR_BASE32_SECRET' }"), this.name = "SecretMissingError";
|
|
7417
|
+
}
|
|
7418
|
+
};
|
|
7419
|
+
new TextEncoder();
|
|
7420
|
+
new TextDecoder();
|
|
7421
|
+
var ur$2 = 3600, pr$2 = Number.MAX_SAFE_INTEGER, er$2 = Symbol("otplib.guardrails.override");
|
|
7422
|
+
function y$1(r, e, t) {
|
|
7423
|
+
if (typeof e != "number" || !Number.isSafeInteger(e)) throw new a$1(`Guardrail '${r}' must be a safe integer`);
|
|
7424
|
+
if (e < t) throw new a$1(`Guardrail '${r}' must be >= ${t}`);
|
|
7425
|
+
}
|
|
7426
|
+
var d$4 = Object.freeze({
|
|
6937
7427
|
MIN_SECRET_BYTES: 16,
|
|
6938
7428
|
MAX_SECRET_BYTES: 64,
|
|
6939
7429
|
MIN_PERIOD: 1,
|
|
@@ -8578,1038 +9068,751 @@ var K512 = /* @__PURE__ */ (() => split([
|
|
|
8578
9068
|
"0xc76c51a30654be30",
|
|
8579
9069
|
"0xd192e819d6ef5218",
|
|
8580
9070
|
"0xd69906245565a910",
|
|
8581
|
-
"0xf40e35855771202a",
|
|
8582
|
-
"0x106aa07032bbd1b8",
|
|
8583
|
-
"0x19a4c116b8d2d0c8",
|
|
8584
|
-
"0x1e376c085141ab53",
|
|
8585
|
-
"0x2748774cdf8eeb99",
|
|
8586
|
-
"0x34b0bcb5e19b48a8",
|
|
8587
|
-
"0x391c0cb3c5c95a63",
|
|
8588
|
-
"0x4ed8aa4ae3418acb",
|
|
8589
|
-
"0x5b9cca4f7763e373",
|
|
8590
|
-
"0x682e6ff3d6b2b8a3",
|
|
8591
|
-
"0x748f82ee5defb2fc",
|
|
8592
|
-
"0x78a5636f43172f60",
|
|
8593
|
-
"0x84c87814a1f0ab72",
|
|
8594
|
-
"0x8cc702081a6439ec",
|
|
8595
|
-
"0x90befffa23631e28",
|
|
8596
|
-
"0xa4506cebde82bde9",
|
|
8597
|
-
"0xbef9a3f7b2c67915",
|
|
8598
|
-
"0xc67178f2e372532b",
|
|
8599
|
-
"0xca273eceea26619c",
|
|
8600
|
-
"0xd186b8c721c0c207",
|
|
8601
|
-
"0xeada7dd6cde0eb1e",
|
|
8602
|
-
"0xf57d4f7fee6ed178",
|
|
8603
|
-
"0x06f067aa72176fba",
|
|
8604
|
-
"0x0a637dc5a2c898a6",
|
|
8605
|
-
"0x113f9804bef90dae",
|
|
8606
|
-
"0x1b710b35131c471b",
|
|
8607
|
-
"0x28db77f523047d84",
|
|
8608
|
-
"0x32caab7b40c72493",
|
|
8609
|
-
"0x3c9ebe0a15c9bebc",
|
|
8610
|
-
"0x431d67c49c100d4c",
|
|
8611
|
-
"0x4cc5d4becb3e42b6",
|
|
8612
|
-
"0x597f299cfc657e2a",
|
|
8613
|
-
"0x5fcb6fab3ad6faec",
|
|
8614
|
-
"0x6c44198c4a475817"
|
|
8615
|
-
].map((n) => BigInt(n))))();
|
|
8616
|
-
var SHA512_Kh = /* @__PURE__ */ (() => K512[0])();
|
|
8617
|
-
var SHA512_Kl = /* @__PURE__ */ (() => K512[1])();
|
|
8618
|
-
var SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
|
|
8619
|
-
var SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
|
|
8620
|
-
/** Internal SHA-384 / SHA-512 compression engine from RFC 6234 §6.4. */
|
|
8621
|
-
var SHA2_64B = class extends HashMD {
|
|
8622
|
-
constructor(outputLen) {
|
|
8623
|
-
super(128, outputLen, 16, false);
|
|
8624
|
-
}
|
|
8625
|
-
get() {
|
|
8626
|
-
const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
8627
|
-
return [
|
|
8628
|
-
Ah,
|
|
8629
|
-
Al,
|
|
8630
|
-
Bh,
|
|
8631
|
-
Bl,
|
|
8632
|
-
Ch,
|
|
8633
|
-
Cl,
|
|
8634
|
-
Dh,
|
|
8635
|
-
Dl,
|
|
8636
|
-
Eh,
|
|
8637
|
-
El,
|
|
8638
|
-
Fh,
|
|
8639
|
-
Fl,
|
|
8640
|
-
Gh,
|
|
8641
|
-
Gl,
|
|
8642
|
-
Hh,
|
|
8643
|
-
Hl
|
|
8644
|
-
];
|
|
8645
|
-
}
|
|
8646
|
-
set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
|
|
8647
|
-
this.Ah = Ah | 0;
|
|
8648
|
-
this.Al = Al | 0;
|
|
8649
|
-
this.Bh = Bh | 0;
|
|
8650
|
-
this.Bl = Bl | 0;
|
|
8651
|
-
this.Ch = Ch | 0;
|
|
8652
|
-
this.Cl = Cl | 0;
|
|
8653
|
-
this.Dh = Dh | 0;
|
|
8654
|
-
this.Dl = Dl | 0;
|
|
8655
|
-
this.Eh = Eh | 0;
|
|
8656
|
-
this.El = El | 0;
|
|
8657
|
-
this.Fh = Fh | 0;
|
|
8658
|
-
this.Fl = Fl | 0;
|
|
8659
|
-
this.Gh = Gh | 0;
|
|
8660
|
-
this.Gl = Gl | 0;
|
|
8661
|
-
this.Hh = Hh | 0;
|
|
8662
|
-
this.Hl = Hl | 0;
|
|
8663
|
-
}
|
|
8664
|
-
process(view, offset) {
|
|
8665
|
-
for (let i = 0; i < 16; i++, offset += 4) {
|
|
8666
|
-
SHA512_W_H[i] = view.getUint32(offset);
|
|
8667
|
-
SHA512_W_L[i] = view.getUint32(offset += 4);
|
|
8668
|
-
}
|
|
8669
|
-
for (let i = 16; i < 80; i++) {
|
|
8670
|
-
const W15h = SHA512_W_H[i - 15] | 0;
|
|
8671
|
-
const W15l = SHA512_W_L[i - 15] | 0;
|
|
8672
|
-
const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
|
|
8673
|
-
const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
|
|
8674
|
-
const W2h = SHA512_W_H[i - 2] | 0;
|
|
8675
|
-
const W2l = SHA512_W_L[i - 2] | 0;
|
|
8676
|
-
const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
|
|
8677
|
-
const SUMl = add4L(s0l, rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6), SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
|
|
8678
|
-
SHA512_W_H[i] = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]) | 0;
|
|
8679
|
-
SHA512_W_L[i] = SUMl | 0;
|
|
8680
|
-
}
|
|
8681
|
-
let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
8682
|
-
for (let i = 0; i < 80; i++) {
|
|
8683
|
-
const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
|
|
8684
|
-
const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
|
|
8685
|
-
const CHIh = Eh & Fh ^ ~Eh & Gh;
|
|
8686
|
-
const CHIl = El & Fl ^ ~El & Gl;
|
|
8687
|
-
const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
|
|
8688
|
-
const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
|
|
8689
|
-
const T1l = T1ll | 0;
|
|
8690
|
-
const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
|
|
8691
|
-
const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
|
|
8692
|
-
const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
|
|
8693
|
-
const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
|
|
8694
|
-
Hh = Gh | 0;
|
|
8695
|
-
Hl = Gl | 0;
|
|
8696
|
-
Gh = Fh | 0;
|
|
8697
|
-
Gl = Fl | 0;
|
|
8698
|
-
Fh = Eh | 0;
|
|
8699
|
-
Fl = El | 0;
|
|
8700
|
-
({h: Eh, l: El} = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
|
|
8701
|
-
Dh = Ch | 0;
|
|
8702
|
-
Dl = Cl | 0;
|
|
8703
|
-
Ch = Bh | 0;
|
|
8704
|
-
Cl = Bl | 0;
|
|
8705
|
-
Bh = Ah | 0;
|
|
8706
|
-
Bl = Al | 0;
|
|
8707
|
-
const All = add3L(T1l, sigma0l, MAJl);
|
|
8708
|
-
Ah = add3H(All, T1h, sigma0h, MAJh);
|
|
8709
|
-
Al = All | 0;
|
|
8710
|
-
}
|
|
8711
|
-
({h: Ah, l: Al} = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
|
|
8712
|
-
({h: Bh, l: Bl} = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
|
|
8713
|
-
({h: Ch, l: Cl} = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
|
|
8714
|
-
({h: Dh, l: Dl} = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
|
|
8715
|
-
({h: Eh, l: El} = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
|
|
8716
|
-
({h: Fh, l: Fl} = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
|
|
8717
|
-
({h: Gh, l: Gl} = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
|
|
8718
|
-
({h: Hh, l: Hl} = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
|
|
8719
|
-
this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
|
|
8720
|
-
}
|
|
8721
|
-
roundClean() {
|
|
8722
|
-
clean(SHA512_W_H, SHA512_W_L);
|
|
8723
|
-
}
|
|
8724
|
-
destroy() {
|
|
8725
|
-
this.destroyed = true;
|
|
8726
|
-
clean(this.buffer);
|
|
8727
|
-
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
8728
|
-
}
|
|
8729
|
-
};
|
|
8730
|
-
/** Internal SHA-512 hash class grounded in RFC 6234 §6.3 and §6.4. */
|
|
8731
|
-
var _SHA512 = class extends SHA2_64B {
|
|
8732
|
-
Ah = SHA512_IV[0] | 0;
|
|
8733
|
-
Al = SHA512_IV[1] | 0;
|
|
8734
|
-
Bh = SHA512_IV[2] | 0;
|
|
8735
|
-
Bl = SHA512_IV[3] | 0;
|
|
8736
|
-
Ch = SHA512_IV[4] | 0;
|
|
8737
|
-
Cl = SHA512_IV[5] | 0;
|
|
8738
|
-
Dh = SHA512_IV[6] | 0;
|
|
8739
|
-
Dl = SHA512_IV[7] | 0;
|
|
8740
|
-
Eh = SHA512_IV[8] | 0;
|
|
8741
|
-
El = SHA512_IV[9] | 0;
|
|
8742
|
-
Fh = SHA512_IV[10] | 0;
|
|
8743
|
-
Fl = SHA512_IV[11] | 0;
|
|
8744
|
-
Gh = SHA512_IV[12] | 0;
|
|
8745
|
-
Gl = SHA512_IV[13] | 0;
|
|
8746
|
-
Hh = SHA512_IV[14] | 0;
|
|
8747
|
-
Hl = SHA512_IV[15] | 0;
|
|
8748
|
-
constructor() {
|
|
8749
|
-
super(64);
|
|
8750
|
-
}
|
|
8751
|
-
};
|
|
8752
|
-
/**
|
|
8753
|
-
* SHA2-256 hash function from RFC 4634. In JS it's the fastest: even faster than Blake3. Some info:
|
|
8754
|
-
*
|
|
8755
|
-
* - Trying 2^128 hashes would get 50% chance of collision, using birthday attack.
|
|
8756
|
-
* - BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
|
|
8757
|
-
* - Each sha256 hash is executing 2^18 bit operations.
|
|
8758
|
-
* - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.
|
|
8759
|
-
* @param msg - message bytes to hash
|
|
8760
|
-
* @returns Digest bytes.
|
|
8761
|
-
* @example
|
|
8762
|
-
* Hash a message with SHA2-256.
|
|
8763
|
-
* ```ts
|
|
8764
|
-
* sha256(new Uint8Array([97, 98, 99]));
|
|
8765
|
-
* ```
|
|
8766
|
-
*/
|
|
8767
|
-
var sha256 = /* @__PURE__ */ createHasher(() => new _SHA256(), /* @__PURE__ */ oidNist(1));
|
|
8768
|
-
/**
|
|
8769
|
-
* SHA2-512 hash function from RFC 4634.
|
|
8770
|
-
* @param msg - message bytes to hash
|
|
8771
|
-
* @returns Digest bytes.
|
|
8772
|
-
* @example
|
|
8773
|
-
* Hash a message with SHA2-512.
|
|
8774
|
-
* ```ts
|
|
8775
|
-
* sha512(new Uint8Array([97, 98, 99]));
|
|
8776
|
-
* ```
|
|
8777
|
-
*/
|
|
8778
|
-
var sha512 = /* @__PURE__ */ createHasher(() => new _SHA512(), /* @__PURE__ */ oidNist(3)), yr = new TextEncoder();
|
|
8779
|
-
new TextDecoder();
|
|
8780
|
-
//#endregion
|
|
8781
|
-
//#region ../../node_modules/@otplib/plugin-crypto-noble/node_modules/@otplib/core/dist/index.js
|
|
8782
|
-
var ur = 3600, pr = Number.MAX_SAFE_INTEGER;
|
|
8783
|
-
Object.freeze({
|
|
8784
|
-
MIN_SECRET_BYTES: 16,
|
|
8785
|
-
MAX_SECRET_BYTES: 64,
|
|
8786
|
-
MIN_PERIOD: 1,
|
|
8787
|
-
MAX_PERIOD: ur,
|
|
8788
|
-
MAX_COUNTER: pr,
|
|
8789
|
-
MAX_WINDOW: 99,
|
|
8790
|
-
[Symbol("otplib.guardrails.override")]: !1
|
|
8791
|
-
});
|
|
8792
|
-
function gr(r, e) {
|
|
8793
|
-
return r.length === e.length;
|
|
8794
|
-
}
|
|
8795
|
-
function tr(r, e) {
|
|
8796
|
-
let t = rr(r), o = rr(e);
|
|
8797
|
-
if (!gr(t, o)) return !1;
|
|
8798
|
-
let n = 0;
|
|
8799
|
-
for (let s = 0; s < t.length; s++) n |= t[s] ^ o[s];
|
|
8800
|
-
return n === 0;
|
|
8801
|
-
}
|
|
8802
|
-
function rr(r) {
|
|
8803
|
-
return typeof r == "string" ? yr.encode(r) : r;
|
|
8804
|
-
}
|
|
8805
|
-
//#endregion
|
|
8806
|
-
//#region ../../node_modules/@otplib/plugin-crypto-noble/dist/index.js
|
|
8807
|
-
var t = class {
|
|
8808
|
-
name = "noble";
|
|
8809
|
-
hmac(r, n, a) {
|
|
8810
|
-
return hmac(r === "sha1" ? sha1 : r === "sha256" ? sha256 : sha512, n, a);
|
|
8811
|
-
}
|
|
8812
|
-
randomBytes(r) {
|
|
8813
|
-
return randomBytes(r);
|
|
8814
|
-
}
|
|
8815
|
-
constantTimeEqual(r, n) {
|
|
8816
|
-
return tr(r, n);
|
|
8817
|
-
}
|
|
8818
|
-
}, A = Object.freeze(new t());
|
|
8819
|
-
//#endregion
|
|
8820
|
-
//#region node_modules/otplib/dist/index.js
|
|
8821
|
-
function c(t) {
|
|
8822
|
-
return {
|
|
8823
|
-
secret: t.secret,
|
|
8824
|
-
strategy: t.strategy ?? "totp",
|
|
8825
|
-
crypto: t.crypto ?? A,
|
|
8826
|
-
base32: t.base32 ?? d$2,
|
|
8827
|
-
algorithm: t.algorithm ?? "sha1",
|
|
8828
|
-
digits: t.digits ?? 6,
|
|
8829
|
-
period: t.period ?? 30,
|
|
8830
|
-
epoch: t.epoch ?? Math.floor(Date.now() / 1e3),
|
|
8831
|
-
t0: t.t0 ?? 0,
|
|
8832
|
-
counter: t.counter,
|
|
8833
|
-
guardrails: t.guardrails ?? hr$2(),
|
|
8834
|
-
hooks: t.hooks
|
|
8835
|
-
};
|
|
8836
|
-
}
|
|
8837
|
-
function O(t) {
|
|
8838
|
-
return {
|
|
8839
|
-
...c(t),
|
|
8840
|
-
token: t.token,
|
|
8841
|
-
epochTolerance: t.epochTolerance ?? 0,
|
|
8842
|
-
counterTolerance: t.counterTolerance ?? 0,
|
|
8843
|
-
afterTimeStep: t.afterTimeStep
|
|
8844
|
-
};
|
|
8845
|
-
}
|
|
8846
|
-
function l(t, e, r) {
|
|
8847
|
-
if (t === "totp") return r.totp();
|
|
8848
|
-
if (t === "hotp") {
|
|
8849
|
-
if (e === void 0) throw new a$2("Counter is required for HOTP strategy. Example: { strategy: 'hotp', counter: 0 }");
|
|
8850
|
-
return r.hotp(e);
|
|
8851
|
-
}
|
|
8852
|
-
throw new a$2(`Unknown OTP strategy: ${t}. Valid strategies are 'totp' or 'hotp'.`);
|
|
8853
|
-
}
|
|
8854
|
-
function I(t) {
|
|
8855
|
-
let { crypto: e = A, base32: r = d$2, length: a = 20 } = t || {};
|
|
8856
|
-
return Dr({
|
|
8857
|
-
crypto: e,
|
|
8858
|
-
base32: r,
|
|
8859
|
-
length: a
|
|
8860
|
-
});
|
|
8861
|
-
}
|
|
8862
|
-
function T(t) {
|
|
8863
|
-
let { strategy: e = "totp", issuer: r, label: a, secret: o, algorithm: i = "sha1", digits: s = 6, period: n = 30, counter: p } = t;
|
|
8864
|
-
return l(e, p, {
|
|
8865
|
-
totp: () => x$1({
|
|
8866
|
-
issuer: r,
|
|
8867
|
-
label: a,
|
|
8868
|
-
secret: o,
|
|
8869
|
-
algorithm: i,
|
|
8870
|
-
digits: s,
|
|
8871
|
-
period: n
|
|
8872
|
-
}),
|
|
8873
|
-
hotp: (y) => U$1({
|
|
8874
|
-
issuer: r,
|
|
8875
|
-
label: a,
|
|
8876
|
-
secret: o,
|
|
8877
|
-
algorithm: i,
|
|
8878
|
-
digits: s,
|
|
8879
|
-
counter: y
|
|
8880
|
-
})
|
|
8881
|
-
});
|
|
8882
|
-
}
|
|
8883
|
-
function d(t) {
|
|
8884
|
-
let e = O(t), { secret: r, token: a, crypto: o, base32: i, algorithm: s, digits: n, hooks: p } = e, y = {
|
|
8885
|
-
secret: r,
|
|
8886
|
-
token: a,
|
|
8887
|
-
crypto: o,
|
|
8888
|
-
base32: i,
|
|
8889
|
-
algorithm: s,
|
|
8890
|
-
digits: n,
|
|
8891
|
-
hooks: p
|
|
8892
|
-
};
|
|
8893
|
-
return l(e.strategy, e.counter, {
|
|
8894
|
-
totp: () => ie({
|
|
8895
|
-
...y,
|
|
8896
|
-
period: e.period,
|
|
8897
|
-
epoch: e.epoch,
|
|
8898
|
-
t0: e.t0,
|
|
8899
|
-
epochTolerance: e.epochTolerance,
|
|
8900
|
-
afterTimeStep: e.afterTimeStep,
|
|
8901
|
-
guardrails: e.guardrails
|
|
8902
|
-
}),
|
|
8903
|
-
hotp: (f) => Y$1({
|
|
8904
|
-
...y,
|
|
8905
|
-
counter: f,
|
|
8906
|
-
counterTolerance: e.counterTolerance,
|
|
8907
|
-
guardrails: e.guardrails
|
|
8908
|
-
})
|
|
8909
|
-
});
|
|
8910
|
-
}
|
|
8911
|
-
//#endregion
|
|
8912
|
-
//#region src/auth/totp-manager.ts
|
|
8913
|
-
/**
|
|
8914
|
-
* TOTP / 2FA enrollment manager.
|
|
8915
|
-
*
|
|
8916
|
-
* Three-state lifecycle keyed by `userId` (PK of `user_totp` collection):
|
|
8917
|
-
*
|
|
8918
|
-
* - No row → not set up. `setup()` inserts a row with a
|
|
8919
|
-
* fresh secret and `confirmedAt = null`.
|
|
8920
|
-
* - `confirmedAt = null` → setup in progress; user has the secret but
|
|
8921
|
-
* hasn't proved possession of the
|
|
8922
|
-
* authenticator yet. `verify()` returns
|
|
8923
|
-
* false even with a valid code — the row is
|
|
8924
|
-
* inert until confirmed.
|
|
8925
|
-
* - `confirmedAt != null` → enrolled; `verify()` accepts codes.
|
|
8926
|
-
*
|
|
8927
|
-
* Disable removes the row entirely. The next `setup()` starts fresh —
|
|
8928
|
-
* we do NOT reuse the old secret on re-enroll (avoids the case where a
|
|
8929
|
-
* leaked secret stays valid after a "rotation").
|
|
8930
|
-
*
|
|
8931
|
-
* `otplib` v13's functional API (`generateSync`/`verifySync`) uses RFC 6238
|
|
8932
|
-
* TOTP with a default 30-second period. We pass a per-call `epochTolerance`
|
|
8933
|
-
* of one step in either direction (±30 s) — operator clock skew is the usual
|
|
8934
|
-
* culprit for "valid code rejected" complaints.
|
|
8935
|
-
*/
|
|
8936
|
-
var TOTP_COLLECTION = "user_totp";
|
|
8937
|
-
var ISSUER = "CamStack";
|
|
8938
|
-
/** RFC 6238 default time step. otplib v13 measures `epochTolerance` in
|
|
8939
|
-
* seconds, so a `window` of N steps maps to `N * TOTP_PERIOD_SECONDS`. */
|
|
8940
|
-
var TOTP_PERIOD_SECONDS = 30;
|
|
8941
|
-
function parseRow(data) {
|
|
8942
|
-
const userId = data["userId"];
|
|
8943
|
-
const secret = data["secret"];
|
|
8944
|
-
const confirmedAt = data["confirmedAt"];
|
|
8945
|
-
const createdAt = data["createdAt"];
|
|
8946
|
-
if (typeof userId !== "string") throw new Error("user_totp row: missing userId");
|
|
8947
|
-
if (typeof secret !== "string") throw new Error("user_totp row: missing secret");
|
|
8948
|
-
if (typeof createdAt !== "number") throw new Error("user_totp row: missing createdAt");
|
|
8949
|
-
return {
|
|
8950
|
-
userId,
|
|
8951
|
-
secret,
|
|
8952
|
-
confirmedAt: typeof confirmedAt === "number" ? confirmedAt : null,
|
|
8953
|
-
createdAt
|
|
8954
|
-
};
|
|
8955
|
-
}
|
|
8956
|
-
var TotpManager = class {
|
|
8957
|
-
store;
|
|
8958
|
-
/** ±N steps of clock-skew tolerance, expressed in seconds for otplib v13's
|
|
8959
|
-
* per-call `epochTolerance`. Default window 1 = ±30 s. */
|
|
8960
|
-
epochTolerance;
|
|
8961
|
-
constructor(store, opts = {}) {
|
|
8962
|
-
this.store = store;
|
|
8963
|
-
this.epochTolerance = (opts.window ?? 1) * TOTP_PERIOD_SECONDS;
|
|
8964
|
-
}
|
|
8965
|
-
/**
|
|
8966
|
-
* Begin enrollment. Always generates a fresh secret — calling `setup`
|
|
8967
|
-
* a second time on the same user replaces any pending half-enrollment
|
|
8968
|
-
* (the user must rescan the QR / re-enter the secret).
|
|
8969
|
-
*
|
|
8970
|
-
* The user identity (`label`) is what an authenticator displays as
|
|
8971
|
-
* the account name; we use the supplied `username` for human-readable
|
|
8972
|
-
* recognition. `ISSUER` is the CamStack brand.
|
|
8973
|
-
*/
|
|
8974
|
-
async setup(userId, username) {
|
|
8975
|
-
const secret = I();
|
|
8976
|
-
const otpauthUrl = T({
|
|
8977
|
-
secret,
|
|
8978
|
-
label: username,
|
|
8979
|
-
issuer: ISSUER
|
|
8980
|
-
});
|
|
8981
|
-
const row = {
|
|
8982
|
-
userId,
|
|
8983
|
-
secret,
|
|
8984
|
-
confirmedAt: null,
|
|
8985
|
-
createdAt: Date.now()
|
|
8986
|
-
};
|
|
8987
|
-
await this.store.delete.mutate({
|
|
8988
|
-
collection: TOTP_COLLECTION,
|
|
8989
|
-
key: userId
|
|
8990
|
-
});
|
|
8991
|
-
await this.store.insert.mutate({
|
|
8992
|
-
collection: TOTP_COLLECTION,
|
|
8993
|
-
record: {
|
|
8994
|
-
id: userId,
|
|
8995
|
-
data: { ...row }
|
|
8996
|
-
}
|
|
8997
|
-
});
|
|
8998
|
-
return {
|
|
8999
|
-
secret,
|
|
9000
|
-
otpauthUrl
|
|
9001
|
-
};
|
|
9071
|
+
"0xf40e35855771202a",
|
|
9072
|
+
"0x106aa07032bbd1b8",
|
|
9073
|
+
"0x19a4c116b8d2d0c8",
|
|
9074
|
+
"0x1e376c085141ab53",
|
|
9075
|
+
"0x2748774cdf8eeb99",
|
|
9076
|
+
"0x34b0bcb5e19b48a8",
|
|
9077
|
+
"0x391c0cb3c5c95a63",
|
|
9078
|
+
"0x4ed8aa4ae3418acb",
|
|
9079
|
+
"0x5b9cca4f7763e373",
|
|
9080
|
+
"0x682e6ff3d6b2b8a3",
|
|
9081
|
+
"0x748f82ee5defb2fc",
|
|
9082
|
+
"0x78a5636f43172f60",
|
|
9083
|
+
"0x84c87814a1f0ab72",
|
|
9084
|
+
"0x8cc702081a6439ec",
|
|
9085
|
+
"0x90befffa23631e28",
|
|
9086
|
+
"0xa4506cebde82bde9",
|
|
9087
|
+
"0xbef9a3f7b2c67915",
|
|
9088
|
+
"0xc67178f2e372532b",
|
|
9089
|
+
"0xca273eceea26619c",
|
|
9090
|
+
"0xd186b8c721c0c207",
|
|
9091
|
+
"0xeada7dd6cde0eb1e",
|
|
9092
|
+
"0xf57d4f7fee6ed178",
|
|
9093
|
+
"0x06f067aa72176fba",
|
|
9094
|
+
"0x0a637dc5a2c898a6",
|
|
9095
|
+
"0x113f9804bef90dae",
|
|
9096
|
+
"0x1b710b35131c471b",
|
|
9097
|
+
"0x28db77f523047d84",
|
|
9098
|
+
"0x32caab7b40c72493",
|
|
9099
|
+
"0x3c9ebe0a15c9bebc",
|
|
9100
|
+
"0x431d67c49c100d4c",
|
|
9101
|
+
"0x4cc5d4becb3e42b6",
|
|
9102
|
+
"0x597f299cfc657e2a",
|
|
9103
|
+
"0x5fcb6fab3ad6faec",
|
|
9104
|
+
"0x6c44198c4a475817"
|
|
9105
|
+
].map((n) => BigInt(n))))();
|
|
9106
|
+
var SHA512_Kh = /* @__PURE__ */ (() => K512[0])();
|
|
9107
|
+
var SHA512_Kl = /* @__PURE__ */ (() => K512[1])();
|
|
9108
|
+
var SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
|
|
9109
|
+
var SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
|
|
9110
|
+
/** Internal SHA-384 / SHA-512 compression engine from RFC 6234 §6.4. */
|
|
9111
|
+
var SHA2_64B = class extends HashMD {
|
|
9112
|
+
constructor(outputLen) {
|
|
9113
|
+
super(128, outputLen, 16, false);
|
|
9002
9114
|
}
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
await this.store.update.mutate({
|
|
9024
|
-
collection: TOTP_COLLECTION,
|
|
9025
|
-
id: userId,
|
|
9026
|
-
data: { ...next }
|
|
9027
|
-
});
|
|
9028
|
-
}
|
|
9029
|
-
return true;
|
|
9115
|
+
get() {
|
|
9116
|
+
const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
9117
|
+
return [
|
|
9118
|
+
Ah,
|
|
9119
|
+
Al,
|
|
9120
|
+
Bh,
|
|
9121
|
+
Bl,
|
|
9122
|
+
Ch,
|
|
9123
|
+
Cl,
|
|
9124
|
+
Dh,
|
|
9125
|
+
Dl,
|
|
9126
|
+
Eh,
|
|
9127
|
+
El,
|
|
9128
|
+
Fh,
|
|
9129
|
+
Fl,
|
|
9130
|
+
Gh,
|
|
9131
|
+
Gl,
|
|
9132
|
+
Hh,
|
|
9133
|
+
Hl
|
|
9134
|
+
];
|
|
9030
9135
|
}
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9136
|
+
set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
|
|
9137
|
+
this.Ah = Ah | 0;
|
|
9138
|
+
this.Al = Al | 0;
|
|
9139
|
+
this.Bh = Bh | 0;
|
|
9140
|
+
this.Bl = Bl | 0;
|
|
9141
|
+
this.Ch = Ch | 0;
|
|
9142
|
+
this.Cl = Cl | 0;
|
|
9143
|
+
this.Dh = Dh | 0;
|
|
9144
|
+
this.Dl = Dl | 0;
|
|
9145
|
+
this.Eh = Eh | 0;
|
|
9146
|
+
this.El = El | 0;
|
|
9147
|
+
this.Fh = Fh | 0;
|
|
9148
|
+
this.Fl = Fl | 0;
|
|
9149
|
+
this.Gh = Gh | 0;
|
|
9150
|
+
this.Gl = Gl | 0;
|
|
9151
|
+
this.Hh = Hh | 0;
|
|
9152
|
+
this.Hl = Hl | 0;
|
|
9039
9153
|
}
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
|
|
9154
|
+
process(view, offset) {
|
|
9155
|
+
for (let i = 0; i < 16; i++, offset += 4) {
|
|
9156
|
+
SHA512_W_H[i] = view.getUint32(offset);
|
|
9157
|
+
SHA512_W_L[i] = view.getUint32(offset += 4);
|
|
9158
|
+
}
|
|
9159
|
+
for (let i = 16; i < 80; i++) {
|
|
9160
|
+
const W15h = SHA512_W_H[i - 15] | 0;
|
|
9161
|
+
const W15l = SHA512_W_L[i - 15] | 0;
|
|
9162
|
+
const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
|
|
9163
|
+
const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
|
|
9164
|
+
const W2h = SHA512_W_H[i - 2] | 0;
|
|
9165
|
+
const W2l = SHA512_W_L[i - 2] | 0;
|
|
9166
|
+
const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
|
|
9167
|
+
const SUMl = add4L(s0l, rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6), SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
|
|
9168
|
+
SHA512_W_H[i] = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]) | 0;
|
|
9169
|
+
SHA512_W_L[i] = SUMl | 0;
|
|
9170
|
+
}
|
|
9171
|
+
let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
9172
|
+
for (let i = 0; i < 80; i++) {
|
|
9173
|
+
const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
|
|
9174
|
+
const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
|
|
9175
|
+
const CHIh = Eh & Fh ^ ~Eh & Gh;
|
|
9176
|
+
const CHIl = El & Fl ^ ~El & Gl;
|
|
9177
|
+
const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
|
|
9178
|
+
const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
|
|
9179
|
+
const T1l = T1ll | 0;
|
|
9180
|
+
const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
|
|
9181
|
+
const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
|
|
9182
|
+
const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
|
|
9183
|
+
const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
|
|
9184
|
+
Hh = Gh | 0;
|
|
9185
|
+
Hl = Gl | 0;
|
|
9186
|
+
Gh = Fh | 0;
|
|
9187
|
+
Gl = Fl | 0;
|
|
9188
|
+
Fh = Eh | 0;
|
|
9189
|
+
Fl = El | 0;
|
|
9190
|
+
({h: Eh, l: El} = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
|
|
9191
|
+
Dh = Ch | 0;
|
|
9192
|
+
Dl = Cl | 0;
|
|
9193
|
+
Ch = Bh | 0;
|
|
9194
|
+
Cl = Bl | 0;
|
|
9195
|
+
Bh = Ah | 0;
|
|
9196
|
+
Bl = Al | 0;
|
|
9197
|
+
const All = add3L(T1l, sigma0l, MAJl);
|
|
9198
|
+
Ah = add3H(All, T1h, sigma0h, MAJh);
|
|
9199
|
+
Al = All | 0;
|
|
9200
|
+
}
|
|
9201
|
+
({h: Ah, l: Al} = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
|
|
9202
|
+
({h: Bh, l: Bl} = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
|
|
9203
|
+
({h: Ch, l: Cl} = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
|
|
9204
|
+
({h: Dh, l: Dl} = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
|
|
9205
|
+
({h: Eh, l: El} = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
|
|
9206
|
+
({h: Fh, l: Fl} = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
|
|
9207
|
+
({h: Gh, l: Gl} = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
|
|
9208
|
+
({h: Hh, l: Hl} = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
|
|
9209
|
+
this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
|
|
9055
9210
|
}
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
* reported as `enabled: false` — only a confirmed row counts.
|
|
9059
|
-
*/
|
|
9060
|
-
async getStatus(userId) {
|
|
9061
|
-
const row = await this.find(userId);
|
|
9062
|
-
if (!row) return {
|
|
9063
|
-
enabled: false,
|
|
9064
|
-
confirmedAt: null
|
|
9065
|
-
};
|
|
9066
|
-
return {
|
|
9067
|
-
enabled: row.confirmedAt !== null,
|
|
9068
|
-
confirmedAt: row.confirmedAt
|
|
9069
|
-
};
|
|
9211
|
+
roundClean() {
|
|
9212
|
+
clean(SHA512_W_H, SHA512_W_L);
|
|
9070
9213
|
}
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9214
|
+
destroy() {
|
|
9215
|
+
this.destroyed = true;
|
|
9216
|
+
clean(this.buffer);
|
|
9217
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
9218
|
+
}
|
|
9219
|
+
};
|
|
9220
|
+
/** Internal SHA-512 hash class grounded in RFC 6234 §6.3 and §6.4. */
|
|
9221
|
+
var _SHA512 = class extends SHA2_64B {
|
|
9222
|
+
Ah = SHA512_IV[0] | 0;
|
|
9223
|
+
Al = SHA512_IV[1] | 0;
|
|
9224
|
+
Bh = SHA512_IV[2] | 0;
|
|
9225
|
+
Bl = SHA512_IV[3] | 0;
|
|
9226
|
+
Ch = SHA512_IV[4] | 0;
|
|
9227
|
+
Cl = SHA512_IV[5] | 0;
|
|
9228
|
+
Dh = SHA512_IV[6] | 0;
|
|
9229
|
+
Dl = SHA512_IV[7] | 0;
|
|
9230
|
+
Eh = SHA512_IV[8] | 0;
|
|
9231
|
+
El = SHA512_IV[9] | 0;
|
|
9232
|
+
Fh = SHA512_IV[10] | 0;
|
|
9233
|
+
Fl = SHA512_IV[11] | 0;
|
|
9234
|
+
Gh = SHA512_IV[12] | 0;
|
|
9235
|
+
Gl = SHA512_IV[13] | 0;
|
|
9236
|
+
Hh = SHA512_IV[14] | 0;
|
|
9237
|
+
Hl = SHA512_IV[15] | 0;
|
|
9238
|
+
constructor() {
|
|
9239
|
+
super(64);
|
|
9078
9240
|
}
|
|
9079
9241
|
};
|
|
9080
|
-
//#endregion
|
|
9081
|
-
//#region src/kernel/config-schema.ts
|
|
9082
|
-
/** Default data directory — used when CAMSTACK_DATA env var is not set */
|
|
9083
|
-
var DEFAULT_DATA_PATH = "camstack-data";
|
|
9084
|
-
/**
|
|
9085
|
-
* A list of browser origins from config.yaml (a YAML list) or from the
|
|
9086
|
-
* `CAMSTACK_CORS_ORIGINS` env var (one comma-separated string — env carries
|
|
9087
|
-
* only strings). Validation of each entry is the CORS policy's job
|
|
9088
|
-
* (`server/backend/src/api/cors/cors-policy.ts`), which logs what it rejects;
|
|
9089
|
-
* the schema only fixes the SHAPE so a typo cannot stop the hub from booting.
|
|
9090
|
-
*/
|
|
9091
|
-
var originListSchema = zod.z.preprocess((value) => typeof value === "string" ? value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) : value, zod.z.array(zod.z.string()));
|
|
9092
|
-
var serverSchema = zod.z.object({
|
|
9093
|
-
port: zod.z.number().default(4443),
|
|
9094
|
-
host: zod.z.string().default("0.0.0.0"),
|
|
9095
|
-
dataPath: zod.z.string().default(DEFAULT_DATA_PATH),
|
|
9096
|
-
/**
|
|
9097
|
-
* EXTRA browser origins allowed to call this hub cross-origin (D707), on
|
|
9098
|
-
* top of the built-ins: the hub's own origin, the hosted viewer PWA and the
|
|
9099
|
-
* native app's opaque `null` origin. Bootstrap-owned on purpose — widening
|
|
9100
|
-
* who may read hub responses in a browser is a security decision, and the
|
|
9101
|
-
* policy is live from the first request, before the settings store exists.
|
|
9102
|
-
*/
|
|
9103
|
-
corsOrigins: originListSchema.default([])
|
|
9104
|
-
});
|
|
9105
|
-
var authSchema = zod.z.object({
|
|
9106
|
-
jwtSecret: zod.z.string().nullable().default(null),
|
|
9107
|
-
adminUsername: zod.z.string().default("admin"),
|
|
9108
|
-
adminPassword: zod.z.string().default("changeme")
|
|
9109
|
-
});
|
|
9110
|
-
var hubSchema = zod.z.object({
|
|
9111
|
-
url: zod.z.string().default("ws://localhost:4443/agent"),
|
|
9112
|
-
token: zod.z.string().default("")
|
|
9113
|
-
});
|
|
9114
|
-
var agentSchema = zod.z.object({
|
|
9115
|
-
name: zod.z.string().default(""),
|
|
9116
|
-
/** Port for the agent status page (minimal HTML) */
|
|
9117
|
-
statusPort: zod.z.number().default(4444)
|
|
9118
|
-
});
|
|
9119
|
-
var tlsSchema = zod.z.object({
|
|
9120
9242
|
/**
|
|
9121
|
-
*
|
|
9122
|
-
*
|
|
9123
|
-
*
|
|
9243
|
+
* SHA2-256 hash function from RFC 4634. In JS it's the fastest: even faster than Blake3. Some info:
|
|
9244
|
+
*
|
|
9245
|
+
* - Trying 2^128 hashes would get 50% chance of collision, using birthday attack.
|
|
9246
|
+
* - BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
|
|
9247
|
+
* - Each sha256 hash is executing 2^18 bit operations.
|
|
9248
|
+
* - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.
|
|
9249
|
+
* @param msg - message bytes to hash
|
|
9250
|
+
* @returns Digest bytes.
|
|
9251
|
+
* @example
|
|
9252
|
+
* Hash a message with SHA2-256.
|
|
9253
|
+
* ```ts
|
|
9254
|
+
* sha256(new Uint8Array([97, 98, 99]));
|
|
9255
|
+
* ```
|
|
9124
9256
|
*/
|
|
9125
|
-
|
|
9257
|
+
var sha256 = /* @__PURE__ */ createHasher(() => new _SHA256(), /* @__PURE__ */ oidNist(1));
|
|
9126
9258
|
/**
|
|
9127
|
-
*
|
|
9128
|
-
*
|
|
9129
|
-
*
|
|
9130
|
-
*
|
|
9131
|
-
*
|
|
9132
|
-
*
|
|
9133
|
-
*
|
|
9134
|
-
*
|
|
9259
|
+
* SHA2-512 hash function from RFC 4634.
|
|
9260
|
+
* @param msg - message bytes to hash
|
|
9261
|
+
* @returns Digest bytes.
|
|
9262
|
+
* @example
|
|
9263
|
+
* Hash a message with SHA2-512.
|
|
9264
|
+
* ```ts
|
|
9265
|
+
* sha512(new Uint8Array([97, 98, 99]));
|
|
9266
|
+
* ```
|
|
9135
9267
|
*/
|
|
9136
|
-
var
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
* on startup.
|
|
9150
|
-
*
|
|
9151
|
-
* - `'npm'` — pull from the npm registry (production default).
|
|
9152
|
-
* - `'local'` — copy `packages/<X>/` from the workspace into
|
|
9153
|
-
* `data/addons/<X>/`. Use for the Electron-
|
|
9154
|
-
* packaged bundle and the e2e harness.
|
|
9155
|
-
* - `'symlink'` — point `data/addons/<X>` at the workspace source
|
|
9156
|
-
* dir via a directory symlink. Dev-only: a
|
|
9157
|
-
* workspace rebuild (`npm run build -w <X>`) is
|
|
9158
|
-
* immediately reflected at the next addon load,
|
|
9159
|
-
* no copy step. Falls back to `'local'` on
|
|
9160
|
-
* platforms that reject symlink creation (Windows
|
|
9161
|
-
* without developer-mode).
|
|
9162
|
-
*
|
|
9163
|
-
* The `CAMSTACK_INSTALL_SOURCE` env var, when set, wins over this
|
|
9164
|
-
* field. Leave undefined to keep the launcher's default behaviour
|
|
9165
|
-
* (npm when no workspace, local when a bundled addons dir is
|
|
9166
|
-
* present, …).
|
|
9167
|
-
*/
|
|
9168
|
-
installSource: zod.z.enum([
|
|
9169
|
-
"npm",
|
|
9170
|
-
"local",
|
|
9171
|
-
"symlink"
|
|
9172
|
-
]).optional(),
|
|
9173
|
-
/**
|
|
9174
|
-
* Override the kernel's hard-coded `AddonInstaller.REQUIRED_PACKAGES`.
|
|
9175
|
-
* Passing an array here REPLACES the default list entirely — useful
|
|
9176
|
-
* for stripped-down deployments (only the bundle of addons the
|
|
9177
|
-
* operator actually wants) and for forcing a different storage
|
|
9178
|
-
* backend at first boot.
|
|
9179
|
-
*
|
|
9180
|
-
* Leave undefined to use the kernel-shipped defaults.
|
|
9181
|
-
*/
|
|
9182
|
-
requiredAddons: zod.z.array(zod.z.string()).optional(),
|
|
9183
|
-
/**
|
|
9184
|
-
* Disable list applied AFTER the settings-store reports the enabled
|
|
9185
|
-
* set. Operator escape hatch — boot without a known-broken addon
|
|
9186
|
-
* without having to mutate the store.
|
|
9187
|
-
*/
|
|
9188
|
-
forceDisable: zod.z.array(zod.z.string()).default([])
|
|
9189
|
-
});
|
|
9190
|
-
/** Bootstrap config -- loaded from config.yaml ONLY at startup.
|
|
9191
|
-
* All other settings live in SQL (system_settings table). */
|
|
9192
|
-
var bootstrapSchema = zod.z.object({
|
|
9193
|
-
/** Server mode: 'hub' (full server) or 'agent' (worker node) */
|
|
9194
|
-
mode: zod.z.enum(["hub", "agent"]).default("hub"),
|
|
9195
|
-
server: serverSchema.default(serverSchema.parse({})),
|
|
9196
|
-
auth: authSchema.default(authSchema.parse({})),
|
|
9197
|
-
/** Hub connection config — only used when mode='agent' */
|
|
9198
|
-
hub: hubSchema.default(hubSchema.parse({})),
|
|
9199
|
-
/** Agent-specific config — only used when mode='agent' */
|
|
9200
|
-
agent: agentSchema.default(agentSchema.parse({})),
|
|
9201
|
-
/** TLS configuration */
|
|
9202
|
-
tls: tlsSchema.default(tlsSchema.parse({})),
|
|
9203
|
-
/**
|
|
9204
|
-
* Addon bootstrap directives — minimum addon set needed before the
|
|
9205
|
-
* kernel can reach the settings-store. See bootstrapAddonsSchema.
|
|
9206
|
-
* Spec: docs/superpowers/specs/2026-05-15-yaml-bootstrap-expansion.md
|
|
9207
|
-
*/
|
|
9208
|
-
bootstrap: bootstrapAddonsSchema.default(bootstrapAddonsSchema.parse({}))
|
|
9268
|
+
var sha512 = /* @__PURE__ */ createHasher(() => new _SHA512(), /* @__PURE__ */ oidNist(3)), yr = new TextEncoder();
|
|
9269
|
+
new TextDecoder();
|
|
9270
|
+
//#endregion
|
|
9271
|
+
//#region ../../node_modules/@otplib/plugin-crypto-noble/node_modules/@otplib/core/dist/index.js
|
|
9272
|
+
var ur = 3600, pr = Number.MAX_SAFE_INTEGER;
|
|
9273
|
+
Object.freeze({
|
|
9274
|
+
MIN_SECRET_BYTES: 16,
|
|
9275
|
+
MAX_SECRET_BYTES: 64,
|
|
9276
|
+
MIN_PERIOD: 1,
|
|
9277
|
+
MAX_PERIOD: ur,
|
|
9278
|
+
MAX_COUNTER: pr,
|
|
9279
|
+
MAX_WINDOW: 99,
|
|
9280
|
+
[Symbol("otplib.guardrails.override")]: !1
|
|
9209
9281
|
});
|
|
9282
|
+
function gr(r, e) {
|
|
9283
|
+
return r.length === e.length;
|
|
9284
|
+
}
|
|
9285
|
+
function tr(r, e) {
|
|
9286
|
+
let t = rr(r), o = rr(e);
|
|
9287
|
+
if (!gr(t, o)) return !1;
|
|
9288
|
+
let n = 0;
|
|
9289
|
+
for (let s = 0; s < t.length; s++) n |= t[s] ^ o[s];
|
|
9290
|
+
return n === 0;
|
|
9291
|
+
}
|
|
9292
|
+
function rr(r) {
|
|
9293
|
+
return typeof r == "string" ? yr.encode(r) : r;
|
|
9294
|
+
}
|
|
9295
|
+
//#endregion
|
|
9296
|
+
//#region ../../node_modules/@otplib/plugin-crypto-noble/dist/index.js
|
|
9297
|
+
var t = class {
|
|
9298
|
+
name = "noble";
|
|
9299
|
+
hmac(r, n, a) {
|
|
9300
|
+
return hmac(r === "sha1" ? sha1 : r === "sha256" ? sha256 : sha512, n, a);
|
|
9301
|
+
}
|
|
9302
|
+
randomBytes(r) {
|
|
9303
|
+
return randomBytes(r);
|
|
9304
|
+
}
|
|
9305
|
+
constantTimeEqual(r, n) {
|
|
9306
|
+
return tr(r, n);
|
|
9307
|
+
}
|
|
9308
|
+
}, A = Object.freeze(new t());
|
|
9309
|
+
//#endregion
|
|
9310
|
+
//#region node_modules/otplib/dist/index.js
|
|
9311
|
+
function c(t) {
|
|
9312
|
+
return {
|
|
9313
|
+
secret: t.secret,
|
|
9314
|
+
strategy: t.strategy ?? "totp",
|
|
9315
|
+
crypto: t.crypto ?? A,
|
|
9316
|
+
base32: t.base32 ?? d$2,
|
|
9317
|
+
algorithm: t.algorithm ?? "sha1",
|
|
9318
|
+
digits: t.digits ?? 6,
|
|
9319
|
+
period: t.period ?? 30,
|
|
9320
|
+
epoch: t.epoch ?? Math.floor(Date.now() / 1e3),
|
|
9321
|
+
t0: t.t0 ?? 0,
|
|
9322
|
+
counter: t.counter,
|
|
9323
|
+
guardrails: t.guardrails ?? hr$2(),
|
|
9324
|
+
hooks: t.hooks
|
|
9325
|
+
};
|
|
9326
|
+
}
|
|
9327
|
+
function O(t) {
|
|
9328
|
+
return {
|
|
9329
|
+
...c(t),
|
|
9330
|
+
token: t.token,
|
|
9331
|
+
epochTolerance: t.epochTolerance ?? 0,
|
|
9332
|
+
counterTolerance: t.counterTolerance ?? 0,
|
|
9333
|
+
afterTimeStep: t.afterTimeStep
|
|
9334
|
+
};
|
|
9335
|
+
}
|
|
9336
|
+
function l(t, e, r) {
|
|
9337
|
+
if (t === "totp") return r.totp();
|
|
9338
|
+
if (t === "hotp") {
|
|
9339
|
+
if (e === void 0) throw new a$2("Counter is required for HOTP strategy. Example: { strategy: 'hotp', counter: 0 }");
|
|
9340
|
+
return r.hotp(e);
|
|
9341
|
+
}
|
|
9342
|
+
throw new a$2(`Unknown OTP strategy: ${t}. Valid strategies are 'totp' or 'hotp'.`);
|
|
9343
|
+
}
|
|
9344
|
+
function I(t) {
|
|
9345
|
+
let { crypto: e = A, base32: r = d$2, length: a = 20 } = t || {};
|
|
9346
|
+
return Dr({
|
|
9347
|
+
crypto: e,
|
|
9348
|
+
base32: r,
|
|
9349
|
+
length: a
|
|
9350
|
+
});
|
|
9351
|
+
}
|
|
9352
|
+
function T(t) {
|
|
9353
|
+
let { strategy: e = "totp", issuer: r, label: a, secret: o, algorithm: i = "sha1", digits: s = 6, period: n = 30, counter: p } = t;
|
|
9354
|
+
return l(e, p, {
|
|
9355
|
+
totp: () => x$1({
|
|
9356
|
+
issuer: r,
|
|
9357
|
+
label: a,
|
|
9358
|
+
secret: o,
|
|
9359
|
+
algorithm: i,
|
|
9360
|
+
digits: s,
|
|
9361
|
+
period: n
|
|
9362
|
+
}),
|
|
9363
|
+
hotp: (y) => U$1({
|
|
9364
|
+
issuer: r,
|
|
9365
|
+
label: a,
|
|
9366
|
+
secret: o,
|
|
9367
|
+
algorithm: i,
|
|
9368
|
+
digits: s,
|
|
9369
|
+
counter: y
|
|
9370
|
+
})
|
|
9371
|
+
});
|
|
9372
|
+
}
|
|
9373
|
+
function d(t) {
|
|
9374
|
+
let e = O(t), { secret: r, token: a, crypto: o, base32: i, algorithm: s, digits: n, hooks: p } = e, y = {
|
|
9375
|
+
secret: r,
|
|
9376
|
+
token: a,
|
|
9377
|
+
crypto: o,
|
|
9378
|
+
base32: i,
|
|
9379
|
+
algorithm: s,
|
|
9380
|
+
digits: n,
|
|
9381
|
+
hooks: p
|
|
9382
|
+
};
|
|
9383
|
+
return l(e.strategy, e.counter, {
|
|
9384
|
+
totp: () => ie({
|
|
9385
|
+
...y,
|
|
9386
|
+
period: e.period,
|
|
9387
|
+
epoch: e.epoch,
|
|
9388
|
+
t0: e.t0,
|
|
9389
|
+
epochTolerance: e.epochTolerance,
|
|
9390
|
+
afterTimeStep: e.afterTimeStep,
|
|
9391
|
+
guardrails: e.guardrails
|
|
9392
|
+
}),
|
|
9393
|
+
hotp: (f) => Y$1({
|
|
9394
|
+
...y,
|
|
9395
|
+
counter: f,
|
|
9396
|
+
counterTolerance: e.counterTolerance,
|
|
9397
|
+
guardrails: e.guardrails
|
|
9398
|
+
})
|
|
9399
|
+
});
|
|
9400
|
+
}
|
|
9210
9401
|
//#endregion
|
|
9211
|
-
//#region src/
|
|
9402
|
+
//#region src/auth/totp-manager.ts
|
|
9212
9403
|
/**
|
|
9213
|
-
*
|
|
9214
|
-
* explicit one-shot request, and say so when it still has the default password.
|
|
9404
|
+
* TOTP / 2FA enrollment manager.
|
|
9215
9405
|
*
|
|
9216
|
-
*
|
|
9406
|
+
* Three-state lifecycle keyed by `userId` (PK of `user_totp` collection):
|
|
9217
9407
|
*
|
|
9218
|
-
*
|
|
9219
|
-
*
|
|
9220
|
-
*
|
|
9221
|
-
*
|
|
9222
|
-
*
|
|
9223
|
-
*
|
|
9224
|
-
*
|
|
9225
|
-
*
|
|
9226
|
-
*
|
|
9227
|
-
*
|
|
9408
|
+
* - No row → not set up. `setup()` inserts a row with a
|
|
9409
|
+
* fresh secret and `confirmedAt = null`.
|
|
9410
|
+
* - `confirmedAt = null` → setup in progress; user has the secret but
|
|
9411
|
+
* hasn't proved possession of the
|
|
9412
|
+
* authenticator yet. `verify()` returns
|
|
9413
|
+
* false even with a valid code — the row is
|
|
9414
|
+
* inert until confirmed.
|
|
9415
|
+
* - `confirmedAt != null` → enrolled; `verify()` accepts codes.
|
|
9416
|
+
*
|
|
9417
|
+
* Disable removes the row entirely. The next `setup()` starts fresh —
|
|
9418
|
+
* we do NOT reuse the old secret on re-enroll (avoids the case where a
|
|
9419
|
+
* leaked secret stays valid after a "rotation").
|
|
9420
|
+
*
|
|
9421
|
+
* `otplib` v13's functional API (`generateSync`/`verifySync`) uses RFC 6238
|
|
9422
|
+
* TOTP with a default 30-second period. We pass a per-call `epochTolerance`
|
|
9423
|
+
* of one step in either direction (±30 s) — operator clock skew is the usual
|
|
9424
|
+
* culprit for "valid code rejected" complaints.
|
|
9228
9425
|
*/
|
|
9229
|
-
|
|
9230
|
-
var
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
const
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
const
|
|
9239
|
-
|
|
9240
|
-
if (
|
|
9426
|
+
var TOTP_COLLECTION = "user_totp";
|
|
9427
|
+
var ISSUER = "CamStack";
|
|
9428
|
+
/** RFC 6238 default time step. otplib v13 measures `epochTolerance` in
|
|
9429
|
+
* seconds, so a `window` of N steps maps to `N * TOTP_PERIOD_SECONDS`. */
|
|
9430
|
+
var TOTP_PERIOD_SECONDS = 30;
|
|
9431
|
+
function parseRow(data) {
|
|
9432
|
+
const userId = data["userId"];
|
|
9433
|
+
const secret = data["secret"];
|
|
9434
|
+
const confirmedAt = data["confirmedAt"];
|
|
9435
|
+
const createdAt = data["createdAt"];
|
|
9436
|
+
if (typeof userId !== "string") throw new Error("user_totp row: missing userId");
|
|
9437
|
+
if (typeof secret !== "string") throw new Error("user_totp row: missing secret");
|
|
9438
|
+
if (typeof createdAt !== "number") throw new Error("user_totp row: missing createdAt");
|
|
9241
9439
|
return {
|
|
9242
|
-
|
|
9243
|
-
|
|
9440
|
+
userId,
|
|
9441
|
+
secret,
|
|
9442
|
+
confirmedAt: typeof confirmedAt === "number" ? confirmedAt : null,
|
|
9443
|
+
createdAt
|
|
9244
9444
|
};
|
|
9245
9445
|
}
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
case "other-admin-exists":
|
|
9255
|
-
logger.info("Configured admin username not seeded: another admin account already exists", { meta: { username: outcome.username } });
|
|
9256
|
-
return;
|
|
9257
|
-
case "not-configured":
|
|
9258
|
-
logger.warn("No admin username/password configured — no admin account seeded");
|
|
9259
|
-
return;
|
|
9260
|
-
case "exists": return;
|
|
9261
|
-
}
|
|
9262
|
-
}
|
|
9263
|
-
/** Delete the trigger after acting on it — a reset must never repeat by itself. */
|
|
9264
|
-
function consumeTrigger(triggerPath, outcome, input) {
|
|
9265
|
-
if (outcome.kind !== "reset") input.logger.warn("Admin password reset requested, but no configured admin account to reset", { meta: {
|
|
9266
|
-
trigger: triggerPath,
|
|
9267
|
-
outcome: outcome.kind
|
|
9268
|
-
} });
|
|
9269
|
-
try {
|
|
9270
|
-
input.fs.remove(triggerPath);
|
|
9271
|
-
} catch (err) {
|
|
9272
|
-
input.logger.error("Could not delete the admin password reset trigger — delete it by hand, or every restart resets the admin password again", { meta: {
|
|
9273
|
-
trigger: triggerPath,
|
|
9274
|
-
error: err instanceof Error ? err.message : String(err)
|
|
9275
|
-
} });
|
|
9446
|
+
var TotpManager = class {
|
|
9447
|
+
store;
|
|
9448
|
+
/** ±N steps of clock-skew tolerance, expressed in seconds for otplib v13's
|
|
9449
|
+
* per-call `epochTolerance`. Default window 1 = ±30 s. */
|
|
9450
|
+
epochTolerance;
|
|
9451
|
+
constructor(store, opts = {}) {
|
|
9452
|
+
this.store = store;
|
|
9453
|
+
this.epochTolerance = (opts.window ?? 1) * TOTP_PERIOD_SECONDS;
|
|
9276
9454
|
}
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
*
|
|
9282
|
-
* `
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9455
|
+
/**
|
|
9456
|
+
* Begin enrollment. Always generates a fresh secret — calling `setup`
|
|
9457
|
+
* a second time on the same user replaces any pending half-enrollment
|
|
9458
|
+
* (the user must rescan the QR / re-enter the secret).
|
|
9459
|
+
*
|
|
9460
|
+
* The user identity (`label`) is what an authenticator displays as
|
|
9461
|
+
* the account name; we use the supplied `username` for human-readable
|
|
9462
|
+
* recognition. `ISSUER` is the CamStack brand.
|
|
9463
|
+
*/
|
|
9464
|
+
async setup(userId, username) {
|
|
9465
|
+
const secret = I();
|
|
9466
|
+
const otpauthUrl = T({
|
|
9467
|
+
secret,
|
|
9468
|
+
label: username,
|
|
9469
|
+
issuer: ISSUER
|
|
9470
|
+
});
|
|
9471
|
+
const row = {
|
|
9472
|
+
userId,
|
|
9473
|
+
secret,
|
|
9474
|
+
confirmedAt: null,
|
|
9475
|
+
createdAt: Date.now()
|
|
9476
|
+
};
|
|
9477
|
+
await this.store.delete.mutate({
|
|
9478
|
+
collection: TOTP_COLLECTION,
|
|
9479
|
+
key: userId
|
|
9480
|
+
});
|
|
9481
|
+
await this.store.insert.mutate({
|
|
9482
|
+
collection: TOTP_COLLECTION,
|
|
9483
|
+
record: {
|
|
9484
|
+
id: userId,
|
|
9485
|
+
data: { ...row }
|
|
9293
9486
|
}
|
|
9294
|
-
}
|
|
9295
|
-
|
|
9296
|
-
|
|
9487
|
+
});
|
|
9488
|
+
return {
|
|
9489
|
+
secret,
|
|
9490
|
+
otpauthUrl
|
|
9491
|
+
};
|
|
9492
|
+
}
|
|
9493
|
+
/**
|
|
9494
|
+
* Confirm enrollment by checking a code from the authenticator
|
|
9495
|
+
* against the pending secret. On success, flips `confirmedAt` so
|
|
9496
|
+
* `verify()` starts accepting codes. Idempotent on already-confirmed
|
|
9497
|
+
* rows: re-confirming a valid code just succeeds with no state
|
|
9498
|
+
* change.
|
|
9499
|
+
*/
|
|
9500
|
+
async confirm(userId, code) {
|
|
9501
|
+
const row = await this.find(userId);
|
|
9502
|
+
if (!row) return false;
|
|
9503
|
+
if (!d({
|
|
9504
|
+
token: code,
|
|
9505
|
+
secret: row.secret,
|
|
9506
|
+
epochTolerance: this.epochTolerance
|
|
9507
|
+
}).valid) return false;
|
|
9508
|
+
if (row.confirmedAt === null) {
|
|
9509
|
+
const next = {
|
|
9510
|
+
...row,
|
|
9511
|
+
confirmedAt: Date.now()
|
|
9512
|
+
};
|
|
9513
|
+
await this.store.update.mutate({
|
|
9514
|
+
collection: TOTP_COLLECTION,
|
|
9515
|
+
id: userId,
|
|
9516
|
+
data: { ...next }
|
|
9517
|
+
});
|
|
9297
9518
|
}
|
|
9298
|
-
|
|
9299
|
-
}
|
|
9300
|
-
/**
|
|
9301
|
-
*
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
*
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
*
|
|
9327
|
-
*
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
|
|
9519
|
+
return true;
|
|
9520
|
+
}
|
|
9521
|
+
/**
|
|
9522
|
+
* Remove enrollment. Idempotent — no error if the user had no row.
|
|
9523
|
+
*/
|
|
9524
|
+
async disable(userId) {
|
|
9525
|
+
await this.store.delete.mutate({
|
|
9526
|
+
collection: TOTP_COLLECTION,
|
|
9527
|
+
key: userId
|
|
9528
|
+
});
|
|
9529
|
+
}
|
|
9530
|
+
/**
|
|
9531
|
+
* Verify a code against the confirmed secret. Returns `false` when:
|
|
9532
|
+
* - The user has no row (not set up).
|
|
9533
|
+
* - The row is pending (`confirmedAt === null`).
|
|
9534
|
+
* - The code doesn't match within the configured window.
|
|
9535
|
+
*/
|
|
9536
|
+
async verify(userId, code) {
|
|
9537
|
+
const row = await this.find(userId);
|
|
9538
|
+
if (!row) return false;
|
|
9539
|
+
if (row.confirmedAt === null) return false;
|
|
9540
|
+
return d({
|
|
9541
|
+
token: code,
|
|
9542
|
+
secret: row.secret,
|
|
9543
|
+
epochTolerance: this.epochTolerance
|
|
9544
|
+
}).valid;
|
|
9545
|
+
}
|
|
9546
|
+
/**
|
|
9547
|
+
* Read the user's enrollment status. Pending half-enrollments are
|
|
9548
|
+
* reported as `enabled: false` — only a confirmed row counts.
|
|
9549
|
+
*/
|
|
9550
|
+
async getStatus(userId) {
|
|
9551
|
+
const row = await this.find(userId);
|
|
9552
|
+
if (!row) return {
|
|
9553
|
+
enabled: false,
|
|
9554
|
+
confirmedAt: null
|
|
9555
|
+
};
|
|
9556
|
+
return {
|
|
9557
|
+
enabled: row.confirmedAt !== null,
|
|
9558
|
+
confirmedAt: row.confirmedAt
|
|
9559
|
+
};
|
|
9560
|
+
}
|
|
9561
|
+
async find(userId) {
|
|
9562
|
+
const results = await this.store.query.query({
|
|
9563
|
+
collection: TOTP_COLLECTION,
|
|
9564
|
+
filter: { where: { userId } }
|
|
9565
|
+
});
|
|
9566
|
+
if (results.length === 0) return null;
|
|
9567
|
+
return parseRow(results[0].data);
|
|
9568
|
+
}
|
|
9569
|
+
};
|
|
9570
|
+
//#endregion
|
|
9571
|
+
//#region src/kernel/config-schema.ts
|
|
9572
|
+
/** Default data directory — used when CAMSTACK_DATA env var is not set */
|
|
9573
|
+
var DEFAULT_DATA_PATH = "camstack-data";
|
|
9332
9574
|
/**
|
|
9333
|
-
*
|
|
9334
|
-
*
|
|
9335
|
-
*
|
|
9336
|
-
*
|
|
9337
|
-
*
|
|
9338
|
-
* never matches it. `cleanupOrphans` drops tokens owned by the legacy 'system'
|
|
9339
|
-
* id or by a user that no longer exists."
|
|
9575
|
+
* A list of browser origins from config.yaml (a YAML list) or from the
|
|
9576
|
+
* `CAMSTACK_CORS_ORIGINS` env var (one comma-separated string — env carries
|
|
9577
|
+
* only strings). Validation of each entry is the CORS policy's job
|
|
9578
|
+
* (`server/backend/src/api/cors/cors-policy.ts`), which logs what it rejects;
|
|
9579
|
+
* the schema only fixes the SHAPE so a typo cannot stop the hub from booting.
|
|
9340
9580
|
*/
|
|
9341
|
-
var
|
|
9581
|
+
var originListSchema = zod.z.preprocess((value) => typeof value === "string" ? value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) : value, zod.z.array(zod.z.string()));
|
|
9582
|
+
var serverSchema = zod.z.object({
|
|
9583
|
+
port: zod.z.number().default(4443),
|
|
9584
|
+
host: zod.z.string().default("0.0.0.0"),
|
|
9585
|
+
dataPath: zod.z.string().default(DEFAULT_DATA_PATH),
|
|
9586
|
+
/**
|
|
9587
|
+
* EXTRA browser origins allowed to call this hub cross-origin (D707), on
|
|
9588
|
+
* top of the built-ins: the hub's own origin, the hosted viewer PWA and the
|
|
9589
|
+
* native app's opaque `null` origin. Bootstrap-owned on purpose — widening
|
|
9590
|
+
* who may read hub responses in a browser is a security decision, and the
|
|
9591
|
+
* policy is live from the first request, before the settings store exists.
|
|
9592
|
+
*/
|
|
9593
|
+
corsOrigins: originListSchema.default([])
|
|
9594
|
+
});
|
|
9595
|
+
var authSchema = zod.z.object({
|
|
9596
|
+
jwtSecret: zod.z.string().nullable().default(null),
|
|
9597
|
+
adminUsername: zod.z.string().default("admin"),
|
|
9598
|
+
adminPassword: zod.z.string().default("changeme")
|
|
9599
|
+
});
|
|
9600
|
+
var hubSchema = zod.z.object({
|
|
9601
|
+
url: zod.z.string().default("ws://localhost:4443/agent"),
|
|
9602
|
+
token: zod.z.string().default("")
|
|
9603
|
+
});
|
|
9604
|
+
var agentSchema = zod.z.object({
|
|
9605
|
+
name: zod.z.string().default(""),
|
|
9606
|
+
/** Port for the agent status page (minimal HTML) */
|
|
9607
|
+
statusPort: zod.z.number().default(4444)
|
|
9608
|
+
});
|
|
9609
|
+
var tlsSchema = zod.z.object({
|
|
9342
9610
|
/**
|
|
9343
|
-
*
|
|
9344
|
-
*
|
|
9345
|
-
*
|
|
9346
|
-
* retention="ONLY revoked rows are pruned: the daily sweep deletes sessions
|
|
9347
|
-
* revoked more than OAUTH_REVOKED_RETENTION_MS (30 d) ago. An ACTIVE session
|
|
9348
|
-
* has `revokedAt` NULL, never expires and is never swept — the table grows one
|
|
9349
|
-
* row per login until an operator revokes the link."
|
|
9611
|
+
* Enable HTTPS (default: true). The certificate itself is never configured
|
|
9612
|
+
* here: the admin UI (Network → Local access) generates or uploads it
|
|
9613
|
+
* (D726), and boot serves whatever `tls/mode` says.
|
|
9350
9614
|
*/
|
|
9351
|
-
|
|
9615
|
+
enabled: zod.z.boolean().default(true) });
|
|
9352
9616
|
/**
|
|
9353
|
-
*
|
|
9354
|
-
*
|
|
9355
|
-
*
|
|
9356
|
-
*
|
|
9357
|
-
*
|
|
9358
|
-
* - Setup: row inserted with `confirmedAt = null` and a fresh secret.
|
|
9359
|
-
* - Confirm: a valid code from the secret flips `confirmedAt` to now().
|
|
9360
|
-
* - Disable: the row is deleted (next setup starts fresh).
|
|
9361
|
-
*
|
|
9362
|
-
* "Enabled" means `confirmedAt != null`. A half-enrolled row (`confirmedAt
|
|
9363
|
-
* = null`) is INACTIVE — `verifyTotp` returns false until confirmation.
|
|
9617
|
+
* Bootstrap-time addon configuration — read PURELY from YAML, before the
|
|
9618
|
+
* kernel can talk to the settings-store. This breaks the chicken-and-egg
|
|
9619
|
+
* where the settings store itself is implemented by an addon: the kernel
|
|
9620
|
+
* needs to know WHICH addon to load first to provide the store, and that
|
|
9621
|
+
* decision can't itself live in the store.
|
|
9364
9622
|
*
|
|
9365
|
-
*
|
|
9366
|
-
*
|
|
9367
|
-
* first); `confirm` flips `confirmedAt` once a valid code proves possession"
|
|
9368
|
-
* retention="`disable` deletes the row, and `setup` deletes before re-inserting.
|
|
9369
|
-
* Nothing else prunes — deleting a USER does not cascade here, so their
|
|
9370
|
-
* enrollment row outlives the account."
|
|
9623
|
+
* Backward-compatible: every field is optional + defaults reproduce the
|
|
9624
|
+
* current hard-coded behavior. Existing config.yaml files keep working.
|
|
9371
9625
|
*/
|
|
9372
|
-
var
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
},
|
|
9447
|
-
{
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
{
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
},
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
}
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
{
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
}
|
|
9494
|
-
{
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
},
|
|
9515
|
-
{
|
|
9516
|
-
name: "scopes",
|
|
9517
|
-
type: "JSON"
|
|
9518
|
-
},
|
|
9519
|
-
{
|
|
9520
|
-
name: "expiresAt",
|
|
9521
|
-
type: "INTEGER"
|
|
9522
|
-
},
|
|
9523
|
-
{
|
|
9524
|
-
name: "lastUsedAt",
|
|
9525
|
-
type: "INTEGER"
|
|
9526
|
-
},
|
|
9527
|
-
{
|
|
9528
|
-
name: "createdAt",
|
|
9529
|
-
type: "INTEGER",
|
|
9530
|
-
notNull: true
|
|
9626
|
+
var bootstrapAddonsSchema = zod.z.object({
|
|
9627
|
+
/**
|
|
9628
|
+
* Addon implementing the `settings-store` capability. Loaded FIRST,
|
|
9629
|
+
* before any other addon is enabled — every other addon's enable
|
|
9630
|
+
* state, settings, and ordering lives in the store it provides.
|
|
9631
|
+
*
|
|
9632
|
+
* Default: '@camstack/addon-sqlite-storage' (the workspace's
|
|
9633
|
+
* default SQLite-backed store). Future alternatives could swap this
|
|
9634
|
+
* for postgres/redis without touching kernel code.
|
|
9635
|
+
*/
|
|
9636
|
+
settingsStoreAddon: zod.z.string().default("@camstack/addon-sqlite-storage"),
|
|
9637
|
+
/**
|
|
9638
|
+
* How the launcher provisions REQUIRED_PACKAGES into `data/addons/`
|
|
9639
|
+
* on startup.
|
|
9640
|
+
*
|
|
9641
|
+
* - `'npm'` — pull from the npm registry (production default).
|
|
9642
|
+
* - `'local'` — copy `packages/<X>/` from the workspace into
|
|
9643
|
+
* `data/addons/<X>/`. Use for the Electron-
|
|
9644
|
+
* packaged bundle and the e2e harness.
|
|
9645
|
+
* - `'symlink'` — point `data/addons/<X>` at the workspace source
|
|
9646
|
+
* dir via a directory symlink. Dev-only: a
|
|
9647
|
+
* workspace rebuild (`npm run build -w <X>`) is
|
|
9648
|
+
* immediately reflected at the next addon load,
|
|
9649
|
+
* no copy step. Falls back to `'local'` on
|
|
9650
|
+
* platforms that reject symlink creation (Windows
|
|
9651
|
+
* without developer-mode).
|
|
9652
|
+
*
|
|
9653
|
+
* The `CAMSTACK_INSTALL_SOURCE` env var, when set, wins over this
|
|
9654
|
+
* field. Leave undefined to keep the launcher's default behaviour
|
|
9655
|
+
* (npm when no workspace, local when a bundled addons dir is
|
|
9656
|
+
* present, …).
|
|
9657
|
+
*/
|
|
9658
|
+
installSource: zod.z.enum([
|
|
9659
|
+
"npm",
|
|
9660
|
+
"local",
|
|
9661
|
+
"symlink"
|
|
9662
|
+
]).optional(),
|
|
9663
|
+
/**
|
|
9664
|
+
* Override the kernel's hard-coded `AddonInstaller.REQUIRED_PACKAGES`.
|
|
9665
|
+
* Passing an array here REPLACES the default list entirely — useful
|
|
9666
|
+
* for stripped-down deployments (only the bundle of addons the
|
|
9667
|
+
* operator actually wants) and for forcing a different storage
|
|
9668
|
+
* backend at first boot.
|
|
9669
|
+
*
|
|
9670
|
+
* Leave undefined to use the kernel-shipped defaults.
|
|
9671
|
+
*/
|
|
9672
|
+
requiredAddons: zod.z.array(zod.z.string()).optional(),
|
|
9673
|
+
/**
|
|
9674
|
+
* Disable list applied AFTER the settings-store reports the enabled
|
|
9675
|
+
* set. Operator escape hatch — boot without a known-broken addon
|
|
9676
|
+
* without having to mutate the store.
|
|
9677
|
+
*/
|
|
9678
|
+
forceDisable: zod.z.array(zod.z.string()).default([])
|
|
9679
|
+
});
|
|
9680
|
+
/**
|
|
9681
|
+
* Where the keychain's master key lives (D788). config.yaml ONLY: no env var
|
|
9682
|
+
* maps here, and the settings store may not answer it
|
|
9683
|
+
* (`BOOTSTRAP_OWNED_SECTIONS`). Absent → the desktop OS keychain when the hub
|
|
9684
|
+
* runs inside the desktop app, else `<dataDir>/.keychain/master.key`.
|
|
9685
|
+
*/
|
|
9686
|
+
var keychainSchema = zod.z.object({
|
|
9687
|
+
/** A file holding 32 random bytes, base64 (or hex). Keep it OFF the data volume. */
|
|
9688
|
+
keyFile: zod.z.string().min(1).optional(),
|
|
9689
|
+
/**
|
|
9690
|
+
* The key being rotated away from — consulted only to UNLOCK, so a move to a
|
|
9691
|
+
* new key file is: new file, old one here, restart, rotate, remove this.
|
|
9692
|
+
*/
|
|
9693
|
+
previousKeyFile: zod.z.string().min(1).optional()
|
|
9694
|
+
});
|
|
9695
|
+
/** Bootstrap config -- loaded from config.yaml ONLY at startup.
|
|
9696
|
+
* All other settings live in SQL (system_settings table). */
|
|
9697
|
+
var bootstrapSchema = zod.z.object({
|
|
9698
|
+
/** Server mode: 'hub' (full server) or 'agent' (worker node) */
|
|
9699
|
+
mode: zod.z.enum(["hub", "agent"]).default("hub"),
|
|
9700
|
+
server: serverSchema.default(serverSchema.parse({})),
|
|
9701
|
+
auth: authSchema.default(authSchema.parse({})),
|
|
9702
|
+
/** Hub connection config — only used when mode='agent' */
|
|
9703
|
+
hub: hubSchema.default(hubSchema.parse({})),
|
|
9704
|
+
/** Agent-specific config — only used when mode='agent' */
|
|
9705
|
+
agent: agentSchema.default(agentSchema.parse({})),
|
|
9706
|
+
/** TLS configuration */
|
|
9707
|
+
tls: tlsSchema.default(tlsSchema.parse({})),
|
|
9708
|
+
/**
|
|
9709
|
+
* Addon bootstrap directives — minimum addon set needed before the
|
|
9710
|
+
* kernel can reach the settings-store. See bootstrapAddonsSchema.
|
|
9711
|
+
* Spec: docs/superpowers/specs/2026-05-15-yaml-bootstrap-expansion.md
|
|
9712
|
+
*/
|
|
9713
|
+
bootstrap: bootstrapAddonsSchema.default(bootstrapAddonsSchema.parse({})),
|
|
9714
|
+
/** Keychain master-key location (config.yaml only). */
|
|
9715
|
+
keychain: keychainSchema.default({})
|
|
9716
|
+
});
|
|
9717
|
+
//#endregion
|
|
9718
|
+
//#region src/builtins/local-auth/admin-account-boot.ts
|
|
9719
|
+
/**
|
|
9720
|
+
* The admin account at boot: seed it once, reset it only on the operator's
|
|
9721
|
+
* explicit one-shot request, and say so when it still has the default password.
|
|
9722
|
+
*
|
|
9723
|
+
* ## Why a trigger FILE, not a variable
|
|
9724
|
+
*
|
|
9725
|
+
* The configured password (`CAMSTACK_ADMIN_PASS` / `auth.adminPassword`) seeds
|
|
9726
|
+
* the admin on first boot and is never applied again — a password the operator
|
|
9727
|
+
* changes in the admin UI survives every restart. Recovering a forgotten
|
|
9728
|
+
* password therefore needs an explicit request, and that request must be
|
|
9729
|
+
* ONE-SHOT: an environment flag stays set in a container template until someone
|
|
9730
|
+
* remembers to remove it, and while it is set it reverts the password on every
|
|
9731
|
+
* boot — the very defect this replaced. A file the hub DELETES after acting on
|
|
9732
|
+
* it cannot be left armed: create `RESET_ADMIN_PASSWORD` in the node's data
|
|
9733
|
+
* directory (`/data` in the container), restart, and the configured admin's
|
|
9734
|
+
* password is set back to the configured one, once.
|
|
9735
|
+
*/
|
|
9736
|
+
/** The trigger's name, at the root of the node's data directory. */
|
|
9737
|
+
var ADMIN_PASSWORD_RESET_TRIGGER = "RESET_ADMIN_PASSWORD";
|
|
9738
|
+
async function reconcileAdminAccountAtBoot(input) {
|
|
9739
|
+
const { users, fs, logger } = input;
|
|
9740
|
+
const triggerPath = node_path.join(input.dataDir, ADMIN_PASSWORD_RESET_TRIGGER);
|
|
9741
|
+
const resetRequested = fs.exists(triggerPath);
|
|
9742
|
+
const outcome = await users.ensureAdminExists({ resetPassword: resetRequested });
|
|
9743
|
+
logOutcome(outcome, logger);
|
|
9744
|
+
if (resetRequested) consumeTrigger(triggerPath, outcome, input);
|
|
9745
|
+
const username = "username" in outcome ? outcome.username : null;
|
|
9746
|
+
const usesDefaultPassword = username !== null && await users.hasPassword(username, "changeme");
|
|
9747
|
+
if (usesDefaultPassword && username !== null) logger.warn("The admin account still uses the default password — change it in the admin UI (the first sign-in with it asks for a new one)", { meta: { username } });
|
|
9748
|
+
return {
|
|
9749
|
+
outcome,
|
|
9750
|
+
usesDefaultPassword
|
|
9751
|
+
};
|
|
9752
|
+
}
|
|
9753
|
+
function logOutcome(outcome, logger) {
|
|
9754
|
+
switch (outcome.kind) {
|
|
9755
|
+
case "seeded":
|
|
9756
|
+
logger.info("Admin account seeded from the configured credentials (first boot)", { meta: { username: outcome.username } });
|
|
9757
|
+
return;
|
|
9758
|
+
case "reset":
|
|
9759
|
+
logger.warn("Admin password reset to the configured password, as requested", { meta: { username: outcome.username } });
|
|
9760
|
+
return;
|
|
9761
|
+
case "other-admin-exists":
|
|
9762
|
+
logger.info("Configured admin username not seeded: another admin account already exists", { meta: { username: outcome.username } });
|
|
9763
|
+
return;
|
|
9764
|
+
case "not-configured":
|
|
9765
|
+
logger.warn("No admin username/password configured — no admin account seeded");
|
|
9766
|
+
return;
|
|
9767
|
+
case "exists": return;
|
|
9531
9768
|
}
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
name: "username",
|
|
9547
|
-
type: "TEXT",
|
|
9548
|
-
notNull: true
|
|
9549
|
-
},
|
|
9550
|
-
{
|
|
9551
|
-
name: "integrationId",
|
|
9552
|
-
type: "TEXT",
|
|
9553
|
-
notNull: true
|
|
9554
|
-
},
|
|
9555
|
-
{
|
|
9556
|
-
name: "scopes",
|
|
9557
|
-
type: "JSON"
|
|
9558
|
-
},
|
|
9559
|
-
{
|
|
9560
|
-
name: "createdAt",
|
|
9561
|
-
type: "INTEGER",
|
|
9562
|
-
notNull: true
|
|
9563
|
-
},
|
|
9564
|
-
{
|
|
9565
|
-
name: "lastUsedAt",
|
|
9566
|
-
type: "INTEGER",
|
|
9567
|
-
notNull: true
|
|
9568
|
-
},
|
|
9569
|
-
{
|
|
9570
|
-
name: "revokedAt",
|
|
9571
|
-
type: "INTEGER"
|
|
9769
|
+
}
|
|
9770
|
+
/** Delete the trigger after acting on it — a reset must never repeat by itself. */
|
|
9771
|
+
function consumeTrigger(triggerPath, outcome, input) {
|
|
9772
|
+
if (outcome.kind !== "reset") input.logger.warn("Admin password reset requested, but no configured admin account to reset", { meta: {
|
|
9773
|
+
trigger: triggerPath,
|
|
9774
|
+
outcome: outcome.kind
|
|
9775
|
+
} });
|
|
9776
|
+
try {
|
|
9777
|
+
input.fs.remove(triggerPath);
|
|
9778
|
+
} catch (err) {
|
|
9779
|
+
input.logger.error("Could not delete the admin password reset trigger — delete it by hand, or every restart resets the admin password again", { meta: {
|
|
9780
|
+
trigger: triggerPath,
|
|
9781
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9782
|
+
} });
|
|
9572
9783
|
}
|
|
9573
|
-
|
|
9784
|
+
}
|
|
9785
|
+
//#endregion
|
|
9786
|
+
//#region src/builtins/local-auth/auth-config-reader.ts
|
|
9574
9787
|
/**
|
|
9575
|
-
*
|
|
9576
|
-
* `
|
|
9577
|
-
* managers. Idempotent — `declareCollection` is a no-op when the
|
|
9578
|
-
* collection has already been declared (same shape).
|
|
9788
|
+
* Every config path this reader can answer. Anything else resolves to
|
|
9789
|
+
* `undefined` by construction — see the module docblock.
|
|
9579
9790
|
*/
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
await store.declareCollection.mutate({
|
|
9606
|
-
collection: OAUTH_SESSIONS_COLLECTION,
|
|
9607
|
-
columns: OAUTH_SESSIONS_COLUMNS,
|
|
9608
|
-
indexes: [{
|
|
9609
|
-
name: "idx_oauth_sessions_user_id",
|
|
9610
|
-
columns: ["userId"]
|
|
9611
|
-
}]
|
|
9612
|
-
});
|
|
9791
|
+
function createAuthConfigReader(resolved, writeAuthSection) {
|
|
9792
|
+
return {
|
|
9793
|
+
get(path) {
|
|
9794
|
+
switch (path) {
|
|
9795
|
+
case "auth.jwtSecret": return narrow(resolved.jwtSecret);
|
|
9796
|
+
case "auth.adminUsername": return narrow(resolved.adminUsername);
|
|
9797
|
+
case "auth.adminPassword": return narrow(resolved.adminPassword);
|
|
9798
|
+
case "auth.tokenExpiry": return narrow(resolved.tokenExpiry);
|
|
9799
|
+
default: return;
|
|
9800
|
+
}
|
|
9801
|
+
},
|
|
9802
|
+
update(_section, data) {
|
|
9803
|
+
if (typeof data["jwtSecret"] === "string") writeAuthSection({ jwtSecret: data["jwtSecret"] });
|
|
9804
|
+
}
|
|
9805
|
+
};
|
|
9806
|
+
}
|
|
9807
|
+
/**
|
|
9808
|
+
* The one documented type boundary in this module. `AuthConfigReader.get` is
|
|
9809
|
+
* the legacy `get<T>(path)` shape shared with `ConfigManager.get`, which is
|
|
9810
|
+
* likewise unchecked at runtime — every value returned here is a `string |
|
|
9811
|
+
* undefined` the caller immediately re-validates (`parseTokenExpiry`, the
|
|
9812
|
+
* truthiness gate on `jwtSecret`).
|
|
9813
|
+
*/
|
|
9814
|
+
function narrow(value) {
|
|
9815
|
+
return value;
|
|
9613
9816
|
}
|
|
9614
9817
|
//#endregion
|
|
9615
9818
|
//#region src/builtins/local-auth/oauth-grants.ts
|
|
@@ -9965,6 +10168,7 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
|
|
|
9965
10168
|
oauthSessionManager = null;
|
|
9966
10169
|
reapTimer = null;
|
|
9967
10170
|
totpManager = null;
|
|
10171
|
+
externalIdentities = null;
|
|
9968
10172
|
constructor() {
|
|
9969
10173
|
super({
|
|
9970
10174
|
jwtSecret: "",
|
|
@@ -9994,6 +10198,7 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
|
|
|
9994
10198
|
this.scopedTokenManager = new ScopedTokenManager(store);
|
|
9995
10199
|
this.oauthSessionManager = new OauthSessionManager(store);
|
|
9996
10200
|
this.totpManager = new TotpManager(store);
|
|
10201
|
+
this.externalIdentities = new ExternalIdentityManager(store);
|
|
9997
10202
|
try {
|
|
9998
10203
|
await reconcileAdminAccountAtBoot({
|
|
9999
10204
|
users: this.userManager,
|
|
@@ -10086,6 +10291,7 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
|
|
|
10086
10291
|
deleteUser: async (input) => {
|
|
10087
10292
|
if (!this.userManager) throw new Error("User management not available");
|
|
10088
10293
|
await this.userManager.delete(input.id);
|
|
10294
|
+
await this.externalIdentities?.removeAllForUser(input.id);
|
|
10089
10295
|
return { success: true };
|
|
10090
10296
|
},
|
|
10091
10297
|
resetPassword: async (input) => {
|
|
@@ -10179,6 +10385,28 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
|
|
|
10179
10385
|
revokedAt: s.revokedAt
|
|
10180
10386
|
}));
|
|
10181
10387
|
},
|
|
10388
|
+
listExternalIdentities: async (input) => {
|
|
10389
|
+
if (!this.externalIdentities) return [];
|
|
10390
|
+
return [...await this.externalIdentities.listForUser(input.userId)];
|
|
10391
|
+
},
|
|
10392
|
+
findUserByExternalIdentity: async (input) => {
|
|
10393
|
+
if (!this.externalIdentities) return null;
|
|
10394
|
+
const link = await this.externalIdentities.findLink(input.provider, input.subject);
|
|
10395
|
+
return link === null ? null : {
|
|
10396
|
+
userId: link.userId,
|
|
10397
|
+
issuer: link.issuer
|
|
10398
|
+
};
|
|
10399
|
+
},
|
|
10400
|
+
linkExternalIdentity: async (input) => {
|
|
10401
|
+
if (!this.externalIdentities || !this.userManager) throw new Error("External sign-in linking not available");
|
|
10402
|
+
if (!await this.userManager.findById(input.userId)) throw new Error(`User not found: ${input.userId}`);
|
|
10403
|
+
await this.externalIdentities.link(input);
|
|
10404
|
+
return { success: true };
|
|
10405
|
+
},
|
|
10406
|
+
unlinkExternalIdentity: async (input) => {
|
|
10407
|
+
if (!this.externalIdentities) return { success: false };
|
|
10408
|
+
return { success: await this.externalIdentities.unlink(input) };
|
|
10409
|
+
},
|
|
10182
10410
|
revokeOauthSession: async (input) => {
|
|
10183
10411
|
if (!this.oauthSessionManager) return { success: false };
|
|
10184
10412
|
return { success: await this.oauthSessionManager.markRevoked(input.id) };
|
|
@@ -10252,6 +10480,7 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
|
|
|
10252
10480
|
this.apiKeyManager = null;
|
|
10253
10481
|
this.scopedTokenManager = null;
|
|
10254
10482
|
this.oauthSessionManager = null;
|
|
10483
|
+
this.externalIdentities = null;
|
|
10255
10484
|
}
|
|
10256
10485
|
};
|
|
10257
10486
|
//#endregion
|