@onlineapps/service-wrapper 3.1.0 → 3.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/service-wrapper",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -29,6 +29,7 @@
29
29
  "@onlineapps/conn-base-state": "1.0.1",
30
30
  "@onlineapps/conn-infra-error-handler": "1.0.11",
31
31
  "@onlineapps/conn-infra-mq": "1.1.70",
32
+ "@onlineapps/conn-infra-secrets": "1.0.0",
32
33
  "@onlineapps/conn-orch-cookbook": "2.1.3",
33
34
  "@onlineapps/conn-orch-orchestrator": "2.1.0",
34
35
  "@onlineapps/conn-orch-registry": "1.2.1",
@@ -92,6 +92,9 @@ class ContextBuilder {
92
92
  const httpClient = this._connectors.http;
93
93
  const secretsAdapter = this._connectors.secrets;
94
94
  const secrets = {
95
+ // Handlers call ctx.secrets.get(ref); the facade injects the invocation
96
+ // scope so the connector can resolve the tenant/workspace projection
97
+ // (see api/docs/architecture/secretbox.md §6).
95
98
  get: (name) => {
96
99
  if (!secretsAdapter || typeof secretsAdapter.get !== 'function') {
97
100
  throw new Error(
@@ -99,7 +102,10 @@ class ContextBuilder {
99
102
  'wire a secrets connector before using ctx.secrets'
100
103
  );
101
104
  }
102
- return secretsAdapter.get(name);
105
+ return secretsAdapter.get(name, {
106
+ tenant_id: envelope.tenant_id ?? null,
107
+ workspace_id: envelope.workspace_id ?? null
108
+ });
103
109
  }
104
110
  };
105
111
 
@@ -20,6 +20,7 @@ const OrchestratorConnector = require('@onlineapps/conn-orch-orchestrator');
20
20
  const CookbookConnector = require('@onlineapps/conn-orch-cookbook');
21
21
  const CacheConnector = require('@onlineapps/conn-base-cache');
22
22
  const StateConnector = require('@onlineapps/conn-base-state');
23
+ const SecretsConnector = require('@onlineapps/conn-infra-secrets');
23
24
  const ErrorHandlerConnector = require('@onlineapps/conn-infra-error-handler');
24
25
  const { ValidationOrchestrator } = require('@onlineapps/conn-orch-validator');
25
26
  const runtimeCfg = require('./config');
@@ -146,6 +147,7 @@ class ServiceWrapper {
146
147
  this.registryClient = null;
147
148
  this.orchestrator = null;
148
149
  this.cacheConnector = null;
150
+ this.secretsConnector = null;
149
151
  this.monitoring = null;
150
152
  this.logger = null;
151
153
 
@@ -734,6 +736,15 @@ class ServiceWrapper {
734
736
  }
735
737
  }
736
738
 
739
+ if (this.secretsConnector) {
740
+ try {
741
+ await this.secretsConnector.disconnect();
742
+ console.log(`[CLEANUP] ✓ Closed Redis secrets connection`);
743
+ } catch (err) {
744
+ console.warn(`[CLEANUP] Failed to close Redis secrets connection:`, err.message);
745
+ }
746
+ }
747
+
737
748
  // 5. Clear state variables
738
749
  this.workflowInitConsumerTag = null;
739
750
  this.workflowControlConsumerTag = null;
@@ -897,6 +908,12 @@ class ServiceWrapper {
897
908
  await this._initializeState();
898
909
  }
899
910
 
911
+ // Initialize secrets connector if configured (SecretBox resolution for
912
+ // ctx.secrets.get(ref)). Critical when enabled — let it throw.
913
+ if (this.config.wrapper?.secrets?.enabled === true) {
914
+ await this._initializeSecrets();
915
+ }
916
+
900
917
  // v3 handler-registry pipeline (RFC §5.9). Replaces the v2 HTTP-loopback
901
918
  // invocation path. Must run AFTER connectors are instantiated (storage,
902
919
  // cache, monitoring, mq) and BEFORE MQ messages are dispatched.
@@ -1463,6 +1480,32 @@ class ServiceWrapper {
1463
1480
  this.logger?.info('State connector initialized');
1464
1481
  }
1465
1482
 
1483
+ /**
1484
+ * Initialize secrets connector (SecretBox). Resolves ctx.secrets.get(ref) by
1485
+ * reading the encrypted Redis projection written by api_secrets and decrypting
1486
+ * in-process. Redis reuses wrapper.cache.url (shared api_node_cache); the AES
1487
+ * master key is required from env (no fallback).
1488
+ * @private
1489
+ */
1490
+ async _initializeSecrets() {
1491
+ const { requireEnv } = require('@onlineapps/service-common');
1492
+ const secretsConfig = this.config.wrapper?.secrets || {};
1493
+ const redisUrl = secretsConfig.url || this.config.wrapper?.cache?.url
1494
+ || requireEnv('REDIS_URL', 'Redis connection URL for secrets projection');
1495
+ const masterKeyBase64 = secretsConfig.masterKey
1496
+ || requireEnv('SECRETS_MASTER_KEY', 'base64-encoded 32-byte AES-256-GCM master key for secret resolution');
1497
+
1498
+ this.secretsConnector = new SecretsConnector({
1499
+ redisUrl,
1500
+ masterKeyBase64,
1501
+ password: secretsConfig.password,
1502
+ db: secretsConfig.db
1503
+ });
1504
+
1505
+ await this.secretsConnector.connect();
1506
+ this.logger?.info('Secrets connector initialized');
1507
+ }
1508
+
1466
1509
  /**
1467
1510
  * Setup health check endpoint
1468
1511
  * @private
@@ -2287,7 +2330,7 @@ class ServiceWrapper {
2287
2330
  storage: null,
2288
2331
  cache: this.cacheConnector || null,
2289
2332
  http: null,
2290
- secrets: null,
2333
+ secrets: this.secretsConnector || null,
2291
2334
  monitoring: this.monitoring || null,
2292
2335
  mq: this.mqClient || null,
2293
2336
  state: this.stateConnector || null
@@ -2765,6 +2808,11 @@ class ServiceWrapper {
2765
2808
  await this.stateConnector.disconnect();
2766
2809
  }
2767
2810
 
2811
+ // Disconnect from secrets
2812
+ if (this.secretsConnector) {
2813
+ await this.secretsConnector.disconnect();
2814
+ }
2815
+
2768
2816
  // Disconnect from MQ
2769
2817
  if (this.mqClient) {
2770
2818
  await this.mqClient.disconnect();