@camstack/system 1.2.378 → 1.2.379
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/auth-manager.d.ts +10 -2
- package/dist/auth/jwt-signing-key.d.ts +42 -0
- package/dist/auth/user-manager.d.ts +19 -19
- 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.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +34 -4
- package/dist/builtins/keychain/index.mjs +34 -4
- package/dist/builtins/keychain/keychain-service.d.ts +14 -2
- package/dist/builtins/keychain/system-held-secrets.d.ts +11 -0
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/admin-account-boot.d.ts +1 -1
- package/dist/builtins/local-auth/auth-config-reader.d.ts +9 -7
- package/dist/builtins/local-auth/jwt-signing-key-keeper.d.ts +30 -0
- package/dist/builtins/local-auth/local-auth.addon.d.ts +2 -6
- package/dist/builtins/local-auth/local-auth.addon.js +127 -221
- package/dist/builtins/local-auth/local-auth.addon.mjs +126 -220
- 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/cluster-secret-keeper.d.ts +36 -0
- package/dist/builtins/system-config/system-config.addon.d.ts +13 -0
- package/dist/builtins/system-config/system-config.addon.js +244 -53
- package/dist/builtins/system-config/system-config.addon.mjs +244 -54
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-C9lmrWFT.js → child-cap-dispatch-CZeRUgYF.js} +1 -1
- package/dist/{child-cap-dispatch-DpHovtQd.mjs → child-cap-dispatch-CpjohToR.mjs} +1 -1
- package/dist/cluster-secret-BDue-uJj.mjs +83 -0
- package/dist/cluster-secret-Dg0OOOSr.js +148 -0
- package/dist/{composition-sources-CxhGdtdh.js → composition-sources-CHvLMrFI.js} +1 -1
- package/dist/{composition-sources-BLBd338N.mjs → composition-sources-DT_uJwI3.mjs} +1 -1
- package/dist/{dist-Bvl5JgNt.mjs → dist-B9oFw2jh.mjs} +58 -2
- package/dist/{dist-KxsOMQik.js → dist-Cc6KOdY-.js} +69 -1
- package/dist/{event-loop-stall-monitor-D8rqSWfW.mjs → event-loop-stall-monitor-anUH2IE1.mjs} +14 -14
- package/dist/{event-loop-stall-monitor-CbSmCWkp.js → event-loop-stall-monitor-mk6WGxir.js} +14 -14
- package/dist/index.d.ts +4 -2
- package/dist/index.js +192 -62
- package/dist/index.mjs +171 -55
- package/dist/jwt-signing-key-C2rE3wH6.js +174 -0
- package/dist/jwt-signing-key-Qhde8r82.mjs +127 -0
- package/dist/kernel/config-manager.d.ts +1 -1
- package/dist/kernel/config-schema.d.ts +4 -11
- package/dist/kernel/index.d.ts +2 -1
- package/dist/kernel/moleculer/broker-factory.d.ts +0 -2
- package/dist/kernel/moleculer/cluster-secret.d.ts +31 -0
- package/dist/kernel/moleculer/hub-service.d.ts +7 -4
- package/dist/kernel/moleculer/node-registry.d.ts +1 -1
- package/dist/kernel/system-settings-policy.d.ts +7 -9
- package/dist/{retired-settings-keys-BAcfvFtm.mjs → retired-settings-keys-DlZ3VV6x.mjs} +1 -1
- package/dist/{retired-settings-keys-gTsZfpmP.js → retired-settings-keys-YOOKLA3v.js} +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SystemHeldSecretName } from '@camstack/types';
|
|
2
|
+
import { SYSTEM_SECRET_OWNER } from '../../auth/jwt-signing-key.js';
|
|
3
|
+
import { CLUSTER_SECRET_REF } from '../../kernel/moleculer/cluster-secret.js';
|
|
4
|
+
export { SYSTEM_SECRET_OWNER };
|
|
5
|
+
/** The refs `keychain-admin.setSystemSecret` may write, by name. */
|
|
6
|
+
export declare const SYSTEM_HELD_SECRET_REFS: Readonly<Record<SystemHeldSecretName, string>>;
|
|
7
|
+
/** System refs no blob points at — in use for as long as they exist. */
|
|
8
|
+
export declare const PROCESS_HELD_SECRET_REFS: ReadonlySet<string>;
|
|
9
|
+
/** `system-settings` keys that may hold a system-owned secret's marker. */
|
|
10
|
+
export declare const SYSTEM_SECRET_SETTINGS_KEYS: readonly string[];
|
|
11
|
+
export { CLUSTER_SECRET_REF };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-Cc6KOdY-.js");
|
|
4
4
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
5
5
|
var NO_DEVICES = "liveness:no-devices";
|
|
6
6
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Cr as createEvent, F as RecordingStatusSchema, N as RECORDING_SOURCE_CAMSTACK_ADDON, cr as errMsg, lr as BaseAddon, qr as EventCategory } from "../../dist-B9oFw2jh.mjs";
|
|
2
2
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
3
3
|
var NO_DEVICES = "liveness:no-devices";
|
|
4
4
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -21,7 +21,7 @@ export interface AdminAccountBootInput {
|
|
|
21
21
|
}
|
|
22
22
|
export interface AdminAccountBootResult {
|
|
23
23
|
readonly outcome: AdminSeedOutcome;
|
|
24
|
-
/** The admin still signs in with {@link
|
|
24
|
+
/** The admin still signs in with {@link FIRST_BOOT_ADMIN_PASSWORD}. */
|
|
25
25
|
readonly usesDefaultPassword: boolean;
|
|
26
26
|
}
|
|
27
27
|
export declare function reconcileAdminAccountAtBoot(input: AdminAccountBootInput): Promise<AdminAccountBootResult>;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { AuthConfigReader } from '../../auth/auth-manager.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* The values `local-auth` pre-resolves before constructing its AuthManager.
|
|
4
|
+
*
|
|
5
|
+
* No credential is among them (D797): the session signing key lives in the
|
|
6
|
+
* keychain (`jwt-signing-key.ts`) and the first-boot admin is fixed
|
|
7
|
+
* (`admin` / `changeme`), so `auth.jwtSecret`, `auth.adminUsername` and
|
|
8
|
+
* `auth.adminPassword` are answered by nothing.
|
|
9
|
+
*/
|
|
3
10
|
export interface ResolvedAuthConfig {
|
|
4
|
-
readonly jwtSecret: string;
|
|
5
|
-
readonly adminUsername: string;
|
|
6
|
-
readonly adminPassword: string;
|
|
7
11
|
/**
|
|
8
12
|
* Operator-configured session lifetime. `undefined` when the `auth` section
|
|
9
13
|
* carries no value — the signer then applies `DEFAULT_TOKEN_EXPIRY`, which
|
|
@@ -11,10 +15,8 @@ export interface ResolvedAuthConfig {
|
|
|
11
15
|
*/
|
|
12
16
|
readonly tokenExpiry: string | undefined;
|
|
13
17
|
}
|
|
14
|
-
/** Persist the generated JWT secret. Called by AuthManager on first boot. */
|
|
15
|
-
export type AuthSectionWriter = (patch: Record<string, unknown>) => void;
|
|
16
18
|
/**
|
|
17
19
|
* Every config path this reader can answer. Anything else resolves to
|
|
18
20
|
* `undefined` by construction — see the module docblock.
|
|
19
21
|
*/
|
|
20
|
-
export declare function createAuthConfigReader(resolved: ResolvedAuthConfig
|
|
22
|
+
export declare function createAuthConfigReader(resolved: ResolvedAuthConfig): AuthConfigReader;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SecretsAccess } from '@camstack/types';
|
|
2
|
+
/** How often a locked keychain is asked again. */
|
|
3
|
+
export declare const SIGNING_KEY_RETRY_MS = 30000;
|
|
4
|
+
interface KeeperLogger {
|
|
5
|
+
info(message: string, meta?: {
|
|
6
|
+
meta?: Record<string, unknown>;
|
|
7
|
+
}): void;
|
|
8
|
+
error(message: string, meta?: {
|
|
9
|
+
meta?: Record<string, unknown>;
|
|
10
|
+
}): void;
|
|
11
|
+
}
|
|
12
|
+
export interface JwtSigningKeyKeeperDeps {
|
|
13
|
+
/** A keychain handle for owner `system`; `null` when this process has no keychain door. */
|
|
14
|
+
readonly secrets: SecretsAccess | null;
|
|
15
|
+
readonly logger: KeeperLogger;
|
|
16
|
+
readonly retryMs?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class JwtSigningKeyKeeper {
|
|
19
|
+
private readonly deps;
|
|
20
|
+
private retryTimer;
|
|
21
|
+
private unsubscribe;
|
|
22
|
+
private stopped;
|
|
23
|
+
constructor(deps: JwtSigningKeyKeeperDeps);
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
stop(): void;
|
|
26
|
+
private load;
|
|
27
|
+
private apply;
|
|
28
|
+
private scheduleRetry;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { ProviderRegistration, BaseAddon } from '@camstack/types';
|
|
2
|
-
|
|
3
|
-
jwtSecret?: string;
|
|
4
|
-
adminUsername?: string;
|
|
5
|
-
adminPassword?: string;
|
|
6
|
-
}
|
|
7
|
-
export declare class LocalAuthAddon extends BaseAddon<LocalAuthConfig> {
|
|
2
|
+
export declare class LocalAuthAddon extends BaseAddon {
|
|
8
3
|
private authManager;
|
|
9
4
|
private userManager;
|
|
10
5
|
private apiKeyManager;
|
|
@@ -13,6 +8,7 @@ export declare class LocalAuthAddon extends BaseAddon<LocalAuthConfig> {
|
|
|
13
8
|
private reapTimer;
|
|
14
9
|
private totpManager;
|
|
15
10
|
private externalIdentities;
|
|
11
|
+
private signingKeyKeeper;
|
|
16
12
|
constructor();
|
|
17
13
|
protected onInitialize(): Promise<ProviderRegistration[]>;
|
|
18
14
|
/**
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
|
|
6
|
+
const require_dist = require("../../dist-Cc6KOdY-.js");
|
|
7
|
+
const require_jwt_signing_key = require("../../jwt-signing-key-C2rE3wH6.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
10
10
|
let crypto$1 = require("crypto");
|
|
@@ -6205,7 +6205,7 @@ var noopLogger = {
|
|
|
6205
6205
|
};
|
|
6206
6206
|
var AuthManager = class {
|
|
6207
6207
|
config;
|
|
6208
|
-
|
|
6208
|
+
signingKey;
|
|
6209
6209
|
logger;
|
|
6210
6210
|
/**
|
|
6211
6211
|
* Offending `auth.tokenExpiry` values already reported by this instance.
|
|
@@ -6217,17 +6217,19 @@ var AuthManager = class {
|
|
|
6217
6217
|
* rest resolve silently to the same default until the value changes.
|
|
6218
6218
|
*/
|
|
6219
6219
|
reportedInvalidExpiries = /* @__PURE__ */ new Set();
|
|
6220
|
-
|
|
6220
|
+
/**
|
|
6221
|
+
* @param signingKey where the session signing key comes from — the process
|
|
6222
|
+
* slot `local-auth` fills from the keychain (D797). Never config, never
|
|
6223
|
+
* env; read on every mint and verify, and it THROWS while unavailable.
|
|
6224
|
+
*/
|
|
6225
|
+
constructor(config, logger = noopLogger, signingKey = require_jwt_signing_key.processJwtSigningKey) {
|
|
6221
6226
|
this.config = config;
|
|
6227
|
+
this.signingKey = signingKey;
|
|
6222
6228
|
this.logger = logger;
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
this.config.update("auth", { jwtSecret: secret });
|
|
6228
|
-
this.logger.info("Generated JWT secret and saved to config.yaml (auth.jwtSecret)");
|
|
6229
|
-
this.jwtSecret = secret;
|
|
6230
|
-
}
|
|
6229
|
+
}
|
|
6230
|
+
/** The current signing key; throws `JwtSigningKeyUnavailableError` while there is none. */
|
|
6231
|
+
get jwtSecret() {
|
|
6232
|
+
return this.signingKey.current();
|
|
6231
6233
|
}
|
|
6232
6234
|
signToken(payload) {
|
|
6233
6235
|
const configured = this.readConfiguredExpiry();
|
|
@@ -6620,17 +6622,23 @@ var ScopedTokenManager = class {
|
|
|
6620
6622
|
//#endregion
|
|
6621
6623
|
//#region src/auth/user-manager.ts
|
|
6622
6624
|
var USERS_COLLECTION$1 = "users";
|
|
6625
|
+
/**
|
|
6626
|
+
* The account a fresh hub seeds on its first boot (D797). Not configurable —
|
|
6627
|
+
* no env variable, no config.yaml key: the first sign-in with this password
|
|
6628
|
+
* forces a change (admin-ui `mustChangePassword`), and an existing user is
|
|
6629
|
+
* never overwritten (D728).
|
|
6630
|
+
*/
|
|
6631
|
+
var FIRST_BOOT_ADMIN_USERNAME = "admin";
|
|
6632
|
+
var FIRST_BOOT_ADMIN_PASSWORD = "changeme";
|
|
6623
6633
|
function parseUser(data) {
|
|
6624
6634
|
return parseRecord("user", require_dist.UserRecordSchema, data);
|
|
6625
6635
|
}
|
|
6626
6636
|
var UserManager = class {
|
|
6627
6637
|
storageAccess;
|
|
6628
6638
|
auth;
|
|
6629
|
-
|
|
6630
|
-
constructor(storageAccess, auth, config) {
|
|
6639
|
+
constructor(storageAccess, auth) {
|
|
6631
6640
|
this.storageAccess = storageAccess;
|
|
6632
6641
|
this.auth = auth;
|
|
6633
|
-
this.config = config;
|
|
6634
6642
|
}
|
|
6635
6643
|
get store() {
|
|
6636
6644
|
return this.storageAccess.getStore();
|
|
@@ -6720,53 +6728,50 @@ var UserManager = class {
|
|
|
6720
6728
|
});
|
|
6721
6729
|
}
|
|
6722
6730
|
/**
|
|
6723
|
-
* Seed
|
|
6724
|
-
*
|
|
6731
|
+
* Seed {@link FIRST_BOOT_ADMIN_USERNAME} / {@link FIRST_BOOT_ADMIN_PASSWORD}
|
|
6732
|
+
* — on FIRST boot only, i.e. when no admin account exists.
|
|
6725
6733
|
*
|
|
6726
|
-
* The
|
|
6727
|
-
*
|
|
6728
|
-
*
|
|
6729
|
-
*
|
|
6730
|
-
*
|
|
6731
|
-
* (`changeme` on a Mac hub and on any container without `CAMSTACK_ADMIN_PASS`)
|
|
6732
|
-
* at the next restart.
|
|
6734
|
+
* The seed is never an authority. Once any admin exists, this never writes a
|
|
6735
|
+
* password: the operator's own change in the admin UI is the truth from then
|
|
6736
|
+
* on (D728). This method once compared the stored hash with a configured
|
|
6737
|
+
* password on every boot and rewrote it when they differed, so a password
|
|
6738
|
+
* changed in the UI went back at the next restart.
|
|
6733
6739
|
*
|
|
6734
6740
|
* `resetPassword: true` is the one explicit exception — the operator's
|
|
6735
|
-
* one-shot recovery request (
|
|
6736
|
-
* admin's password
|
|
6741
|
+
* one-shot recovery request (`RESET_ADMIN_PASSWORD`, see
|
|
6742
|
+
* `admin-account-boot.ts`): the `admin` user's password goes back to
|
|
6743
|
+
* `changeme`, and the next sign-in forces a change again.
|
|
6737
6744
|
*/
|
|
6738
6745
|
async ensureAdminExists(options = {}) {
|
|
6739
|
-
const
|
|
6740
|
-
const
|
|
6741
|
-
if (!adminUsername || !adminPassword) return { kind: "not-configured" };
|
|
6742
|
-
const existing = await this.findByUsername(adminUsername);
|
|
6746
|
+
const username = FIRST_BOOT_ADMIN_USERNAME;
|
|
6747
|
+
const existing = await this.findByUsername(username);
|
|
6743
6748
|
if (existing) {
|
|
6744
6749
|
if (options.resetPassword === true) {
|
|
6745
|
-
await this.resetPassword(existing.id,
|
|
6750
|
+
await this.resetPassword(existing.id, FIRST_BOOT_ADMIN_PASSWORD);
|
|
6746
6751
|
return {
|
|
6747
6752
|
kind: "reset",
|
|
6748
|
-
username
|
|
6753
|
+
username
|
|
6749
6754
|
};
|
|
6750
6755
|
}
|
|
6751
6756
|
return {
|
|
6752
6757
|
kind: "exists",
|
|
6753
|
-
username
|
|
6758
|
+
username
|
|
6754
6759
|
};
|
|
6755
6760
|
}
|
|
6756
6761
|
if ((await this.listAll()).some((u) => u.isAdmin)) return {
|
|
6757
6762
|
kind: "other-admin-exists",
|
|
6758
|
-
username
|
|
6763
|
+
username
|
|
6759
6764
|
};
|
|
6760
6765
|
await this.create({
|
|
6761
|
-
username
|
|
6762
|
-
password:
|
|
6766
|
+
username,
|
|
6767
|
+
password: FIRST_BOOT_ADMIN_PASSWORD,
|
|
6763
6768
|
isAdmin: true,
|
|
6764
6769
|
allowedProviders: "*",
|
|
6765
6770
|
allowedDevices: {}
|
|
6766
6771
|
});
|
|
6767
6772
|
return {
|
|
6768
6773
|
kind: "seeded",
|
|
6769
|
-
username
|
|
6774
|
+
username
|
|
6770
6775
|
};
|
|
6771
6776
|
}
|
|
6772
6777
|
/**
|
|
@@ -6779,6 +6784,65 @@ var UserManager = class {
|
|
|
6779
6784
|
return this.auth.comparePassword(password, user.passwordHash);
|
|
6780
6785
|
}
|
|
6781
6786
|
};
|
|
6787
|
+
var JwtSigningKeyKeeper = class {
|
|
6788
|
+
deps;
|
|
6789
|
+
retryTimer = null;
|
|
6790
|
+
unsubscribe = null;
|
|
6791
|
+
stopped = false;
|
|
6792
|
+
constructor(deps) {
|
|
6793
|
+
this.deps = deps;
|
|
6794
|
+
}
|
|
6795
|
+
async start() {
|
|
6796
|
+
const secrets = this.deps.secrets;
|
|
6797
|
+
if (secrets === null) {
|
|
6798
|
+
this.apply({
|
|
6799
|
+
state: "unavailable",
|
|
6800
|
+
reason: "no keychain door in this process"
|
|
6801
|
+
});
|
|
6802
|
+
return;
|
|
6803
|
+
}
|
|
6804
|
+
this.unsubscribe = secrets.onChange((ref) => {
|
|
6805
|
+
if (ref === require_jwt_signing_key.JWT_SIGNING_KEY_REF) this.load(secrets);
|
|
6806
|
+
});
|
|
6807
|
+
await this.load(secrets);
|
|
6808
|
+
}
|
|
6809
|
+
stop() {
|
|
6810
|
+
this.stopped = true;
|
|
6811
|
+
if (this.retryTimer !== null) clearTimeout(this.retryTimer);
|
|
6812
|
+
this.retryTimer = null;
|
|
6813
|
+
this.unsubscribe?.();
|
|
6814
|
+
this.unsubscribe = null;
|
|
6815
|
+
}
|
|
6816
|
+
async load(secrets) {
|
|
6817
|
+
if (this.stopped) return;
|
|
6818
|
+
const next = await require_jwt_signing_key.loadJwtSigningKey({ secrets });
|
|
6819
|
+
if (this.stopped) return;
|
|
6820
|
+
this.apply(next);
|
|
6821
|
+
if (next.state === "unavailable" && require_jwt_signing_key.getProcessJwtSigningKeyState().state !== "ready") this.scheduleRetry(secrets);
|
|
6822
|
+
}
|
|
6823
|
+
apply(next) {
|
|
6824
|
+
const previous = require_jwt_signing_key.getProcessJwtSigningKeyState();
|
|
6825
|
+
if (next.state === "ready") {
|
|
6826
|
+
if (previous.state !== "ready") this.deps.logger.info("Session signing key loaded from the keychain");
|
|
6827
|
+
require_jwt_signing_key.setProcessJwtSigningKey(next);
|
|
6828
|
+
return;
|
|
6829
|
+
}
|
|
6830
|
+
if (previous.state === "ready") return;
|
|
6831
|
+
require_jwt_signing_key.setProcessJwtSigningKey(next);
|
|
6832
|
+
this.deps.logger.error("Sessions are UNAVAILABLE: the session signing key cannot be read from the keychain. No one can sign in and no session token verifies until the keychain unlocks (System → Secrets; or fix keychain.keyFile in config.yaml and restart). No fallback key is used.", { meta: {
|
|
6833
|
+
reason: next.reason,
|
|
6834
|
+
ref: require_jwt_signing_key.JWT_SIGNING_KEY_REF
|
|
6835
|
+
} });
|
|
6836
|
+
}
|
|
6837
|
+
scheduleRetry(secrets) {
|
|
6838
|
+
if (this.stopped || this.retryTimer !== null) return;
|
|
6839
|
+
this.retryTimer = setTimeout(() => {
|
|
6840
|
+
this.retryTimer = null;
|
|
6841
|
+
this.load(secrets);
|
|
6842
|
+
}, this.deps.retryMs ?? 3e4);
|
|
6843
|
+
this.retryTimer.unref?.();
|
|
6844
|
+
}
|
|
6845
|
+
};
|
|
6782
6846
|
//#endregion
|
|
6783
6847
|
//#region src/builtins/local-auth/auth-schema.ts
|
|
6784
6848
|
/**
|
|
@@ -9568,153 +9632,6 @@ var TotpManager = class {
|
|
|
9568
9632
|
}
|
|
9569
9633
|
};
|
|
9570
9634
|
//#endregion
|
|
9571
|
-
//#region src/kernel/config-schema.ts
|
|
9572
|
-
/** Default data directory — used when CAMSTACK_DATA env var is not set */
|
|
9573
|
-
var DEFAULT_DATA_PATH = "camstack-data";
|
|
9574
|
-
/**
|
|
9575
|
-
* A list of browser origins from config.yaml (a YAML list) or from the
|
|
9576
|
-
* `CAMSTACK_CORS_ORIGINS` env var (one comma-separated string — env carries
|
|
9577
|
-
* only strings). Validation of each entry is the CORS policy's job
|
|
9578
|
-
* (`server/backend/src/api/cors/cors-policy.ts`), which logs what it rejects;
|
|
9579
|
-
* the schema only fixes the SHAPE so a typo cannot stop the hub from booting.
|
|
9580
|
-
*/
|
|
9581
|
-
var originListSchema = zod.z.preprocess((value) => typeof value === "string" ? value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) : value, zod.z.array(zod.z.string()));
|
|
9582
|
-
var serverSchema = zod.z.object({
|
|
9583
|
-
port: zod.z.number().default(4443),
|
|
9584
|
-
host: zod.z.string().default("0.0.0.0"),
|
|
9585
|
-
dataPath: zod.z.string().default(DEFAULT_DATA_PATH),
|
|
9586
|
-
/**
|
|
9587
|
-
* EXTRA browser origins allowed to call this hub cross-origin (D707), on
|
|
9588
|
-
* top of the built-ins: the hub's own origin, the hosted viewer PWA and the
|
|
9589
|
-
* native app's opaque `null` origin. Bootstrap-owned on purpose — widening
|
|
9590
|
-
* who may read hub responses in a browser is a security decision, and the
|
|
9591
|
-
* policy is live from the first request, before the settings store exists.
|
|
9592
|
-
*/
|
|
9593
|
-
corsOrigins: originListSchema.default([])
|
|
9594
|
-
});
|
|
9595
|
-
var authSchema = zod.z.object({
|
|
9596
|
-
jwtSecret: zod.z.string().nullable().default(null),
|
|
9597
|
-
adminUsername: zod.z.string().default("admin"),
|
|
9598
|
-
adminPassword: zod.z.string().default("changeme")
|
|
9599
|
-
});
|
|
9600
|
-
var hubSchema = zod.z.object({
|
|
9601
|
-
url: zod.z.string().default("ws://localhost:4443/agent"),
|
|
9602
|
-
token: zod.z.string().default("")
|
|
9603
|
-
});
|
|
9604
|
-
var agentSchema = zod.z.object({
|
|
9605
|
-
name: zod.z.string().default(""),
|
|
9606
|
-
/** Port for the agent status page (minimal HTML) */
|
|
9607
|
-
statusPort: zod.z.number().default(4444)
|
|
9608
|
-
});
|
|
9609
|
-
var tlsSchema = zod.z.object({
|
|
9610
|
-
/**
|
|
9611
|
-
* Enable HTTPS (default: true). The certificate itself is never configured
|
|
9612
|
-
* here: the admin UI (Network → Local access) generates or uploads it
|
|
9613
|
-
* (D726), and boot serves whatever `tls/mode` says.
|
|
9614
|
-
*/
|
|
9615
|
-
enabled: zod.z.boolean().default(true) });
|
|
9616
|
-
/**
|
|
9617
|
-
* Bootstrap-time addon configuration — read PURELY from YAML, before the
|
|
9618
|
-
* kernel can talk to the settings-store. This breaks the chicken-and-egg
|
|
9619
|
-
* where the settings store itself is implemented by an addon: the kernel
|
|
9620
|
-
* needs to know WHICH addon to load first to provide the store, and that
|
|
9621
|
-
* decision can't itself live in the store.
|
|
9622
|
-
*
|
|
9623
|
-
* Backward-compatible: every field is optional + defaults reproduce the
|
|
9624
|
-
* current hard-coded behavior. Existing config.yaml files keep working.
|
|
9625
|
-
*/
|
|
9626
|
-
var bootstrapAddonsSchema = zod.z.object({
|
|
9627
|
-
/**
|
|
9628
|
-
* Addon implementing the `settings-store` capability. Loaded FIRST,
|
|
9629
|
-
* before any other addon is enabled — every other addon's enable
|
|
9630
|
-
* state, settings, and ordering lives in the store it provides.
|
|
9631
|
-
*
|
|
9632
|
-
* Default: '@camstack/addon-sqlite-storage' (the workspace's
|
|
9633
|
-
* default SQLite-backed store). Future alternatives could swap this
|
|
9634
|
-
* for postgres/redis without touching kernel code.
|
|
9635
|
-
*/
|
|
9636
|
-
settingsStoreAddon: zod.z.string().default("@camstack/addon-sqlite-storage"),
|
|
9637
|
-
/**
|
|
9638
|
-
* How the launcher provisions REQUIRED_PACKAGES into `data/addons/`
|
|
9639
|
-
* on startup.
|
|
9640
|
-
*
|
|
9641
|
-
* - `'npm'` — pull from the npm registry (production default).
|
|
9642
|
-
* - `'local'` — copy `packages/<X>/` from the workspace into
|
|
9643
|
-
* `data/addons/<X>/`. Use for the Electron-
|
|
9644
|
-
* packaged bundle and the e2e harness.
|
|
9645
|
-
* - `'symlink'` — point `data/addons/<X>` at the workspace source
|
|
9646
|
-
* dir via a directory symlink. Dev-only: a
|
|
9647
|
-
* workspace rebuild (`npm run build -w <X>`) is
|
|
9648
|
-
* immediately reflected at the next addon load,
|
|
9649
|
-
* no copy step. Falls back to `'local'` on
|
|
9650
|
-
* platforms that reject symlink creation (Windows
|
|
9651
|
-
* without developer-mode).
|
|
9652
|
-
*
|
|
9653
|
-
* The `CAMSTACK_INSTALL_SOURCE` env var, when set, wins over this
|
|
9654
|
-
* field. Leave undefined to keep the launcher's default behaviour
|
|
9655
|
-
* (npm when no workspace, local when a bundled addons dir is
|
|
9656
|
-
* present, …).
|
|
9657
|
-
*/
|
|
9658
|
-
installSource: zod.z.enum([
|
|
9659
|
-
"npm",
|
|
9660
|
-
"local",
|
|
9661
|
-
"symlink"
|
|
9662
|
-
]).optional(),
|
|
9663
|
-
/**
|
|
9664
|
-
* Override the kernel's hard-coded `AddonInstaller.REQUIRED_PACKAGES`.
|
|
9665
|
-
* Passing an array here REPLACES the default list entirely — useful
|
|
9666
|
-
* for stripped-down deployments (only the bundle of addons the
|
|
9667
|
-
* operator actually wants) and for forcing a different storage
|
|
9668
|
-
* backend at first boot.
|
|
9669
|
-
*
|
|
9670
|
-
* Leave undefined to use the kernel-shipped defaults.
|
|
9671
|
-
*/
|
|
9672
|
-
requiredAddons: zod.z.array(zod.z.string()).optional(),
|
|
9673
|
-
/**
|
|
9674
|
-
* Disable list applied AFTER the settings-store reports the enabled
|
|
9675
|
-
* set. Operator escape hatch — boot without a known-broken addon
|
|
9676
|
-
* without having to mutate the store.
|
|
9677
|
-
*/
|
|
9678
|
-
forceDisable: zod.z.array(zod.z.string()).default([])
|
|
9679
|
-
});
|
|
9680
|
-
/**
|
|
9681
|
-
* Where the keychain's master key lives (D788). config.yaml ONLY: no env var
|
|
9682
|
-
* maps here, and the settings store may not answer it
|
|
9683
|
-
* (`BOOTSTRAP_OWNED_SECTIONS`). Absent → the desktop OS keychain when the hub
|
|
9684
|
-
* runs inside the desktop app, else `<dataDir>/.keychain/master.key`.
|
|
9685
|
-
*/
|
|
9686
|
-
var keychainSchema = zod.z.object({
|
|
9687
|
-
/** A file holding 32 random bytes, base64 (or hex). Keep it OFF the data volume. */
|
|
9688
|
-
keyFile: zod.z.string().min(1).optional(),
|
|
9689
|
-
/**
|
|
9690
|
-
* The key being rotated away from — consulted only to UNLOCK, so a move to a
|
|
9691
|
-
* new key file is: new file, old one here, restart, rotate, remove this.
|
|
9692
|
-
*/
|
|
9693
|
-
previousKeyFile: zod.z.string().min(1).optional()
|
|
9694
|
-
});
|
|
9695
|
-
/** Bootstrap config -- loaded from config.yaml ONLY at startup.
|
|
9696
|
-
* All other settings live in SQL (system_settings table). */
|
|
9697
|
-
var bootstrapSchema = zod.z.object({
|
|
9698
|
-
/** Server mode: 'hub' (full server) or 'agent' (worker node) */
|
|
9699
|
-
mode: zod.z.enum(["hub", "agent"]).default("hub"),
|
|
9700
|
-
server: serverSchema.default(serverSchema.parse({})),
|
|
9701
|
-
auth: authSchema.default(authSchema.parse({})),
|
|
9702
|
-
/** Hub connection config — only used when mode='agent' */
|
|
9703
|
-
hub: hubSchema.default(hubSchema.parse({})),
|
|
9704
|
-
/** Agent-specific config — only used when mode='agent' */
|
|
9705
|
-
agent: agentSchema.default(agentSchema.parse({})),
|
|
9706
|
-
/** TLS configuration */
|
|
9707
|
-
tls: tlsSchema.default(tlsSchema.parse({})),
|
|
9708
|
-
/**
|
|
9709
|
-
* Addon bootstrap directives — minimum addon set needed before the
|
|
9710
|
-
* kernel can reach the settings-store. See bootstrapAddonsSchema.
|
|
9711
|
-
* Spec: docs/superpowers/specs/2026-05-15-yaml-bootstrap-expansion.md
|
|
9712
|
-
*/
|
|
9713
|
-
bootstrap: bootstrapAddonsSchema.default(bootstrapAddonsSchema.parse({})),
|
|
9714
|
-
/** Keychain master-key location (config.yaml only). */
|
|
9715
|
-
keychain: keychainSchema.default({})
|
|
9716
|
-
});
|
|
9717
|
-
//#endregion
|
|
9718
9635
|
//#region src/builtins/local-auth/admin-account-boot.ts
|
|
9719
9636
|
/**
|
|
9720
9637
|
* The admin account at boot: seed it once, reset it only on the operator's
|
|
@@ -9722,16 +9639,16 @@ var bootstrapSchema = zod.z.object({
|
|
|
9722
9639
|
*
|
|
9723
9640
|
* ## Why a trigger FILE, not a variable
|
|
9724
9641
|
*
|
|
9725
|
-
*
|
|
9726
|
-
*
|
|
9727
|
-
*
|
|
9642
|
+
* A fresh hub seeds `admin` / `changeme` on its first boot (D797) and never
|
|
9643
|
+
* applies it again — a password the operator changes in the admin UI survives
|
|
9644
|
+
* every restart. Recovering a forgotten
|
|
9728
9645
|
* password therefore needs an explicit request, and that request must be
|
|
9729
9646
|
* ONE-SHOT: an environment flag stays set in a container template until someone
|
|
9730
9647
|
* remembers to remove it, and while it is set it reverts the password on every
|
|
9731
9648
|
* boot — the very defect this replaced. A file the hub DELETES after acting on
|
|
9732
9649
|
* it cannot be left armed: create `RESET_ADMIN_PASSWORD` in the node's data
|
|
9733
|
-
* directory (`/data` in the container), restart, and the
|
|
9734
|
-
* password is set back to the
|
|
9650
|
+
* directory (`/data` in the container), restart, and the `admin` user's
|
|
9651
|
+
* password is set back to `changeme`, once — the next sign-in forces a change.
|
|
9735
9652
|
*/
|
|
9736
9653
|
/** The trigger's name, at the root of the node's data directory. */
|
|
9737
9654
|
var ADMIN_PASSWORD_RESET_TRIGGER = "RESET_ADMIN_PASSWORD";
|
|
@@ -9753,23 +9670,20 @@ async function reconcileAdminAccountAtBoot(input) {
|
|
|
9753
9670
|
function logOutcome(outcome, logger) {
|
|
9754
9671
|
switch (outcome.kind) {
|
|
9755
9672
|
case "seeded":
|
|
9756
|
-
logger.info("Admin account seeded
|
|
9673
|
+
logger.info("Admin account seeded (first boot) — sign in as admin / changeme and set a password", { meta: { username: outcome.username } });
|
|
9757
9674
|
return;
|
|
9758
9675
|
case "reset":
|
|
9759
|
-
logger.warn("Admin password reset to the
|
|
9676
|
+
logger.warn("Admin password reset to the first-boot password, as requested", { meta: { username: outcome.username } });
|
|
9760
9677
|
return;
|
|
9761
9678
|
case "other-admin-exists":
|
|
9762
|
-
logger.info("
|
|
9763
|
-
return;
|
|
9764
|
-
case "not-configured":
|
|
9765
|
-
logger.warn("No admin username/password configured — no admin account seeded");
|
|
9679
|
+
logger.info("First-boot admin not seeded: another admin account already exists", { meta: { username: outcome.username } });
|
|
9766
9680
|
return;
|
|
9767
9681
|
case "exists": return;
|
|
9768
9682
|
}
|
|
9769
9683
|
}
|
|
9770
9684
|
/** Delete the trigger after acting on it — a reset must never repeat by itself. */
|
|
9771
9685
|
function consumeTrigger(triggerPath, outcome, input) {
|
|
9772
|
-
if (outcome.kind !== "reset") input.logger.warn("Admin password reset requested, but no
|
|
9686
|
+
if (outcome.kind !== "reset") input.logger.warn("Admin password reset requested, but there is no `admin` user to reset", { meta: {
|
|
9773
9687
|
trigger: triggerPath,
|
|
9774
9688
|
outcome: outcome.kind
|
|
9775
9689
|
} });
|
|
@@ -9788,28 +9702,22 @@ function consumeTrigger(triggerPath, outcome, input) {
|
|
|
9788
9702
|
* Every config path this reader can answer. Anything else resolves to
|
|
9789
9703
|
* `undefined` by construction — see the module docblock.
|
|
9790
9704
|
*/
|
|
9791
|
-
function createAuthConfigReader(resolved
|
|
9705
|
+
function createAuthConfigReader(resolved) {
|
|
9792
9706
|
return {
|
|
9793
9707
|
get(path) {
|
|
9794
9708
|
switch (path) {
|
|
9795
|
-
case "auth.jwtSecret": return narrow(resolved.jwtSecret);
|
|
9796
|
-
case "auth.adminUsername": return narrow(resolved.adminUsername);
|
|
9797
|
-
case "auth.adminPassword": return narrow(resolved.adminPassword);
|
|
9798
9709
|
case "auth.tokenExpiry": return narrow(resolved.tokenExpiry);
|
|
9799
9710
|
default: return;
|
|
9800
9711
|
}
|
|
9801
9712
|
},
|
|
9802
|
-
update(
|
|
9803
|
-
if (typeof data["jwtSecret"] === "string") writeAuthSection({ jwtSecret: data["jwtSecret"] });
|
|
9804
|
-
}
|
|
9713
|
+
update() {}
|
|
9805
9714
|
};
|
|
9806
9715
|
}
|
|
9807
9716
|
/**
|
|
9808
9717
|
* The one documented type boundary in this module. `AuthConfigReader.get` is
|
|
9809
9718
|
* the legacy `get<T>(path)` shape shared with `ConfigManager.get`, which is
|
|
9810
9719
|
* likewise unchecked at runtime — every value returned here is a `string |
|
|
9811
|
-
* undefined` the caller immediately re-validates (`parseTokenExpiry
|
|
9812
|
-
* truthiness gate on `jwtSecret`).
|
|
9720
|
+
* undefined` the caller immediately re-validates (`parseTokenExpiry`).
|
|
9813
9721
|
*/
|
|
9814
9722
|
function narrow(value) {
|
|
9815
9723
|
return value;
|
|
@@ -10169,23 +10077,19 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
|
|
|
10169
10077
|
reapTimer = null;
|
|
10170
10078
|
totpManager = null;
|
|
10171
10079
|
externalIdentities = null;
|
|
10080
|
+
signingKeyKeeper = null;
|
|
10172
10081
|
constructor() {
|
|
10173
|
-
super({
|
|
10174
|
-
jwtSecret: "",
|
|
10175
|
-
adminUsername: "",
|
|
10176
|
-
adminPassword: ""
|
|
10177
|
-
});
|
|
10082
|
+
super({});
|
|
10178
10083
|
}
|
|
10179
10084
|
async onInitialize() {
|
|
10180
|
-
const
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
adminPassword: typeof authSection["adminPassword"] === "string" && authSection["adminPassword"] ? authSection["adminPassword"] : this.config.adminPassword ?? "",
|
|
10185
|
-
tokenExpiry: typeof authSection["tokenExpiry"] === "string" && authSection["tokenExpiry"] ? authSection["tokenExpiry"] : void 0
|
|
10186
|
-
}, (patch) => {
|
|
10187
|
-
this.ctx.settings?.setSection("auth", patch);
|
|
10085
|
+
const forOwner = this.ctx.secrets?.forOwner ?? null;
|
|
10086
|
+
this.signingKeyKeeper = new JwtSigningKeyKeeper({
|
|
10087
|
+
secrets: forOwner === null ? null : forOwner("system"),
|
|
10088
|
+
logger: this.ctx.logger
|
|
10188
10089
|
});
|
|
10090
|
+
await this.signingKeyKeeper.start();
|
|
10091
|
+
const authSection = await this.ctx.settings?.getSection("auth") ?? {};
|
|
10092
|
+
const reader = createAuthConfigReader({ tokenExpiry: typeof authSection["tokenExpiry"] === "string" && authSection["tokenExpiry"] ? authSection["tokenExpiry"] : void 0 });
|
|
10189
10093
|
this.authManager = new AuthManager(reader, this.ctx.logger);
|
|
10190
10094
|
const store = this.ctx.api?.settingsStore;
|
|
10191
10095
|
if (store) {
|
|
@@ -10193,7 +10097,7 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
|
|
|
10193
10097
|
const storageAccess = { getStore() {
|
|
10194
10098
|
return store;
|
|
10195
10099
|
} };
|
|
10196
|
-
this.userManager = new UserManager(storageAccess, this.authManager
|
|
10100
|
+
this.userManager = new UserManager(storageAccess, this.authManager);
|
|
10197
10101
|
this.apiKeyManager = new ApiKeyManager(storageAccess, this.authManager);
|
|
10198
10102
|
this.scopedTokenManager = new ScopedTokenManager(store);
|
|
10199
10103
|
this.oauthSessionManager = new OauthSessionManager(store);
|
|
@@ -10471,6 +10375,8 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
|
|
|
10471
10375
|
}
|
|
10472
10376
|
}
|
|
10473
10377
|
async onShutdown() {
|
|
10378
|
+
this.signingKeyKeeper?.stop();
|
|
10379
|
+
this.signingKeyKeeper = null;
|
|
10474
10380
|
if (this.reapTimer !== null) {
|
|
10475
10381
|
clearInterval(this.reapTimer);
|
|
10476
10382
|
this.reapTimer = null;
|
|
@@ -10486,13 +10392,13 @@ var LocalAuthAddon = class extends require_dist.BaseAddon {
|
|
|
10486
10392
|
//#endregion
|
|
10487
10393
|
exports.ApiKeyManager = ApiKeyManager;
|
|
10488
10394
|
exports.AuthManager = AuthManager;
|
|
10489
|
-
exports.
|
|
10395
|
+
exports.FIRST_BOOT_ADMIN_PASSWORD = FIRST_BOOT_ADMIN_PASSWORD;
|
|
10396
|
+
exports.FIRST_BOOT_ADMIN_USERNAME = FIRST_BOOT_ADMIN_USERNAME;
|
|
10490
10397
|
exports.LocalAuthAddon = LocalAuthAddon;
|
|
10491
10398
|
exports.default = LocalAuthAddon;
|
|
10492
10399
|
exports.ScopedTokenManager = ScopedTokenManager;
|
|
10493
10400
|
exports.SettingsReadUnavailableError = SettingsReadUnavailableError;
|
|
10494
10401
|
exports.UserManager = UserManager;
|
|
10495
|
-
exports.bootstrapSchema = bootstrapSchema;
|
|
10496
10402
|
exports.isSettingsReadUnavailable = isSettingsReadUnavailable;
|
|
10497
10403
|
Object.defineProperty(exports, "require_ms", {
|
|
10498
10404
|
enumerable: true,
|