@onlineapps/service-wrapper 3.0.17 → 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.0.17",
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,8 +29,9 @@
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
- "@onlineapps/conn-orch-orchestrator": "2.0.3",
34
+ "@onlineapps/conn-orch-orchestrator": "2.1.0",
34
35
  "@onlineapps/conn-orch-registry": "1.2.1",
35
36
  "@onlineapps/conn-orch-validator": "3.1.2",
36
37
  "@onlineapps/monitoring-core": "1.0.23",
@@ -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
@@ -1761,7 +1804,24 @@ class ServiceWrapper {
1761
1804
  cache: this.cacheConnector,
1762
1805
  errorHandler: errorHandler,
1763
1806
  logger: this.logger,
1764
- defaultTimeout: timeout
1807
+ defaultTimeout: timeout,
1808
+ operationMutatesLookup: (_executingServiceName, step) => {
1809
+ const opsMap =
1810
+ (this.operations && this.operations.operations) ||
1811
+ this.operations ||
1812
+ {};
1813
+ const opKey =
1814
+ step && typeof step.operation === 'string' && step.operation.length > 0
1815
+ ? step.operation
1816
+ : step && typeof step.step_id === 'string'
1817
+ ? step.step_id
1818
+ : '';
1819
+ const spec = opsMap[opKey];
1820
+ return {
1821
+ mutates: !!(spec && spec.mutates === true),
1822
+ resource_type: spec ? spec.resource_type : undefined
1823
+ };
1824
+ }
1765
1825
  });
1766
1826
 
1767
1827
  this.logger?.info('Orchestrator initialized');
@@ -2270,7 +2330,7 @@ class ServiceWrapper {
2270
2330
  storage: null,
2271
2331
  cache: this.cacheConnector || null,
2272
2332
  http: null,
2273
- secrets: null,
2333
+ secrets: this.secretsConnector || null,
2274
2334
  monitoring: this.monitoring || null,
2275
2335
  mq: this.mqClient || null,
2276
2336
  state: this.stateConnector || null
@@ -2748,6 +2808,11 @@ class ServiceWrapper {
2748
2808
  await this.stateConnector.disconnect();
2749
2809
  }
2750
2810
 
2811
+ // Disconnect from secrets
2812
+ if (this.secretsConnector) {
2813
+ await this.secretsConnector.disconnect();
2814
+ }
2815
+
2751
2816
  // Disconnect from MQ
2752
2817
  if (this.mqClient) {
2753
2818
  await this.mqClient.disconnect();