@3sixty/api-tenant-processor 0.0.0-stage → 0.20.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +24 -2
  3. package/dist/es/entities/tenant.js +77 -0
  4. package/dist/es/entities/tenant.js.map +1 -0
  5. package/dist/es/entities/tenantV0.js +61 -0
  6. package/dist/es/entities/tenantV0.js.map +1 -0
  7. package/dist/es/entities/tenantV1.js +77 -0
  8. package/dist/es/entities/tenantV1.js.map +1 -0
  9. package/dist/es/index.js +29 -0
  10. package/dist/es/index.js.map +1 -0
  11. package/dist/es/models/ISingleTenantProcessorConfig.js +4 -0
  12. package/dist/es/models/ISingleTenantProcessorConfig.js.map +1 -0
  13. package/dist/es/models/ISingleTenantProcessorConstructorOptions.js +2 -0
  14. package/dist/es/models/ISingleTenantProcessorConstructorOptions.js.map +1 -0
  15. package/dist/es/models/ITenantAdminServiceConfig.js +4 -0
  16. package/dist/es/models/ITenantAdminServiceConfig.js.map +1 -0
  17. package/dist/es/models/ITenantAdminServiceConstructorOptions.js +2 -0
  18. package/dist/es/models/ITenantAdminServiceConstructorOptions.js.map +1 -0
  19. package/dist/es/models/ITenantOverrideProcessorConfig.js +4 -0
  20. package/dist/es/models/ITenantOverrideProcessorConfig.js.map +1 -0
  21. package/dist/es/models/ITenantOverrideProcessorConstructorOptions.js +2 -0
  22. package/dist/es/models/ITenantOverrideProcessorConstructorOptions.js.map +1 -0
  23. package/dist/es/models/ITenantProcessorConfig.js +4 -0
  24. package/dist/es/models/ITenantProcessorConfig.js.map +1 -0
  25. package/dist/es/models/ITenantProcessorConstructorOptions.js +2 -0
  26. package/dist/es/models/ITenantProcessorConstructorOptions.js.map +1 -0
  27. package/dist/es/models/api/ITenantCreateRequest.js +2 -0
  28. package/dist/es/models/api/ITenantCreateRequest.js.map +1 -0
  29. package/dist/es/models/api/ITenantGetByApiKeyRequest.js +4 -0
  30. package/dist/es/models/api/ITenantGetByApiKeyRequest.js.map +1 -0
  31. package/dist/es/models/api/ITenantGetByIdRequest.js +4 -0
  32. package/dist/es/models/api/ITenantGetByIdRequest.js.map +1 -0
  33. package/dist/es/models/api/ITenantGetByPublicOriginRequest.js +4 -0
  34. package/dist/es/models/api/ITenantGetByPublicOriginRequest.js.map +1 -0
  35. package/dist/es/models/api/ITenantGetResponse.js +2 -0
  36. package/dist/es/models/api/ITenantGetResponse.js.map +1 -0
  37. package/dist/es/models/api/ITenantListRequest.js +4 -0
  38. package/dist/es/models/api/ITenantListRequest.js.map +1 -0
  39. package/dist/es/models/api/ITenantListResponse.js +2 -0
  40. package/dist/es/models/api/ITenantListResponse.js.map +1 -0
  41. package/dist/es/models/api/ITenantRemoveRequest.js +4 -0
  42. package/dist/es/models/api/ITenantRemoveRequest.js.map +1 -0
  43. package/dist/es/models/api/ITenantUpdateRequest.js +2 -0
  44. package/dist/es/models/api/ITenantUpdateRequest.js.map +1 -0
  45. package/dist/es/restEntryPoints.js +13 -0
  46. package/dist/es/restEntryPoints.js.map +1 -0
  47. package/dist/es/schema.js +64 -0
  48. package/dist/es/schema.js.map +1 -0
  49. package/dist/es/singleTenantProcessor.js +81 -0
  50. package/dist/es/singleTenantProcessor.js.map +1 -0
  51. package/dist/es/tenantAdminService.js +500 -0
  52. package/dist/es/tenantAdminService.js.map +1 -0
  53. package/dist/es/tenantIdContextIdHandler.js +47 -0
  54. package/dist/es/tenantIdContextIdHandler.js.map +1 -0
  55. package/dist/es/tenantOverrideProcessor.js +119 -0
  56. package/dist/es/tenantOverrideProcessor.js.map +1 -0
  57. package/dist/es/tenantProcessor.js +164 -0
  58. package/dist/es/tenantProcessor.js.map +1 -0
  59. package/dist/es/tenantRoutes.js +404 -0
  60. package/dist/es/tenantRoutes.js.map +1 -0
  61. package/dist/es/utils/tenantIdHelper.js +23 -0
  62. package/dist/es/utils/tenantIdHelper.js.map +1 -0
  63. package/dist/types/entities/tenant.d.ts +37 -0
  64. package/dist/types/entities/tenantV0.d.ts +29 -0
  65. package/dist/types/entities/tenantV1.d.ts +37 -0
  66. package/dist/types/index.d.ts +26 -0
  67. package/dist/types/models/ISingleTenantProcessorConfig.d.ts +9 -0
  68. package/dist/types/models/ISingleTenantProcessorConstructorOptions.d.ts +10 -0
  69. package/dist/types/models/ITenantAdminServiceConfig.d.ts +24 -0
  70. package/dist/types/models/ITenantAdminServiceConstructorOptions.d.ts +15 -0
  71. package/dist/types/models/ITenantOverrideProcessorConfig.d.ts +13 -0
  72. package/dist/types/models/ITenantOverrideProcessorConstructorOptions.d.ts +15 -0
  73. package/dist/types/models/ITenantProcessorConfig.d.ts +18 -0
  74. package/dist/types/models/ITenantProcessorConstructorOptions.d.ts +15 -0
  75. package/dist/types/models/api/ITenantCreateRequest.d.ts +12 -0
  76. package/dist/types/models/api/ITenantGetByApiKeyRequest.d.ts +14 -0
  77. package/dist/types/models/api/ITenantGetByIdRequest.d.ts +14 -0
  78. package/dist/types/models/api/ITenantGetByPublicOriginRequest.d.ts +14 -0
  79. package/dist/types/models/api/ITenantGetResponse.d.ts +10 -0
  80. package/dist/types/models/api/ITenantListRequest.d.ts +27 -0
  81. package/dist/types/models/api/ITenantListResponse.d.ts +17 -0
  82. package/dist/types/models/api/ITenantRemoveRequest.d.ts +14 -0
  83. package/dist/types/models/api/ITenantUpdateRequest.d.ts +19 -0
  84. package/dist/types/restEntryPoints.d.ts +5 -0
  85. package/dist/types/schema.d.ts +5 -0
  86. package/dist/types/singleTenantProcessor.d.ts +41 -0
  87. package/dist/types/tenantAdminService.d.ts +109 -0
  88. package/dist/types/tenantIdContextIdHandler.d.ts +33 -0
  89. package/dist/types/tenantOverrideProcessor.d.ts +44 -0
  90. package/dist/types/tenantProcessor.d.ts +34 -0
  91. package/dist/types/tenantRoutes.d.ts +78 -0
  92. package/dist/types/utils/tenantIdHelper.d.ts +15 -0
  93. package/docs/changelog.md +0 -0
  94. package/docs/examples.md +81 -0
  95. package/docs/reference/classes/SingleTenantProcessor.md +128 -0
  96. package/docs/reference/classes/Tenant.md +77 -0
  97. package/docs/reference/classes/TenantAdminService.md +393 -0
  98. package/docs/reference/classes/TenantIdContextIdHandler.md +123 -0
  99. package/docs/reference/classes/TenantIdHelper.md +41 -0
  100. package/docs/reference/classes/TenantOverrideProcessor.md +119 -0
  101. package/docs/reference/classes/TenantProcessor.md +101 -0
  102. package/docs/reference/functions/generateRestRoutesTenants.md +25 -0
  103. package/docs/reference/functions/initSchema.md +10 -0
  104. package/docs/reference/functions/tenantByApiKey.md +31 -0
  105. package/docs/reference/functions/tenantById.md +31 -0
  106. package/docs/reference/functions/tenantByPublicOrigin.md +31 -0
  107. package/docs/reference/functions/tenantCreate.md +37 -0
  108. package/docs/reference/functions/tenantList.md +31 -0
  109. package/docs/reference/functions/tenantRemove.md +31 -0
  110. package/docs/reference/functions/tenantUpdate.md +31 -0
  111. package/docs/reference/index.md +47 -0
  112. package/docs/reference/interfaces/ISingleTenantProcessorConfig.md +11 -0
  113. package/docs/reference/interfaces/ISingleTenantProcessorConstructorOptions.md +11 -0
  114. package/docs/reference/interfaces/ITenantAdminServiceConfig.md +49 -0
  115. package/docs/reference/interfaces/ITenantAdminServiceConstructorOptions.md +25 -0
  116. package/docs/reference/interfaces/ITenantCreateRequest.md +17 -0
  117. package/docs/reference/interfaces/ITenantGetByApiKeyRequest.md +17 -0
  118. package/docs/reference/interfaces/ITenantGetByIdRequest.md +17 -0
  119. package/docs/reference/interfaces/ITenantGetByPublicOriginRequest.md +17 -0
  120. package/docs/reference/interfaces/ITenantGetResponse.md +11 -0
  121. package/docs/reference/interfaces/ITenantListRequest.md +36 -0
  122. package/docs/reference/interfaces/ITenantListResponse.md +23 -0
  123. package/docs/reference/interfaces/ITenantOverrideProcessorConfig.md +19 -0
  124. package/docs/reference/interfaces/ITenantOverrideProcessorConstructorOptions.md +25 -0
  125. package/docs/reference/interfaces/ITenantProcessorConfig.md +33 -0
  126. package/docs/reference/interfaces/ITenantProcessorConstructorOptions.md +25 -0
  127. package/docs/reference/interfaces/ITenantRemoveRequest.md +17 -0
  128. package/docs/reference/interfaces/ITenantUpdateRequest.md +25 -0
  129. package/docs/reference/variables/tagsTenants.md +5 -0
  130. package/locales/en.json +24 -0
  131. package/package.json +57 -5
