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.
- package/README.MD +28 -1
- package/dist/EmilsoftwareModule.d.ts +11 -3
- package/dist/EmilsoftwareModule.js +1 -1
- package/dist/EmilsoftwareModule.js.map +1 -1
- package/dist/Orm.d.ts +1 -1
- package/dist/Orm.js +4 -23
- package/dist/Orm.js.map +1 -1
- package/dist/Utilities.js +1 -0
- package/dist/Utilities.js.map +1 -1
- package/dist/accessi/client/index.d.ts +26 -0
- package/dist/accessi/client/index.js +49 -0
- package/dist/accessi/client/index.js.map +1 -0
- package/dist/accessi/console/index.d.ts +15 -0
- package/dist/accessi/console/index.js +25 -0
- package/dist/accessi/console/index.js.map +1 -0
- package/dist/accessi/contracts/dtos.d.ts +8 -0
- package/dist/accessi/contracts/dtos.js +25 -0
- package/dist/accessi/contracts/dtos.js.map +1 -0
- package/dist/accessi/contracts/index.d.ts +25 -0
- package/dist/accessi/contracts/index.js +28 -0
- package/dist/accessi/contracts/index.js.map +1 -0
- package/dist/accessi/openapi/index.d.ts +2 -0
- package/dist/accessi/openapi/index.js +19 -0
- package/dist/accessi/openapi/index.js.map +1 -0
- package/dist/accessi/resource-server/core.d.ts +6 -0
- package/dist/accessi/resource-server/core.js +23 -0
- package/dist/accessi/resource-server/core.js.map +1 -0
- package/dist/accessi/resource-server/errors.d.ts +10 -0
- package/dist/accessi/resource-server/errors.js +19 -0
- package/dist/accessi/resource-server/errors.js.map +1 -0
- package/dist/accessi/resource-server/express.d.ts +17 -0
- package/dist/accessi/resource-server/express.js +66 -0
- package/dist/accessi/resource-server/express.js.map +1 -0
- package/dist/accessi/resource-server/index.d.ts +3 -0
- package/dist/accessi/resource-server/index.js +20 -0
- package/dist/accessi/resource-server/index.js.map +1 -0
- package/dist/accessi/resource-server/introspection.d.ts +16 -0
- package/dist/accessi/resource-server/introspection.js +115 -0
- package/dist/accessi/resource-server/introspection.js.map +1 -0
- package/dist/accessi/resource-server/keys.d.ts +11 -0
- package/dist/accessi/resource-server/keys.js +22 -0
- package/dist/accessi/resource-server/keys.js.map +1 -0
- package/dist/accessi/resource-server/nest.d.ts +9 -0
- package/dist/accessi/resource-server/nest.js +62 -0
- package/dist/accessi/resource-server/nest.js.map +1 -0
- package/dist/accessi/resource-server/runtime.d.ts +27 -0
- package/dist/accessi/resource-server/runtime.js +128 -0
- package/dist/accessi/resource-server/runtime.js.map +1 -0
- package/dist/accessi/resource-server/token.d.ts +23 -0
- package/dist/accessi/resource-server/token.js +230 -0
- package/dist/accessi/resource-server/token.js.map +1 -0
- package/dist/accessi/resource-server/types.d.ts +78 -0
- package/dist/accessi/resource-server/types.js +3 -0
- package/dist/accessi/resource-server/types.js.map +1 -0
- package/dist/accessi/server/index.d.ts +10 -0
- package/dist/accessi/server/index.js +29 -0
- package/dist/accessi/server/index.js.map +1 -0
- package/dist/accessi/ui/index.d.ts +13 -0
- package/dist/accessi/ui/index.js +19 -0
- package/dist/accessi/ui/index.js.map +1 -0
- package/dist/accessi-module/AccessiModule.d.ts +25 -1
- package/dist/accessi-module/AccessiModule.js +2 -0
- package/dist/accessi-module/AccessiModule.js.map +1 -1
- package/dist/accessi-module/Console/accessiFetch.d.ts +4 -0
- package/dist/accessi-module/Console/accessiFetch.js +18 -4
- package/dist/accessi-module/Console/accessiFetch.js.map +1 -1
- package/dist/accessi-module/Console/console.js +114 -9
- package/dist/accessi-module/Console/console.js.map +1 -1
- package/dist/accessi-module/Console/generated/{accessiApi.d.ts → emilsoftware-utilities.d.ts} +11 -1
- package/dist/accessi-module/Console/generated/{accessiApi.js → emilsoftware-utilities.js} +10 -2
- package/dist/accessi-module/Console/generated/emilsoftware-utilities.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/accessiIntrospectionResultDto.d.ts +16 -0
- package/dist/accessi-module/Console/generated/model/accessiIntrospectionResultDto.js +3 -0
- package/dist/accessi-module/Console/generated/model/accessiIntrospectionResultDto.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/index.d.ts +3 -0
- package/dist/accessi-module/Console/generated/model/index.js +3 -0
- package/dist/accessi-module/Console/generated/model/index.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/introspectAccessiTokenRequest.d.ts +10 -0
- package/dist/accessi-module/Console/generated/model/introspectAccessiTokenRequest.js +9 -0
- package/dist/accessi-module/Console/generated/model/introspectAccessiTokenRequest.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/introspectAccessiTokenResponse.d.ts +14 -0
- package/dist/accessi-module/Console/generated/model/introspectAccessiTokenResponse.js +3 -0
- package/dist/accessi-module/Console/generated/model/introspectAccessiTokenResponse.js.map +1 -0
- package/dist/accessi-module/Console/wiki.js +98 -3
- package/dist/accessi-module/Console/wiki.js.map +1 -1
- package/dist/accessi-module/Controllers/AccessiIntrospectionController.d.ts +17 -0
- package/dist/accessi-module/Controllers/AccessiIntrospectionController.js +93 -0
- package/dist/accessi-module/Controllers/AccessiIntrospectionController.js.map +1 -0
- package/dist/accessi-module/Dtos/IntrospectionDtos.d.ts +16 -0
- package/dist/accessi-module/Dtos/IntrospectionDtos.js +59 -0
- package/dist/accessi-module/Dtos/IntrospectionDtos.js.map +1 -0
- package/dist/accessi-module/Dtos/index.d.ts +1 -0
- package/dist/accessi-module/Dtos/index.js +1 -0
- package/dist/accessi-module/Dtos/index.js.map +1 -1
- package/dist/accessi-module/Services/AuthService/AuthService.d.ts +13 -0
- package/dist/accessi-module/Services/AuthService/AuthService.js +42 -10
- package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js +11 -4
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js.map +1 -1
- package/dist/accessi-module/database-updates/menuScopeSeed.d.ts +2 -2
- package/dist/accessi-module/database-updates/menuScopeSeed.js +101 -53
- package/dist/accessi-module/database-updates/menuScopeSeed.js.map +1 -1
- package/dist/accessi-module/database-updates/menuScopeSeeder.js +1 -1
- package/dist/accessi-module/database-updates/menuScopeSeeder.js.map +1 -1
- package/dist/accessi-module/index.d.ts +16 -3
- package/dist/accessi-module/index.js +15 -19
- package/dist/accessi-module/index.js.map +1 -1
- package/dist/accessi-module/jwt/jwt.strategy.js +5 -5
- package/dist/accessi-module/jwt/jwt.strategy.js.map +1 -1
- package/dist/accessi-module/middleware/authenticateGen.js +5 -5
- package/dist/accessi-module/middleware/authenticateGen.js.map +1 -1
- package/dist/accessi-module/security/jwtTokenOptions.d.ts +15 -0
- package/dist/accessi-module/security/jwtTokenOptions.js +44 -0
- package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -0
- package/dist/accessi-module/startupLog.d.ts +3 -2
- package/dist/accessi-module/startupLog.js +8 -4
- package/dist/accessi-module/startupLog.js.map +1 -1
- package/dist/allegati-module/index.js +2 -2
- package/dist/allegati-module/index.js.map +1 -1
- package/dist/angular-components/aree-disponibili/demo.html +4 -4
- package/dist/emilsoftware-utilities-2.0.0-dev.67.tgz +0 -0
- package/dist/firebird-compat.d.ts +27 -0
- package/dist/firebird-compat.js +52 -2
- package/dist/firebird-compat.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/initEmilsoftwareModule.d.ts +11 -1
- package/dist/initEmilsoftwareModule.js +11 -23
- package/dist/initEmilsoftwareModule.js.map +1 -1
- package/dist/openapi/OpenApiConfig.d.ts +68 -0
- package/dist/openapi/OpenApiConfig.js +157 -0
- package/dist/openapi/OpenApiConfig.js.map +1 -0
- package/dist/openapi/OpenApiMerge.d.ts +15 -0
- package/dist/openapi/OpenApiMerge.js +119 -0
- package/dist/openapi/OpenApiMerge.js.map +1 -0
- package/integration-prompt.md +481 -0
- package/package.json +89 -6
- package/dist/accessi-module/Console/generated/accessiApi.js.map +0 -1
- package/dist/emilsoftware-utilities-2.0.0-dev.65.tgz +0 -0
- package/dist/swagger/SwaggerConfig.d.ts +0 -8
- package/dist/swagger/SwaggerConfig.js +0 -41
- 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.
|