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