@@ -0,0 +1,4 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=ITenantListRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ITenantListRequest.js","sourceRoot":"","sources":["../../../../src/models/api/ITenantListRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * The list of tenants.\n */\nexport interface ITenantListRequest {\n\t/**\n\t * The query parameters.\n\t */\n\tquery: {\n\t\t/**\n\t\t * The condition for the query as JSON version of EntityCondition type.\n\t\t */\n\t\tconditions?: string;\n\n\t\t/**\n\t\t * The properties to include in the returned tenants, separated by commas.\n\t\t * If not provided, all properties will be returned.\n\t\t */\n\t\tproperties?: string;\n\n\t\t/**\n\t\t * The cursor to get the next chunk of tenants.\n\t\t */\n\t\tcursor?: string;\n\n\t\t/**\n\t\t * The number of tenants to return.\n\t\t */\n\t\tlimit?: string;\n\t};\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ITenantListResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ITenantListResponse.js","sourceRoot":"","sources":["../../../../src/models/api/ITenantListResponse.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ITenant } from \"@3sixty/api-models\";\nimport type { HeaderTypes } from \"@3sixty/web\";\n\n/**\n * The list of tenants.\n */\nexport interface ITenantListResponse {\n\t/**\n\t * The headers which can be used to include the cursor.\n\t */\n\theaders?: {\n\t\t[HeaderTypes.Link]?: string | string[];\n\t};\n\n\t/**\n\t * The list of tenants.\n\t */\n\tbody: ITenant[];\n}\n"]}
@@ -0,0 +1,4 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export {};
4
+ //# sourceMappingURL=ITenantRemoveRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ITenantRemoveRequest.js","sourceRoot":"","sources":["../../../../src/models/api/ITenantRemoveRequest.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n\n/**\n * The tenant to remove by id.\n */\nexport interface ITenantRemoveRequest {\n\t/**\n\t * The path parameters.\n\t */\n\tpathParams: {\n\t\t/**\n\t\t * The id of the tenant to remove.\n\t\t */\n\t\tid: string;\n\t};\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ITenantUpdateRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ITenantUpdateRequest.js","sourceRoot":"","sources":["../../../../src/models/api/ITenantUpdateRequest.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ITenant } from \"@3sixty/api-models\";\n\n/**\n * The tenant to update.\n */\nexport interface ITenantUpdateRequest {\n\t/**\n\t * The path parameters.\n\t */\n\tpathParams: {\n\t\t/**\n\t\t * The id of the tenant to update.\n\t\t */\n\t\tid: string;\n\t};\n\n\t/**\n\t * The tenant to update.\n\t */\n\tbody: Omit<ITenant, \"id\" | \"dateCreated\" | \"dateModified\">;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { generateRestRoutesTenants, tagsTenants } from "./tenantRoutes.js";
2
+ /**
3
+ * REST entry points for the tenant administration service.
4
+ */
5
+ export const restEntryPoints = [
6
+ {
7
+ name: "tenants",
8
+ defaultBaseRoute: "tenants",
9
+ tags: tagsTenants,
10
+ generateRoutes: generateRestRoutesTenants
11
+ }
12
+ ];
13
+ //# sourceMappingURL=restEntryPoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restEntryPoints.js","sourceRoot":"","sources":["../../src/restEntryPoints.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,yBAAyB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE3E;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAA2B;IACtD;QACC,IAAI,EAAE,SAAS;QACf,gBAAgB,EAAE,SAAS;QAC3B,IAAI,EAAE,WAAW;QACjB,cAAc,EAAE,yBAAyB;KACzC;CACD,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IRestRouteEntryPoint } from \"@3sixty/api-models\";\nimport { generateRestRoutesTenants, tagsTenants } from \"./tenantRoutes.js\";\n\n/**\n * REST entry points for the tenant administration service.\n */\nexport const restEntryPoints: IRestRouteEntryPoint[] = [\n\t{\n\t\tname: \"tenants\",\n\t\tdefaultBaseRoute: \"tenants\",\n\t\ttags: tagsTenants,\n\t\tgenerateRoutes: generateRestRoutesTenants\n\t}\n];\n"]}
@@ -0,0 +1,64 @@
1
+ // Copyright 2025 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { Is } from "@3sixty/core";
4
+ import { EntitySchemaFactory, EntitySchemaHelper } from "@3sixty/entity";
5
+ import { SchemaMigrationFactory } from "@3sixty/entity-storage-models";
6
+ import { Tenant } from "./entities/tenant.js";
7
+ import { TenantV0 } from "./entities/tenantV0.js";
8
+ import { TenantV1 } from "./entities/tenantV1.js";
9
+ /**
10
+ * Initialize the schema for the node tenant processor, including the Tenant 1 to 2 migration
11
+ * step which converts organizationIdLegacy from a pipe-delimited string to an array of ids.
12
+ */
13
+ export function initSchema() {
14
+ EntitySchemaFactory.register("Tenant", () => EntitySchemaHelper.getSchema(Tenant));
15
+ EntitySchemaFactory.register("TenantV0", () => EntitySchemaHelper.getSchema(TenantV0));
16
+ EntitySchemaFactory.register("TenantV1", () => EntitySchemaHelper.getSchema(TenantV1));
17
+ SchemaMigrationFactory.register(`${"Tenant"}_1_2`, () => ({
18
+ transformEntityProperty: (entity, schema1Property, schema2Property, value) => {
19
+ if (schema2Property.property !== "organizationIdLegacy") {
20
+ return;
21
+ }
22
+ return toOrganizationIdList(value);
23
+ }
24
+ }));
25
+ }
26
+ /**
27
+ * Convert a stored organizationIdLegacy value to the array shape the current schema declares.
28
+ * Accepts every physical shape a version 1 table can hold: a pipe-delimited string written by
29
+ * 0.10.0 and earlier, a JSON array written by a fresh 0.10.1-next.4+ table, JSON array text left
30
+ * by a partial hand fix, or an already-migrated array. Ids are trimmed, emptied entries are
31
+ * dropped, duplicates are removed, and case is preserved because the values are DIDs.
32
+ * @param value The stored value to convert.
33
+ * @returns The de-duplicated list of ids, or undefined when there are none.
34
+ */
35
+ function toOrganizationIdList(value) {
36
+ let parts;
37
+ if (Is.array(value)) {
38
+ parts = value;
39
+ }
40
+ else if (Is.stringValue(value) && value.trim().startsWith("[")) {
41
+ try {
42
+ const parsed = JSON.parse(value);
43
+ parts = Is.array(parsed) ? parsed : [];
44
+ }
45
+ catch {
46
+ parts = [];
47
+ }
48
+ }
49
+ else if (Is.stringValue(value)) {
50
+ parts = value.split("|");
51
+ }
52
+ else {
53
+ parts = [];
54
+ }
55
+ const ids = [];
56
+ for (const part of parts) {
57
+ const id = Is.stringValue(part) ? part.trim() : "";
58
+ if (id.length > 0 && !ids.includes(id)) {
59
+ ids.push(id);
60
+ }
61
+ }
62
+ return ids.length > 0 ? ids : undefined;
63
+ }
64
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/schema.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,UAAU;IACzB,mBAAmB,CAAC,QAAQ,WAAmB,GAAG,EAAE,CAAC,kBAAkB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3F,mBAAmB,CAAC,QAAQ,aAAqB,GAAG,EAAE,CAAC,kBAAkB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC/F,mBAAmB,CAAC,QAAQ,aAAqB,GAAG,EAAE,CAAC,kBAAkB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE/F,sBAAsB,CAAC,QAAQ,CAAC,GAAG,QAAgB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACjE,uBAAuB,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE;YAC5E,IAAI,eAAe,CAAC,QAAQ,KAAK,sBAAsB,EAAE,CAAC;gBACzD,OAAO;YACR,CAAC;YACD,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;KACD,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAAC,KAAc;IAC3C,IAAI,KAAgB,CAAC;IAErB,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,KAAK,GAAG,KAAK,CAAC;IACf,CAAC;SAAM,IAAI,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAClE,IAAI,CAAC;YACJ,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1C,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACR,KAAK,GAAG,EAAE,CAAC;QACZ,CAAC;IACF,CAAC;SAAM,IAAI,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;SAAM,CAAC;QACP,KAAK,GAAG,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YACxC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACd,CAAC;IACF,CAAC;IAED,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AACzC,CAAC","sourcesContent":["// Copyright 2025 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"@3sixty/core\";\nimport { EntitySchemaFactory, EntitySchemaHelper } from \"@3sixty/entity\";\nimport { SchemaMigrationFactory } from \"@3sixty/entity-storage-models\";\nimport { nameof } from \"@3sixty/nameof\";\nimport { Tenant } from \"./entities/tenant.js\";\nimport { TenantV0 } from \"./entities/tenantV0.js\";\nimport { TenantV1 } from \"./entities/tenantV1.js\";\n\n/**\n * Initialize the schema for the node tenant processor, including the Tenant 1 to 2 migration\n * step which converts organizationIdLegacy from a pipe-delimited string to an array of ids.\n */\nexport function initSchema(): void {\n\tEntitySchemaFactory.register(nameof<Tenant>(), () => EntitySchemaHelper.getSchema(Tenant));\n\tEntitySchemaFactory.register(nameof<TenantV0>(), () => EntitySchemaHelper.getSchema(TenantV0));\n\tEntitySchemaFactory.register(nameof<TenantV1>(), () => EntitySchemaHelper.getSchema(TenantV1));\n\n\tSchemaMigrationFactory.register(`${nameof<Tenant>()}_1_2`, () => ({\n\t\ttransformEntityProperty: (entity, schema1Property, schema2Property, value) => {\n\t\t\tif (schema2Property.property !== \"organizationIdLegacy\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn toOrganizationIdList(value);\n\t\t}\n\t}));\n}\n\n/**\n * Convert a stored organizationIdLegacy value to the array shape the current schema declares.\n * Accepts every physical shape a version 1 table can hold: a pipe-delimited string written by\n * 0.10.0 and earlier, a JSON array written by a fresh 0.10.1-next.4+ table, JSON array text left\n * by a partial hand fix, or an already-migrated array. Ids are trimmed, emptied entries are\n * dropped, duplicates are removed, and case is preserved because the values are DIDs.\n * @param value The stored value to convert.\n * @returns The de-duplicated list of ids, or undefined when there are none.\n */\nfunction toOrganizationIdList(value: unknown): string[] | undefined {\n\tlet parts: unknown[];\n\n\tif (Is.array(value)) {\n\t\tparts = value;\n\t} else if (Is.stringValue(value) && value.trim().startsWith(\"[\")) {\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(value);\n\t\t\tparts = Is.array(parsed) ? parsed : [];\n\t\t} catch {\n\t\t\tparts = [];\n\t\t}\n\t} else if (Is.stringValue(value)) {\n\t\tparts = value.split(\"|\");\n\t} else {\n\t\tparts = [];\n\t}\n\n\tconst ids: string[] = [];\n\tfor (const part of parts) {\n\t\tconst id = Is.stringValue(part) ? part.trim() : \"\";\n\t\tif (id.length > 0 && !ids.includes(id)) {\n\t\t\tids.push(id);\n\t\t}\n\t}\n\n\treturn ids.length > 0 ? ids : undefined;\n}\n"]}
@@ -0,0 +1,81 @@
1
+ // Copyright 2025 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { HttpErrorHelper } from "@3sixty/api-models";
4
+ import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@3sixty/context";
5
+ import { BaseError, Is, UnauthorizedError } from "@3sixty/core";
6
+ import { HttpStatusCode } from "@3sixty/web";
7
+ /**
8
+ * Injects the single-tenant organization ID into every request context and validates any organization query parameter.
9
+ */
10
+ export class SingleTenantProcessor {
11
+ /**
12
+ * Runtime name for the class.
13
+ */
14
+ static CLASS_NAME = "SingleTenantProcessor";
15
+ /**
16
+ * The organization ID for the single-tenant node, cached at startup.
17
+ * @internal
18
+ */
19
+ _nodeOrganizationId;
20
+ /**
21
+ * Include the stack with errors.
22
+ * @internal
23
+ */
24
+ _includeErrorStack;
25
+ /**
26
+ * Create a new instance of SingleTenantProcessor.
27
+ * @param options Options for the processor.
28
+ */
29
+ constructor(options) {
30
+ this._includeErrorStack = options?.config?.includeErrorStack ?? false;
31
+ }
32
+ /**
33
+ * Returns the class name of the component.
34
+ * @returns The class name of the component.
35
+ */
36
+ className() {
37
+ return SingleTenantProcessor.CLASS_NAME;
38
+ }
39
+ /**
40
+ * Cache the node organization ID from the engine context so it can be injected into each
41
+ * per-request context without requiring a separate ContextIdProcessor for Organization.
42
+ * @param nodeLoggingComponentType The node logging component type.
43
+ * @returns A promise that resolves when the node organization ID has been cached.
44
+ */
45
+ async start(nodeLoggingComponentType) {
46
+ const contextIds = await ContextIdStore.getContextIds();
47
+ ContextIdHelper.guard(contextIds, ContextIdKeys.Organization);
48
+ this._nodeOrganizationId = contextIds[ContextIdKeys.Organization];
49
+ }
50
+ /**
51
+ * Pre process the REST request for the specified route.
52
+ * @param request The incoming request.
53
+ * @param response The outgoing response.
54
+ * @param route The route to process.
55
+ * @param contextIds The context IDs of the request.
56
+ * @param processorState The state handed through the processors.
57
+ * @returns A promise that resolves when the organization ID has been injected and any query parameter validated.
58
+ */
59
+ async pre(request, response, route, contextIds, processorState) {
60
+ contextIds[ContextIdKeys.Organization] = this._nodeOrganizationId;
61
+ if (!Is.empty(route)) {
62
+ try {
63
+ // If an organization ID is provided as a query parameter, validate it against the node organization ID.
64
+ // since this processor is meant for single-tenant use cases, the presence of an organization ID is probably in the form
65
+ // of a callback, so we should still validate it but not require it.
66
+ const organizationIdQueryParam = request.query?.[ContextIdKeys.Organization];
67
+ if (Is.stringValue(organizationIdQueryParam) &&
68
+ organizationIdQueryParam !== this._nodeOrganizationId) {
69
+ throw new UnauthorizedError(SingleTenantProcessor.CLASS_NAME, "invalidOrganizationId", {
70
+ paramName: ContextIdKeys.Organization,
71
+ value: organizationIdQueryParam
72
+ });
73
+ }
74
+ }
75
+ catch (err) {
76
+ HttpErrorHelper.buildResponse(response, BaseError.fromError(err), HttpStatusCode.unauthorized, this._includeErrorStack);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ //# sourceMappingURL=singleTenantProcessor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleTenantProcessor.js","sourceRoot":"","sources":["../../src/singleTenantProcessor.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EACN,eAAe,EAKf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAoB,MAAM,iBAAiB,CAAC;AACnG,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C;;GAEG;AACH,MAAM,OAAO,qBAAqB;IACjC;;OAEG;IACI,MAAM,CAAU,UAAU,2BAA2C;IAE5E;;;OAGG;IACK,mBAAmB,CAAU;IAErC;;;OAGG;IACc,kBAAkB,CAAU;IAE7C;;;OAGG;IACH,YAAY,OAAkD;QAC7D,IAAI,CAAC,kBAAkB,GAAG,OAAO,EAAE,MAAM,EAAE,iBAAiB,IAAI,KAAK,CAAC;IACvE,CAAC;IAED;;;OAGG;IACI,SAAS;QACf,OAAO,qBAAqB,CAAC,UAAU,CAAC;IACzC,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,KAAK,CAAC,wBAAiC;QACnD,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,EAAE,CAAC;QACxD,eAAe,CAAC,KAAK,CAAC,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;QAC9D,IAAI,CAAC,mBAAmB,GAAG,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IACnE,CAAC;IAED;;;;;;;;OAQG;IACI,KAAK,CAAC,GAAG,CACf,OAA2B,EAC3B,QAAuB,EACvB,KAA6B,EAC7B,UAAuB,EACvB,cAAyC;QAEzC,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,mBAAmB,CAAC;QAElE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC;gBACJ,wGAAwG;gBACxG,wHAAwH;gBACxH,oEAAoE;gBACpE,MAAM,wBAAwB,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;gBAC7E,IACC,EAAE,CAAC,WAAW,CAAC,wBAAwB,CAAC;oBACxC,wBAAwB,KAAK,IAAI,CAAC,mBAAmB,EACpD,CAAC;oBACF,MAAM,IAAI,iBAAiB,CAAC,qBAAqB,CAAC,UAAU,EAAE,uBAAuB,EAAE;wBACtF,SAAS,EAAE,aAAa,CAAC,YAAY;wBACrC,KAAK,EAAE,wBAAwB;qBAC/B,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,eAAe,CAAC,aAAa,CAC5B,QAAQ,EACR,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,EACxB,cAAc,CAAC,YAAY,EAC3B,IAAI,CAAC,kBAAkB,CACvB,CAAC;YACH,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 \"@3sixty/api-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore, type IContextIds } from \"@3sixty/context\";\nimport { BaseError, Is, UnauthorizedError } from \"@3sixty/core\";\nimport { nameof } from \"@3sixty/nameof\";\nimport { HttpStatusCode } from \"@3sixty/web\";\nimport type { ISingleTenantProcessorConstructorOptions } from \"./models/ISingleTenantProcessorConstructorOptions.js\";\n\n/**\n * Injects the single-tenant organization ID into every request context and validates any organization query parameter.\n */\nexport class SingleTenantProcessor implements IBaseRouteProcessor {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<SingleTenantProcessor>();\n\n\t/**\n\t * The organization ID for the single-tenant node, cached at startup.\n\t * @internal\n\t */\n\tprivate _nodeOrganizationId?: string;\n\n\t/**\n\t * Include the stack with errors.\n\t * @internal\n\t */\n\tprivate readonly _includeErrorStack: boolean;\n\n\t/**\n\t * Create a new instance of SingleTenantProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: ISingleTenantProcessorConstructorOptions) {\n\t\tthis._includeErrorStack = options?.config?.includeErrorStack ?? false;\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 SingleTenantProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * Cache the node organization ID from the engine context so it can be injected into each\n\t * per-request context without requiring a separate ContextIdProcessor for Organization.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns A promise that resolves when the node organization ID has been cached.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.Organization);\n\t\tthis._nodeOrganizationId = contextIds[ContextIdKeys.Organization];\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 * @returns A promise that resolves when the organization ID has been injected and any query parameter validated.\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\tcontextIds[ContextIdKeys.Organization] = this._nodeOrganizationId;\n\n\t\tif (!Is.empty(route)) {\n\t\t\ttry {\n\t\t\t\t// If an organization ID is provided as a query parameter, validate it against the node organization ID.\n\t\t\t\t// since this processor is meant for single-tenant use cases, the presence of an organization ID is probably in the form\n\t\t\t\t// of a callback, so we should still validate it but not require it.\n\t\t\t\tconst organizationIdQueryParam = request.query?.[ContextIdKeys.Organization];\n\t\t\t\tif (\n\t\t\t\t\tIs.stringValue(organizationIdQueryParam) &&\n\t\t\t\t\torganizationIdQueryParam !== this._nodeOrganizationId\n\t\t\t\t) {\n\t\t\t\t\tthrow new UnauthorizedError(SingleTenantProcessor.CLASS_NAME, \"invalidOrganizationId\", {\n\t\t\t\t\t\tparamName: ContextIdKeys.Organization,\n\t\t\t\t\t\tvalue: organizationIdQueryParam\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tHttpErrorHelper.buildResponse(\n\t\t\t\t\tresponse,\n\t\t\t\t\tBaseError.fromError(err),\n\t\t\t\t\tHttpStatusCode.unauthorized,\n\t\t\t\t\tthis._includeErrorStack\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}