@objectstack/service-settings 7.5.0 → 7.7.0
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/README.md +38 -4
- package/dist/index.cjs +114 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -8
- package/dist/index.d.ts +40 -8
- package/dist/index.js +113 -62
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.cts
CHANGED
|
@@ -332,12 +332,36 @@ declare class SettingsService {
|
|
|
332
332
|
private materialiseRow;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
|
|
335
|
+
type EnvMap = Record<string, string | undefined>;
|
|
336
|
+
/** Where the provider resolved its data key from (for diagnostics). */
|
|
337
|
+
type KeySource = 'explicit' | 'env:OS_SECRET_KEY' | 'env:OS_DEV_CRYPTO_KEY' | 'file' | 'generated-file' | 'ephemeral';
|
|
338
|
+
type CryptoMode = 'production' | 'development' | 'test';
|
|
339
|
+
interface LocalCryptoProviderOptions {
|
|
340
|
+
/** Explicit 32-byte data key. Overrides all env / file resolution. */
|
|
341
|
+
key?: Buffer;
|
|
342
|
+
/**
|
|
343
|
+
* Env source. Defaults to `process.env`. Injectable so embedders and
|
|
344
|
+
* tests can drive key resolution deterministically.
|
|
345
|
+
*/
|
|
346
|
+
env?: EnvMap;
|
|
347
|
+
/**
|
|
348
|
+
* Deployment mode. Controls whether an ephemeral / auto-generated key is
|
|
349
|
+
* tolerated. Defaults to auto-detection from `NODE_ENV`:
|
|
350
|
+
* - `production` → a stable key (env var or pre-existing file) is
|
|
351
|
+
* REQUIRED; construction throws otherwise (fail loud).
|
|
352
|
+
* - `development` → persists an auto-generated key to disk so restarts
|
|
353
|
+
* reuse it; falls back to an ephemeral key (loud warning) if disk is
|
|
354
|
+
* unwritable.
|
|
355
|
+
* - `test` → never touches disk; uses an ephemeral key silently.
|
|
356
|
+
*/
|
|
357
|
+
mode?: CryptoMode;
|
|
358
|
+
}
|
|
359
|
+
declare class LocalCryptoProvider implements ICryptoProvider {
|
|
336
360
|
private readonly key;
|
|
337
361
|
private readonly useNoble;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
362
|
+
/** Where the active data key came from. Exposed for diagnostics/tests. */
|
|
363
|
+
readonly keySource: KeySource;
|
|
364
|
+
constructor(opts?: LocalCryptoProviderOptions);
|
|
341
365
|
encrypt(plain: string, ctx: CryptoContext): Promise<CryptoHandle>;
|
|
342
366
|
decrypt(handle: CryptoHandle, ctx: CryptoContext): Promise<string>;
|
|
343
367
|
rotateKey(handle: CryptoHandle, ctx: CryptoContext): Promise<CryptoHandle>;
|
|
@@ -345,6 +369,12 @@ declare class InMemoryCryptoProvider implements ICryptoProvider {
|
|
|
345
369
|
private encryptNode;
|
|
346
370
|
private aadOf;
|
|
347
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* @deprecated Renamed to {@link LocalCryptoProvider}. The old name implied an
|
|
374
|
+
* "in-memory / ephemeral" key when the provider in fact persists its key
|
|
375
|
+
* (env var or on-disk file). Kept as an alias for backward compatibility.
|
|
376
|
+
*/
|
|
377
|
+
declare const InMemoryCryptoProvider: typeof LocalCryptoProvider;
|
|
348
378
|
|
|
349
379
|
/** Configuration options for the SettingsServicePlugin. */
|
|
350
380
|
interface SettingsServicePluginOptions {
|
|
@@ -360,9 +390,11 @@ interface SettingsServicePluginOptions {
|
|
|
360
390
|
/**
|
|
361
391
|
* Phase 3 KMS hook. When provided, encrypted specifier values are
|
|
362
392
|
* routed through this provider into `sys_secret`; `sys_setting.value_enc`
|
|
363
|
-
* holds the handle id only. Defaults to `
|
|
364
|
-
*
|
|
365
|
-
*
|
|
393
|
+
* holds the handle id only. Defaults to `LocalCryptoProvider`, an
|
|
394
|
+
* AES-256-GCM provider keyed off `OS_SECRET_KEY` (or a persisted dev key).
|
|
395
|
+
* In production it refuses to boot without a stable key rather than
|
|
396
|
+
* silently minting an ephemeral one. Swap in an AWS / GCP / Vault
|
|
397
|
+
* KMS-backed implementation for managed custody and per-tenant keys.
|
|
366
398
|
*/
|
|
367
399
|
cryptoProvider?: ICryptoProvider;
|
|
368
400
|
/** Override the default base path (`/api/settings`). */
|
|
@@ -612,4 +644,4 @@ declare const jaJP: TranslationData;
|
|
|
612
644
|
|
|
613
645
|
declare const settingsBuiltinTranslations: TranslationBundle;
|
|
614
646
|
|
|
615
|
-
export { type CryptoAdapter, InMemoryCryptoProvider, NoopCryptoAdapter, SETTINGS_PLUGIN_ID, SETTINGS_PLUGIN_VERSION, type SettingsActionHandler, type SettingsAuditSink, type SettingsContext, type SettingsEngine, SettingsLockedError, type SettingsRoutesOptions, type SettingsRow, SettingsService, type SettingsServiceOptions, SettingsServicePlugin, type SettingsServicePluginOptions, UnknownKeyError, UnknownNamespaceError, brandingSettingsManifest, builtinSettingsManifests, envKeyOf, featureFlagsSettingsManifest, mailSettingsManifest, mailTestActionHandler, registerSettingsRoutes, settingsBuiltinTranslations, settingsObjects, settingsPluginManifestHeader, en as settingsTranslationsEn, jaJP as settingsTranslationsJaJP, zhCN as settingsTranslationsZhCN, storageSettingsManifest, storageTestActionHandler };
|
|
647
|
+
export { type CryptoAdapter, type CryptoMode, InMemoryCryptoProvider, type KeySource, LocalCryptoProvider, type LocalCryptoProviderOptions, NoopCryptoAdapter, SETTINGS_PLUGIN_ID, SETTINGS_PLUGIN_VERSION, type SettingsActionHandler, type SettingsAuditSink, type SettingsContext, type SettingsEngine, SettingsLockedError, type SettingsRoutesOptions, type SettingsRow, SettingsService, type SettingsServiceOptions, SettingsServicePlugin, type SettingsServicePluginOptions, UnknownKeyError, UnknownNamespaceError, brandingSettingsManifest, builtinSettingsManifests, envKeyOf, featureFlagsSettingsManifest, mailSettingsManifest, mailTestActionHandler, registerSettingsRoutes, settingsBuiltinTranslations, settingsObjects, settingsPluginManifestHeader, en as settingsTranslationsEn, jaJP as settingsTranslationsJaJP, zhCN as settingsTranslationsZhCN, storageSettingsManifest, storageTestActionHandler };
|
package/dist/index.d.ts
CHANGED
|
@@ -332,12 +332,36 @@ declare class SettingsService {
|
|
|
332
332
|
private materialiseRow;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
|
|
335
|
+
type EnvMap = Record<string, string | undefined>;
|
|
336
|
+
/** Where the provider resolved its data key from (for diagnostics). */
|
|
337
|
+
type KeySource = 'explicit' | 'env:OS_SECRET_KEY' | 'env:OS_DEV_CRYPTO_KEY' | 'file' | 'generated-file' | 'ephemeral';
|
|
338
|
+
type CryptoMode = 'production' | 'development' | 'test';
|
|
339
|
+
interface LocalCryptoProviderOptions {
|
|
340
|
+
/** Explicit 32-byte data key. Overrides all env / file resolution. */
|
|
341
|
+
key?: Buffer;
|
|
342
|
+
/**
|
|
343
|
+
* Env source. Defaults to `process.env`. Injectable so embedders and
|
|
344
|
+
* tests can drive key resolution deterministically.
|
|
345
|
+
*/
|
|
346
|
+
env?: EnvMap;
|
|
347
|
+
/**
|
|
348
|
+
* Deployment mode. Controls whether an ephemeral / auto-generated key is
|
|
349
|
+
* tolerated. Defaults to auto-detection from `NODE_ENV`:
|
|
350
|
+
* - `production` → a stable key (env var or pre-existing file) is
|
|
351
|
+
* REQUIRED; construction throws otherwise (fail loud).
|
|
352
|
+
* - `development` → persists an auto-generated key to disk so restarts
|
|
353
|
+
* reuse it; falls back to an ephemeral key (loud warning) if disk is
|
|
354
|
+
* unwritable.
|
|
355
|
+
* - `test` → never touches disk; uses an ephemeral key silently.
|
|
356
|
+
*/
|
|
357
|
+
mode?: CryptoMode;
|
|
358
|
+
}
|
|
359
|
+
declare class LocalCryptoProvider implements ICryptoProvider {
|
|
336
360
|
private readonly key;
|
|
337
361
|
private readonly useNoble;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
362
|
+
/** Where the active data key came from. Exposed for diagnostics/tests. */
|
|
363
|
+
readonly keySource: KeySource;
|
|
364
|
+
constructor(opts?: LocalCryptoProviderOptions);
|
|
341
365
|
encrypt(plain: string, ctx: CryptoContext): Promise<CryptoHandle>;
|
|
342
366
|
decrypt(handle: CryptoHandle, ctx: CryptoContext): Promise<string>;
|
|
343
367
|
rotateKey(handle: CryptoHandle, ctx: CryptoContext): Promise<CryptoHandle>;
|
|
@@ -345,6 +369,12 @@ declare class InMemoryCryptoProvider implements ICryptoProvider {
|
|
|
345
369
|
private encryptNode;
|
|
346
370
|
private aadOf;
|
|
347
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* @deprecated Renamed to {@link LocalCryptoProvider}. The old name implied an
|
|
374
|
+
* "in-memory / ephemeral" key when the provider in fact persists its key
|
|
375
|
+
* (env var or on-disk file). Kept as an alias for backward compatibility.
|
|
376
|
+
*/
|
|
377
|
+
declare const InMemoryCryptoProvider: typeof LocalCryptoProvider;
|
|
348
378
|
|
|
349
379
|
/** Configuration options for the SettingsServicePlugin. */
|
|
350
380
|
interface SettingsServicePluginOptions {
|
|
@@ -360,9 +390,11 @@ interface SettingsServicePluginOptions {
|
|
|
360
390
|
/**
|
|
361
391
|
* Phase 3 KMS hook. When provided, encrypted specifier values are
|
|
362
392
|
* routed through this provider into `sys_secret`; `sys_setting.value_enc`
|
|
363
|
-
* holds the handle id only. Defaults to `
|
|
364
|
-
*
|
|
365
|
-
*
|
|
393
|
+
* holds the handle id only. Defaults to `LocalCryptoProvider`, an
|
|
394
|
+
* AES-256-GCM provider keyed off `OS_SECRET_KEY` (or a persisted dev key).
|
|
395
|
+
* In production it refuses to boot without a stable key rather than
|
|
396
|
+
* silently minting an ephemeral one. Swap in an AWS / GCP / Vault
|
|
397
|
+
* KMS-backed implementation for managed custody and per-tenant keys.
|
|
366
398
|
*/
|
|
367
399
|
cryptoProvider?: ICryptoProvider;
|
|
368
400
|
/** Override the default base path (`/api/settings`). */
|
|
@@ -612,4 +644,4 @@ declare const jaJP: TranslationData;
|
|
|
612
644
|
|
|
613
645
|
declare const settingsBuiltinTranslations: TranslationBundle;
|
|
614
646
|
|
|
615
|
-
export { type CryptoAdapter, InMemoryCryptoProvider, NoopCryptoAdapter, SETTINGS_PLUGIN_ID, SETTINGS_PLUGIN_VERSION, type SettingsActionHandler, type SettingsAuditSink, type SettingsContext, type SettingsEngine, SettingsLockedError, type SettingsRoutesOptions, type SettingsRow, SettingsService, type SettingsServiceOptions, SettingsServicePlugin, type SettingsServicePluginOptions, UnknownKeyError, UnknownNamespaceError, brandingSettingsManifest, builtinSettingsManifests, envKeyOf, featureFlagsSettingsManifest, mailSettingsManifest, mailTestActionHandler, registerSettingsRoutes, settingsBuiltinTranslations, settingsObjects, settingsPluginManifestHeader, en as settingsTranslationsEn, jaJP as settingsTranslationsJaJP, zhCN as settingsTranslationsZhCN, storageSettingsManifest, storageTestActionHandler };
|
|
647
|
+
export { type CryptoAdapter, type CryptoMode, InMemoryCryptoProvider, type KeySource, LocalCryptoProvider, type LocalCryptoProviderOptions, NoopCryptoAdapter, SETTINGS_PLUGIN_ID, SETTINGS_PLUGIN_VERSION, type SettingsActionHandler, type SettingsAuditSink, type SettingsContext, type SettingsEngine, SettingsLockedError, type SettingsRoutesOptions, type SettingsRow, SettingsService, type SettingsServiceOptions, SettingsServicePlugin, type SettingsServicePluginOptions, UnknownKeyError, UnknownNamespaceError, brandingSettingsManifest, builtinSettingsManifests, envKeyOf, featureFlagsSettingsManifest, mailSettingsManifest, mailTestActionHandler, registerSettingsRoutes, settingsBuiltinTranslations, settingsObjects, settingsPluginManifestHeader, en as settingsTranslationsEn, jaJP as settingsTranslationsJaJP, zhCN as settingsTranslationsZhCN, storageSettingsManifest, storageTestActionHandler };
|
package/dist/index.js
CHANGED
|
@@ -572,36 +572,25 @@ function coerceEnvValue(raw, hint) {
|
|
|
572
572
|
return raw;
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
-
// src/
|
|
576
|
-
import { readEnvWithDeprecation } from "@objectstack/types";
|
|
575
|
+
// src/local-crypto-provider.ts
|
|
577
576
|
import { createHash, randomBytes, createCipheriv, createDecipheriv } from "crypto";
|
|
578
577
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
579
578
|
import { homedir } from "os";
|
|
580
579
|
import { dirname, join } from "path";
|
|
580
|
+
var SECRET_KEY_ENV = "OS_SECRET_KEY";
|
|
581
581
|
var DEV_KEY_ENV = "OS_DEV_CRYPTO_KEY";
|
|
582
582
|
var DEV_KEY_LEGACY_ENV = "OBJECTSTACK_DEV_CRYPTO_KEY";
|
|
583
|
-
var
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
583
|
+
var processEnv = () => globalThis.process?.env ?? {};
|
|
584
|
+
var detectMode = (env) => {
|
|
585
|
+
if (env.VITEST || env.NODE_ENV === "test") return "test";
|
|
586
|
+
if (env.NODE_ENV === "production") return "production";
|
|
587
|
+
return "development";
|
|
587
588
|
};
|
|
588
|
-
var
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if (existsSync(path)) {
|
|
592
|
-
const raw = readFileSync(path, "utf8").trim();
|
|
593
|
-
const parsed = parseDevKey(raw);
|
|
594
|
-
if (parsed) return { key: parsed, path, generated: false };
|
|
595
|
-
}
|
|
596
|
-
const key = randomBytes(32);
|
|
597
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
598
|
-
writeFileSync(path, key.toString("base64"), { mode: 384 });
|
|
599
|
-
return { key, path, generated: true };
|
|
600
|
-
} catch {
|
|
601
|
-
return void 0;
|
|
602
|
-
}
|
|
589
|
+
var keyFilePath = (env) => {
|
|
590
|
+
const home = env.OS_HOME || env.OBJECTSTACK_HOME || (env.HOME ? join(env.HOME, ".objectstack") : void 0) || join(homedir(), ".objectstack");
|
|
591
|
+
return join(home, "dev-crypto-key");
|
|
603
592
|
};
|
|
604
|
-
var
|
|
593
|
+
var parseKey = (raw) => {
|
|
605
594
|
if (!raw) return void 0;
|
|
606
595
|
const trimmed = raw.trim();
|
|
607
596
|
if (!trimmed) return void 0;
|
|
@@ -612,11 +601,99 @@ var parseDevKey = (raw) => {
|
|
|
612
601
|
if (buf.length === 32) return buf;
|
|
613
602
|
} catch {
|
|
614
603
|
}
|
|
615
|
-
console.warn(
|
|
616
|
-
`[InMemoryCryptoProvider] ${DEV_KEY_ENV} is set but is not 32 bytes (hex or base64). Ignoring and generating an ephemeral key.`
|
|
617
|
-
);
|
|
618
604
|
return void 0;
|
|
619
605
|
};
|
|
606
|
+
var loadExistingKey = (path) => {
|
|
607
|
+
try {
|
|
608
|
+
if (!existsSync(path)) return void 0;
|
|
609
|
+
return parseKey(readFileSync(path, "utf8").trim());
|
|
610
|
+
} catch {
|
|
611
|
+
return void 0;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
var loadOrCreateKey = (path) => {
|
|
615
|
+
try {
|
|
616
|
+
const existing = loadExistingKey(path);
|
|
617
|
+
if (existing) return { key: existing, generated: false };
|
|
618
|
+
const key = randomBytes(32);
|
|
619
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
620
|
+
writeFileSync(path, key.toString("base64"), { mode: 384 });
|
|
621
|
+
return { key, generated: true };
|
|
622
|
+
} catch {
|
|
623
|
+
return void 0;
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
var INVALID_KEY_MSG = (name) => `[LocalCryptoProvider] ${name} is set but is not a 32-byte key (expected 64 hex chars or base64 of 32 bytes). Generate one with \`openssl rand -hex 32\`.`;
|
|
627
|
+
var MISSING_PROD_KEY_MSG = (path) => `[LocalCryptoProvider] Refusing to start in production without a stable encryption key.
|
|
628
|
+
No ${SECRET_KEY_ENV} (or ${DEV_KEY_ENV}) is set and no persisted key file was found at:
|
|
629
|
+
${path}
|
|
630
|
+
Minting a key here would make every sys_secret value (encrypted settings, secret
|
|
631
|
+
fields, datasource credentials) undecryptable after the next restart or on another node.
|
|
632
|
+
Fix: generate a 32-byte key and set it in the environment (identical across every
|
|
633
|
+
restart and every node), e.g.
|
|
634
|
+
${SECRET_KEY_ENV}=$(openssl rand -hex 32)`;
|
|
635
|
+
var warn = (msg) => {
|
|
636
|
+
try {
|
|
637
|
+
globalThis.console?.warn?.(msg);
|
|
638
|
+
} catch {
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
var legacyDeprecationWarned = { value: false };
|
|
642
|
+
function resolveDataKey(opts) {
|
|
643
|
+
if (opts.key) return { key: opts.key, source: "explicit" };
|
|
644
|
+
const env = opts.env ?? processEnv();
|
|
645
|
+
const mode = opts.mode ?? detectMode(env);
|
|
646
|
+
if (env[SECRET_KEY_ENV] !== void 0) {
|
|
647
|
+
const parsed = parseKey(env[SECRET_KEY_ENV]);
|
|
648
|
+
if (parsed) return { key: parsed, source: "env:OS_SECRET_KEY" };
|
|
649
|
+
throw new Error(INVALID_KEY_MSG(SECRET_KEY_ENV));
|
|
650
|
+
}
|
|
651
|
+
let devRaw = env[DEV_KEY_ENV];
|
|
652
|
+
if (devRaw === void 0 && env[DEV_KEY_LEGACY_ENV] !== void 0) {
|
|
653
|
+
devRaw = env[DEV_KEY_LEGACY_ENV];
|
|
654
|
+
if (!legacyDeprecationWarned.value) {
|
|
655
|
+
legacyDeprecationWarned.value = true;
|
|
656
|
+
warn(
|
|
657
|
+
`[ObjectStack] Env var \`${DEV_KEY_LEGACY_ENV}\` is deprecated; rename it to \`${DEV_KEY_ENV}\`.`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (devRaw !== void 0) {
|
|
662
|
+
const parsed = parseKey(devRaw);
|
|
663
|
+
if (parsed) return { key: parsed, source: "env:OS_DEV_CRYPTO_KEY" };
|
|
664
|
+
if (mode === "production") throw new Error(INVALID_KEY_MSG(DEV_KEY_ENV));
|
|
665
|
+
warn(`${INVALID_KEY_MSG(DEV_KEY_ENV)} Ignoring and generating a local key.`);
|
|
666
|
+
}
|
|
667
|
+
if (mode === "test") {
|
|
668
|
+
return { key: randomBytes(32), source: "ephemeral" };
|
|
669
|
+
}
|
|
670
|
+
const path = keyFilePath(env);
|
|
671
|
+
if (mode === "production") {
|
|
672
|
+
const existing = loadExistingKey(path);
|
|
673
|
+
if (existing) {
|
|
674
|
+
warn(
|
|
675
|
+
`[LocalCryptoProvider] No ${SECRET_KEY_ENV} set \u2014 using the persisted key at ${path}. For containers / multi-node, prefer setting ${SECRET_KEY_ENV} so every node shares one key.`
|
|
676
|
+
);
|
|
677
|
+
return { key: existing, source: "file" };
|
|
678
|
+
}
|
|
679
|
+
throw new Error(MISSING_PROD_KEY_MSG(path));
|
|
680
|
+
}
|
|
681
|
+
const persisted = loadOrCreateKey(path);
|
|
682
|
+
if (persisted) {
|
|
683
|
+
if (persisted.generated) {
|
|
684
|
+
warn(
|
|
685
|
+
`[LocalCryptoProvider] No ${SECRET_KEY_ENV}/${DEV_KEY_ENV} set \u2014 generated a new AES-256-GCM key and persisted it to ${path} (mode 0600). Restarts on this host reuse it automatically. For containers, CI, or multi-node, set ${SECRET_KEY_ENV} explicitly so the key survives.`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
return { key: persisted.key, source: persisted.generated ? "generated-file" : "file" };
|
|
689
|
+
}
|
|
690
|
+
const key = randomBytes(32);
|
|
691
|
+
warn(
|
|
692
|
+
`[LocalCryptoProvider] No ${SECRET_KEY_ENV} set and could not persist a fallback key at ${path} \u2014 generated an EPHEMERAL key. Existing encrypted settings/secrets will fail to decrypt after restart. Set ${SECRET_KEY_ENV} to a stable 32-byte key:
|
|
693
|
+
${SECRET_KEY_ENV}=${key.toString("base64")}`
|
|
694
|
+
);
|
|
695
|
+
return { key, source: "ephemeral" };
|
|
696
|
+
}
|
|
620
697
|
var isWebContainerRuntime = () => {
|
|
621
698
|
const g = globalThis;
|
|
622
699
|
return typeof g !== "undefined" && (Boolean(g.process?.versions?.webcontainer) || Boolean(g.process?.env?.SHELL?.includes?.("jsh")) || Boolean(g.process?.env?.STACKBLITZ));
|
|
@@ -629,8 +706,8 @@ var loadNobleGcm = () => {
|
|
|
629
706
|
const mod = await import("@noble/ciphers/aes.js");
|
|
630
707
|
return mod.gcm;
|
|
631
708
|
} catch (err) {
|
|
632
|
-
|
|
633
|
-
`[
|
|
709
|
+
warn(
|
|
710
|
+
`[LocalCryptoProvider] WebContainer detected but @noble/ciphers not installed: ${err?.message ?? err}. Falling back to node:crypto (will throw).`
|
|
634
711
|
);
|
|
635
712
|
return void 0;
|
|
636
713
|
}
|
|
@@ -638,39 +715,11 @@ var loadNobleGcm = () => {
|
|
|
638
715
|
}
|
|
639
716
|
return nobleGcmPromise;
|
|
640
717
|
};
|
|
641
|
-
var
|
|
718
|
+
var LocalCryptoProvider = class {
|
|
642
719
|
constructor(opts = {}) {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const fromEnv = parseDevKey(
|
|
647
|
-
readEnvWithDeprecation(DEV_KEY_ENV, DEV_KEY_LEGACY_ENV)
|
|
648
|
-
);
|
|
649
|
-
if (fromEnv) {
|
|
650
|
-
this.key = fromEnv;
|
|
651
|
-
} else {
|
|
652
|
-
const isTest = Boolean(
|
|
653
|
-
globalThis?.process?.env?.VITEST || globalThis?.process?.env?.NODE_ENV === "test"
|
|
654
|
-
);
|
|
655
|
-
const persisted = isTest ? void 0 : loadOrCreateDevKey();
|
|
656
|
-
if (persisted) {
|
|
657
|
-
this.key = persisted.key;
|
|
658
|
-
if (persisted.generated) {
|
|
659
|
-
console.warn(
|
|
660
|
-
`[InMemoryCryptoProvider] No ${DEV_KEY_ENV} set \u2014 generated a new AES-256-GCM key and persisted it to ${persisted.path} (mode 0600). Future restarts will reuse it automatically. For shared/CI environments, set ${DEV_KEY_ENV} explicitly in your environment.`
|
|
661
|
-
);
|
|
662
|
-
}
|
|
663
|
-
} else {
|
|
664
|
-
this.key = randomBytes(32);
|
|
665
|
-
if (!isTest) {
|
|
666
|
-
console.warn(
|
|
667
|
-
`[InMemoryCryptoProvider] No ${DEV_KEY_ENV} set and could not persist a fallback key \u2014 generated an ephemeral AES-256-GCM key. Existing encrypted settings (e.g. AI API keys) will fail to decrypt on next restart. To make the key survive restarts, add this to your .env:
|
|
668
|
-
${DEV_KEY_ENV}=${this.key.toString("base64")}`
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
720
|
+
const resolved = resolveDataKey(opts);
|
|
721
|
+
this.key = resolved.key;
|
|
722
|
+
this.keySource = resolved.source;
|
|
674
723
|
this.useNoble = isWebContainerRuntime();
|
|
675
724
|
}
|
|
676
725
|
async encrypt(plain, ctx) {
|
|
@@ -694,7 +743,7 @@ var InMemoryCryptoProvider = class {
|
|
|
694
743
|
}
|
|
695
744
|
return {
|
|
696
745
|
id: "sec_" + randomBytes(16).toString("hex"),
|
|
697
|
-
kmsKeyId: "local:
|
|
746
|
+
kmsKeyId: "local:v1",
|
|
698
747
|
alg: "aes-256-gcm",
|
|
699
748
|
version: 1,
|
|
700
749
|
ciphertext: blob
|
|
@@ -726,7 +775,7 @@ var InMemoryCryptoProvider = class {
|
|
|
726
775
|
return {
|
|
727
776
|
...next,
|
|
728
777
|
id: handle.id,
|
|
729
|
-
kmsKeyId: `local:
|
|
778
|
+
kmsKeyId: `local:v${handle.version + 1}`,
|
|
730
779
|
version: handle.version + 1
|
|
731
780
|
};
|
|
732
781
|
}
|
|
@@ -744,6 +793,7 @@ var InMemoryCryptoProvider = class {
|
|
|
744
793
|
return [ctx.namespace, ctx.key].join("|");
|
|
745
794
|
}
|
|
746
795
|
};
|
|
796
|
+
var InMemoryCryptoProvider = LocalCryptoProvider;
|
|
747
797
|
|
|
748
798
|
// src/settings-routes.ts
|
|
749
799
|
var defaultContext = (req) => {
|
|
@@ -3023,7 +3073,7 @@ var SettingsServicePlugin = class {
|
|
|
3023
3073
|
{
|
|
3024
3074
|
secretStore: this.buildSecretStore(engine),
|
|
3025
3075
|
auditWriter: this.buildAuditWriter(ctx, engine),
|
|
3026
|
-
cryptoProvider: this.opts.cryptoProvider ?? new
|
|
3076
|
+
cryptoProvider: this.opts.cryptoProvider ?? new LocalCryptoProvider()
|
|
3027
3077
|
}
|
|
3028
3078
|
);
|
|
3029
3079
|
}
|
|
@@ -3159,6 +3209,7 @@ function wrapEngineAsSettingsEngine(engine) {
|
|
|
3159
3209
|
}
|
|
3160
3210
|
export {
|
|
3161
3211
|
InMemoryCryptoProvider,
|
|
3212
|
+
LocalCryptoProvider,
|
|
3162
3213
|
NoopCryptoAdapter,
|
|
3163
3214
|
SETTINGS_PLUGIN_ID,
|
|
3164
3215
|
SETTINGS_PLUGIN_VERSION,
|