emilsoftware-utilities 2.0.0-dev.65 → 2.0.0-dev.67

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 (143) hide show
  1. package/README.MD +28 -1
  2. package/dist/EmilsoftwareModule.d.ts +11 -3
  3. package/dist/EmilsoftwareModule.js +1 -1
  4. package/dist/EmilsoftwareModule.js.map +1 -1
  5. package/dist/Orm.d.ts +1 -1
  6. package/dist/Orm.js +4 -23
  7. package/dist/Orm.js.map +1 -1
  8. package/dist/Utilities.js +1 -0
  9. package/dist/Utilities.js.map +1 -1
  10. package/dist/accessi/client/index.d.ts +26 -0
  11. package/dist/accessi/client/index.js +49 -0
  12. package/dist/accessi/client/index.js.map +1 -0
  13. package/dist/accessi/console/index.d.ts +15 -0
  14. package/dist/accessi/console/index.js +25 -0
  15. package/dist/accessi/console/index.js.map +1 -0
  16. package/dist/accessi/contracts/dtos.d.ts +8 -0
  17. package/dist/accessi/contracts/dtos.js +25 -0
  18. package/dist/accessi/contracts/dtos.js.map +1 -0
  19. package/dist/accessi/contracts/index.d.ts +25 -0
  20. package/dist/accessi/contracts/index.js +28 -0
  21. package/dist/accessi/contracts/index.js.map +1 -0
  22. package/dist/accessi/openapi/index.d.ts +2 -0
  23. package/dist/accessi/openapi/index.js +19 -0
  24. package/dist/accessi/openapi/index.js.map +1 -0
  25. package/dist/accessi/resource-server/core.d.ts +6 -0
  26. package/dist/accessi/resource-server/core.js +23 -0
  27. package/dist/accessi/resource-server/core.js.map +1 -0
  28. package/dist/accessi/resource-server/errors.d.ts +10 -0
  29. package/dist/accessi/resource-server/errors.js +19 -0
  30. package/dist/accessi/resource-server/errors.js.map +1 -0
  31. package/dist/accessi/resource-server/express.d.ts +17 -0
  32. package/dist/accessi/resource-server/express.js +66 -0
  33. package/dist/accessi/resource-server/express.js.map +1 -0
  34. package/dist/accessi/resource-server/index.d.ts +3 -0
  35. package/dist/accessi/resource-server/index.js +20 -0
  36. package/dist/accessi/resource-server/index.js.map +1 -0
  37. package/dist/accessi/resource-server/introspection.d.ts +16 -0
  38. package/dist/accessi/resource-server/introspection.js +115 -0
  39. package/dist/accessi/resource-server/introspection.js.map +1 -0
  40. package/dist/accessi/resource-server/keys.d.ts +11 -0
  41. package/dist/accessi/resource-server/keys.js +22 -0
  42. package/dist/accessi/resource-server/keys.js.map +1 -0
  43. package/dist/accessi/resource-server/nest.d.ts +9 -0
  44. package/dist/accessi/resource-server/nest.js +62 -0
  45. package/dist/accessi/resource-server/nest.js.map +1 -0
  46. package/dist/accessi/resource-server/runtime.d.ts +27 -0
  47. package/dist/accessi/resource-server/runtime.js +128 -0
  48. package/dist/accessi/resource-server/runtime.js.map +1 -0
  49. package/dist/accessi/resource-server/token.d.ts +23 -0
  50. package/dist/accessi/resource-server/token.js +230 -0
  51. package/dist/accessi/resource-server/token.js.map +1 -0
  52. package/dist/accessi/resource-server/types.d.ts +78 -0
  53. package/dist/accessi/resource-server/types.js +3 -0
  54. package/dist/accessi/resource-server/types.js.map +1 -0
  55. package/dist/accessi/server/index.d.ts +10 -0
  56. package/dist/accessi/server/index.js +29 -0
  57. package/dist/accessi/server/index.js.map +1 -0
  58. package/dist/accessi/ui/index.d.ts +13 -0
  59. package/dist/accessi/ui/index.js +19 -0
  60. package/dist/accessi/ui/index.js.map +1 -0
  61. package/dist/accessi-module/AccessiModule.d.ts +25 -1
  62. package/dist/accessi-module/AccessiModule.js +2 -0
  63. package/dist/accessi-module/AccessiModule.js.map +1 -1
  64. package/dist/accessi-module/Console/accessiFetch.d.ts +4 -0
  65. package/dist/accessi-module/Console/accessiFetch.js +18 -4
  66. package/dist/accessi-module/Console/accessiFetch.js.map +1 -1
  67. package/dist/accessi-module/Console/console.js +114 -9
  68. package/dist/accessi-module/Console/console.js.map +1 -1
  69. package/dist/accessi-module/Console/generated/{accessiApi.d.ts → emilsoftware-utilities.d.ts} +11 -1
  70. package/dist/accessi-module/Console/generated/{accessiApi.js → emilsoftware-utilities.js} +10 -2
  71. package/dist/accessi-module/Console/generated/emilsoftware-utilities.js.map +1 -0
  72. package/dist/accessi-module/Console/generated/model/accessiIntrospectionResultDto.d.ts +16 -0
  73. package/dist/accessi-module/Console/generated/model/accessiIntrospectionResultDto.js +3 -0
  74. package/dist/accessi-module/Console/generated/model/accessiIntrospectionResultDto.js.map +1 -0
  75. package/dist/accessi-module/Console/generated/model/index.d.ts +3 -0
  76. package/dist/accessi-module/Console/generated/model/index.js +3 -0
  77. package/dist/accessi-module/Console/generated/model/index.js.map +1 -1
  78. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenRequest.d.ts +10 -0
  79. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenRequest.js +9 -0
  80. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenRequest.js.map +1 -0
  81. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenResponse.d.ts +14 -0
  82. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenResponse.js +3 -0
  83. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenResponse.js.map +1 -0
  84. package/dist/accessi-module/Console/wiki.js +98 -3
  85. package/dist/accessi-module/Console/wiki.js.map +1 -1
  86. package/dist/accessi-module/Controllers/AccessiIntrospectionController.d.ts +17 -0
  87. package/dist/accessi-module/Controllers/AccessiIntrospectionController.js +93 -0
  88. package/dist/accessi-module/Controllers/AccessiIntrospectionController.js.map +1 -0
  89. package/dist/accessi-module/Dtos/IntrospectionDtos.d.ts +16 -0
  90. package/dist/accessi-module/Dtos/IntrospectionDtos.js +59 -0
  91. package/dist/accessi-module/Dtos/IntrospectionDtos.js.map +1 -0
  92. package/dist/accessi-module/Dtos/index.d.ts +1 -0
  93. package/dist/accessi-module/Dtos/index.js +1 -0
  94. package/dist/accessi-module/Dtos/index.js.map +1 -1
  95. package/dist/accessi-module/Services/AuthService/AuthService.d.ts +13 -0
  96. package/dist/accessi-module/Services/AuthService/AuthService.js +42 -10
  97. package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
  98. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js +11 -4
  99. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js.map +1 -1
  100. package/dist/accessi-module/database-updates/menuScopeSeed.d.ts +2 -2
  101. package/dist/accessi-module/database-updates/menuScopeSeed.js +101 -53
  102. package/dist/accessi-module/database-updates/menuScopeSeed.js.map +1 -1
  103. package/dist/accessi-module/database-updates/menuScopeSeeder.js +1 -1
  104. package/dist/accessi-module/database-updates/menuScopeSeeder.js.map +1 -1
  105. package/dist/accessi-module/index.d.ts +16 -3
  106. package/dist/accessi-module/index.js +15 -19
  107. package/dist/accessi-module/index.js.map +1 -1
  108. package/dist/accessi-module/jwt/jwt.strategy.js +5 -5
  109. package/dist/accessi-module/jwt/jwt.strategy.js.map +1 -1
  110. package/dist/accessi-module/middleware/authenticateGen.js +5 -5
  111. package/dist/accessi-module/middleware/authenticateGen.js.map +1 -1
  112. package/dist/accessi-module/security/jwtTokenOptions.d.ts +15 -0
  113. package/dist/accessi-module/security/jwtTokenOptions.js +44 -0
  114. package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -0
  115. package/dist/accessi-module/startupLog.d.ts +3 -2
  116. package/dist/accessi-module/startupLog.js +8 -4
  117. package/dist/accessi-module/startupLog.js.map +1 -1
  118. package/dist/allegati-module/index.js +2 -2
  119. package/dist/allegati-module/index.js.map +1 -1
  120. package/dist/angular-components/aree-disponibili/demo.html +4 -4
  121. package/dist/emilsoftware-utilities-2.0.0-dev.67.tgz +0 -0
  122. package/dist/firebird-compat.d.ts +27 -0
  123. package/dist/firebird-compat.js +52 -2
  124. package/dist/firebird-compat.js.map +1 -1
  125. package/dist/index.d.ts +2 -1
  126. package/dist/index.js +2 -1
  127. package/dist/index.js.map +1 -1
  128. package/dist/initEmilsoftwareModule.d.ts +11 -1
  129. package/dist/initEmilsoftwareModule.js +11 -23
  130. package/dist/initEmilsoftwareModule.js.map +1 -1
  131. package/dist/openapi/OpenApiConfig.d.ts +68 -0
  132. package/dist/openapi/OpenApiConfig.js +157 -0
  133. package/dist/openapi/OpenApiConfig.js.map +1 -0
  134. package/dist/openapi/OpenApiMerge.d.ts +15 -0
  135. package/dist/openapi/OpenApiMerge.js +119 -0
  136. package/dist/openapi/OpenApiMerge.js.map +1 -0
  137. package/integration-prompt.md +481 -0
  138. package/package.json +89 -6
  139. package/dist/accessi-module/Console/generated/accessiApi.js.map +0 -1
  140. package/dist/emilsoftware-utilities-2.0.0-dev.65.tgz +0 -0
  141. package/dist/swagger/SwaggerConfig.d.ts +0 -8
  142. package/dist/swagger/SwaggerConfig.js +0 -41
  143. package/dist/swagger/SwaggerConfig.js.map +0 -1
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeOpenApiDocuments = mergeOpenApiDocuments;
4
+ function isRecord(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function cloneDocument(document) {
8
+ return JSON.parse(JSON.stringify(document));
9
+ }
10
+ function sameValue(left, right) {
11
+ return JSON.stringify(left) === JSON.stringify(right);
12
+ }
13
+ function mergeRecord(target, source, policy, section) {
14
+ if (!source)
15
+ return target;
16
+ const merged = Object.assign({}, (target !== null && target !== void 0 ? target : {}));
17
+ for (const [key, value] of Object.entries(source)) {
18
+ if (!(key in merged)) {
19
+ merged[key] = value;
20
+ continue;
21
+ }
22
+ if (sameValue(merged[key], value))
23
+ continue;
24
+ if (policy === "keep")
25
+ continue;
26
+ if (policy === "overwrite") {
27
+ merged[key] = value;
28
+ continue;
29
+ }
30
+ throw new Error(`Conflitto OpenAPI in ${section}.${key}.`);
31
+ }
32
+ return merged;
33
+ }
34
+ function mergeTags(target, source, policy) {
35
+ if (!Array.isArray(target) || !Array.isArray(source))
36
+ return target !== null && target !== void 0 ? target : source;
37
+ const merged = [...target];
38
+ for (const tag of source) {
39
+ if (!isRecord(tag) || typeof tag.name !== "string") {
40
+ merged.push(tag);
41
+ continue;
42
+ }
43
+ const existingIndex = merged.findIndex((candidate) => isRecord(candidate) && candidate.name === tag.name);
44
+ if (existingIndex < 0) {
45
+ merged.push(tag);
46
+ continue;
47
+ }
48
+ const existing = merged[existingIndex];
49
+ if (sameValue(existing, tag))
50
+ continue;
51
+ if (policy === "keep")
52
+ continue;
53
+ if (policy === "overwrite") {
54
+ merged[existingIndex] = tag;
55
+ continue;
56
+ }
57
+ throw new Error(`Conflitto OpenAPI nel tag "${tag.name}".`);
58
+ }
59
+ return merged;
60
+ }
61
+ function mergeServers(target, source, policy) {
62
+ if (!Array.isArray(target) || !Array.isArray(source))
63
+ return target !== null && target !== void 0 ? target : source;
64
+ const merged = [...target];
65
+ for (const server of source) {
66
+ if (!isRecord(server) || typeof server.url !== "string") {
67
+ merged.push(server);
68
+ continue;
69
+ }
70
+ const existingIndex = merged.findIndex((candidate) => isRecord(candidate) && candidate.url === server.url);
71
+ if (existingIndex < 0) {
72
+ merged.push(server);
73
+ continue;
74
+ }
75
+ const existing = merged[existingIndex];
76
+ if (sameValue(existing, server))
77
+ continue;
78
+ if (policy === "keep")
79
+ continue;
80
+ if (policy === "overwrite") {
81
+ merged[existingIndex] = server;
82
+ continue;
83
+ }
84
+ throw new Error(`Conflitto OpenAPI nel server "${server.url}".`);
85
+ }
86
+ return merged;
87
+ }
88
+ /**
89
+ * Unisce il documento Accessi nel documento OpenAPI dell'host.
90
+ *
91
+ * Per default un conflitto su path, schema o security scheme produce errore:
92
+ * e meglio accorgersi subito di due contratti incompatibili. Usare
93
+ * `conflict: "overwrite"` solo quando il documento host deve vincere.
94
+ */
95
+ function mergeOpenApiDocuments(target, source, options = {}) {
96
+ var _a;
97
+ if (!target || !source) {
98
+ throw new Error("mergeOpenApiDocuments richiede due documenti OpenAPI validi.");
99
+ }
100
+ const policy = (_a = options.conflict) !== null && _a !== void 0 ? _a : "error";
101
+ const result = cloneDocument(target);
102
+ const sourceDocument = source;
103
+ result.paths = mergeRecord(isRecord(result.paths) ? result.paths : undefined, isRecord(sourceDocument.paths) ? sourceDocument.paths : undefined, policy, "paths");
104
+ const targetComponents = isRecord(result.components) ? result.components : {};
105
+ const sourceComponents = isRecord(sourceDocument.components) ? sourceDocument.components : {};
106
+ const componentSections = new Set([
107
+ ...Object.keys(targetComponents),
108
+ ...Object.keys(sourceComponents),
109
+ ]);
110
+ const mergedComponents = Object.assign({}, targetComponents);
111
+ for (const section of componentSections) {
112
+ mergedComponents[section] = mergeRecord(isRecord(targetComponents[section]) ? targetComponents[section] : undefined, isRecord(sourceComponents[section]) ? sourceComponents[section] : undefined, policy, `components.${section}`);
113
+ }
114
+ result.components = mergedComponents;
115
+ result.tags = mergeTags(result.tags, sourceDocument.tags, policy);
116
+ result.servers = mergeServers(result.servers, sourceDocument.servers, policy);
117
+ return result;
118
+ }
119
+ //# sourceMappingURL=OpenApiMerge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenApiMerge.js","sourceRoot":"","sources":["../../src/openapi/OpenApiMerge.ts"],"names":[],"mappings":"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","sourcesContent":["import type { OpenAPIObject } from \"@nestjs/swagger\";\n\n/** Politica di risoluzione quando due documenti definiscono la stessa chiave. */\nexport type OpenApiMergeConflictPolicy = \"error\" | \"keep\" | \"overwrite\";\n\nexport interface MergeOpenApiOptions {\n /** Default: `error`. */\n conflict?: OpenApiMergeConflictPolicy;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction cloneDocument(document: OpenAPIObject): Record<string, unknown> {\n return JSON.parse(JSON.stringify(document)) as Record<string, unknown>;\n}\n\nfunction sameValue(left: unknown, right: unknown): boolean {\n return JSON.stringify(left) === JSON.stringify(right);\n}\n\nfunction mergeRecord(\n target: Record<string, unknown> | undefined,\n source: Record<string, unknown> | undefined,\n policy: OpenApiMergeConflictPolicy,\n section: string,\n): Record<string, unknown> | undefined {\n if (!source) return target;\n const merged: Record<string, unknown> = { ...(target ?? {}) };\n\n for (const [key, value] of Object.entries(source)) {\n if (!(key in merged)) {\n merged[key] = value;\n continue;\n }\n if (sameValue(merged[key], value)) continue;\n if (policy === \"keep\") continue;\n if (policy === \"overwrite\") {\n merged[key] = value;\n continue;\n }\n throw new Error(`Conflitto OpenAPI in ${section}.${key}.`);\n }\n\n return merged;\n}\n\nfunction mergeTags(\n target: unknown,\n source: unknown,\n policy: OpenApiMergeConflictPolicy,\n): unknown {\n if (!Array.isArray(target) || !Array.isArray(source)) return target ?? source;\n const merged = [...target];\n for (const tag of source) {\n if (!isRecord(tag) || typeof tag.name !== \"string\") {\n merged.push(tag);\n continue;\n }\n const existingIndex = merged.findIndex(\n (candidate) => isRecord(candidate) && candidate.name === tag.name,\n );\n if (existingIndex < 0) {\n merged.push(tag);\n continue;\n }\n const existing = merged[existingIndex];\n if (sameValue(existing, tag)) continue;\n if (policy === \"keep\") continue;\n if (policy === \"overwrite\") {\n merged[existingIndex] = tag;\n continue;\n }\n throw new Error(`Conflitto OpenAPI nel tag \"${tag.name}\".`);\n }\n return merged;\n}\n\nfunction mergeServers(\n target: unknown,\n source: unknown,\n policy: OpenApiMergeConflictPolicy,\n): unknown {\n if (!Array.isArray(target) || !Array.isArray(source)) return target ?? source;\n const merged = [...target];\n for (const server of source) {\n if (!isRecord(server) || typeof server.url !== \"string\") {\n merged.push(server);\n continue;\n }\n const existingIndex = merged.findIndex(\n (candidate) => isRecord(candidate) && candidate.url === server.url,\n );\n if (existingIndex < 0) {\n merged.push(server);\n continue;\n }\n const existing = merged[existingIndex];\n if (sameValue(existing, server)) continue;\n if (policy === \"keep\") continue;\n if (policy === \"overwrite\") {\n merged[existingIndex] = server;\n continue;\n }\n throw new Error(`Conflitto OpenAPI nel server \"${server.url}\".`);\n }\n return merged;\n}\n\n/**\n * Unisce il documento Accessi nel documento OpenAPI dell'host.\n *\n * Per default un conflitto su path, schema o security scheme produce errore:\n * e meglio accorgersi subito di due contratti incompatibili. Usare\n * `conflict: \"overwrite\"` solo quando il documento host deve vincere.\n */\nexport function mergeOpenApiDocuments(\n target: OpenAPIObject,\n source: OpenAPIObject,\n options: MergeOpenApiOptions = {},\n): OpenAPIObject {\n if (!target || !source) {\n throw new Error(\"mergeOpenApiDocuments richiede due documenti OpenAPI validi.\");\n }\n\n const policy = options.conflict ?? \"error\";\n const result = cloneDocument(target);\n const sourceDocument = source as unknown as Record<string, unknown>;\n\n result.paths = mergeRecord(\n isRecord(result.paths) ? result.paths : undefined,\n isRecord(sourceDocument.paths) ? sourceDocument.paths : undefined,\n policy,\n \"paths\",\n );\n\n const targetComponents = isRecord(result.components) ? result.components : {};\n const sourceComponents = isRecord(sourceDocument.components) ? sourceDocument.components : {};\n const componentSections = new Set([\n ...Object.keys(targetComponents),\n ...Object.keys(sourceComponents),\n ]);\n const mergedComponents: Record<string, unknown> = { ...targetComponents };\n for (const section of componentSections) {\n mergedComponents[section] = mergeRecord(\n isRecord(targetComponents[section]) ? targetComponents[section] : undefined,\n isRecord(sourceComponents[section]) ? sourceComponents[section] : undefined,\n policy,\n `components.${section}`,\n );\n }\n result.components = mergedComponents;\n\n result.tags = mergeTags(result.tags, sourceDocument.tags, policy);\n result.servers = mergeServers(result.servers, sourceDocument.servers, policy);\n\n return result as unknown as OpenAPIObject;\n}\n"]}
@@ -0,0 +1,481 @@
1
+ # Integration prompt — Accessi (`emilsoftware-utilities`)
2
+
3
+ Questo documento e il contesto unico da passare a un agente AI o a uno sviluppatore che deve integrare Accessi in un backend, in un frontend o in un altro servizio.
4
+
5
+ L'obiettivo e evitare l'errore piu comune: **montare `AccessiModule` ovunque**. Accessi e un servizio di identita; gli altri backend sono resource server.
6
+
7
+ ---
8
+
9
+ ## 1. Architettura da seguire
10
+
11
+ ```text
12
+ Frontend A ─┐
13
+ Frontend B ─┼──> Accessi Service (DB, SMTP, utenti, ruoli, console, SSO, 2FA)
14
+ │ ▲
15
+ Backend A ──┼─────────┤ introspection / service token
16
+ Backend B ──┘ │
17
+ └──> Accessi DB (solo Accessi Service)
18
+ ```
19
+
20
+ Regole:
21
+
22
+ - **Accessi Service** e l'unico proprietario di:
23
+ - database Accessi e migrazioni
24
+ - `encryptionKey`
25
+ - SMTP
26
+ - chiavi JWT
27
+ - admin bootstrap
28
+ - console amministrativa
29
+ - **Backend A/B** non hanno credenziali del DB Accessi e non eseguono migrazioni Accessi.
30
+ - **Frontend A/B** fanno login su Accessi Service e usano il JWT verso i backend.
31
+ - La console e centralizzata su Accessi Service.
32
+ - Non montare `AccessiModule` in due backend puntati allo stesso database.
33
+
34
+ Se per ora Accessi e montato dentro Backend A, va bene: Backend A e il proprietario. Backend B usa solo `accessi/resource-server` e `accessi/client`.
35
+
36
+ ---
37
+
38
+ ## 2. Entry point del pacchetto
39
+
40
+ | Import | Quando usarlo |
41
+ |---|---|
42
+ | `emilsoftware-utilities/accessi/server` | Backend che ospita Accessi: `AccessiModule`, `initializeAccessiModule`, updater DB, migrazioni, console. |
43
+ | `emilsoftware-utilities/accessi/resource-server` | Backend fratello: middleware Express, guard Nest, verifica JWT e introspection. |
44
+ | `emilsoftware-utilities/accessi/client` | Client OpenAPI generato per chiamare Accessi Service. |
45
+ | `emilsoftware-utilities/accessi/contracts` | Tipi, enum e helper puri browser-safe. |
46
+ | `emilsoftware-utilities/accessi/contracts/dtos` | DTO Nest con `class-validator` e decoratori OpenAPI. Solo backend. |
47
+ | `emilsoftware-utilities/accessi/openapi` | `setupOpenApi`, `mergeOpenApiDocuments`. |
48
+ | `emilsoftware-utilities/accessi/console` | `buildAccessiConsoleUrl`, URL console e ticket. |
49
+ | `emilsoftware-utilities/accessi/ui` | Percorsi asset Web Component frameworkless. |
50
+
51
+ Il root export `emilsoftware-utilities` resta per retrocompatibilita, ma nei nuovi progetti usare gli entry point sopra.
52
+
53
+ ---
54
+
55
+ ## 3. Backend che ospita Accessi
56
+
57
+ ### Bootstrap Express
58
+
59
+ ```ts
60
+ import { initializeAccessiModule } from "emilsoftware-utilities/accessi/server";
61
+
62
+ const { nestApp, document } = await initializeAccessiModule(app, options);
63
+ // document e il documento OpenAPI Accessi, gia con /api/accessi/*
64
+ ```
65
+
66
+ `initializeAccessiModule` restituisce:
67
+
68
+ ```ts
69
+ {
70
+ nestApp: INestApplication;
71
+ document: OpenAPIObject;
72
+ }
73
+ ```
74
+
75
+ Aprire la porta HTTP solo dopo l'await.
76
+
77
+ ### Bootstrap Nest
78
+
79
+ ```ts
80
+ import { AccessiModule } from "emilsoftware-utilities/accessi/server";
81
+
82
+ @Module({
83
+ imports: [AccessiModule.forRoot(options)],
84
+ })
85
+ export class AppModule {}
86
+ ```
87
+
88
+ ### Configurazione minima
89
+
90
+ ```ts
91
+ import type { AccessiServerOptions } from "emilsoftware-utilities/accessi/server";
92
+
93
+ const options: AccessiServerOptions = {
94
+ databaseOptions: {
95
+ host: process.env.ACCESSI_DB_HOST!,
96
+ port: Number(process.env.ACCESSI_DB_PORT ?? 3050),
97
+ database: process.env.ACCESSI_DB_NAME!,
98
+ user: process.env.ACCESSI_DB_USER!,
99
+ password: process.env.ACCESSI_DB_PASSWORD!,
100
+ },
101
+ confirmationEmailUrl: "https://app.example.com",
102
+ confirmationEmailReturnUrl: "https://app.example.com/login",
103
+ encryptionKey: process.env.ACCESSI_ENCRYPTION_KEY!,
104
+ mockDemoUser: false,
105
+ autoUpdateDatabase: true,
106
+ legacyPasswordMigrationOnStartup: true,
107
+ jwtOptions: {
108
+ secret: process.env.ACCESSI_JWT_SECRET!,
109
+ expiresIn: "24h",
110
+ issuer: "accessi",
111
+ audience: "backend-app",
112
+ // Per multi-servizio:
113
+ // algorithm: "RS256",
114
+ // privateKey: process.env.ACCESSI_JWT_PRIVATE_KEY,
115
+ // publicKey: process.env.ACCESSI_JWT_PUBLIC_KEY,
116
+ // keyId: "accessi-2026-01",
117
+ },
118
+ emailOptions: {
119
+ host: process.env.SMTP_HOST!,
120
+ port: Number(process.env.SMTP_PORT ?? 587),
121
+ secure: false,
122
+ requireTLS: true,
123
+ tls: { rejectUnauthorized: true },
124
+ from: "no-reply@example.com",
125
+ auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! },
126
+ },
127
+ serviceTokens: { enabled: true, defaultTtlDays: 365 },
128
+ };
129
+ ```
130
+
131
+ ### OpenAPI
132
+
133
+ ```ts
134
+ import { setupOpenApi, mergeOpenApiDocuments } from "emilsoftware-utilities/accessi/openapi";
135
+
136
+ const document = setupOpenApi(nestApp, {
137
+ openApiPath: "accessi/openapi",
138
+ openApiJsonPath: "accessi/openapi.json",
139
+ title: "Accessi API Documentation",
140
+ description: "API del modulo accessi",
141
+ enabled: true,
142
+ });
143
+ ```
144
+
145
+ Per un documento unico host:
146
+
147
+ ```ts
148
+ const merged = mergeOpenApiDocuments(hostDocument, accessiDocument, {
149
+ conflict: "error", // "keep" | "overwrite"
150
+ });
151
+ ```
152
+
153
+ ---
154
+
155
+ ## 4. Backend fratello — Express
156
+
157
+ ```ts
158
+ import { createAccessiAuthMiddleware } from "emilsoftware-utilities/accessi/resource-server";
159
+
160
+ app.use(createAccessiAuthMiddleware({
161
+ mode: "local",
162
+ jwt: {
163
+ // HS256: passare il segreto grezzo Accessi, la KDF viene applicata qui
164
+ serverSecret: process.env.ACCESSI_JWT_SECRET,
165
+ issuer: "accessi",
166
+ audience: "backend-a",
167
+ },
168
+ // Opzionale: serve per leggere i grant correnti
169
+ introspection: {
170
+ url: "https://accessi.example.com/api/accessi/auth/introspect",
171
+ serviceToken: process.env.ACCESSI_SERVICE_TOKEN!,
172
+ cacheTtlMs: 30_000,
173
+ timeoutMs: 3_000,
174
+ },
175
+ authorization: {
176
+ requirements: [{ menuCode: "ORDINI", minPermissionLevel: 20 }],
177
+ },
178
+ }));
179
+ ```
180
+
181
+ Dopo il middleware:
182
+
183
+ - `req.user`: payload autenticato (compatibile con il middleware storico)
184
+ - `req.data`: alias di `req.user`
185
+ - `req.userGrants`: presente solo se la policy richiede grant
186
+ - `req.accessi`: `{ token, user, codiceUtente, grants?, payload? }`
187
+
188
+ ### HS256 vs RS256/JWKS
189
+
190
+ - **HS256**: usare `jwt.serverSecret` con `ACCESSI_JWT_SECRET`. Il resource server deriva la chiave access con la stessa KDF del server.
191
+ - **RS256/ES256**: usare `jwt.publicKey` PEM oppure `jwt.jwks.url`. Consigliato in multi-servizio.
192
+ - Il server Accessi supporta `jwtOptions.algorithm`, `privateKey`, `publicKey`, `keyId`.
193
+ - Accessi non espone ancora un endpoint JWKS integrato: pubblicare la chiave pubblica con il proprio hosting/reverse proxy oppure usare `publicKey` PEM.
194
+
195
+ ### Errori
196
+
197
+ | HTTP | Codice | Significato |
198
+ |---|---|---|
199
+ | 401 | `AUTH_TOKEN_MISSING` | Header Bearer assente o malformato. |
200
+ | 401 | `AUTH_TOKEN_INVALID` | Firma, scadenza, issuer/audience o tipo token non validi. |
201
+ | 401 | `AUTH_USER_CODE_MISSING` | Token senza `codiceUtente`. |
202
+ | 403 | `AUTH_INSUFFICIENT_PERMISSIONS` | Policy di autorizzazione non soddisfatta. |
203
+ | 500 | `AUTH_CONFIG_INVALID` | Configurazione resource server incompleta. |
204
+ | 500 | `AUTH_GRANTS_SOURCE_MISSING` | Policy richiede grant ma introspection non configurata. |
205
+ | 503 | `AUTH_INTROSPECTION_UNAVAILABLE` | Accessi Service non raggiungibile; fail closed. |
206
+ | 503 | `AUTH_INTROSPECTION_UNAUTHORIZED` | Service token invalido o senza `accessi:introspect`. |
207
+ | 503 | `AUTH_JWKS_UNAVAILABLE` | JWKS non raggiungibile. |
208
+
209
+ ---
210
+
211
+ ## 5. Backend fratello — NestJS
212
+
213
+ ```ts
214
+ import {
215
+ createAccessiAuthGuard,
216
+ type AccessiAuthenticatedRequest,
217
+ } from "emilsoftware-utilities/accessi/resource-server";
218
+
219
+ @UseGuards(createAccessiAuthGuard({
220
+ jwt: { publicKey: process.env.ACCESSI_JWT_PUBLIC_KEY, algorithms: ["RS256"] },
221
+ introspection: {
222
+ url: process.env.ACCESSI_INTROSPECT_URL!,
223
+ serviceToken: process.env.ACCESSI_SERVICE_TOKEN!,
224
+ },
225
+ authorization: {
226
+ requirements: [{ menuCode: "ORDINI", minPermissionLevel: 20 }],
227
+ },
228
+ }))
229
+ @Get("ordini")
230
+ list(@Req() req: AccessiAuthenticatedRequest) {
231
+ return req.user;
232
+ }
233
+ ```
234
+
235
+ Il guard mappa gli errori in `UnauthorizedException`, `ForbiddenException`, `ServiceUnavailableException` o `InternalServerErrorException`.
236
+
237
+ ---
238
+
239
+ ## 6. Introspection
240
+
241
+ Endpoint:
242
+
243
+ ```http
244
+ POST /api/accessi/auth/introspect
245
+ Authorization: Bearer <service-token>
246
+ Content-Type: application/json
247
+
248
+ { "token": "<access-token-utente>" }
249
+ ```
250
+
251
+ Scope richiesto:
252
+
253
+ ```text
254
+ accessi:introspect
255
+ ```
256
+
257
+ Risposta:
258
+
259
+ ```json
260
+ {
261
+ "Status": { "errorCode": "0", "errorDescription": "" },
262
+ "Result": {
263
+ "active": true,
264
+ "user": { "codiceUtente": 123, "email": "utente@example.com" },
265
+ "grants": { "abilitazioni": [], "ruoli": [], "grants": [] }
266
+ },
267
+ "Message": "Dati recuperati con successo."
268
+ }
269
+ ```
270
+
271
+ - `active: false` per token invalido, scaduto o utente disabilitato.
272
+ - Gli errori di database/configurazione sono 500: non vanno interpretati come token scaduto.
273
+ - Il client `createAccessiIntrospectionClient` normalizza `Result`, applica timeout e cache.
274
+ - Il resource server cachea i risultati attivi per `cacheTtlMs` (default 30s). I risultati inattivi non sono cachati.
275
+ - Fail closed: se Accessi Service non risponde, la richiesta non viene autorizzata.
276
+
277
+ ---
278
+
279
+ ## 7. Frontend
280
+
281
+ ### Login
282
+
283
+ Il frontend chiama Accessi Service:
284
+
285
+ ```ts
286
+ import { configureAccessiClient, login } from "emilsoftware-utilities/accessi/client";
287
+
288
+ configureAccessiClient({
289
+ baseUrl: "https://accessi.example.com",
290
+ token: null,
291
+ });
292
+
293
+ const response = await login({ email, password });
294
+ const token = response.data.Result.token.value;
295
+ ```
296
+
297
+ Dopo il login:
298
+
299
+ ```ts
300
+ configureAccessiClient({
301
+ baseUrl: "https://accessi.example.com",
302
+ token,
303
+ });
304
+ ```
305
+
306
+ Per token che cambiano nel tempo:
307
+
308
+ ```ts
309
+ configureAccessiClient({
310
+ baseUrl: "https://accessi.example.com",
311
+ getToken: () => authStore.getToken(),
312
+ });
313
+ ```
314
+
315
+ ### Chiamate ai backend
316
+
317
+ Il frontend usa il JWT Accessi come Bearer verso Backend A/B. Non serve che il frontend importi il resource server.
318
+
319
+ ### Console
320
+
321
+ Non incorporare la console in ogni applicazione. Linkare Accessi Service:
322
+
323
+ ```ts
324
+ import { buildAccessiConsoleUrl } from "emilsoftware-utilities/accessi/console";
325
+
326
+ const consoleUrl = buildAccessiConsoleUrl("https://accessi.example.com", {
327
+ entryTicket: ticket, // da POST /api/accessi/console/entry
328
+ });
329
+ ```
330
+
331
+ - Il JWT non va mai nell'URL.
332
+ - Il ticket e monouso, dura 60 secondi.
333
+ - Per il 2FA handoff usare `twoFactorChallengeId`.
334
+ - Se la console deve stare sullo stesso dominio, fare reverse proxy di `/api/accessi/*` verso Accessi Service, non copiare gli asset.
335
+
336
+ ### Web component
337
+
338
+ ```html
339
+ <script type="module" src="/assets/aree-disponibili/aree-disponibili.js"></script>
340
+ <es-aree-disponibili></es-aree-disponibili>
341
+ ```
342
+
343
+ I componenti sono frameworkless e ricevono i dati via proprieta; non fanno chiamate di rete.
344
+
345
+ ---
346
+
347
+ ## 8. Contratti e DTO
348
+
349
+ - `accessi/contracts`: tipi/enum/helper puri, adatti anche al browser.
350
+ - `accessi/contracts/dtos`: DTO Nest con decoratori; non usarli in bundle frontend leggeri.
351
+ - `accessi/client`: tipi generati dall'OpenAPI e funzioni HTTP; e la fonte consigliata per frontend e backend fratelli.
352
+
353
+ Non usare i DTO server come contratto pubblico se il frontend non ha `class-validator`, `class-transformer` e `reflect-metadata`.
354
+
355
+ ---
356
+
357
+ ## 9. Service token
358
+
359
+ Per chiamare l'introspection o API tecniche:
360
+
361
+ 1. Abilitare `serviceTokens: { enabled: true }` su Accessi Service.
362
+ 2. Creare un token dalla console o con `POST /api/accessi/service-token`.
363
+ 3. Assegnare lo scope minimo necessario:
364
+ - `accessi:introspect` per l'introspection
365
+ - altri scope applicativi secondo necessita
366
+ 4. Conservarlo come secret nel backend fratello.
367
+ 5. Ruotarlo periodicamente; il segreto e mostrato una sola volta.
368
+
369
+ Non usare un service token nel browser.
370
+
371
+ ---
372
+
373
+ ## 10. Sicurezza: regole non negoziabili
374
+
375
+ - Il database Accessi è solo di Accessi Service.
376
+ - `encryptionKey`, SMTP, chiavi private JWT e admin bootstrap restano solo su Accessi Service.
377
+ - In multi-servizio usare RS256/ES256 con chiave pubblica/JWKS. HS256 con segreto condiviso e accettabile solo come fase transitoria.
378
+ - Non inserire JWT, ticket console o service token nei log.
379
+ - Non mettere JWT o ticket nell'URL; il ticket console va nel fragment `#entry=`.
380
+ - La console va centralizzata.
381
+ - Le rotte amministrative richiedono policy esplicite; non fidarsi dei flag nel token senza introspection.
382
+ - Le richieste con introspection non raggiungibile devono fallire chiuse.
383
+ - Usare `issuer` e `audience` per evitare token di ambienti diversi.
384
+
385
+ ---
386
+
387
+ ## 11. Variabili ambiente consigliate
388
+
389
+ Accessi Service:
390
+
391
+ ```env
392
+ ACCESSI_DB_HOST=
393
+ ACCESSI_DB_PORT=3050
394
+ ACCESSI_DB_NAME=
395
+ ACCESSI_DB_USER=
396
+ ACCESSI_DB_PASSWORD=
397
+ ACCESSI_ENCRYPTION_KEY=
398
+ ACCESSI_JWT_SECRET=
399
+ ACCESSI_JWT_EXPIRES=24h
400
+ ACCESSI_JWT_ISSUER=accessi
401
+ ACCESSI_JWT_AUDIENCE=backend-app
402
+ # Opzionale RS256
403
+ ACCESSI_JWT_ALGORITHM=RS256
404
+ ACCESSI_JWT_PRIVATE_KEY=
405
+ ACCESSI_JWT_PUBLIC_KEY=
406
+ ACCESSI_JWT_KEY_ID=
407
+ SMTP_HOST=
408
+ SMTP_PORT=587
409
+ SMTP_USER=
410
+ SMTP_PASS=
411
+ ACCESSI_EMAIL_FROM=
412
+ ```
413
+
414
+ Backend fratello:
415
+
416
+ ```env
417
+ ACCESSI_JWT_PUBLIC_KEY=
418
+ # oppure, solo HS256 transitorio:
419
+ ACCESSI_JWT_SECRET=
420
+ ACCESSI_JWT_ISSUER=accessi
421
+ ACCESSI_JWT_AUDIENCE=backend-a
422
+ ACCESSI_INTROSPECT_URL=https://accessi.example.com/api/accessi/auth/introspect
423
+ ACCESSI_SERVICE_TOKEN=
424
+ ```
425
+
426
+ Frontend:
427
+
428
+ ```env
429
+ ACCESSI_API_BASE_URL=https://accessi.example.com
430
+ ACCESSI_CONSOLE_URL=https://accessi.example.com/api/accessi/console
431
+ ```
432
+
433
+ ---
434
+
435
+ ## 12. Migrazione da Accessi embedded
436
+
437
+ Checklist:
438
+
439
+ 1. Scegliere il proprietario: Accessi Service o un backend esistente.
440
+ 2. Se il DB resta lo stesso, non migrare i dati; cambiare solo rete e credenziali.
441
+ 3. Mantenere la stessa `encryptionKey` finche tutte le password legacy non sono migrate.
442
+ 4. Introdurre `issuer`/`audience` o RS256 solo con una finestra di migrazione: i token vecchi possono diventare invalidi.
443
+ 5. Spostare la console una sola volta.
444
+ 6. Passare i backend fratelli a `resource-server`.
445
+ 7. Configurare un service token con `accessi:introspect`.
446
+ 8. Testare:
447
+ - login
448
+ - token valido
449
+ - token scaduto
450
+ - utente disabilitato
451
+ - revoca grants
452
+ - Accessi Service spento (fail closed)
453
+ - console entry ticket
454
+ 9. Aggiornare CORS se i frontend cambiano dominio.
455
+ 10. Aggiornare monitoraggio e log senza includere token.
456
+
457
+ ---
458
+
459
+ ## 13. Prompt operativo da copiare
460
+
461
+ ### Per un backend
462
+
463
+ > Integra Accessi usando l'entry point corretto. Se questo backend possiede il database Accessi, usa `emilsoftware-utilities/accessi/server` e `initializeAccessiModule`/`AccessiModule.forRoot`. Se non lo possiede, non importare `AccessiModule` e non configurare il database Accessi: usa `emilsoftware-utilities/accessi/resource-server` con `createAccessiAuthMiddleware` (Express) o `createAccessiAuthGuard` (Nest). Configura `jwt` con `publicKey`/`jwks` (preferito) o `serverSecret` (HS256), `issuer` e `audience`. Se servono grant, configura `introspection` con URL e service token scope `accessi:introspect`, e una cache breve. Applica una policy `authorization` esplicita sulle rotte sensibili. Non fidarti dei flag nel token senza introspection. Usa `accessi/client` per chiamare Accessi Service e `accessi/contracts` per i tipi. Non inserire token nei log o negli URL.
464
+
465
+ ### Per un frontend
466
+
467
+ > Usa `emilsoftware-utilities/accessi/client`: chiama `configureAccessiClient({ baseUrl, token })` dopo il login e `getToken` se il token cambia. Il login, il refresh, il 2FA e il reset password passano da Accessi Service. Verso i backend usa `Authorization: Bearer <access-token>`. Non incorporare il database o il server Accessi. Per la console usa `buildAccessiConsoleUrl` con il ticket monouso restituito da `POST /api/accessi/console/entry`; non mettere mai il JWT nell'URL. Per i componenti UI usa gli asset di `accessi/ui`. Gestisci gli errori 401 (`AUTH_TOKEN_INVALID`) portando l'utente al login e 403 (`AUTH_INSUFFICIENT_PERMISSIONS`) mostrando un messaggio di permessi insufficienti.
468
+
469
+ ### Per un agente AI
470
+
471
+ > Prima di modificare codice, leggi `integration-prompt.md`, `WIKI.md` e `src/accessi-module/Console/changelog.md`. Rispetta la separazione dei ruoli: `accessi/server` solo sul proprietario del DB, `accessi/resource-server` sui backend fratelli. Non aggiungere dipendenze dal database Accessi nei resource server. Non inventare endpoint: l'introspection e `POST /api/accessi/auth/introspect` con service token scope `accessi:introspect`. Non cambiare i default HS256 senza una finestra di migrazione. Aggiungi test per token valido, scaduto, utente disabilitato, permessi insufficienti e introspection non raggiungibile.
472
+
473
+ ---
474
+
475
+ ## 14. Limiti attuali da conoscere
476
+
477
+ - I ticket di ingresso console sono in memoria del processo: con piu istanze di Accessi Service servono sticky session o uno store condiviso.
478
+ - I rate limit pubblici e dei service token sono in memoria.
479
+ - Accessi non espone ancora un endpoint JWKS pronto: il resource server sa verificare una JWKS, ma la chiave pubblica va pubblicata dall'host.
480
+ - Il client generato e completo (include endpoint amministrativi): non distribuirlo a frontend non amministrativi senza valutare gli endpoint esposti. Per un client pubblico ridotto, generare un sottoinsieme per tag con Orval.
481
+ - I DTO in `accessi/contracts/dtos` dipendono da Nest/class-validator: non sono pensati per bundle frontend minimali.