@twin.org/api-tenant-processor 0.0.3-next.26 → 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 +1 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/ITenantProcessorConfig.js.map +1 -1
- package/dist/es/models/ITenantProcessorConstructorOptions.js.map +1 -1
- package/dist/es/tenantProcessor.js +132 -26
- package/dist/es/tenantProcessor.js.map +1 -1
- package/dist/es/utils/tenantUrlHelper.js +65 -0
- package/dist/es/utils/tenantUrlHelper.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/models/ITenantProcessorConfig.d.ts +10 -0
- package/dist/types/models/ITenantProcessorConstructorOptions.d.ts +6 -0
- package/dist/types/tenantProcessor.d.ts +8 -0
- package/dist/types/utils/tenantUrlHelper.d.ts +34 -0
- package/docs/changelog.md +80 -52
- package/docs/reference/classes/TenantProcessor.md +28 -0
- package/docs/reference/classes/TenantUrlHelper.md +111 -0
- package/docs/reference/index.md +1 -0
- package/docs/reference/interfaces/ITenantProcessorConfig.md +28 -0
- package/docs/reference/interfaces/ITenantProcessorConstructorOptions.md +15 -0
- package/locales/en.json +7 -1
- package/package.json +3 -2
package/dist/es/index.js
CHANGED
package/dist/es/index.js.map
CHANGED
|
@@ -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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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"]}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [0.0.3-next.
|
|
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)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Miscellaneous Chores
|
|
21
|
+
|
|
22
|
+
* **api-tenant-processor:** Synchronize repo versions
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Dependencies
|
|
26
|
+
|
|
27
|
+
* The following workspace dependencies were updated
|
|
28
|
+
* dependencies
|
|
29
|
+
* @twin.org/api-models bumped from 0.0.3-next.26 to 0.0.3-next.27
|
|
30
|
+
|
|
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)
|
|
4
32
|
|
|
5
33
|
|
|
6
34
|
### Miscellaneous Chores
|
|
@@ -14,7 +42,7 @@
|
|
|
14
42
|
* dependencies
|
|
15
43
|
* @twin.org/api-models bumped from 0.0.3-next.25 to 0.0.3-next.26
|
|
16
44
|
|
|
17
|
-
## [0.0.3-next.25](https://github.com/
|
|
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)
|
|
18
46
|
|
|
19
47
|
|
|
20
48
|
### Miscellaneous Chores
|
|
@@ -28,7 +56,7 @@
|
|
|
28
56
|
* dependencies
|
|
29
57
|
* @twin.org/api-models bumped from 0.0.3-next.24 to 0.0.3-next.25
|
|
30
58
|
|
|
31
|
-
## [0.0.3-next.24](https://github.com/
|
|
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)
|
|
32
60
|
|
|
33
61
|
|
|
34
62
|
### Miscellaneous Chores
|
|
@@ -42,7 +70,7 @@
|
|
|
42
70
|
* dependencies
|
|
43
71
|
* @twin.org/api-models bumped from 0.0.3-next.23 to 0.0.3-next.24
|
|
44
72
|
|
|
45
|
-
## [0.0.3-next.23](https://github.com/
|
|
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)
|
|
46
74
|
|
|
47
75
|
|
|
48
76
|
### Miscellaneous Chores
|
|
@@ -56,7 +84,7 @@
|
|
|
56
84
|
* dependencies
|
|
57
85
|
* @twin.org/api-models bumped from 0.0.3-next.22 to 0.0.3-next.23
|
|
58
86
|
|
|
59
|
-
## [0.0.3-next.22](https://github.com/
|
|
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)
|
|
60
88
|
|
|
61
89
|
|
|
62
90
|
### Miscellaneous Chores
|
|
@@ -70,7 +98,7 @@
|
|
|
70
98
|
* dependencies
|
|
71
99
|
* @twin.org/api-models bumped from 0.0.3-next.21 to 0.0.3-next.22
|
|
72
100
|
|
|
73
|
-
## [0.0.3-next.21](https://github.com/
|
|
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)
|
|
74
102
|
|
|
75
103
|
|
|
76
104
|
### Miscellaneous Chores
|
|
@@ -84,12 +112,12 @@
|
|
|
84
112
|
* dependencies
|
|
85
113
|
* @twin.org/api-models bumped from 0.0.3-next.20 to 0.0.3-next.21
|
|
86
114
|
|
|
87
|
-
## [0.0.3-next.20](https://github.com/
|
|
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)
|
|
88
116
|
|
|
89
117
|
|
|
90
118
|
### Features
|
|
91
119
|
|
|
92
|
-
* location encoding ([#79](https://github.com/
|
|
120
|
+
* location encoding ([#79](https://github.com/iotaledger/twin-api/issues/79)) ([c684465](https://github.com/iotaledger/twin-api/commit/c684465f2a871376152472bdecb6aa230b1101a1))
|
|
93
121
|
|
|
94
122
|
|
|
95
123
|
### Dependencies
|
|
@@ -98,7 +126,7 @@
|
|
|
98
126
|
* dependencies
|
|
99
127
|
* @twin.org/api-models bumped from 0.0.3-next.19 to 0.0.3-next.20
|
|
100
128
|
|
|
101
|
-
## [0.0.3-next.19](https://github.com/
|
|
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)
|
|
102
130
|
|
|
103
131
|
|
|
104
132
|
### Miscellaneous Chores
|
|
@@ -112,12 +140,12 @@
|
|
|
112
140
|
* dependencies
|
|
113
141
|
* @twin.org/api-models bumped from 0.0.3-next.18 to 0.0.3-next.19
|
|
114
142
|
|
|
115
|
-
## [0.0.3-next.18](https://github.com/
|
|
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)
|
|
116
144
|
|
|
117
145
|
|
|
118
146
|
### Features
|
|
119
147
|
|
|
120
|
-
* tenant api and scopes ([#75](https://github.com/
|
|
148
|
+
* tenant api and scopes ([#75](https://github.com/iotaledger/twin-api/issues/75)) ([c663141](https://github.com/iotaledger/twin-api/commit/c663141091e8974d769f8f9904ecdab009ebd083))
|
|
121
149
|
|
|
122
150
|
|
|
123
151
|
### Dependencies
|
|
@@ -126,7 +154,7 @@
|
|
|
126
154
|
* dependencies
|
|
127
155
|
* @twin.org/api-models bumped from 0.0.3-next.17 to 0.0.3-next.18
|
|
128
156
|
|
|
129
|
-
## [0.0.3-next.17](https://github.com/
|
|
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)
|
|
130
158
|
|
|
131
159
|
|
|
132
160
|
### Miscellaneous Chores
|
|
@@ -140,12 +168,12 @@
|
|
|
140
168
|
* dependencies
|
|
141
169
|
* @twin.org/api-models bumped from 0.0.3-next.16 to 0.0.3-next.17
|
|
142
170
|
|
|
143
|
-
## [0.0.3-next.16](https://github.com/
|
|
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)
|
|
144
172
|
|
|
145
173
|
|
|
146
174
|
### Features
|
|
147
175
|
|
|
148
|
-
* public base url ([#70](https://github.com/
|
|
176
|
+
* public base url ([#70](https://github.com/iotaledger/twin-api/issues/70)) ([5b958cd](https://github.com/iotaledger/twin-api/commit/5b958cd91e8a38cdae2835ff5f2356c7e48d37c3))
|
|
149
177
|
|
|
150
178
|
|
|
151
179
|
### Dependencies
|
|
@@ -154,7 +182,7 @@
|
|
|
154
182
|
* dependencies
|
|
155
183
|
* @twin.org/api-models bumped from 0.0.3-next.15 to 0.0.3-next.16
|
|
156
184
|
|
|
157
|
-
## [0.0.3-next.15](https://github.com/
|
|
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)
|
|
158
186
|
|
|
159
187
|
|
|
160
188
|
### Miscellaneous Chores
|
|
@@ -168,7 +196,7 @@
|
|
|
168
196
|
* dependencies
|
|
169
197
|
* @twin.org/api-models bumped from 0.0.3-next.14 to 0.0.3-next.15
|
|
170
198
|
|
|
171
|
-
## [0.0.3-next.14](https://github.com/
|
|
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)
|
|
172
200
|
|
|
173
201
|
|
|
174
202
|
### Miscellaneous Chores
|
|
@@ -182,7 +210,7 @@
|
|
|
182
210
|
* dependencies
|
|
183
211
|
* @twin.org/api-models bumped from 0.0.3-next.13 to 0.0.3-next.14
|
|
184
212
|
|
|
185
|
-
## [0.0.3-next.13](https://github.com/
|
|
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)
|
|
186
214
|
|
|
187
215
|
|
|
188
216
|
### Miscellaneous Chores
|
|
@@ -196,7 +224,7 @@
|
|
|
196
224
|
* dependencies
|
|
197
225
|
* @twin.org/api-models bumped from 0.0.3-next.12 to 0.0.3-next.13
|
|
198
226
|
|
|
199
|
-
## [0.0.3-next.12](https://github.com/
|
|
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)
|
|
200
228
|
|
|
201
229
|
|
|
202
230
|
### Miscellaneous Chores
|
|
@@ -210,12 +238,12 @@
|
|
|
210
238
|
* dependencies
|
|
211
239
|
* @twin.org/api-models bumped from 0.0.3-next.11 to 0.0.3-next.12
|
|
212
240
|
|
|
213
|
-
## [0.0.3-next.11](https://github.com/
|
|
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)
|
|
214
242
|
|
|
215
243
|
|
|
216
244
|
### Bug Fixes
|
|
217
245
|
|
|
218
|
-
* duplicate api keys ([#61](https://github.com/
|
|
246
|
+
* duplicate api keys ([#61](https://github.com/iotaledger/twin-api/issues/61)) ([5519c2d](https://github.com/iotaledger/twin-api/commit/5519c2d077d9b3d4b5fc7d5f073ef67cd000a367))
|
|
219
247
|
|
|
220
248
|
|
|
221
249
|
### Dependencies
|
|
@@ -224,7 +252,7 @@
|
|
|
224
252
|
* dependencies
|
|
225
253
|
* @twin.org/api-models bumped from 0.0.3-next.10 to 0.0.3-next.11
|
|
226
254
|
|
|
227
|
-
## [0.0.3-next.10](https://github.com/
|
|
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)
|
|
228
256
|
|
|
229
257
|
|
|
230
258
|
### Miscellaneous Chores
|
|
@@ -238,24 +266,24 @@
|
|
|
238
266
|
* dependencies
|
|
239
267
|
* @twin.org/api-models bumped from 0.0.3-next.9 to 0.0.3-next.10
|
|
240
268
|
|
|
241
|
-
## [0.0.3-next.9](https://github.com/
|
|
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)
|
|
242
270
|
|
|
243
271
|
|
|
244
272
|
### Features
|
|
245
273
|
|
|
246
|
-
* add context id features ([#42](https://github.com/
|
|
247
|
-
* add livez endpoint ([#57](https://github.com/
|
|
248
|
-
* add tests for tenant id handler ([c868e7e](https://github.com/
|
|
249
|
-
* check tenant id in auth if set ([937ba0c](https://github.com/
|
|
250
|
-
* check tenant id in auth if set ([66f7337](https://github.com/
|
|
251
|
-
* reduce short form tenant id length ([bcda377](https://github.com/
|
|
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))
|
|
252
280
|
|
|
253
281
|
|
|
254
282
|
### Bug Fixes
|
|
255
283
|
|
|
256
|
-
* do not check x-api-key for health resourcr ([#54](https://github.com/
|
|
257
|
-
* remove extraneous type ([f85c52c](https://github.com/
|
|
258
|
-
* use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/
|
|
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))
|
|
259
287
|
|
|
260
288
|
|
|
261
289
|
### Dependencies
|
|
@@ -264,12 +292,12 @@
|
|
|
264
292
|
* dependencies
|
|
265
293
|
* @twin.org/api-models bumped from 0.0.3-next.8 to 0.0.3-next.9
|
|
266
294
|
|
|
267
|
-
## [0.0.3-next.8](https://github.com/
|
|
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)
|
|
268
296
|
|
|
269
297
|
|
|
270
298
|
### Bug Fixes
|
|
271
299
|
|
|
272
|
-
* do not check x-api-key for health resourcr ([#54](https://github.com/
|
|
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))
|
|
273
301
|
|
|
274
302
|
|
|
275
303
|
### Dependencies
|
|
@@ -278,7 +306,7 @@
|
|
|
278
306
|
* dependencies
|
|
279
307
|
* @twin.org/api-models bumped from 0.0.3-next.7 to 0.0.3-next.8
|
|
280
308
|
|
|
281
|
-
## [0.0.3-next.7](https://github.com/
|
|
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)
|
|
282
310
|
|
|
283
311
|
|
|
284
312
|
### Miscellaneous Chores
|
|
@@ -292,13 +320,13 @@
|
|
|
292
320
|
* dependencies
|
|
293
321
|
* @twin.org/api-models bumped from 0.0.3-next.6 to 0.0.3-next.7
|
|
294
322
|
|
|
295
|
-
## [0.0.3-next.6](https://github.com/
|
|
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)
|
|
296
324
|
|
|
297
325
|
|
|
298
326
|
### Features
|
|
299
327
|
|
|
300
|
-
* check tenant id in auth if set ([937ba0c](https://github.com/
|
|
301
|
-
* check tenant id in auth if set ([66f7337](https://github.com/
|
|
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))
|
|
302
330
|
|
|
303
331
|
|
|
304
332
|
### Dependencies
|
|
@@ -307,7 +335,7 @@
|
|
|
307
335
|
* dependencies
|
|
308
336
|
* @twin.org/api-models bumped from 0.0.3-next.5 to 0.0.3-next.6
|
|
309
337
|
|
|
310
|
-
## [0.0.3-next.5](https://github.com/
|
|
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)
|
|
311
339
|
|
|
312
340
|
|
|
313
341
|
### Miscellaneous Chores
|
|
@@ -321,20 +349,20 @@
|
|
|
321
349
|
* dependencies
|
|
322
350
|
* @twin.org/api-models bumped from 0.0.3-next.4 to 0.0.3-next.5
|
|
323
351
|
|
|
324
|
-
## [0.0.3-next.4](https://github.com/
|
|
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)
|
|
325
353
|
|
|
326
354
|
|
|
327
355
|
### Features
|
|
328
356
|
|
|
329
|
-
* add context id features ([#42](https://github.com/
|
|
330
|
-
* add tests for tenant id handler ([c868e7e](https://github.com/
|
|
331
|
-
* reduce short form tenant id length ([bcda377](https://github.com/
|
|
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))
|
|
332
360
|
|
|
333
361
|
|
|
334
362
|
### Bug Fixes
|
|
335
363
|
|
|
336
|
-
* remove extraneous type ([f85c52c](https://github.com/
|
|
337
|
-
* use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/
|
|
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))
|
|
338
366
|
|
|
339
367
|
|
|
340
368
|
### Dependencies
|
|
@@ -343,17 +371,17 @@
|
|
|
343
371
|
* dependencies
|
|
344
372
|
* @twin.org/api-models bumped from 0.0.3-next.3 to 0.0.3-next.4
|
|
345
373
|
|
|
346
|
-
## [0.0.3-next.3](https://github.com/
|
|
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)
|
|
347
375
|
|
|
348
376
|
|
|
349
377
|
### Features
|
|
350
378
|
|
|
351
|
-
* add tests for tenant id handler ([c868e7e](https://github.com/
|
|
379
|
+
* add tests for tenant id handler ([c868e7e](https://github.com/iotaledger/twin-api/commit/c868e7e5831e13df39b8994c40834e51f69fa0b9))
|
|
352
380
|
|
|
353
381
|
|
|
354
382
|
### Bug Fixes
|
|
355
383
|
|
|
356
|
-
* use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/
|
|
384
|
+
* use base64Url for tenant id short form so it doesn't include slash ([7210771](https://github.com/iotaledger/twin-api/commit/72107718650acd05440ce9d9bf10905e6052281c))
|
|
357
385
|
|
|
358
386
|
|
|
359
387
|
### Dependencies
|
|
@@ -362,17 +390,17 @@
|
|
|
362
390
|
* dependencies
|
|
363
391
|
* @twin.org/api-models bumped from 0.0.3-next.2 to 0.0.3-next.3
|
|
364
392
|
|
|
365
|
-
## [0.0.3-next.2](https://github.com/
|
|
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)
|
|
366
394
|
|
|
367
395
|
|
|
368
396
|
### Features
|
|
369
397
|
|
|
370
|
-
* reduce short form tenant id length ([bcda377](https://github.com/
|
|
398
|
+
* reduce short form tenant id length ([bcda377](https://github.com/iotaledger/twin-api/commit/bcda377daed03b21fd1c6ffe47bad4a45d96602b))
|
|
371
399
|
|
|
372
400
|
|
|
373
401
|
### Bug Fixes
|
|
374
402
|
|
|
375
|
-
* remove extraneous type ([f85c52c](https://github.com/
|
|
403
|
+
* remove extraneous type ([f85c52c](https://github.com/iotaledger/twin-api/commit/f85c52c55caa3a7a64f6f675842f0ea59289a5d9))
|
|
376
404
|
|
|
377
405
|
|
|
378
406
|
### Dependencies
|
|
@@ -381,12 +409,12 @@
|
|
|
381
409
|
* dependencies
|
|
382
410
|
* @twin.org/api-models bumped from 0.0.3-next.1 to 0.0.3-next.2
|
|
383
411
|
|
|
384
|
-
## [0.0.3-next.1](https://github.com/
|
|
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)
|
|
385
413
|
|
|
386
414
|
|
|
387
415
|
### Features
|
|
388
416
|
|
|
389
|
-
* add context id features ([#42](https://github.com/
|
|
417
|
+
* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40))
|
|
390
418
|
|
|
391
419
|
|
|
392
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.
|
package/docs/reference/index.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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",
|