@twin.org/api-tenant-processor 0.0.3-next.27 → 0.0.3-next.28

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/es/index.js CHANGED
@@ -20,4 +20,5 @@ export * from "./tenantIdContextIdHandler.js";
20
20
  export * from "./tenantProcessor.js";
21
21
  export * from "./tenantRoutes.js";
22
22
  export * from "./utils/tenantIdHelper.js";
23
+ export * from "./utils/tenantUrlHelper.js";
23
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,iDAAiD,CAAC;AAChE,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,mDAAmD,CAAC;AAClE,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./entities/tenant.js\";\nexport * from \"./models/api/ITenantCreateRequest.js\";\nexport * from \"./models/api/ITenantGetByApiKeyRequest.js\";\nexport * from \"./models/api/ITenantGetByIdRequest.js\";\nexport * from \"./models/api/ITenantGetByPublicOriginRequest.js\";\nexport * from \"./models/api/ITenantGetResponse.js\";\nexport * from \"./models/api/ITenantListRequest.js\";\nexport * from \"./models/api/ITenantListResponse.js\";\nexport * from \"./models/api/ITenantRemoveRequest.js\";\nexport * from \"./models/api/ITenantUpdateRequest.js\";\nexport * from \"./models/ITenantAdminServiceConfig.js\";\nexport * from \"./models/ITenantAdminServiceConstructorOptions.js\";\nexport * from \"./models/ITenantProcessorConfig.js\";\nexport * from \"./models/ITenantProcessorConstructorOptions.js\";\nexport * from \"./schema.js\";\nexport * from \"./tenantAdminService.js\";\nexport * from \"./tenantIdContextIdHandler.js\";\nexport * from \"./tenantProcessor.js\";\nexport * from \"./tenantRoutes.js\";\nexport * from \"./utils/tenantIdHelper.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,uCAAuC,CAAC;AACtD,cAAc,iDAAiD,CAAC;AAChE,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,qCAAqC,CAAC;AACpD,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uCAAuC,CAAC;AACtD,cAAc,mDAAmD,CAAC;AAClE,cAAc,oCAAoC,CAAC;AACnD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./entities/tenant.js\";\nexport * from \"./models/api/ITenantCreateRequest.js\";\nexport * from \"./models/api/ITenantGetByApiKeyRequest.js\";\nexport * from \"./models/api/ITenantGetByIdRequest.js\";\nexport * from \"./models/api/ITenantGetByPublicOriginRequest.js\";\nexport * from \"./models/api/ITenantGetResponse.js\";\nexport * from \"./models/api/ITenantListRequest.js\";\nexport * from \"./models/api/ITenantListResponse.js\";\nexport * from \"./models/api/ITenantRemoveRequest.js\";\nexport * from \"./models/api/ITenantUpdateRequest.js\";\nexport * from \"./models/ITenantAdminServiceConfig.js\";\nexport * from \"./models/ITenantAdminServiceConstructorOptions.js\";\nexport * from \"./models/ITenantProcessorConfig.js\";\nexport * from \"./models/ITenantProcessorConstructorOptions.js\";\nexport * from \"./schema.js\";\nexport * from \"./tenantAdminService.js\";\nexport * from \"./tenantIdContextIdHandler.js\";\nexport * from \"./tenantProcessor.js\";\nexport * from \"./tenantRoutes.js\";\nexport * from \"./utils/tenantIdHelper.js\";\nexport * from \"./utils/tenantUrlHelper.js\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ITenantProcessorConfig.js","sourceRoot":"","sources":["../../../src/models/ITenantProcessorConfig.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Configuration for the tenant processor\n */\nexport interface ITenantProcessorConfig {\n\t/**\n\t * The key to look for in the header or query params for the api key.\n\t * @default x-api-key\n\t */\n\tapiKeyName?: string;\n}\n"]}
1
+ {"version":3,"file":"ITenantProcessorConfig.js","sourceRoot":"","sources":["../../../src/models/ITenantProcessorConfig.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * Configuration for the tenant processor\n */\nexport interface ITenantProcessorConfig {\n\t/**\n\t * The key to look for in the header or query params for the api key.\n\t * @default x-api-key\n\t */\n\tapiKeyName?: string;\n\n\t/**\n\t * The name of the symmetric key in the vault used to encrypt/decrypt tenant tokens.\n\t * @default tenant-token-encryption\n\t */\n\tsigningKeyName?: string;\n\n\t/**\n\t * The query param name to look for the encrypted tenant token.\n\t * @default tenantToken\n\t */\n\ttenantTokenName?: string;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ITenantProcessorConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/ITenantProcessorConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ITenantProcessorConfig } from \"./ITenantProcessorConfig.js\";\n\n/**\n * Options for the Tenant Processor constructor.\n */\nexport interface ITenantProcessorConstructorOptions {\n\t/**\n\t * The entity storage for the tenants.\n\t * @default tenant\n\t */\n\ttenantEntityStorageType?: string;\n\n\t/**\n\t * Configuration for the processor.\n\t */\n\tconfig?: ITenantProcessorConfig;\n}\n"]}
1
+ {"version":3,"file":"ITenantProcessorConstructorOptions.js","sourceRoot":"","sources":["../../../src/models/ITenantProcessorConstructorOptions.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ITenantProcessorConfig } from \"./ITenantProcessorConfig.js\";\n\n/**\n * Options for the Tenant Processor constructor.\n */\nexport interface ITenantProcessorConstructorOptions {\n\t/**\n\t * The entity storage for the tenants.\n\t * @default tenant\n\t */\n\ttenantEntityStorageType?: string;\n\n\t/**\n\t * The vault connector used to decrypt tenant tokens. Only resolved when\n\t * `config.signingKeyName` is set.\n\t * @default vault\n\t */\n\tvaultConnectorType?: string;\n\n\t/**\n\t * Configuration for the processor.\n\t */\n\tconfig?: ITenantProcessorConfig;\n}\n"]}
@@ -1,10 +1,12 @@
1
1
  // Copyright 2025 IOTA Stiftung.
2
2
  // SPDX-License-Identifier: Apache-2.0.
3
3
  import { HttpErrorHelper } from "@twin.org/api-models";
4
- import { ContextIdKeys } from "@twin.org/context";
4
+ import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
5
5
  import { BaseError, Is, UnauthorizedError } from "@twin.org/core";
6
6
  import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
7
+ import { VaultConnectorFactory } from "@twin.org/vault-models";
7
8
  import { HttpStatusCode } from "@twin.org/web";
9
+ import { TenantUrlHelper } from "./utils/tenantUrlHelper.js";
8
10
  /**
9
11
  * Handles incoming api keys and maps them to tenant ids.
10
12
  */
