@objectstack/service-settings 6.7.0 → 6.8.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/LICENSE +93 -202
- package/dist/index.cjs +144 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +144 -25
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -86,6 +86,15 @@ var SettingsService = class {
|
|
|
86
86
|
if (extras?.secretStore) this.secretStore = extras.secretStore;
|
|
87
87
|
if (extras?.auditWriter) this.auditWriter = extras.auditWriter;
|
|
88
88
|
if (extras?.cryptoProvider) this.cryptoProvider = extras.cryptoProvider;
|
|
89
|
+
for (const ns of this.registry.keys()) {
|
|
90
|
+
this.emitChange({
|
|
91
|
+
namespace: ns,
|
|
92
|
+
key: "*",
|
|
93
|
+
scope: "global",
|
|
94
|
+
action: "set",
|
|
95
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
96
|
+
});
|
|
97
|
+
}
|
|
89
98
|
}
|
|
90
99
|
/**
|
|
91
100
|
* Cascade priority ranks for lock comparisons (lower = higher
|
|
@@ -505,24 +514,31 @@ var SettingsService = class {
|
|
|
505
514
|
if (row.encrypted) {
|
|
506
515
|
if (!row.value_enc) return null;
|
|
507
516
|
let plain;
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
517
|
+
try {
|
|
518
|
+
if (this.cryptoProvider && this.secretStore && typeof row.value_enc === "string" && row.value_enc.startsWith("sec_")) {
|
|
519
|
+
const secret = await this.secretStore.get(row.value_enc);
|
|
520
|
+
if (!secret) return null;
|
|
521
|
+
plain = await this.cryptoProvider.decrypt(
|
|
522
|
+
{
|
|
523
|
+
id: secret.id,
|
|
524
|
+
kmsKeyId: secret.kms_key_id,
|
|
525
|
+
alg: secret.alg,
|
|
526
|
+
version: secret.version,
|
|
527
|
+
ciphertext: secret.ciphertext
|
|
528
|
+
},
|
|
529
|
+
{ namespace: row.namespace, key: row.key }
|
|
530
|
+
);
|
|
531
|
+
} else {
|
|
532
|
+
plain = await this.crypto.decrypt(row.value_enc, {
|
|
533
|
+
namespace: row.namespace,
|
|
534
|
+
key: row.key
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
} catch (err) {
|
|
538
|
+
console.warn(
|
|
539
|
+
`[SettingsService] failed to decrypt ${row.namespace}.${row.key}: ${err?.message ?? err}. Returning null so the namespace remains readable; re-save the field to repair.`
|
|
520
540
|
);
|
|
521
|
-
|
|
522
|
-
plain = await this.crypto.decrypt(row.value_enc, {
|
|
523
|
-
namespace: row.namespace,
|
|
524
|
-
key: row.key
|
|
525
|
-
});
|
|
541
|
+
return null;
|
|
526
542
|
}
|
|
527
543
|
try {
|
|
528
544
|
return JSON.parse(plain);
|
|
@@ -558,6 +574,47 @@ function coerceEnvValue(raw, hint) {
|
|
|
558
574
|
|
|
559
575
|
// src/in-memory-crypto-provider.ts
|
|
560
576
|
import { createHash, randomBytes, createCipheriv, createDecipheriv } from "crypto";
|
|
577
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
578
|
+
import { homedir } from "os";
|
|
579
|
+
import { dirname, join } from "path";
|
|
580
|
+
var DEV_KEY_ENV = "OBJECTSTACK_DEV_CRYPTO_KEY";
|
|
581
|
+
var devKeyFallbackPath = () => {
|
|
582
|
+
const proc = globalThis?.process;
|
|
583
|
+
const home = proc?.env?.OBJECTSTACK_HOME || (proc?.env?.HOME ? join(proc.env.HOME, ".objectstack") : void 0) || join(homedir(), ".objectstack");
|
|
584
|
+
return join(home, "dev-crypto-key");
|
|
585
|
+
};
|
|
586
|
+
var loadOrCreateDevKey = () => {
|
|
587
|
+
try {
|
|
588
|
+
const path = devKeyFallbackPath();
|
|
589
|
+
if (existsSync(path)) {
|
|
590
|
+
const raw = readFileSync(path, "utf8").trim();
|
|
591
|
+
const parsed = parseDevKey(raw);
|
|
592
|
+
if (parsed) return { key: parsed, path, generated: false };
|
|
593
|
+
}
|
|
594
|
+
const key = randomBytes(32);
|
|
595
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
596
|
+
writeFileSync(path, key.toString("base64"), { mode: 384 });
|
|
597
|
+
return { key, path, generated: true };
|
|
598
|
+
} catch {
|
|
599
|
+
return void 0;
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
var parseDevKey = (raw) => {
|
|
603
|
+
if (!raw) return void 0;
|
|
604
|
+
const trimmed = raw.trim();
|
|
605
|
+
if (!trimmed) return void 0;
|
|
606
|
+
if (/^[0-9a-fA-F]{64}$/.test(trimmed)) return Buffer.from(trimmed, "hex");
|
|
607
|
+
try {
|
|
608
|
+
const normalised = trimmed.replace(/-/g, "+").replace(/_/g, "/");
|
|
609
|
+
const buf = Buffer.from(normalised, "base64");
|
|
610
|
+
if (buf.length === 32) return buf;
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
613
|
+
console.warn(
|
|
614
|
+
`[InMemoryCryptoProvider] ${DEV_KEY_ENV} is set but is not 32 bytes (hex or base64). Ignoring and generating an ephemeral key.`
|
|
615
|
+
);
|
|
616
|
+
return void 0;
|
|
617
|
+
};
|
|
561
618
|
var isWebContainerRuntime = () => {
|
|
562
619
|
const g = globalThis;
|
|
563
620
|
return typeof g !== "undefined" && (Boolean(g.process?.versions?.webcontainer) || Boolean(g.process?.env?.SHELL?.includes?.("jsh")) || Boolean(g.process?.env?.STACKBLITZ));
|
|
@@ -581,7 +638,37 @@ var loadNobleGcm = () => {
|
|
|
581
638
|
};
|
|
582
639
|
var InMemoryCryptoProvider = class {
|
|
583
640
|
constructor(opts = {}) {
|
|
584
|
-
|
|
641
|
+
if (opts.key) {
|
|
642
|
+
this.key = opts.key;
|
|
643
|
+
} else {
|
|
644
|
+
const fromEnv = parseDevKey(
|
|
645
|
+
globalThis?.process?.env?.[DEV_KEY_ENV]
|
|
646
|
+
);
|
|
647
|
+
if (fromEnv) {
|
|
648
|
+
this.key = fromEnv;
|
|
649
|
+
} else {
|
|
650
|
+
const isTest = Boolean(
|
|
651
|
+
globalThis?.process?.env?.VITEST || globalThis?.process?.env?.NODE_ENV === "test"
|
|
652
|
+
);
|
|
653
|
+
const persisted = isTest ? void 0 : loadOrCreateDevKey();
|
|
654
|
+
if (persisted) {
|
|
655
|
+
this.key = persisted.key;
|
|
656
|
+
if (persisted.generated) {
|
|
657
|
+
console.warn(
|
|
658
|
+
`[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.`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
} else {
|
|
662
|
+
this.key = randomBytes(32);
|
|
663
|
+
if (!isTest) {
|
|
664
|
+
console.warn(
|
|
665
|
+
`[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:
|
|
666
|
+
${DEV_KEY_ENV}=${this.key.toString("base64")}`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
585
672
|
this.useNoble = isWebContainerRuntime();
|
|
586
673
|
}
|
|
587
674
|
async encrypt(plain, ctx) {
|
|
@@ -1449,7 +1536,7 @@ var manifest3 = {
|
|
|
1449
1536
|
};
|
|
1450
1537
|
var aiSettingsManifest = manifest3;
|
|
1451
1538
|
var aiTestActionHandler = async ({ values, payload }) => {
|
|
1452
|
-
const overrides = payload
|
|
1539
|
+
const overrides = extractOverrides(payload);
|
|
1453
1540
|
const merged = { ...values, ...overrides };
|
|
1454
1541
|
const provider = String(merged.provider ?? "memory");
|
|
1455
1542
|
values = merged;
|
|
@@ -1482,6 +1569,14 @@ var aiTestActionHandler = async ({ values, payload }) => {
|
|
|
1482
1569
|
message: `${provider} configured (model=${model}). Mount @objectstack/service-ai to exercise live calls.`
|
|
1483
1570
|
};
|
|
1484
1571
|
};
|
|
1572
|
+
function extractOverrides(payload) {
|
|
1573
|
+
if (!payload || typeof payload !== "object") return {};
|
|
1574
|
+
const p = payload;
|
|
1575
|
+
if (p.values && typeof p.values === "object" && p.values !== null) {
|
|
1576
|
+
return p.values;
|
|
1577
|
+
}
|
|
1578
|
+
return p;
|
|
1579
|
+
}
|
|
1485
1580
|
|
|
1486
1581
|
// src/manifests/knowledge.manifest.ts
|
|
1487
1582
|
var manifest4 = {
|
|
@@ -2394,7 +2489,7 @@ var SettingsServicePlugin = class {
|
|
|
2394
2489
|
}
|
|
2395
2490
|
if (engine) {
|
|
2396
2491
|
this.service.bindEngine(
|
|
2397
|
-
engine,
|
|
2492
|
+
wrapEngineAsSettingsEngine(engine),
|
|
2398
2493
|
this.buildAuditSink(ctx, engine),
|
|
2399
2494
|
{
|
|
2400
2495
|
secretStore: this.buildSecretStore(engine),
|
|
@@ -2470,11 +2565,11 @@ var SettingsServicePlugin = class {
|
|
|
2470
2565
|
return row ?? null;
|
|
2471
2566
|
},
|
|
2472
2567
|
async update(id, patch) {
|
|
2473
|
-
await eng.update(
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
bypassTenantAudit: true
|
|
2477
|
-
|
|
2568
|
+
await eng.update(
|
|
2569
|
+
"sys_secret",
|
|
2570
|
+
{ id, ...patch },
|
|
2571
|
+
{ bypassTenantAudit: true }
|
|
2572
|
+
);
|
|
2478
2573
|
}
|
|
2479
2574
|
};
|
|
2480
2575
|
}
|
|
@@ -2509,6 +2604,30 @@ var SettingsServicePlugin = class {
|
|
|
2509
2604
|
};
|
|
2510
2605
|
}
|
|
2511
2606
|
};
|
|
2607
|
+
function wrapEngineAsSettingsEngine(engine) {
|
|
2608
|
+
const eng = engine;
|
|
2609
|
+
return {
|
|
2610
|
+
async find(objectName, opts) {
|
|
2611
|
+
return eng.find(objectName, opts);
|
|
2612
|
+
},
|
|
2613
|
+
async insert(objectName, data, opts) {
|
|
2614
|
+
return eng.insert(objectName, data, opts);
|
|
2615
|
+
},
|
|
2616
|
+
async update(objectName, opts) {
|
|
2617
|
+
const { where, data, bypassTenantAudit } = opts;
|
|
2618
|
+
const driverOpts = bypassTenantAudit ? { bypassTenantAudit: true } : void 0;
|
|
2619
|
+
const id = where?.id;
|
|
2620
|
+
if (id !== void 0 && id !== null) {
|
|
2621
|
+
return eng.update(objectName, { id, ...data }, driverOpts);
|
|
2622
|
+
}
|
|
2623
|
+
return eng.update(objectName, data, {
|
|
2624
|
+
where,
|
|
2625
|
+
multi: true,
|
|
2626
|
+
...driverOpts ?? {}
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2512
2631
|
export {
|
|
2513
2632
|
NoopCryptoAdapter,
|
|
2514
2633
|
SETTINGS_PLUGIN_ID,
|