@@ -28,6 +30,27 @@ export class TenantProcessor {
28
30
  * @internal
29
31
  */
30
32
  _apiKeyName;
33
+ /**
34
+ * The query param name carrying the encrypted tenant token.
35
+ * @internal
36
+ */
37
+ _tenantTokenName;
38
+ /**
39
+ * The vault connector used to decrypt tenant tokens.
40
+ * Only set when `signingKeyName` is configured.
41
+ * @internal
42
+ */
43
+ _vaultConnector;
44
+ /**
45
+ * The name of the symmetric key in the vault used to decrypt tenant tokens.
46
+ * @internal
47
+ */
48
+ _signingKeyName;
49
+ /**
50
+ * The node identity, captured at start.
51
+ * @internal
52
+ */
53
+ _nodeId;
31
54
  /**
32
55
  * Create a new instance of NodeTenantProcessor.
33
56
  * @param options Options for the processor.
@@ -35,6 +58,15 @@ export class TenantProcessor {
35
58
  constructor(options) {
36
59
  this._entityStorageConnector = EntityStorageConnectorFactory.get(options?.tenantEntityStorageType ?? "tenant");
37
60
  this._apiKeyName = options?.config?.apiKeyName ?? TenantProcessor.DEFAULT_API_KEY_NAME;
61
+ this._tenantTokenName =
62
+ options?.config?.tenantTokenName ?? TenantUrlHelper.DEFAULT_TENANT_TOKEN_NAME;
63
+ // Vault is resolved only when a connector type is explicitly passed, following
64
+ if (Is.stringValue(options?.vaultConnectorType)) {
65
+ this._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType);
66
+ }
67
+ if (Is.stringValue(options?.config?.signingKeyName)) {
68
+ this._signingKeyName = options.config.signingKeyName;
69
+ }
38
70
  }
39
71
  /**
40
72
  * Returns the class name of the component.
@@ -43,6 +75,21 @@ export class TenantProcessor {
43
75
  className() {
44
76
  return TenantProcessor.CLASS_NAME;
45
77
  }
78
+ /**
79
+ * The processor needs to be started when the application is initialized so that
80
+ * the node identity is available for vault key resolution. Only required when
81
+ * the encrypted-token path is wired (i.e. `signingKeyName` is configured).
82
+ * @param nodeLoggingComponentType The node logging component type.
83
+ * @returns Nothing.
84
+ */
85
+ async start(nodeLoggingComponentType) {
86
+ if (Is.empty(this._vaultConnector) || Is.empty(this._signingKeyName)) {
87
+ return;
88
+ }
89
+ const contextIds = await ContextIdStore.getContextIds();
90
+ ContextIdHelper.guard(contextIds, ContextIdKeys.Node);
91
+ this._nodeId = contextIds[ContextIdKeys.Node];
92
+ }
46
93
  /**
47
94
  * Pre process the REST request for the specified route.
48
95
  * @param request The incoming request.
@@ -52,37 +99,96 @@ export class TenantProcessor {
52
99
  * @param processorState The state handed through the processors.
53
100
  */
54
101
  async pre(request, response, route, contextIds, processorState) {
55
- if (!Is.empty(route) && !(route.skipTenant ?? false)) {
56
- const apiKey = request.headers?.[this._apiKeyName] ?? request.query?.[this._apiKeyName];
57
- let errorResponse;
58
- if (Is.stringValue(apiKey)) {
59
- try {
60
- const nodeTenant = await this._entityStorageConnector.get(apiKey, "apiKey");
61
- if (Is.empty(nodeTenant)) {
62
- errorResponse = new UnauthorizedError(TenantProcessor.CLASS_NAME, "apiKeyNotFound", {
63
- key: apiKey
64
- });
65
- }
66
- else {
67
- contextIds[ContextIdKeys.Tenant] = nodeTenant.id;
68
- if (Is.stringValue(nodeTenant.publicOrigin)) {
69
- processorState.publicOrigin = nodeTenant.publicOrigin;
70
- }
71
- }
72
- }
73
- catch (err) {
74
- errorResponse = BaseError.fromError(err);
102
+ const tenantToken = request.query?.[this._tenantTokenName];
103
+ const tokenDecodable = Is.stringValue(tenantToken) &&
104
+ !Is.empty(this._vaultConnector) &&
105
+ Is.stringValue(this._signingKeyName);
106
+ // skipTenant routes (cross-node trust JWT routes) bypass api-key resolution
107
+ // accept an encrypted ?tenantToken= query param so cross-tenant catalogue/DSP/PNP routing reaches the publisher's tenant context
108
+ if (Is.empty(route) || (route.skipTenant ?? false)) {
109
+ if (tokenDecodable) {
110
+ const errorResponse = await this.resolveByTenantToken(tenantToken, contextIds, processorState);
111
+ if (!Is.empty(errorResponse)) {
112
+ HttpErrorHelper.buildResponse(response, errorResponse, HttpStatusCode.unauthorized);
75
113
  }
76
114
  }
77
- else {
78
- errorResponse = new UnauthorizedError(TenantProcessor.CLASS_NAME, "missingApiKey", {
79
- keyName: this._apiKeyName
115
+ return;
116
+ }
117
+ const apiKey = request.headers?.[this._apiKeyName] ?? request.query?.[this._apiKeyName];
118
+ let errorResponse;
119
+ if (Is.stringValue(apiKey)) {
120
+ errorResponse = await this.resolveByApiKey(apiKey, contextIds, processorState);
121
+ }
122
+ else if (tokenDecodable) {
123
+ errorResponse = await this.resolveByTenantToken(tenantToken, contextIds, processorState);
124
+ }
125
+ else {
126
+ errorResponse = new UnauthorizedError(TenantProcessor.CLASS_NAME, "missingApiKey", {
127
+ keyName: this._apiKeyName
128
+ });
129
+ }
130
+ if (!Is.empty(errorResponse)) {
131
+ HttpErrorHelper.buildResponse(response, errorResponse, HttpStatusCode.unauthorized);
132
+ }
133
+ }
134
+ /**
135
+ * Resolve the tenant context from an api key.
136
+ * @param apiKey The api key sent by the caller.
137
+ * @param contextIds The context IDs of the request.
138
+ * @param processorState The state handed through the processors.
139
+ * @returns An error to surface, or undefined on success.
140
+ * @internal
141
+ */
142
+ async resolveByApiKey(apiKey, contextIds, processorState) {
143
+ try {
144
+ const nodeTenant = await this._entityStorageConnector.get(apiKey, "apiKey");
145
+ if (Is.empty(nodeTenant)) {
146
+ return new UnauthorizedError(TenantProcessor.CLASS_NAME, "apiKeyNotFound", {
147
+ key: apiKey
148
+ });
149
+ }
150
+ contextIds[ContextIdKeys.Tenant] = nodeTenant.id;
151
+ if (Is.stringValue(nodeTenant.publicOrigin)) {
152
+ processorState.publicOrigin = nodeTenant.publicOrigin;
153
+ }
154
+ }
155
+ catch (err) {
156
+ return BaseError.fromError(err);
157
+ }
158
+ }
159
+ /**
160
+ * Resolve the tenant context from an encrypted tenant token query param.
161
+ * @param tenantToken The encrypted tenant token.
162
+ * @param contextIds The context IDs of the request.
163
+ * @param processorState The state handed through the processors.
164
+ * @returns An error to surface, or undefined on success.
165
+ * @internal
166
+ */
167
+ async resolveByTenantToken(tenantToken, contextIds, processorState) {
168
+ let tenantId;
169
+ try {
170
+ tenantId = await TenantUrlHelper.decrypt(tenantToken, this._vaultConnector, `${this._nodeId}/${this._signingKeyName}`);
171
+ }
172
+ catch {
173
+ return new UnauthorizedError(TenantProcessor.CLASS_NAME, "tenantTokenInvalid", {
174
+ token: tenantToken
175
+ });
176
+ }
177
+ try {
178
+ const nodeTenant = await this._entityStorageConnector.get(tenantId);
179
+ if (Is.empty(nodeTenant)) {
180
+ return new UnauthorizedError(TenantProcessor.CLASS_NAME, "tenantTokenNotFound", {
181
+ tenantId
80
182
  });
81
183
  }
82
- if (!Is.empty(errorResponse)) {
83
- HttpErrorHelper.buildResponse(response, errorResponse, HttpStatusCode.unauthorized);
184
+ contextIds[ContextIdKeys.Tenant] = nodeTenant.id;
185
+ if (Is.stringValue(nodeTenant.publicOrigin)) {
186
+ processorState.publicOrigin = nodeTenant.publicOrigin;
84
187
  }
85
188
  }
189
+ catch (err) {
190
+ return BaseError.fromError(err);
191
+ }
86
192
  }
87
193
  }
88
194
  //# sourceMappingURL=tenantProcessor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tenantProcessor.js","sourceRoot":"","sources":["../../src/tenantProcessor.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EACN,eAAe,EAKf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAoB,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,SAAS,EAAe,EAAE,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EACN,6BAA6B,EAE7B,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAI/C;;GAEG;AACH,MAAM,OAAO,eAAe;IAC3B;;;OAGG;IACI,MAAM,CAAU,oBAAoB,GAAW,WAAW,CAAC;IAElE;;OAEG;IACI,MAAM,CAAU,UAAU,qBAAqC;IAEtE;;;OAGG;IACc,uBAAuB,CAAkC;IAE1E;;;OAGG;IACc,WAAW,CAAS;IAErC;;;OAGG;IACH,YAAY,OAA4C;QACvD,IAAI,CAAC,uBAAuB,GAAG,6BAA6B,CAAC,GAAG,CAC/D,OAAO,EAAE,uBAAuB,IAAI,QAAQ,CAC5C,CAAC;QACF,IAAI,CAAC,WAAW,GAAG,OAAO,EAAE,MAAM,EAAE,UAAU,IAAI,eAAe,CAAC,oBAAoB,CAAC;IACxF,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,eAAe,CAAC,UAAU,CAAC;IACnC,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,GAAG,CACf,OAA2B,EAC3B,QAAuB,EACvB,KAA6B,EAC7B,UAAuB,EACvB,cAAyC;QAEzC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,EAAE,CAAC;YACtD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACxF,IAAI,aAAiC,CAAC;YAEtC,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC;oBACJ,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;oBAE5E,IAAI,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;wBAC1B,aAAa,GAAG,IAAI,iBAAiB,CAAC,eAAe,CAAC,UAAU,EAAE,gBAAgB,EAAE;4BACnF,GAAG,EAAE,MAAM;yBACX,CAAC,CAAC;oBACJ,CAAC;yBAAM,CAAC;wBACP,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,EAAE,CAAC;wBACjD,IAAI,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;4BAC7C,cAAc,CAAC,YAAY,GAAG,UAAU,CAAC,YAAY,CAAC;wBACvD,CAAC;oBACF,CAAC;gBACF,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,aAAa,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBAC1C,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,aAAa,GAAG,IAAI,iBAAiB,CAAC,eAAe,CAAC,UAAU,EAAE,eAAe,EAAE;oBAClF,OAAO,EAAE,IAAI,CAAC,WAAW;iBACzB,CAAC,CAAC;YACJ,CAAC;YAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC9B,eAAe,CAAC,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;YACrF,CAAC;QACF,CAAC;IACF,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpErrorHelper,\n\ttype IBaseRoute,\n\ttype IBaseRouteProcessor,\n\ttype IHttpResponse,\n\ttype IHttpServerRequest\n} from \"@twin.org/api-models\";\nimport { ContextIdKeys, type IContextIds } from \"@twin.org/context\";\nimport { BaseError, type IError, Is, UnauthorizedError } from \"@twin.org/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { HttpStatusCode } from \"@twin.org/web\";\nimport type { Tenant } from \"./entities/tenant.js\";\nimport type { ITenantProcessorConstructorOptions } from \"./models/ITenantProcessorConstructorOptions.js\";\n\n/**\n * Handles incoming api keys and maps them to tenant ids.\n */\nexport class TenantProcessor implements IBaseRouteProcessor {\n\t/**\n\t * The default name for the api key header.\n\t * @internal\n\t */\n\tpublic static readonly DEFAULT_API_KEY_NAME: string = \"x-api-key\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TenantProcessor>();\n\n\t/**\n\t * The entity storage for api keys.\n\t * @internal\n\t */\n\tprivate readonly _entityStorageConnector: IEntityStorageConnector<Tenant>;\n\n\t/**\n\t * The key in the header to look for the api key.\n\t * @internal\n\t */\n\tprivate readonly _apiKeyName: string;\n\n\t/**\n\t * Create a new instance of NodeTenantProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: ITenantProcessorConstructorOptions) {\n\t\tthis._entityStorageConnector = EntityStorageConnectorFactory.get(\n\t\t\toptions?.tenantEntityStorageType ?? \"tenant\"\n\t\t);\n\t\tthis._apiKeyName = options?.config?.apiKeyName ?? TenantProcessor.DEFAULT_API_KEY_NAME;\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn TenantProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * Pre process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async pre(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tif (!Is.empty(route) && !(route.skipTenant ?? false)) {\n\t\t\tconst apiKey = request.headers?.[this._apiKeyName] ?? request.query?.[this._apiKeyName];\n\t\t\tlet errorResponse: IError | undefined;\n\n\t\t\tif (Is.stringValue(apiKey)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst nodeTenant = await this._entityStorageConnector.get(apiKey, \"apiKey\");\n\n\t\t\t\t\tif (Is.empty(nodeTenant)) {\n\t\t\t\t\t\terrorResponse = new UnauthorizedError(TenantProcessor.CLASS_NAME, \"apiKeyNotFound\", {\n\t\t\t\t\t\t\tkey: apiKey\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontextIds[ContextIdKeys.Tenant] = nodeTenant.id;\n\t\t\t\t\t\tif (Is.stringValue(nodeTenant.publicOrigin)) {\n\t\t\t\t\t\t\tprocessorState.publicOrigin = nodeTenant.publicOrigin;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\terrorResponse = BaseError.fromError(err);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terrorResponse = new UnauthorizedError(TenantProcessor.CLASS_NAME, \"missingApiKey\", {\n\t\t\t\t\tkeyName: this._apiKeyName\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (!Is.empty(errorResponse)) {\n\t\t\t\tHttpErrorHelper.buildResponse(response, errorResponse, HttpStatusCode.unauthorized);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"tenantProcessor.js","sourceRoot":"","sources":["../../src/tenantProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport {\n\tHttpErrorHelper,\n\ttype IBaseRoute,\n\ttype IBaseRouteProcessor,\n\ttype IHttpResponse,\n\ttype IHttpServerRequest\n} from \"@twin.org/api-models\";\nimport {\n\tContextIdHelper,\n\tContextIdKeys,\n\tContextIdStore,\n\ttype IContextIds\n} from \"@twin.org/context\";\nimport { BaseError, type IError, Is, UnauthorizedError } from \"@twin.org/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type IVaultConnector, VaultConnectorFactory } from \"@twin.org/vault-models\";\nimport { HttpStatusCode } from \"@twin.org/web\";\nimport type { Tenant } from \"./entities/tenant.js\";\nimport type { ITenantProcessorConstructorOptions } from \"./models/ITenantProcessorConstructorOptions.js\";\nimport { TenantUrlHelper } from \"./utils/tenantUrlHelper.js\";\n\n/**\n * Handles incoming api keys and maps them to tenant ids.\n */\nexport class TenantProcessor implements IBaseRouteProcessor {\n\t/**\n\t * The default name for the api key header.\n\t * @internal\n\t */\n\tpublic static readonly DEFAULT_API_KEY_NAME: string = \"x-api-key\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TenantProcessor>();\n\n\t/**\n\t * The entity storage for api keys.\n\t * @internal\n\t */\n\tprivate readonly _entityStorageConnector: IEntityStorageConnector<Tenant>;\n\n\t/**\n\t * The key in the header to look for the api key.\n\t * @internal\n\t */\n\tprivate readonly _apiKeyName: string;\n\n\t/**\n\t * The query param name carrying the encrypted tenant token.\n\t * @internal\n\t */\n\tprivate readonly _tenantTokenName: string;\n\n\t/**\n\t * The vault connector used to decrypt tenant tokens.\n\t * Only set when `signingKeyName` is configured.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector?: IVaultConnector;\n\n\t/**\n\t * The name of the symmetric key in the vault used to decrypt tenant tokens.\n\t * @internal\n\t */\n\tprivate readonly _signingKeyName?: string;\n\n\t/**\n\t * The node identity, captured at start.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of NodeTenantProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: ITenantProcessorConstructorOptions) {\n\t\tthis._entityStorageConnector = EntityStorageConnectorFactory.get(\n\t\t\toptions?.tenantEntityStorageType ?? \"tenant\"\n\t\t);\n\t\tthis._apiKeyName = options?.config?.apiKeyName ?? TenantProcessor.DEFAULT_API_KEY_NAME;\n\t\tthis._tenantTokenName =\n\t\t\toptions?.config?.tenantTokenName ?? TenantUrlHelper.DEFAULT_TENANT_TOKEN_NAME;\n\n\t\t// Vault is resolved only when a connector type is explicitly passed, following\n\t\tif (Is.stringValue(options?.vaultConnectorType)) {\n\t\t\tthis._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType);\n\t\t}\n\t\tif (Is.stringValue(options?.config?.signingKeyName)) {\n\t\t\tthis._signingKeyName = options.config.signingKeyName;\n\t\t}\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn TenantProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * The processor needs to be started when the application is initialized so that\n\t * the node identity is available for vault key resolution. Only required when\n\t * the encrypted-token path is wired (i.e. `signingKeyName` is configured).\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tif (Is.empty(this._vaultConnector) || Is.empty(this._signingKeyName)) {\n\t\t\treturn;\n\t\t}\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.Node);\n\t\tthis._nodeId = contextIds[ContextIdKeys.Node];\n\t}\n\n\t/**\n\t * Pre process the REST request for the specified route.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t */\n\tpublic async pre(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tconst tenantToken = request.query?.[this._tenantTokenName];\n\t\tconst tokenDecodable =\n\t\t\tIs.stringValue(tenantToken) &&\n\t\t\t!Is.empty(this._vaultConnector) &&\n\t\t\tIs.stringValue(this._signingKeyName);\n\n\t\t// skipTenant routes (cross-node trust JWT routes) bypass api-key resolution\n\t\t// accept an encrypted ?tenantToken= query param so cross-tenant catalogue/DSP/PNP routing reaches the publisher's tenant context\n\t\tif (Is.empty(route) || (route.skipTenant ?? false)) {\n\t\t\tif (tokenDecodable) {\n\t\t\t\tconst errorResponse = await this.resolveByTenantToken(\n\t\t\t\t\ttenantToken,\n\t\t\t\t\tcontextIds,\n\t\t\t\t\tprocessorState\n\t\t\t\t);\n\t\t\t\tif (!Is.empty(errorResponse)) {\n\t\t\t\t\tHttpErrorHelper.buildResponse(response, errorResponse, HttpStatusCode.unauthorized);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst apiKey = request.headers?.[this._apiKeyName] ?? request.query?.[this._apiKeyName];\n\t\tlet errorResponse: IError | undefined;\n\n\t\tif (Is.stringValue(apiKey)) {\n\t\t\terrorResponse = await this.resolveByApiKey(apiKey, contextIds, processorState);\n\t\t} else if (tokenDecodable) {\n\t\t\terrorResponse = await this.resolveByTenantToken(tenantToken, contextIds, processorState);\n\t\t} else {\n\t\t\terrorResponse = new UnauthorizedError(TenantProcessor.CLASS_NAME, \"missingApiKey\", {\n\t\t\t\tkeyName: this._apiKeyName\n\t\t\t});\n\t\t}\n\n\t\tif (!Is.empty(errorResponse)) {\n\t\t\tHttpErrorHelper.buildResponse(response, errorResponse, HttpStatusCode.unauthorized);\n\t\t}\n\t}\n\n\t/**\n\t * Resolve the tenant context from an api key.\n\t * @param apiKey The api key sent by the caller.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t * @returns An error to surface, or undefined on success.\n\t * @internal\n\t */\n\tprivate async resolveByApiKey(\n\t\tapiKey: string,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<IError | undefined> {\n\t\ttry {\n\t\t\tconst nodeTenant = await this._entityStorageConnector.get(apiKey, \"apiKey\");\n\n\t\t\tif (Is.empty(nodeTenant)) {\n\t\t\t\treturn new UnauthorizedError(TenantProcessor.CLASS_NAME, \"apiKeyNotFound\", {\n\t\t\t\t\tkey: apiKey\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcontextIds[ContextIdKeys.Tenant] = nodeTenant.id;\n\t\t\tif (Is.stringValue(nodeTenant.publicOrigin)) {\n\t\t\t\tprocessorState.publicOrigin = nodeTenant.publicOrigin;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\treturn BaseError.fromError(err);\n\t\t}\n\t}\n\n\t/**\n\t * Resolve the tenant context from an encrypted tenant token query param.\n\t * @param tenantToken The encrypted tenant token.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t * @returns An error to surface, or undefined on success.\n\t * @internal\n\t */\n\tprivate async resolveByTenantToken(\n\t\ttenantToken: string,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<IError | undefined> {\n\t\tlet tenantId: string;\n\t\ttry {\n\t\t\ttenantId = await TenantUrlHelper.decrypt(\n\t\t\t\ttenantToken,\n\t\t\t\tthis._vaultConnector as IVaultConnector,\n\t\t\t\t`${this._nodeId}/${this._signingKeyName}`\n\t\t\t);\n\t\t} catch {\n\t\t\treturn new UnauthorizedError(TenantProcessor.CLASS_NAME, \"tenantTokenInvalid\", {\n\t\t\t\ttoken: tenantToken\n\t\t\t});\n\t\t}\n\n\t\ttry {\n\t\t\tconst nodeTenant = await this._entityStorageConnector.get(tenantId);\n\n\t\t\tif (Is.empty(nodeTenant)) {\n\t\t\t\treturn new UnauthorizedError(TenantProcessor.CLASS_NAME, \"tenantTokenNotFound\", {\n\t\t\t\t\ttenantId\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcontextIds[ContextIdKeys.Tenant] = nodeTenant.id;\n\t\t\tif (Is.stringValue(nodeTenant.publicOrigin)) {\n\t\t\t\tprocessorState.publicOrigin = nodeTenant.publicOrigin;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\treturn BaseError.fromError(err);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,65 @@
1
+ // Copyright 2025 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { Converter, GeneralError, Guards } from "@twin.org/core";
4
+ import { VaultEncryptionType } from "@twin.org/vault-models";
5
+ /**
6
+ * Helper for building and parsing URLs that carry an encrypted tenant token query param.
7
+ * The token is the ChaCha20Poly1305-encrypted UTF-8 bytes of a tenant id, base64url-encoded
8
+ * for URL safety.
9
+ */
10
+ export class TenantUrlHelper {
11
+ /**
12
+ * The default query param name for the encrypted tenant token.
13
+ */
14
+ static DEFAULT_TENANT_TOKEN_NAME = "tenantToken";
15
+ /**
16
+ * Runtime name for the class.
17
+ */
18
+ static CLASS_NAME = "TenantUrlHelper";
19
+ /**
20
+ * Encrypt a tenant id and append it as an opaque query param to the supplied URL.
21
+ * @param url The URL to append the token to.
22
+ * @param tenantId The tenant id to encrypt into the token.
23
+ * @param vaultConnector The vault connector providing the symmetric key.
24
+ * @param keyName The fully-qualified vault key name (e.g. `${nodeId}/tenant-token-encryption`).
25
+ * @param tenantTokenName The query param name. Defaults to `tenantToken`.
26
+ * @returns The URL with the encrypted tenant token appended.
27
+ */
28
+ static async encrypt(url, tenantId, vaultConnector, keyName, tenantTokenName) {
29
+ Guards.stringValue(TenantUrlHelper.CLASS_NAME, "url", url);
30
+ Guards.stringValue(TenantUrlHelper.CLASS_NAME, "tenantId", tenantId);
31
+ Guards.object(TenantUrlHelper.CLASS_NAME, "vaultConnector", vaultConnector);
32
+ Guards.stringValue(TenantUrlHelper.CLASS_NAME, "keyName", keyName);
33
+ try {
34
+ const encrypted = await vaultConnector.encrypt(keyName, VaultEncryptionType.ChaCha20Poly1305, Converter.utf8ToBytes(tenantId));
35
+ const token = Converter.bytesToBase64Url(encrypted);
36
+ const paramName = tenantTokenName ?? TenantUrlHelper.DEFAULT_TENANT_TOKEN_NAME;
37
+ const separator = url.includes("?") ? "&" : "?";
38
+ return `${url}${separator}${paramName}=${token}`;
39
+ }
40
+ catch (err) {
41
+ throw new GeneralError(TenantUrlHelper.CLASS_NAME, "encryptFailed", undefined, err);
42
+ }
43
+ }
44
+ /**
45
+ * Decrypt an encrypted tenant token back into the original tenant id.
46
+ * @param token The base64url-encoded encrypted tenant token.
47
+ * @param vaultConnector The vault connector providing the symmetric key.
48
+ * @param keyName The fully-qualified vault key name used to encrypt the token.
49
+ * @returns The decrypted tenant id.
50
+ */
51
+ static async decrypt(token, vaultConnector, keyName) {
52
+ Guards.stringValue(TenantUrlHelper.CLASS_NAME, "token", token);
53
+ Guards.object(TenantUrlHelper.CLASS_NAME, "vaultConnector", vaultConnector);
54
+ Guards.stringValue(TenantUrlHelper.CLASS_NAME, "keyName", keyName);
55
+ try {
56
+ const encrypted = Converter.base64UrlToBytes(token);
57
+ const decrypted = await vaultConnector.decrypt(keyName, VaultEncryptionType.ChaCha20Poly1305, encrypted);
58
+ return Converter.bytesToUtf8(decrypted);
59
+ }
60
+ catch (err) {
61
+ throw new GeneralError(TenantUrlHelper.CLASS_NAME, "decryptFailed", undefined, err);
62
+ }
63
+ }
64
+ }
65
+ //# sourceMappingURL=tenantUrlHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenantUrlHelper.js","sourceRoot":"","sources":["../../../src/utils/tenantUrlHelper.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,EAAwB,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAEnF;;;;GAIG;AACH,MAAM,OAAO,eAAe;IAC3B;;OAEG;IACI,MAAM,CAAU,yBAAyB,GAAW,aAAa,CAAC;IAEzE;;OAEG;IACI,MAAM,CAAU,UAAU,qBAAqC;IAEtE;;;;;;;;OAQG;IACI,MAAM,CAAC,KAAK,CAAC,OAAO,CAC1B,GAAW,EACX,QAAgB,EAChB,cAA+B,EAC/B,OAAe,EACf,eAAwB;QAExB,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,UAAU,SAAe,GAAG,CAAC,CAAC;QACjE,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,UAAU,cAAoB,QAAQ,CAAC,CAAC;QAC3E,MAAM,CAAC,MAAM,CACZ,eAAe,CAAC,UAAU,oBAE1B,cAAc,CACd,CAAC;QACF,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAEzE,IAAI,CAAC;YACJ,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,OAAO,CAC7C,OAAO,EACP,mBAAmB,CAAC,gBAAgB,EACpC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAC/B,CAAC;YACF,MAAM,KAAK,GAAG,SAAS,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;YACpD,MAAM,SAAS,GAAG,eAAe,IAAI,eAAe,CAAC,yBAAyB,CAAC;YAC/E,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAChD,OAAO,GAAG,GAAG,GAAG,SAAS,GAAG,SAAS,IAAI,KAAK,EAAE,CAAC;QAClD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CAAC,eAAe,CAAC,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACrF,CAAC;IACF,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,KAAK,CAAC,OAAO,CAC1B,KAAa,EACb,cAA+B,EAC/B,OAAe;QAEf,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,UAAU,WAAiB,KAAK,CAAC,CAAC;QACrE,MAAM,CAAC,MAAM,CACZ,eAAe,CAAC,UAAU,oBAE1B,cAAc,CACd,CAAC;QACF,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAEzE,IAAI,CAAC;YACJ,MAAM,SAAS,GAAG,SAAS,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;YACpD,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,OAAO,CAC7C,OAAO,EACP,mBAAmB,CAAC,gBAAgB,EACpC,SAAS,CACT,CAAC;YACF,OAAO,SAAS,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,YAAY,CAAC,eAAe,CAAC,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACrF,CAAC;IACF,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Converter, GeneralError, Guards } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { type IVaultConnector, VaultEncryptionType } from \"@twin.org/vault-models\";\n\n/**\n * Helper for building and parsing URLs that carry an encrypted tenant token query param.\n * The token is the ChaCha20Poly1305-encrypted UTF-8 bytes of a tenant id, base64url-encoded\n * for URL safety.\n */\nexport class TenantUrlHelper {\n\t/**\n\t * The default query param name for the encrypted tenant token.\n\t */\n\tpublic static readonly DEFAULT_TENANT_TOKEN_NAME: string = \"tenantToken\";\n\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TenantUrlHelper>();\n\n\t/**\n\t * Encrypt a tenant id and append it as an opaque query param to the supplied URL.\n\t * @param url The URL to append the token to.\n\t * @param tenantId The tenant id to encrypt into the token.\n\t * @param vaultConnector The vault connector providing the symmetric key.\n\t * @param keyName The fully-qualified vault key name (e.g. `${nodeId}/tenant-token-encryption`).\n\t * @param tenantTokenName The query param name. Defaults to `tenantToken`.\n\t * @returns The URL with the encrypted tenant token appended.\n\t */\n\tpublic static async encrypt(\n\t\turl: string,\n\t\ttenantId: string,\n\t\tvaultConnector: IVaultConnector,\n\t\tkeyName: string,\n\t\ttenantTokenName?: string\n\t): Promise<string> {\n\t\tGuards.stringValue(TenantUrlHelper.CLASS_NAME, nameof(url), url);\n\t\tGuards.stringValue(TenantUrlHelper.CLASS_NAME, nameof(tenantId), tenantId);\n\t\tGuards.object<IVaultConnector>(\n\t\t\tTenantUrlHelper.CLASS_NAME,\n\t\t\tnameof(vaultConnector),\n\t\t\tvaultConnector\n\t\t);\n\t\tGuards.stringValue(TenantUrlHelper.CLASS_NAME, nameof(keyName), keyName);\n\n\t\ttry {\n\t\t\tconst encrypted = await vaultConnector.encrypt(\n\t\t\t\tkeyName,\n\t\t\t\tVaultEncryptionType.ChaCha20Poly1305,\n\t\t\t\tConverter.utf8ToBytes(tenantId)\n\t\t\t);\n\t\t\tconst token = Converter.bytesToBase64Url(encrypted);\n\t\t\tconst paramName = tenantTokenName ?? TenantUrlHelper.DEFAULT_TENANT_TOKEN_NAME;\n\t\t\tconst separator = url.includes(\"?\") ? \"&\" : \"?\";\n\t\t\treturn `${url}${separator}${paramName}=${token}`;\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(TenantUrlHelper.CLASS_NAME, \"encryptFailed\", undefined, err);\n\t\t}\n\t}\n\n\t/**\n\t * Decrypt an encrypted tenant token back into the original tenant id.\n\t * @param token The base64url-encoded encrypted tenant token.\n\t * @param vaultConnector The vault connector providing the symmetric key.\n\t * @param keyName The fully-qualified vault key name used to encrypt the token.\n\t * @returns The decrypted tenant id.\n\t */\n\tpublic static async decrypt(\n\t\ttoken: string,\n\t\tvaultConnector: IVaultConnector,\n\t\tkeyName: string\n\t): Promise<string> {\n\t\tGuards.stringValue(TenantUrlHelper.CLASS_NAME, nameof(token), token);\n\t\tGuards.object<IVaultConnector>(\n\t\t\tTenantUrlHelper.CLASS_NAME,\n\t\t\tnameof(vaultConnector),\n\t\t\tvaultConnector\n\t\t);\n\t\tGuards.stringValue(TenantUrlHelper.CLASS_NAME, nameof(keyName), keyName);\n\n\t\ttry {\n\t\t\tconst encrypted = Converter.base64UrlToBytes(token);\n\t\t\tconst decrypted = await vaultConnector.decrypt(\n\t\t\t\tkeyName,\n\t\t\t\tVaultEncryptionType.ChaCha20Poly1305,\n\t\t\t\tencrypted\n\t\t\t);\n\t\t\treturn Converter.bytesToUtf8(decrypted);\n\t\t} catch (err) {\n\t\t\tthrow new GeneralError(TenantUrlHelper.CLASS_NAME, \"decryptFailed\", undefined, err);\n\t\t}\n\t}\n}\n"]}
@@ -18,3 +18,4 @@ export * from "./tenantIdContextIdHandler.js";
18
18
  export * from "./tenantProcessor.js";
19
19
  export * from "./tenantRoutes.js";
20
20
  export * from "./utils/tenantIdHelper.js";
21
+ export * from "./utils/tenantUrlHelper.js";
@@ -7,4 +7,14 @@ export interface ITenantProcessorConfig {
7
7
  * @default x-api-key
8
8
  */
9
9
  apiKeyName?: string;
10
+ /**
11
+ * The name of the symmetric key in the vault used to encrypt/decrypt tenant tokens.
12
+ * @default tenant-token-encryption
13
+ */
14
+ signingKeyName?: string;
15
+ /**
16
+ * The query param name to look for the encrypted tenant token.
17
+ * @default tenantToken
18
+ */
19
+ tenantTokenName?: string;
10
20
  }
@@ -8,6 +8,12 @@ export interface ITenantProcessorConstructorOptions {
8
8
  * @default tenant
9
9
  */
10
10
  tenantEntityStorageType?: string;
11
+ /**
12
+ * The vault connector used to decrypt tenant tokens. Only resolved when
13
+ * `config.signingKeyName` is set.
14
+ * @default vault
15
+ */
16
+ vaultConnectorType?: string;
11
17
  /**
12
18
  * Configuration for the processor.
13
19
  */
@@ -19,6 +19,14 @@ export declare class TenantProcessor implements IBaseRouteProcessor {
19
19
  * @returns The class name of the component.
20
20
  */
21
21
  className(): string;
22
+ /**
23
+ * The processor needs to be started when the application is initialized so that
24
+ * the node identity is available for vault key resolution. Only required when
25
+ * the encrypted-token path is wired (i.e. `signingKeyName` is configured).
26
+ * @param nodeLoggingComponentType The node logging component type.
27
+ * @returns Nothing.
28
+ */
29
+ start(nodeLoggingComponentType?: string): Promise<void>;
22
30
  /**
23
31
  * Pre process the REST request for the specified route.
24
32
  * @param request The incoming request.
@@ -0,0 +1,34 @@
1
+ import { type IVaultConnector } from "@twin.org/vault-models";
2
+ /**
3
+ * Helper for building and parsing URLs that carry an encrypted tenant token query param.
4
+ * The token is the ChaCha20Poly1305-encrypted UTF-8 bytes of a tenant id, base64url-encoded
5
+ * for URL safety.
6
+ */
7
+ export declare class TenantUrlHelper {
8
+ /**
9
+ * The default query param name for the encrypted tenant token.
10
+ */
11
+ static readonly DEFAULT_TENANT_TOKEN_NAME: string;
12
+ /**
13
+ * Runtime name for the class.
14
+ */
15
+ static readonly CLASS_NAME: string;
16
+ /**
17
+ * Encrypt a tenant id and append it as an opaque query param to the supplied URL.
18
+ * @param url The URL to append the token to.
19
+ * @param tenantId The tenant id to encrypt into the token.
20
+ * @param vaultConnector The vault connector providing the symmetric key.
21
+ * @param keyName The fully-qualified vault key name (e.g. `${nodeId}/tenant-token-encryption`).
22
+ * @param tenantTokenName The query param name. Defaults to `tenantToken`.
23
+ * @returns The URL with the encrypted tenant token appended.
24
+ */
25
+ static encrypt(url: string, tenantId: string, vaultConnector: IVaultConnector, keyName: string, tenantTokenName?: string): Promise<string>;
26
+ /**
27
+ * Decrypt an encrypted tenant token back into the original tenant id.
28
+ * @param token The base64url-encoded encrypted tenant token.
29
+ * @param vaultConnector The vault connector providing the symmetric key.
30
+ * @param keyName The fully-qualified vault key name used to encrypt the token.
31
+ * @returns The decrypted tenant id.
32
+ */
33
+ static decrypt(token: string, vaultConnector: IVaultConnector, keyName: string): Promise<string>;
34
+ }
package/docs/changelog.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## [0.0.3-next.27](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.26...api-tenant-processor-v0.0.3-next.27) (2026-04-23)
3
+ ## [0.0.3-next.28](https://github.com/twinfoundation/twin-api/compare/api-tenant-processor-v0.0.3-next.27...api-tenant-processor-v0.0.3-next.28) (2026-04-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * tenantToken decoder on skipTenant routes + BaseRestClient query-string preservation ([#108](https://github.com/twinfoundation/twin-api/issues/108)) ([1435357](https://github.com/twinfoundation/twin-api/commit/1435357034b41130fc97238c728265e48f746f1e))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/api-models bumped from 0.0.3-next.27 to 0.0.3-next.28
16
+
17
+ ## [0.0.3-next.27](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.26...api-tenant-processor-v0.0.3-next.27) (2026-04-23)
4
18
 
5
19
 
6
20
  ### Miscellaneous Chores
@@ -14,7 +28,7 @@
14
28
  * dependencies
15
29
  * @twin.org/api-models bumped from 0.0.3-next.26 to 0.0.3-next.27
16
30
 
17
- ## [0.0.3-next.26](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.25...api-tenant-processor-v0.0.3-next.26) (2026-04-22)
31
+ ## [0.0.3-next.26](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.25...api-tenant-processor-v0.0.3-next.26) (2026-04-22)
18
32
 
19
33
 
20
34
  ### Miscellaneous Chores
@@ -28,7 +42,7 @@
28
42
  * dependencies
29
43
  * @twin.org/api-models bumped from 0.0.3-next.25 to 0.0.3-next.26
30
44
 
31
- ## [0.0.3-next.25](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.24...api-tenant-processor-v0.0.3-next.25) (2026-04-14)
45
+ ## [0.0.3-next.25](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.24...api-tenant-processor-v0.0.3-next.25) (2026-04-14)
32
46
 
33
47
 
34
48
  ### Miscellaneous Chores
@@ -42,7 +56,7 @@
42
56
  * dependencies
43
57
  * @twin.org/api-models bumped from 0.0.3-next.24 to 0.0.3-next.25
44
58
 
45
- ## [0.0.3-next.24](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.23...api-tenant-processor-v0.0.3-next.24) (2026-04-14)
59
+ ## [0.0.3-next.24](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.23...api-tenant-processor-v0.0.3-next.24) (2026-04-14)
46
60
 
47
61
 
48
62
  ### Miscellaneous Chores
@@ -56,7 +70,7 @@
56
70
  * dependencies
57
71
  * @twin.org/api-models bumped from 0.0.3-next.23 to 0.0.3-next.24
58
72
 
59
- ## [0.0.3-next.23](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.22...api-tenant-processor-v0.0.3-next.23) (2026-04-14)
73
+ ## [0.0.3-next.23](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.22...api-tenant-processor-v0.0.3-next.23) (2026-04-14)
60
74
 
61
75
 
62
76
  ### Miscellaneous Chores
@@ -70,7 +84,7 @@
70
84
  * dependencies
71
85
  * @twin.org/api-models bumped from 0.0.3-next.22 to 0.0.3-next.23
72
86
 
73
- ## [0.0.3-next.22](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.21...api-tenant-processor-v0.0.3-next.22) (2026-03-27)
87
+ ## [0.0.3-next.22](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.21...api-tenant-processor-v0.0.3-next.22) (2026-03-27)
74
88
 
75
89
 
76
90
  ### Miscellaneous Chores
@@ -84,7 +98,7 @@
84
98
  * dependencies
85
99
  * @twin.org/api-models bumped from 0.0.3-next.21 to 0.0.3-next.22
86
100
 
87
- ## [0.0.3-next.21](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.20...api-tenant-processor-v0.0.3-next.21) (2026-03-11)
101
+ ## [0.0.3-next.21](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.20...api-tenant-processor-v0.0.3-next.21) (2026-03-11)
88
102
 
89
103
 
90
104
  ### Miscellaneous Chores
@@ -98,12 +112,12 @@
98
112
  * dependencies
99
113
  * @twin.org/api-models bumped from 0.0.3-next.20 to 0.0.3-next.21
100
114
 
101
- ## [0.0.3-next.20](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.19...api-tenant-processor-v0.0.3-next.20) (2026-02-09)
115
+ ## [0.0.3-next.20](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.19...api-tenant-processor-v0.0.3-next.20) (2026-02-09)
102
116
 
103
117
 
104
118
  ### Features
105
119
 
106
- * location encoding ([#79](https://github.com/twinfoundation/api/issues/79)) ([c684465](https://github.com/twinfoundation/api/commit/c684465f2a871376152472bdecb6aa230b1101a1))
120
+ * location encoding ([#79](https://github.com/iotaledger/twin-api/issues/79)) ([c684465](https://github.com/iotaledger/twin-api/commit/c684465f2a871376152472bdecb6aa230b1101a1))
107
121
 
108
122
 
109
123
  ### Dependencies
@@ -112,7 +126,7 @@
112
126
  * dependencies
113
127
  * @twin.org/api-models bumped from 0.0.3-next.19 to 0.0.3-next.20
114
128
 
115
- ## [0.0.3-next.19](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.18...api-tenant-processor-v0.0.3-next.19) (2026-02-06)
129
+ ## [0.0.3-next.19](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.18...api-tenant-processor-v0.0.3-next.19) (2026-02-06)
116
130
 
117
131
 
118
132
  ### Miscellaneous Chores
@@ -126,12 +140,12 @@
126
140
  * dependencies
127
141
  * @twin.org/api-models bumped from 0.0.3-next.18 to 0.0.3-next.19
128
142
 
129
- ## [0.0.3-next.18](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.17...api-tenant-processor-v0.0.3-next.18) (2026-02-04)
143
+ ## [0.0.3-next.18](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.17...api-tenant-processor-v0.0.3-next.18) (2026-02-04)
130
144
 
131
145
 
132
146
  ### Features
133
147
 
134
- * tenant api and scopes ([#75](https://github.com/twinfoundation/api/issues/75)) ([c663141](https://github.com/twinfoundation/api/commit/c663141091e8974d769f8f9904ecdab009ebd083))
148
+ * tenant api and scopes ([#75](https://github.com/iotaledger/twin-api/issues/75)) ([c663141](https://github.com/iotaledger/twin-api/commit/c663141091e8974d769f8f9904ecdab009ebd083))
135
149
 
136
150
 
137
151
  ### Dependencies
@@ -140,7 +154,7 @@
140
154
  * dependencies
141
155
  * @twin.org/api-models bumped from 0.0.3-next.17 to 0.0.3-next.18
142
156
 
143
- ## [0.0.3-next.17](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.16...api-tenant-processor-v0.0.3-next.17) (2026-01-26)
157
+ ## [0.0.3-next.17](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.16...api-tenant-processor-v0.0.3-next.17) (2026-01-26)
144
158
 
145
159
 
146
160
  ### Miscellaneous Chores
@@ -154,12 +168,12 @@
154
168
  * dependencies
155
169
  * @twin.org/api-models bumped from 0.0.3-next.16 to 0.0.3-next.17
156
170
 
157
- ## [0.0.3-next.16](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.15...api-tenant-processor-v0.0.3-next.16) (2026-01-26)
171
+ ## [0.0.3-next.16](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.15...api-tenant-processor-v0.0.3-next.16) (2026-01-26)
158
172
 
159
173
 
160
174
  ### Features
161
175
 
162
- * public base url ([#70](https://github.com/twinfoundation/api/issues/70)) ([5b958cd](https://github.com/twinfoundation/api/commit/5b958cd91e8a38cdae2835ff5f2356c7e48d37c3))
176
+ * public base url ([#70](https://github.com/iotaledger/twin-api/issues/70)) ([5b958cd](https://github.com/iotaledger/twin-api/commit/5b958cd91e8a38cdae2835ff5f2356c7e48d37c3))
163
177
 
164
178
 
165
179
  ### Dependencies
@@ -168,7 +182,7 @@
168
182
  * dependencies
169
183
  * @twin.org/api-models bumped from 0.0.3-next.15 to 0.0.3-next.16
170
184
 
171
- ## [0.0.3-next.15](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.14...api-tenant-processor-v0.0.3-next.15) (2026-01-22)
185
+ ## [0.0.3-next.15](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.14...api-tenant-processor-v0.0.3-next.15) (2026-01-22)
172
186
 
173
187
 
174
188
  ### Miscellaneous Chores
@@ -182,7 +196,7 @@
182
196
  * dependencies
183
197
  * @twin.org/api-models bumped from 0.0.3-next.14 to 0.0.3-next.15
184
198
 
185
- ## [0.0.3-next.14](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.13...api-tenant-processor-v0.0.3-next.14) (2026-01-20)
199
+ ## [0.0.3-next.14](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.13...api-tenant-processor-v0.0.3-next.14) (2026-01-20)
186
200
 
187
201
 
188
202
  ### Miscellaneous Chores
@@ -196,7 +210,7 @@
196
210
  * dependencies
197
211
  * @twin.org/api-models bumped from 0.0.3-next.13 to 0.0.3-next.14
198
212
 
199
- ## [0.0.3-next.13](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.12...api-tenant-processor-v0.0.3-next.13) (2026-01-19)
213
+ ## [0.0.3-next.13](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.12...api-tenant-processor-v0.0.3-next.13) (2026-01-19)
200
214
 
201
215
 
202
216
  ### Miscellaneous Chores
@@ -210,7 +224,7 @@
210
224
  * dependencies
211
225
  * @twin.org/api-models bumped from 0.0.3-next.12 to 0.0.3-next.13
212
226
 
213
- ## [0.0.3-next.12](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.11...api-tenant-processor-v0.0.3-next.12) (2026-01-12)
227
+ ## [0.0.3-next.12](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.11...api-tenant-processor-v0.0.3-next.12) (2026-01-12)
214
228
 
215
229
 
216
230
  ### Miscellaneous Chores
@@ -224,12 +238,12 @@
224
238
  * dependencies
225
239
  * @twin.org/api-models bumped from 0.0.3-next.11 to 0.0.3-next.12
226
240
 
227
- ## [0.0.3-next.11](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.10...api-tenant-processor-v0.0.3-next.11) (2026-01-08)
241
+ ## [0.0.3-next.11](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.10...api-tenant-processor-v0.0.3-next.11) (2026-01-08)
228
242
 
229
243
 
230
244
  ### Bug Fixes
231
245
 
232
- * duplicate api keys ([#61](https://github.com/twinfoundation/api/issues/61)) ([5519c2d](https://github.com/twinfoundation/api/commit/5519c2d077d9b3d4b5fc7d5f073ef67cd000a367))
246
+ * duplicate api keys ([#61](https://github.com/iotaledger/twin-api/issues/61)) ([5519c2d](https://github.com/iotaledger/twin-api/commit/5519c2d077d9b3d4b5fc7d5f073ef67cd000a367))
233
247
 
234
248
 
235
249
  ### Dependencies
@@ -238,7 +252,7 @@
238
252
  * dependencies
239
253
  * @twin.org/api-models bumped from 0.0.3-next.10 to 0.0.3-next.11
240
254
 
241
- ## [0.0.3-next.10](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.9...api-tenant-processor-v0.0.3-next.10) (2026-01-05)
255
+ ## [0.0.3-next.10](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.9...api-tenant-processor-v0.0.3-next.10) (2026-01-05)
242
256
 
243
257
 
244
258
  ### Miscellaneous Chores
@@ -252,24 +266,24 @@
252
266
  * dependencies
253
267
  * @twin.org/api-models bumped from 0.0.3-next.9 to 0.0.3-next.10
254
268
 
255
- ## [0.0.3-next.9](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.8...api-tenant-processor-v0.0.3-next.9) (2026-01-05)
269
+ ## [0.0.3-next.9](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.8...api-tenant-processor-v0.0.3-next.9) (2026-01-05)
256
270
 
257
271
 
258
272
  ### Features
259
273
 
260
- * add context id features ([#42](https://github.com/twinfoundation/api/issues/42)) ([0186055](https://github.com/twinfoundation/api/commit/0186055c48afde842a4254b4df9ac9249c40fe40))
261
- * add livez endpoint ([#57](https://github.com/twinfoundation/api/issues/57)) ([ef007db](https://github.com/twinfoundation/api/commit/ef007db8201736dd3053211f849ffd03baaa485e))
262
- * add tests for tenant id handler ([c868e7e](https://github.com/twinfoundation/api/commit/c868e7e5831e13df39b8994c40834e51f69fa0b9))
263
- * check tenant id in auth if set ([937ba0c](https://github.com/twinfoundation/api/commit/937ba0cd790038556a7b2af251e52b43cb059df4))
264
- * check tenant id in auth if set ([66f7337](https://github.com/twinfoundation/api/commit/66f73374d3cf4c1c85ea96ec74bb30712fb84dd7))
265
- * reduce short form tenant id length ([bcda377](https://github.com/twinfoundation/api/commit/bcda377daed03b21fd1c6ffe47bad4a45d96602b))
274
+ * add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40))
275
+ * add livez endpoint ([#57](https://github.com/iotaledger/twin-api/issues/57)) ([ef007db](https://github.com/iotaledger/twin-api/commit/ef007db8201736dd3053211f849ffd03baaa485e))
276
+ * add tests for tenant id handler ([c868e7e](https://github.com/iotaledger/twin-api/commit/c868e7e5831e13df39b8994c40834e51f69fa0b9))
277
+ * check tenant id in auth if set ([937ba0c](https://github.com/iotaledger/twin-api/commit/937ba0cd790038556a7b2af251e52b43cb059df4))
278
+ * check tenant id in auth if set ([66f7337](https://github.com/iotaledger/twin-api/commit/66f73374d3cf4c1c85ea96ec74bb30712fb84dd7))
279
+ * reduce short form tenant id length ([bcda377](https://github.com/iotaledger/twin-api/commit/bcda377daed03b21fd1c6ffe47bad4a45d96602b))
266
280
 
267
281
 
268
282
  ### Bug Fixes
269
283
 
270
- * do not check x-api-key for health resourcr ([#54](https://github.com/twinfoundation/api/issues/54)) ([897a747](https://github.com/twinfoundation/api/commit/897a747a57ed76ee37035f7ea3d40953df3f5fb0))
271
- * remove extraneous type ([f85c52c](https://github.com/twinfoundation/api/commit/f85c52c55caa3a7a64f6f675842f0ea59289a5d9))
272
- * use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/twinfoundation/api/commit/72107718650acd05440ce9d9bf10905e6052281c))
284
+ * do not check x-api-key for health resourcr ([#54](https://github.com/iotaledger/twin-api/issues/54)) ([897a747](https://github.com/iotaledger/twin-api/commit/897a747a57ed76ee37035f7ea3d40953df3f5fb0))
285
+ * remove extraneous type ([f85c52c](https://github.com/iotaledger/twin-api/commit/f85c52c55caa3a7a64f6f675842f0ea59289a5d9))
286
+ * use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/iotaledger/twin-api/commit/72107718650acd05440ce9d9bf10905e6052281c))
273
287
 
274
288
 
275
289
  ### Dependencies
@@ -278,12 +292,12 @@
278
292
  * dependencies
279
293
  * @twin.org/api-models bumped from 0.0.3-next.8 to 0.0.3-next.9
280
294
 
281
- ## [0.0.3-next.8](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.7...api-tenant-processor-v0.0.3-next.8) (2025-12-17)
295
+ ## [0.0.3-next.8](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.7...api-tenant-processor-v0.0.3-next.8) (2025-12-17)
282
296
 
283
297
 
284
298
  ### Bug Fixes
285
299
 
286
- * do not check x-api-key for health resourcr ([#54](https://github.com/twinfoundation/api/issues/54)) ([897a747](https://github.com/twinfoundation/api/commit/897a747a57ed76ee37035f7ea3d40953df3f5fb0))
300
+ * do not check x-api-key for health resourcr ([#54](https://github.com/iotaledger/twin-api/issues/54)) ([897a747](https://github.com/iotaledger/twin-api/commit/897a747a57ed76ee37035f7ea3d40953df3f5fb0))
287
301
 
288
302
 
289
303
  ### Dependencies
@@ -292,7 +306,7 @@
292
306
  * dependencies
293
307
  * @twin.org/api-models bumped from 0.0.3-next.7 to 0.0.3-next.8
294
308
 
295
- ## [0.0.3-next.7](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.6...api-tenant-processor-v0.0.3-next.7) (2025-11-26)
309
+ ## [0.0.3-next.7](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.6...api-tenant-processor-v0.0.3-next.7) (2025-11-26)
296
310
 
297
311
 
298
312
  ### Miscellaneous Chores
@@ -306,13 +320,13 @@
306
320
  * dependencies
307
321
  * @twin.org/api-models bumped from 0.0.3-next.6 to 0.0.3-next.7
308
322
 
309
- ## [0.0.3-next.6](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.5...api-tenant-processor-v0.0.3-next.6) (2025-11-20)
323
+ ## [0.0.3-next.6](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.5...api-tenant-processor-v0.0.3-next.6) (2025-11-20)
310
324
 
311
325
 
312
326
  ### Features
313
327
 
314
- * check tenant id in auth if set ([937ba0c](https://github.com/twinfoundation/api/commit/937ba0cd790038556a7b2af251e52b43cb059df4))
315
- * check tenant id in auth if set ([66f7337](https://github.com/twinfoundation/api/commit/66f73374d3cf4c1c85ea96ec74bb30712fb84dd7))
328
+ * check tenant id in auth if set ([937ba0c](https://github.com/iotaledger/twin-api/commit/937ba0cd790038556a7b2af251e52b43cb059df4))
329
+ * check tenant id in auth if set ([66f7337](https://github.com/iotaledger/twin-api/commit/66f73374d3cf4c1c85ea96ec74bb30712fb84dd7))
316
330
 
317
331
 
318
332
  ### Dependencies
@@ -321,7 +335,7 @@
321
335
  * dependencies
322
336
  * @twin.org/api-models bumped from 0.0.3-next.5 to 0.0.3-next.6
323
337
 
324
- ## [0.0.3-next.5](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.4...api-tenant-processor-v0.0.3-next.5) (2025-11-14)
338
+ ## [0.0.3-next.5](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.4...api-tenant-processor-v0.0.3-next.5) (2025-11-14)
325
339
 
326
340
 
327
341
  ### Miscellaneous Chores
@@ -335,20 +349,20 @@
335
349
  * dependencies
336
350
  * @twin.org/api-models bumped from 0.0.3-next.4 to 0.0.3-next.5
337
351
 
338
- ## [0.0.3-next.4](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.3...api-tenant-processor-v0.0.3-next.4) (2025-11-14)
352
+ ## [0.0.3-next.4](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.3...api-tenant-processor-v0.0.3-next.4) (2025-11-14)
339
353
 
340
354
 
341
355
  ### Features
342
356
 
343
- * add context id features ([#42](https://github.com/twinfoundation/api/issues/42)) ([0186055](https://github.com/twinfoundation/api/commit/0186055c48afde842a4254b4df9ac9249c40fe40))
344
- * add tests for tenant id handler ([c868e7e](https://github.com/twinfoundation/api/commit/c868e7e5831e13df39b8994c40834e51f69fa0b9))
345
- * reduce short form tenant id length ([bcda377](https://github.com/twinfoundation/api/commit/bcda377daed03b21fd1c6ffe47bad4a45d96602b))
357
+ * add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40))
358
+ * add tests for tenant id handler ([c868e7e](https://github.com/iotaledger/twin-api/commit/c868e7e5831e13df39b8994c40834e51f69fa0b9))
359
+ * reduce short form tenant id length ([bcda377](https://github.com/iotaledger/twin-api/commit/bcda377daed03b21fd1c6ffe47bad4a45d96602b))
346
360
 
347
361
 
348
362
  ### Bug Fixes
349
363
 
350
- * remove extraneous type ([f85c52c](https://github.com/twinfoundation/api/commit/f85c52c55caa3a7a64f6f675842f0ea59289a5d9))
351
- * use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/twinfoundation/api/commit/72107718650acd05440ce9d9bf10905e6052281c))
364
+ * remove extraneous type ([f85c52c](https://github.com/iotaledger/twin-api/commit/f85c52c55caa3a7a64f6f675842f0ea59289a5d9))
365
+ * use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/iotaledger/twin-api/commit/72107718650acd05440ce9d9bf10905e6052281c))
352
366
 
353
367
 
354
368
  ### Dependencies
@@ -357,17 +371,17 @@
357
371
  * dependencies
358
372
  * @twin.org/api-models bumped from 0.0.3-next.3 to 0.0.3-next.4
359
373
 
360
- ## [0.0.3-next.3](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.2...api-tenant-processor-v0.0.3-next.3) (2025-11-14)
374
+ ## [0.0.3-next.3](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.2...api-tenant-processor-v0.0.3-next.3) (2025-11-14)
361
375
 
362
376
 
363
377
  ### Features
364
378
 
365
- * add tests for tenant id handler ([c868e7e](https://github.com/twinfoundation/api/commit/c868e7e5831e13df39b8994c40834e51f69fa0b9))
379
+ * add tests for tenant id handler ([c868e7e](https://github.com/iotaledger/twin-api/commit/c868e7e5831e13df39b8994c40834e51f69fa0b9))
366
380
 
367
381
 
368
382
  ### Bug Fixes
369
383
 
370
- * use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/twinfoundation/api/commit/72107718650acd05440ce9d9bf10905e6052281c))
384
+ * use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/iotaledger/twin-api/commit/72107718650acd05440ce9d9bf10905e6052281c))
371
385
 
372
386
 
373
387
  ### Dependencies
@@ -376,17 +390,17 @@
376
390
  * dependencies
377
391
  * @twin.org/api-models bumped from 0.0.3-next.2 to 0.0.3-next.3
378
392
 
379
- ## [0.0.3-next.2](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.1...api-tenant-processor-v0.0.3-next.2) (2025-11-12)
393
+ ## [0.0.3-next.2](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.1...api-tenant-processor-v0.0.3-next.2) (2025-11-12)
380
394
 
381
395
 
382
396
  ### Features
383
397
 
384
- * reduce short form tenant id length ([bcda377](https://github.com/twinfoundation/api/commit/bcda377daed03b21fd1c6ffe47bad4a45d96602b))
398
+ * reduce short form tenant id length ([bcda377](https://github.com/iotaledger/twin-api/commit/bcda377daed03b21fd1c6ffe47bad4a45d96602b))
385
399
 
386
400
 
387
401
  ### Bug Fixes
388
402
 
389
- * remove extraneous type ([f85c52c](https://github.com/twinfoundation/api/commit/f85c52c55caa3a7a64f6f675842f0ea59289a5d9))
403
+ * remove extraneous type ([f85c52c](https://github.com/iotaledger/twin-api/commit/f85c52c55caa3a7a64f6f675842f0ea59289a5d9))
390
404
 
391
405
 
392
406
  ### Dependencies
@@ -395,12 +409,12 @@
395
409
  * dependencies
396
410
  * @twin.org/api-models bumped from 0.0.3-next.1 to 0.0.3-next.2
397
411
 
398
- ## [0.0.3-next.1](https://github.com/twinfoundation/api/compare/api-tenant-processor-v0.0.3-next.0...api-tenant-processor-v0.0.3-next.1) (2025-11-10)
412
+ ## [0.0.3-next.1](https://github.com/iotaledger/twin-api/compare/api-tenant-processor-v0.0.3-next.0...api-tenant-processor-v0.0.3-next.1) (2025-11-10)
399
413
 
400
414
 
401
415
  ### Features
402
416
 
403
- * add context id features ([#42](https://github.com/twinfoundation/api/issues/42)) ([0186055](https://github.com/twinfoundation/api/commit/0186055c48afde842a4254b4df9ac9249c40fe40))
417
+ * add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40))
404
418
 
405
419
 
406
420
  ### Dependencies
@@ -54,6 +54,34 @@ The class name of the component.
54
54
 
55
55
  ***
56
56
 
57
+ ### start() {#start}
58
+
59
+ > **start**(`nodeLoggingComponentType?`): `Promise`\<`void`\>
60
+
61
+ The processor needs to be started when the application is initialized so that
62
+ the node identity is available for vault key resolution. Only required when
63
+ the encrypted-token path is wired (i.e. `signingKeyName` is configured).
64
+
65
+ #### Parameters
66
+
67
+ ##### nodeLoggingComponentType?
68
+
69
+ `string`
70
+
71
+ The node logging component type.
72
+
73
+ #### Returns
74
+
75
+ `Promise`\<`void`\>
76
+
77
+ Nothing.
78
+
79
+ #### Implementation of
80
+
81
+ `IBaseRouteProcessor.start`
82
+
83
+ ***
84
+
57
85
  ### pre() {#pre}
58
86
 
59
87
  > **pre**(`request`, `response`, `route`, `contextIds`, `processorState`): `Promise`\<`void`\>
@@ -0,0 +1,111 @@
1
+ # Class: TenantUrlHelper
2
+
3
+ Helper for building and parsing URLs that carry an encrypted tenant token query param.
4
+ The token is the ChaCha20Poly1305-encrypted UTF-8 bytes of a tenant id, base64url-encoded
5
+ for URL safety.
6
+
7
+ ## Constructors
8
+
9
+ ### Constructor
10
+
11
+ > **new TenantUrlHelper**(): `TenantUrlHelper`
12
+
13
+ #### Returns
14
+
15
+ `TenantUrlHelper`
16
+
17
+ ## Properties
18
+
19
+ ### DEFAULT\_TENANT\_TOKEN\_NAME {#default_tenant_token_name}
20
+
21
+ > `readonly` `static` **DEFAULT\_TENANT\_TOKEN\_NAME**: `string` = `"tenantToken"`
22
+
23
+ The default query param name for the encrypted tenant token.
24
+
25
+ ***
26
+
27
+ ### CLASS\_NAME {#class_name}
28
+
29
+ > `readonly` `static` **CLASS\_NAME**: `string`
30
+
31
+ Runtime name for the class.
32
+
33
+ ## Methods
34
+
35
+ ### encrypt() {#encrypt}
36
+
37
+ > `static` **encrypt**(`url`, `tenantId`, `vaultConnector`, `keyName`, `tenantTokenName?`): `Promise`\<`string`\>
38
+
39
+ Encrypt a tenant id and append it as an opaque query param to the supplied URL.
40
+
41
+ #### Parameters
42
+
43
+ ##### url
44
+
45
+ `string`
46
+
47
+ The URL to append the token to.
48
+
49
+ ##### tenantId
50
+
51
+ `string`
52
+
53
+ The tenant id to encrypt into the token.
54
+
55
+ ##### vaultConnector
56
+
57
+ `IVaultConnector`
58
+
59
+ The vault connector providing the symmetric key.
60
+
61
+ ##### keyName
62
+
63
+ `string`
64
+
65
+ The fully-qualified vault key name (e.g. `${nodeId}/tenant-token-encryption`).
66
+
67
+ ##### tenantTokenName?
68
+
69
+ `string`
70
+
71
+ The query param name. Defaults to `tenantToken`.
72
+
73
+ #### Returns
74
+
75
+ `Promise`\<`string`\>
76
+
77
+ The URL with the encrypted tenant token appended.
78
+
79
+ ***
80
+
81
+ ### decrypt() {#decrypt}
82
+
83
+ > `static` **decrypt**(`token`, `vaultConnector`, `keyName`): `Promise`\<`string`\>
84
+
85
+ Decrypt an encrypted tenant token back into the original tenant id.
86
+
87
+ #### Parameters
88
+
89
+ ##### token
90
+
91
+ `string`
92
+
93
+ The base64url-encoded encrypted tenant token.
94
+
95
+ ##### vaultConnector
96
+
97
+ `IVaultConnector`
98
+
99
+ The vault connector providing the symmetric key.
100
+
101
+ ##### keyName
102
+
103
+ `string`
104
+
105
+ The fully-qualified vault key name used to encrypt the token.
106
+
107
+ #### Returns
108
+
109
+ `Promise`\<`string`\>
110
+
111
+ The decrypted tenant id.
@@ -7,6 +7,7 @@
7
7
  - [TenantIdContextIdHandler](classes/TenantIdContextIdHandler.md)
8
8
  - [TenantProcessor](classes/TenantProcessor.md)
9
9
  - [TenantIdHelper](classes/TenantIdHelper.md)
10
+ - [TenantUrlHelper](classes/TenantUrlHelper.md)
10
11
 
11
12
  ## Interfaces
12
13
 
@@ -15,3 +15,31 @@ The key to look for in the header or query params for the api key.
15
15
  ```ts
16
16
  x-api-key
17
17
  ```
18
+
19
+ ***
20
+
21
+ ### signingKeyName? {#signingkeyname}
22
+
23
+ > `optional` **signingKeyName?**: `string`
24
+
25
+ The name of the symmetric key in the vault used to encrypt/decrypt tenant tokens.
26
+
27
+ #### Default
28
+
29
+ ```ts
30
+ tenant-token-encryption
31
+ ```
32
+
33
+ ***
34
+
35
+ ### tenantTokenName? {#tenanttokenname}
36
+
37
+ > `optional` **tenantTokenName?**: `string`
38
+
39
+ The query param name to look for the encrypted tenant token.
40
+
41
+ #### Default
42
+
43
+ ```ts
44
+ tenantToken
45
+ ```
@@ -18,6 +18,21 @@ tenant
18
18
 
19
19
  ***
20
20
 
21
+ ### vaultConnectorType? {#vaultconnectortype}
22
+
23
+ > `optional` **vaultConnectorType?**: `string`
24
+
25
+ The vault connector used to decrypt tenant tokens. Only resolved when
26
+ `config.signingKeyName` is set.
27
+
28
+ #### Default
29
+
30
+ ```ts
31
+ vault
32
+ ```
33
+
34
+ ***
35
+
21
36
  ### config? {#config}
22
37
 
23
38
  > `optional` **config?**: [`ITenantProcessorConfig`](ITenantProcessorConfig.md)
package/locales/en.json CHANGED
@@ -2,7 +2,13 @@
2
2
  "error": {
3
3
  "tenantProcessor": {
4
4
  "missingApiKey": "API key header or query param \"{keyName}\" is missing or invalid.",
5
- "apiKeyNotFound": "No node tenant found for API key \"{key}\"."
5
+ "apiKeyNotFound": "No node tenant found for API key \"{key}\".",
6
+ "tenantTokenInvalid": "The tenant token \"{token}\" could not be decrypted or is malformed.",
7
+ "tenantTokenNotFound": "No node tenant found for the decrypted tenant token \"{tenantId}\"."
8
+ },
9
+ "tenantUrlHelper": {
10
+ "encryptFailed": "Encryption of tenant URL token failed.",
11
+ "decryptFailed": "Decryption of tenant URL token failed."
6
12
  },
7
13
  "tenantAdminService": {
8
14
  "apiKeyAlreadyInUse": "The provided API key is already in use by another tenant.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/api-tenant-processor",
3
- "version": "0.0.3-next.27",
3
+ "version": "0.0.3-next.28",
4
4
  "description": "Tenant resolution services and route handlers that derive tenant context from API keys.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,12 +14,13 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/api-models": "0.0.3-next.27",
17
+ "@twin.org/api-models": "0.0.3-next.28",
18
18
  "@twin.org/context": "next",
19
19
  "@twin.org/core": "next",
20
20
  "@twin.org/entity": "next",
21
21
  "@twin.org/entity-storage-models": "next",
22
22
  "@twin.org/nameof": "next",
23
+ "@twin.org/vault-models": "next",
23
24
  "@twin.org/web": "next"
24
25
  },
25
26
  "main": "./dist/es/index.js",