emilsoftware-utilities 2.0.0-dev.79 → 2.0.0-dev.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accessi/server/index.d.ts +1 -0
- package/dist/accessi/server/index.js +1 -0
- package/dist/accessi/server/index.js.map +1 -1
- package/dist/accessi/server/tenants.d.ts +97 -0
- package/dist/accessi/server/tenants.js +275 -0
- package/dist/accessi/server/tenants.js.map +1 -0
- package/dist/accessi-module/AccessiModule.d.ts +5 -1
- package/dist/accessi-module/AccessiModule.js +3 -1
- package/dist/accessi-module/AccessiModule.js.map +1 -1
- package/dist/accessi-module/Console/console.js +3 -1
- package/dist/accessi-module/Console/wiki.js +2 -0
- package/dist/accessi-module/Console/wiki.js.map +1 -1
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.d.ts +7 -3
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js +32 -28
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js.map +1 -1
- package/dist/accessi-module/observability/metrics.js +6 -2
- package/dist/accessi-module/observability/metrics.js.map +1 -1
- package/dist/accessi-module/security/audit.d.ts +2 -0
- package/dist/accessi-module/security/audit.js +3 -1
- package/dist/accessi-module/security/audit.js.map +1 -1
- package/dist/emilsoftware-utilities-2.0.0-dev.80.tgz +0 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/docs/adr/0002-multi-tenancy.md +154 -0
- package/docs/import-migration.md +65 -0
- package/docs/multi-tenancy.md +305 -0
- package/package.json +4 -3
- package/dist/emilsoftware-utilities-2.0.0-dev.79.tgz +0 -0
|
@@ -7,5 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export * from "../../accessi-module";
|
|
9
9
|
export * from "./standalone";
|
|
10
|
+
export * from "./tenants";
|
|
10
11
|
export type { AccessiOptions as AccessiServerOptions } from "../../accessi-module";
|
|
11
12
|
export { createAccessiOptionsFromEnv, createCatalogScriptsOptionsFromEnv, parseAccessiPreviousPublicKeys, } from "../../accessi-module/database-updates/accessiOptionsFromEnv";
|
|
@@ -24,6 +24,7 @@ exports.parseAccessiPreviousPublicKeys = exports.createCatalogScriptsOptionsFrom
|
|
|
24
24
|
*/
|
|
25
25
|
__exportStar(require("../../accessi-module"), exports);
|
|
26
26
|
__exportStar(require("./standalone"), exports);
|
|
27
|
+
__exportStar(require("./tenants"), exports);
|
|
27
28
|
var accessiOptionsFromEnv_1 = require("../../accessi-module/database-updates/accessiOptionsFromEnv");
|
|
28
29
|
Object.defineProperty(exports, "createAccessiOptionsFromEnv", { enumerable: true, get: function () { return accessiOptionsFromEnv_1.createAccessiOptionsFromEnv; } });
|
|
29
30
|
Object.defineProperty(exports, "createCatalogScriptsOptionsFromEnv", { enumerable: true, get: function () { return accessiOptionsFromEnv_1.createCatalogScriptsOptionsFromEnv; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/accessi/server/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA;;;;;;GAMG;AACH,uDAAqC;AACrC,+CAA6B;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/accessi/server/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA;;;;;;GAMG;AACH,uDAAqC;AACrC,+CAA6B;AAC7B,4CAA0B;AAE1B,qGAIqE;AAHnE,oIAAA,2BAA2B,OAAA;AAC3B,2IAAA,kCAAkC,OAAA;AAClC,uIAAA,8BAA8B,OAAA","sourcesContent":["/**\n * Entry point server Accessi.\n *\n * Questo e l'unico entry point che puo importare database, SMTP, migrazioni,\n * console e servizi Nest completi. I backend fratelli devono usare\n * `accessi/resource-server` e `accessi/client`.\n */\nexport * from \"../../accessi-module\";\nexport * from \"./standalone\";\nexport * from \"./tenants\";\nexport type { AccessiOptions as AccessiServerOptions } from \"../../accessi-module\";\nexport {\n createAccessiOptionsFromEnv,\n createCatalogScriptsOptionsFromEnv,\n parseAccessiPreviousPublicKeys,\n} from \"../../accessi-module/database-updates/accessiOptionsFromEnv\";\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { AccessiOptions } from '../../accessi-module/AccessiModule';
|
|
2
|
+
/** Variabili usate dal manifest del tenant (oltre alle normali `ACCESSI_*`). */
|
|
3
|
+
export interface AccessiTenantEnv extends NodeJS.ProcessEnv {
|
|
4
|
+
ACCESSI_TENANT_ID?: string;
|
|
5
|
+
/** Host separati da virgola; supporta `*.example.com`. */
|
|
6
|
+
ACCESSI_TENANT_HOSTS?: string;
|
|
7
|
+
/** Prefisso path, es. `/tenant-a`. */
|
|
8
|
+
ACCESSI_TENANT_PATH?: string;
|
|
9
|
+
ACCESSI_TENANT_ENABLED?: string;
|
|
10
|
+
/** Porta del servizio quando si usa `startAccessiTenants`. */
|
|
11
|
+
ACCESSI_TENANT_PORT?: string;
|
|
12
|
+
ACCESSI_TENANT_DESCRIPTION?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface AccessiTenant {
|
|
15
|
+
id: string;
|
|
16
|
+
hosts: string[];
|
|
17
|
+
path?: string;
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
description?: string;
|
|
20
|
+
port?: number;
|
|
21
|
+
/** Nome del file `.env` di origine (mai il contenuto). */
|
|
22
|
+
file: string;
|
|
23
|
+
/** Variabili risolte del tenant (base + file); non serializzarle nei log. */
|
|
24
|
+
env: NodeJS.ProcessEnv;
|
|
25
|
+
options: AccessiOptions;
|
|
26
|
+
}
|
|
27
|
+
export interface AccessiTenantSummary {
|
|
28
|
+
id: string;
|
|
29
|
+
hosts: string[];
|
|
30
|
+
path?: string;
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
description?: string;
|
|
33
|
+
port?: number;
|
|
34
|
+
file: string;
|
|
35
|
+
database: {
|
|
36
|
+
host?: string;
|
|
37
|
+
port?: number;
|
|
38
|
+
database?: string;
|
|
39
|
+
user?: string;
|
|
40
|
+
};
|
|
41
|
+
issuer?: string;
|
|
42
|
+
audience?: string;
|
|
43
|
+
jwtAlgorithm: string;
|
|
44
|
+
refreshEnabled: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface AccessiTenantResolution {
|
|
47
|
+
tenant: AccessiTenant;
|
|
48
|
+
matchedBy: 'host' | 'path' | 'header';
|
|
49
|
+
}
|
|
50
|
+
export interface AccessiTenantRegistry {
|
|
51
|
+
directory: string;
|
|
52
|
+
tenants: AccessiTenant[];
|
|
53
|
+
warnings: string[];
|
|
54
|
+
allowHeader: boolean;
|
|
55
|
+
byId: ReadonlyMap<string, AccessiTenant>;
|
|
56
|
+
resolve(input: {
|
|
57
|
+
host?: string;
|
|
58
|
+
path?: string;
|
|
59
|
+
header?: string;
|
|
60
|
+
}, options?: {
|
|
61
|
+
allowHeader?: boolean;
|
|
62
|
+
}): AccessiTenantResolution | undefined;
|
|
63
|
+
summary(): AccessiTenantSummary[];
|
|
64
|
+
}
|
|
65
|
+
export interface LoadAccessiTenantsOptions {
|
|
66
|
+
/** Cartella con `<tenant>.env`. */
|
|
67
|
+
directory: string;
|
|
68
|
+
/** Variabili base (default `process.env`); il file tenant vince. */
|
|
69
|
+
env?: NodeJS.ProcessEnv;
|
|
70
|
+
/** Abilita la risoluzione via header `x-accessi-tenant`. Default: false. */
|
|
71
|
+
allowHeader?: boolean;
|
|
72
|
+
/** Consente issuer/audience uguali tra tenant (sconsigliato). Default: false. */
|
|
73
|
+
allowSharedIssuer?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** Errore aggregato di configurazione tenant. */
|
|
76
|
+
export declare class AccessiTenantConfigurationError extends Error {
|
|
77
|
+
readonly issues: string[];
|
|
78
|
+
constructor(issues: string[]);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parser minimale di un file `.env`.
|
|
82
|
+
*
|
|
83
|
+
* Supporta commenti, `export KEY=...`, valori tra apici singoli o doppi e le
|
|
84
|
+
* sequenze `\n`, `\r`, `\t`, `\"` e `\\` nei valori tra doppi apici. Non
|
|
85
|
+
* interpreta variabili annidate: la configurazione tenant deve restare esplicita.
|
|
86
|
+
*/
|
|
87
|
+
export declare function parseAccessiTenantEnvFile(content: string): Record<string, string>;
|
|
88
|
+
/** Prefisso Redis consigliato per isolare gli store del tenant. */
|
|
89
|
+
export declare function getAccessiTenantRedisPrefix(tenantId: string): string;
|
|
90
|
+
/**
|
|
91
|
+
* Carica i tenant da una cartella di file `<id>.env`.
|
|
92
|
+
*
|
|
93
|
+
* Non modifica `process.env`: ogni tenant riceve una copia isolata delle
|
|
94
|
+
* variabili. Un errore in un file blocca l'intero caricamento, cosi non si
|
|
95
|
+
* avvia un tenant con configurazione parziale.
|
|
96
|
+
*/
|
|
97
|
+
export declare function loadAccessiTenants(options: LoadAccessiTenantsOptions): AccessiTenantRegistry;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AccessiTenantConfigurationError = void 0;
|
|
4
|
+
exports.parseAccessiTenantEnvFile = parseAccessiTenantEnvFile;
|
|
5
|
+
exports.getAccessiTenantRedisPrefix = getAccessiTenantRedisPrefix;
|
|
6
|
+
exports.loadAccessiTenants = loadAccessiTenants;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const accessiOptionsFromEnv_1 = require("../../accessi-module/database-updates/accessiOptionsFromEnv");
|
|
10
|
+
/** Errore aggregato di configurazione tenant. */
|
|
11
|
+
class AccessiTenantConfigurationError extends Error {
|
|
12
|
+
constructor(issues) {
|
|
13
|
+
super(`Configurazione tenant non valida:\n${issues.map((issue) => `- ${issue}`).join('\n')}`);
|
|
14
|
+
this.name = 'AccessiTenantConfigurationError';
|
|
15
|
+
this.issues = issues;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.AccessiTenantConfigurationError = AccessiTenantConfigurationError;
|
|
19
|
+
/**
|
|
20
|
+
* Parser minimale di un file `.env`.
|
|
21
|
+
*
|
|
22
|
+
* Supporta commenti, `export KEY=...`, valori tra apici singoli o doppi e le
|
|
23
|
+
* sequenze `\n`, `\r`, `\t`, `\"` e `\\` nei valori tra doppi apici. Non
|
|
24
|
+
* interpreta variabili annidate: la configurazione tenant deve restare esplicita.
|
|
25
|
+
*/
|
|
26
|
+
function parseAccessiTenantEnvFile(content) {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
const env = {};
|
|
29
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
30
|
+
const line = rawLine.trim();
|
|
31
|
+
if (line === '' || line.startsWith('#'))
|
|
32
|
+
continue;
|
|
33
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
34
|
+
if (!match)
|
|
35
|
+
throw new Error(`Riga .env non valida: "${rawLine}"`);
|
|
36
|
+
const key = (_a = match[1]) !== null && _a !== void 0 ? _a : '';
|
|
37
|
+
let value = (_b = match[2]) !== null && _b !== void 0 ? _b : '';
|
|
38
|
+
if (value.startsWith('"')) {
|
|
39
|
+
if (!value.endsWith('"') || value.length < 2)
|
|
40
|
+
throw new Error(`${key}: virgolette non chiuse.`);
|
|
41
|
+
value = value.slice(1, -1)
|
|
42
|
+
.replace(/\\n/g, '\n')
|
|
43
|
+
.replace(/\\r/g, '\r')
|
|
44
|
+
.replace(/\\t/g, '\t')
|
|
45
|
+
.replace(/\\"/g, '"')
|
|
46
|
+
.replace(/\\\\/g, '\\');
|
|
47
|
+
}
|
|
48
|
+
else if (value.startsWith("'")) {
|
|
49
|
+
if (!value.endsWith("'") || value.length < 2)
|
|
50
|
+
throw new Error(`${key}: virgolette non chiuse.`);
|
|
51
|
+
value = value.slice(1, -1);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const comment = value.indexOf(' #');
|
|
55
|
+
if (comment >= 0)
|
|
56
|
+
value = value.slice(0, comment);
|
|
57
|
+
value = value.trim();
|
|
58
|
+
}
|
|
59
|
+
env[key] = value;
|
|
60
|
+
}
|
|
61
|
+
return env;
|
|
62
|
+
}
|
|
63
|
+
function splitList(value) {
|
|
64
|
+
return (value !== null && value !== void 0 ? value : '')
|
|
65
|
+
.split(',')
|
|
66
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
67
|
+
.filter(Boolean);
|
|
68
|
+
}
|
|
69
|
+
function normalizeHost(host) {
|
|
70
|
+
return host.trim().toLowerCase().replace(/\.$/, '');
|
|
71
|
+
}
|
|
72
|
+
function normalizeTenantPath(path) {
|
|
73
|
+
if (path === undefined || path.trim() === '')
|
|
74
|
+
return undefined;
|
|
75
|
+
const normalized = `/${path.trim().replace(/^\/+|\/+$/g, '')}`;
|
|
76
|
+
return normalized === '/' ? '/' : normalized;
|
|
77
|
+
}
|
|
78
|
+
function matchesHost(tenant, host) {
|
|
79
|
+
return tenant.hosts.some((candidate) => {
|
|
80
|
+
if (candidate === host)
|
|
81
|
+
return true;
|
|
82
|
+
if (candidate.startsWith('*.')) {
|
|
83
|
+
const suffix = candidate.slice(1);
|
|
84
|
+
return host.endsWith(suffix) && host.length > suffix.length;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function matchesPath(tenant, path) {
|
|
90
|
+
if (!tenant.path)
|
|
91
|
+
return false;
|
|
92
|
+
if (tenant.path === '/')
|
|
93
|
+
return true;
|
|
94
|
+
return path === tenant.path || path.startsWith(`${tenant.path}/`);
|
|
95
|
+
}
|
|
96
|
+
/** Prefisso Redis consigliato per isolare gli store del tenant. */
|
|
97
|
+
function getAccessiTenantRedisPrefix(tenantId) {
|
|
98
|
+
return `accessi:${tenantId}:`;
|
|
99
|
+
}
|
|
100
|
+
function tenantSummary(tenant) {
|
|
101
|
+
var _a;
|
|
102
|
+
const database = tenant.options.databaseOptions;
|
|
103
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ id: tenant.id, hosts: [...tenant.hosts] }, (tenant.path ? { path: tenant.path } : {})), { enabled: tenant.enabled }), (tenant.description ? { description: tenant.description } : {})), (tenant.port ? { port: tenant.port } : {})), { file: tenant.file, database: {
|
|
104
|
+
host: database.host,
|
|
105
|
+
port: database.port,
|
|
106
|
+
database: database.database,
|
|
107
|
+
user: database.user,
|
|
108
|
+
}, issuer: tenant.options.jwtOptions.issuer }), (tenant.options.jwtOptions.audience
|
|
109
|
+
? { audience: typeof tenant.options.jwtOptions.audience === 'string' ? tenant.options.jwtOptions.audience : tenant.options.jwtOptions.audience.join(',') }
|
|
110
|
+
: {})), { jwtAlgorithm: (_a = tenant.options.jwtOptions.algorithm) !== null && _a !== void 0 ? _a : 'HS256', refreshEnabled: Boolean(tenant.options.jwtOptions.refreshExpiresIn) });
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Carica i tenant da una cartella di file `<id>.env`.
|
|
114
|
+
*
|
|
115
|
+
* Non modifica `process.env`: ogni tenant riceve una copia isolata delle
|
|
116
|
+
* variabili. Un errore in un file blocca l'intero caricamento, cosi non si
|
|
117
|
+
* avvia un tenant con configurazione parziale.
|
|
118
|
+
*/
|
|
119
|
+
function loadAccessiTenants(options) {
|
|
120
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
121
|
+
if (!(options === null || options === void 0 ? void 0 : options.directory))
|
|
122
|
+
throw new Error('loadAccessiTenants: directory obbligatoria.');
|
|
123
|
+
const directory = (0, path_1.resolve)(options.directory);
|
|
124
|
+
const baseEnv = (_a = options.env) !== null && _a !== void 0 ? _a : process.env;
|
|
125
|
+
const issues = [];
|
|
126
|
+
const warnings = [];
|
|
127
|
+
let files;
|
|
128
|
+
try {
|
|
129
|
+
files = (0, fs_1.readdirSync)(directory, { withFileTypes: true })
|
|
130
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.env'))
|
|
131
|
+
.map((entry) => entry.name)
|
|
132
|
+
.sort();
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
throw new AccessiTenantConfigurationError([
|
|
136
|
+
`Cartella tenant non leggibile (${directory}): ${error instanceof Error ? error.message : String(error)}`,
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
if (files.length === 0) {
|
|
140
|
+
throw new AccessiTenantConfigurationError([`Nessun file .env nella cartella ${directory}.`]);
|
|
141
|
+
}
|
|
142
|
+
const tenants = [];
|
|
143
|
+
for (const file of files) {
|
|
144
|
+
let fileEnv;
|
|
145
|
+
try {
|
|
146
|
+
fileEnv = parseAccessiTenantEnvFile((0, fs_1.readFileSync)((0, path_1.join)(directory, file), 'utf8'));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
issues.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const env = Object.assign(Object.assign({}, baseEnv), fileEnv);
|
|
153
|
+
const id = ((_b = env.ACCESSI_TENANT_ID) !== null && _b !== void 0 ? _b : (0, path_1.basename)(file, '.env')).trim();
|
|
154
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,62}$/.test(id)) {
|
|
155
|
+
issues.push(`${file}: ACCESSI_TENANT_ID "${id}" non valido (minuscole, numeri, - e _, max 63).`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const hosts = splitList(env.ACCESSI_TENANT_HOSTS).map(normalizeHost);
|
|
159
|
+
const path = normalizeTenantPath(env.ACCESSI_TENANT_PATH);
|
|
160
|
+
if (hosts.length === 0 && !path) {
|
|
161
|
+
issues.push(`${file}: configurare ACCESSI_TENANT_HOSTS oppure ACCESSI_TENANT_PATH.`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const portValue = env.ACCESSI_TENANT_PORT;
|
|
165
|
+
const port = portValue ? Number(portValue) : undefined;
|
|
166
|
+
if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
167
|
+
issues.push(`${file}: ACCESSI_TENANT_PORT non valida ("${portValue}").`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const accessiOptions = (0, accessiOptionsFromEnv_1.createAccessiOptionsFromEnv)(env);
|
|
172
|
+
tenants.push(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ id,
|
|
173
|
+
hosts }, (path ? { path } : {})), { enabled: env.ACCESSI_TENANT_ENABLED !== 'false' }), (env.ACCESSI_TENANT_DESCRIPTION ? { description: env.ACCESSI_TENANT_DESCRIPTION } : {})), (port ? { port } : {})), { file,
|
|
174
|
+
env, options: Object.assign(Object.assign({}, accessiOptions), { tenantId: id }) }));
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
issues.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const byId = new Map();
|
|
181
|
+
const hostsSeen = new Map();
|
|
182
|
+
const pathsSeen = new Map();
|
|
183
|
+
const jwtSecrets = new Map();
|
|
184
|
+
const issuerAudience = new Map();
|
|
185
|
+
for (const tenant of tenants) {
|
|
186
|
+
if (byId.has(tenant.id)) {
|
|
187
|
+
issues.push(`Tenant duplicato "${tenant.id}" (${tenant.file} e ${(_c = byId.get(tenant.id)) === null || _c === void 0 ? void 0 : _c.file}).`);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
byId.set(tenant.id, tenant);
|
|
191
|
+
}
|
|
192
|
+
for (const host of tenant.hosts) {
|
|
193
|
+
const existing = hostsSeen.get(host);
|
|
194
|
+
if (existing)
|
|
195
|
+
issues.push(`Host "${host}" assegnato a "${existing}" e "${tenant.id}".`);
|
|
196
|
+
else
|
|
197
|
+
hostsSeen.set(host, tenant.id);
|
|
198
|
+
}
|
|
199
|
+
if (tenant.path) {
|
|
200
|
+
const existing = pathsSeen.get(tenant.path);
|
|
201
|
+
if (existing)
|
|
202
|
+
issues.push(`Path "${tenant.path}" assegnato a "${existing}" e "${tenant.id}".`);
|
|
203
|
+
else
|
|
204
|
+
pathsSeen.set(tenant.path, tenant.id);
|
|
205
|
+
}
|
|
206
|
+
// Un segreto JWT condiviso rende un token valido su piu tenant.
|
|
207
|
+
const signingKey = (_d = tenant.options.jwtOptions.privateKey) !== null && _d !== void 0 ? _d : tenant.options.jwtOptions.secret;
|
|
208
|
+
const fingerprintKey = `${(_e = tenant.options.jwtOptions.algorithm) !== null && _e !== void 0 ? _e : 'HS256'}|${signingKey}`;
|
|
209
|
+
const existingKey = jwtSecrets.get(fingerprintKey);
|
|
210
|
+
if (existingKey) {
|
|
211
|
+
issues.push(`Chiave JWT condivisa tra "${existingKey}" e "${tenant.id}": usare segreti distinti.`);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
jwtSecrets.set(fingerprintKey, tenant.id);
|
|
215
|
+
}
|
|
216
|
+
const issuer = (_f = tenant.options.jwtOptions.issuer) !== null && _f !== void 0 ? _f : '(assente)';
|
|
217
|
+
const audience = Array.isArray(tenant.options.jwtOptions.audience)
|
|
218
|
+
? tenant.options.jwtOptions.audience.join(',')
|
|
219
|
+
: ((_g = tenant.options.jwtOptions.audience) !== null && _g !== void 0 ? _g : '(assente)');
|
|
220
|
+
const issuerKey = `${issuer}|${audience}`;
|
|
221
|
+
const existingIssuer = issuerAudience.get(issuerKey);
|
|
222
|
+
if (existingIssuer && options.allowSharedIssuer !== true) {
|
|
223
|
+
warnings.push(`Issuer/audience "${issuerKey}" condivisi tra "${existingIssuer}" e "${tenant.id}".`);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
issuerAudience.set(issuerKey, tenant.id);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const enabled = tenants.filter((tenant) => tenant.enabled);
|
|
230
|
+
if (enabled.length > 1) {
|
|
231
|
+
const defaultTenant = enabled.find((tenant) => tenant.hosts.length === 0 && tenant.path === '/');
|
|
232
|
+
if (defaultTenant) {
|
|
233
|
+
issues.push(`Tenant "${defaultTenant.id}" intercetta tutte le richieste (path "/") insieme ad altri tenant.`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (issues.length > 0)
|
|
237
|
+
throw new AccessiTenantConfigurationError(issues);
|
|
238
|
+
const registry = {
|
|
239
|
+
directory,
|
|
240
|
+
tenants,
|
|
241
|
+
warnings,
|
|
242
|
+
allowHeader: options.allowHeader === true,
|
|
243
|
+
byId,
|
|
244
|
+
resolve(input, resolveOptions) {
|
|
245
|
+
var _a, _b, _c;
|
|
246
|
+
const allowHeader = (_a = resolveOptions === null || resolveOptions === void 0 ? void 0 : resolveOptions.allowHeader) !== null && _a !== void 0 ? _a : registry.allowHeader;
|
|
247
|
+
const host = input.host ? normalizeHost((_b = input.host.split(':')[0]) !== null && _b !== void 0 ? _b : input.host) : undefined;
|
|
248
|
+
if (host) {
|
|
249
|
+
const byHost = enabled.find((tenant) => matchesHost(tenant, host));
|
|
250
|
+
if (byHost)
|
|
251
|
+
return { tenant: byHost, matchedBy: 'host' };
|
|
252
|
+
}
|
|
253
|
+
const requestPath = (_c = input.path) !== null && _c !== void 0 ? _c : '/';
|
|
254
|
+
const byPath = enabled
|
|
255
|
+
.filter((tenant) => matchesPath(tenant, requestPath))
|
|
256
|
+
.sort((left, right) => { var _a, _b, _c, _d; return ((_b = (_a = right.path) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) - ((_d = (_c = left.path) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0); })[0];
|
|
257
|
+
if (byPath && byPath.path !== '/')
|
|
258
|
+
return { tenant: byPath, matchedBy: 'path' };
|
|
259
|
+
if (allowHeader && input.header) {
|
|
260
|
+
const headerId = input.header.trim().toLowerCase();
|
|
261
|
+
const tenant = byId.get(headerId);
|
|
262
|
+
if (tenant === null || tenant === void 0 ? void 0 : tenant.enabled)
|
|
263
|
+
return { tenant, matchedBy: 'header' };
|
|
264
|
+
}
|
|
265
|
+
if (byPath)
|
|
266
|
+
return { tenant: byPath, matchedBy: 'path' };
|
|
267
|
+
return undefined;
|
|
268
|
+
},
|
|
269
|
+
summary() {
|
|
270
|
+
return tenants.map(tenantSummary);
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
return registry;
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=tenants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenants.js","sourceRoot":"","sources":["../../../src/accessi/server/tenants.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync, readFileSync } from 'fs';\nimport { basename, join, resolve } from 'path';\nimport type { AccessiOptions } from '../../accessi-module/AccessiModule';\nimport { createAccessiOptionsFromEnv } from '../../accessi-module/database-updates/accessiOptionsFromEnv';\n\n/** Variabili usate dal manifest del tenant (oltre alle normali `ACCESSI_*`). */\nexport interface AccessiTenantEnv extends NodeJS.ProcessEnv {\n ACCESSI_TENANT_ID?: string;\n /** Host separati da virgola; supporta `*.example.com`. */\n ACCESSI_TENANT_HOSTS?: string;\n /** Prefisso path, es. `/tenant-a`. */\n ACCESSI_TENANT_PATH?: string;\n ACCESSI_TENANT_ENABLED?: string;\n /** Porta del servizio quando si usa `startAccessiTenants`. */\n ACCESSI_TENANT_PORT?: string;\n ACCESSI_TENANT_DESCRIPTION?: string;\n}\n\nexport interface AccessiTenant {\n id: string;\n hosts: string[];\n path?: string;\n enabled: boolean;\n description?: string;\n port?: number;\n /** Nome del file `.env` di origine (mai il contenuto). */\n file: string;\n /** Variabili risolte del tenant (base + file); non serializzarle nei log. */\n env: NodeJS.ProcessEnv;\n options: AccessiOptions;\n}\n\nexport interface AccessiTenantSummary {\n id: string;\n hosts: string[];\n path?: string;\n enabled: boolean;\n description?: string;\n port?: number;\n file: string;\n database: {\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n };\n issuer?: string;\n audience?: string;\n jwtAlgorithm: string;\n refreshEnabled: boolean;\n}\n\nexport interface AccessiTenantResolution {\n tenant: AccessiTenant;\n matchedBy: 'host' | 'path' | 'header';\n}\n\nexport interface AccessiTenantRegistry {\n directory: string;\n tenants: AccessiTenant[];\n warnings: string[];\n allowHeader: boolean;\n byId: ReadonlyMap<string, AccessiTenant>;\n resolve(input: { host?: string; path?: string; header?: string }, options?: { allowHeader?: boolean }): AccessiTenantResolution | undefined;\n summary(): AccessiTenantSummary[];\n}\n\nexport interface LoadAccessiTenantsOptions {\n /** Cartella con `<tenant>.env`. */\n directory: string;\n /** Variabili base (default `process.env`); il file tenant vince. */\n env?: NodeJS.ProcessEnv;\n /** Abilita la risoluzione via header `x-accessi-tenant`. Default: false. */\n allowHeader?: boolean;\n /** Consente issuer/audience uguali tra tenant (sconsigliato). Default: false. */\n allowSharedIssuer?: boolean;\n}\n\n/** Errore aggregato di configurazione tenant. */\nexport class AccessiTenantConfigurationError extends Error {\n readonly issues: string[];\n\n constructor(issues: string[]) {\n super(`Configurazione tenant non valida:\\n${issues.map((issue) => `- ${issue}`).join('\\n')}`);\n this.name = 'AccessiTenantConfigurationError';\n this.issues = issues;\n }\n}\n\n/**\n * Parser minimale di un file `.env`.\n *\n * Supporta commenti, `export KEY=...`, valori tra apici singoli o doppi e le\n * sequenze `\\n`, `\\r`, `\\t`, `\\\"` e `\\\\` nei valori tra doppi apici. Non\n * interpreta variabili annidate: la configurazione tenant deve restare esplicita.\n */\nexport function parseAccessiTenantEnvFile(content: string): Record<string, string> {\n const env: Record<string, string> = {};\n for (const rawLine of content.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line === '' || line.startsWith('#')) continue;\n const match = /^(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*)$/.exec(line);\n if (!match) throw new Error(`Riga .env non valida: \"${rawLine}\"`);\n const key = match[1] ?? '';\n let value = match[2] ?? '';\n if (value.startsWith('\"')) {\n if (!value.endsWith('\"') || value.length < 2) throw new Error(`${key}: virgolette non chiuse.`);\n value = value.slice(1, -1)\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\\\\\/g, '\\\\');\n } else if (value.startsWith(\"'\")) {\n if (!value.endsWith(\"'\") || value.length < 2) throw new Error(`${key}: virgolette non chiuse.`);\n value = value.slice(1, -1);\n } else {\n const comment = value.indexOf(' #');\n if (comment >= 0) value = value.slice(0, comment);\n value = value.trim();\n }\n env[key] = value;\n }\n return env;\n}\n\nfunction splitList(value: string | undefined): string[] {\n return (value ?? '')\n .split(',')\n .map((entry) => entry.trim().toLowerCase())\n .filter(Boolean);\n}\n\nfunction normalizeHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.$/, '');\n}\n\nfunction normalizeTenantPath(path: string | undefined): string | undefined {\n if (path === undefined || path.trim() === '') return undefined;\n const normalized = `/${path.trim().replace(/^\\/+|\\/+$/g, '')}`;\n return normalized === '/' ? '/' : normalized;\n}\n\nfunction matchesHost(tenant: AccessiTenant, host: string): boolean {\n return tenant.hosts.some((candidate) => {\n if (candidate === host) return true;\n if (candidate.startsWith('*.')) {\n const suffix = candidate.slice(1);\n return host.endsWith(suffix) && host.length > suffix.length;\n }\n return false;\n });\n}\n\nfunction matchesPath(tenant: AccessiTenant, path: string): boolean {\n if (!tenant.path) return false;\n if (tenant.path === '/') return true;\n return path === tenant.path || path.startsWith(`${tenant.path}/`);\n}\n\n/** Prefisso Redis consigliato per isolare gli store del tenant. */\nexport function getAccessiTenantRedisPrefix(tenantId: string): string {\n return `accessi:${tenantId}:`;\n}\n\nfunction tenantSummary(tenant: AccessiTenant): AccessiTenantSummary {\n const database = tenant.options.databaseOptions as {\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n };\n return {\n id: tenant.id,\n hosts: [...tenant.hosts],\n ...(tenant.path ? { path: tenant.path } : {}),\n enabled: tenant.enabled,\n ...(tenant.description ? { description: tenant.description } : {}),\n ...(tenant.port ? { port: tenant.port } : {}),\n file: tenant.file,\n database: {\n host: database.host,\n port: database.port,\n database: database.database,\n user: database.user,\n },\n issuer: tenant.options.jwtOptions.issuer,\n ...(tenant.options.jwtOptions.audience\n ? { audience: typeof tenant.options.jwtOptions.audience === 'string' ? tenant.options.jwtOptions.audience : tenant.options.jwtOptions.audience.join(',') }\n : {}),\n jwtAlgorithm: tenant.options.jwtOptions.algorithm ?? 'HS256',\n refreshEnabled: Boolean(tenant.options.jwtOptions.refreshExpiresIn),\n };\n}\n\n/**\n * Carica i tenant da una cartella di file `<id>.env`.\n *\n * Non modifica `process.env`: ogni tenant riceve una copia isolata delle\n * variabili. Un errore in un file blocca l'intero caricamento, cosi non si\n * avvia un tenant con configurazione parziale.\n */\nexport function loadAccessiTenants(options: LoadAccessiTenantsOptions): AccessiTenantRegistry {\n if (!options?.directory) throw new Error('loadAccessiTenants: directory obbligatoria.');\n const directory = resolve(options.directory);\n const baseEnv = options.env ?? process.env;\n const issues: string[] = [];\n const warnings: string[] = [];\n\n let files: string[];\n try {\n files = readdirSync(directory, { withFileTypes: true })\n .filter((entry) => entry.isFile() && entry.name.endsWith('.env'))\n .map((entry) => entry.name)\n .sort();\n } catch (error) {\n throw new AccessiTenantConfigurationError([\n `Cartella tenant non leggibile (${directory}): ${error instanceof Error ? error.message : String(error)}`,\n ]);\n }\n if (files.length === 0) {\n throw new AccessiTenantConfigurationError([`Nessun file .env nella cartella ${directory}.`]);\n }\n\n const tenants: AccessiTenant[] = [];\n for (const file of files) {\n let fileEnv: Record<string, string>;\n try {\n fileEnv = parseAccessiTenantEnvFile(readFileSync(join(directory, file), 'utf8'));\n } catch (error) {\n issues.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);\n continue;\n }\n\n const env: NodeJS.ProcessEnv = { ...baseEnv, ...fileEnv };\n const id = (env.ACCESSI_TENANT_ID ?? basename(file, '.env')).trim();\n if (!/^[a-z0-9][a-z0-9_-]{0,62}$/.test(id)) {\n issues.push(`${file}: ACCESSI_TENANT_ID \"${id}\" non valido (minuscole, numeri, - e _, max 63).`);\n continue;\n }\n const hosts = splitList(env.ACCESSI_TENANT_HOSTS).map(normalizeHost);\n const path = normalizeTenantPath(env.ACCESSI_TENANT_PATH);\n if (hosts.length === 0 && !path) {\n issues.push(`${file}: configurare ACCESSI_TENANT_HOSTS oppure ACCESSI_TENANT_PATH.`);\n continue;\n }\n const portValue = env.ACCESSI_TENANT_PORT;\n const port = portValue ? Number(portValue) : undefined;\n if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535)) {\n issues.push(`${file}: ACCESSI_TENANT_PORT non valida (\"${portValue}\").`);\n continue;\n }\n\n try {\n const accessiOptions = createAccessiOptionsFromEnv(env);\n tenants.push({\n id,\n hosts,\n ...(path ? { path } : {}),\n enabled: env.ACCESSI_TENANT_ENABLED !== 'false',\n ...(env.ACCESSI_TENANT_DESCRIPTION ? { description: env.ACCESSI_TENANT_DESCRIPTION } : {}),\n ...(port ? { port } : {}),\n file,\n env,\n options: { ...accessiOptions, tenantId: id },\n });\n } catch (error) {\n issues.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n const byId = new Map<string, AccessiTenant>();\n const hostsSeen = new Map<string, string>();\n const pathsSeen = new Map<string, string>();\n const jwtSecrets = new Map<string, string>();\n const issuerAudience = new Map<string, string>();\n for (const tenant of tenants) {\n if (byId.has(tenant.id)) {\n issues.push(`Tenant duplicato \"${tenant.id}\" (${tenant.file} e ${byId.get(tenant.id)?.file}).`);\n } else {\n byId.set(tenant.id, tenant);\n }\n for (const host of tenant.hosts) {\n const existing = hostsSeen.get(host);\n if (existing) issues.push(`Host \"${host}\" assegnato a \"${existing}\" e \"${tenant.id}\".`);\n else hostsSeen.set(host, tenant.id);\n }\n if (tenant.path) {\n const existing = pathsSeen.get(tenant.path);\n if (existing) issues.push(`Path \"${tenant.path}\" assegnato a \"${existing}\" e \"${tenant.id}\".`);\n else pathsSeen.set(tenant.path, tenant.id);\n }\n\n // Un segreto JWT condiviso rende un token valido su piu tenant.\n const signingKey = tenant.options.jwtOptions.privateKey ?? tenant.options.jwtOptions.secret;\n const fingerprintKey = `${tenant.options.jwtOptions.algorithm ?? 'HS256'}|${signingKey}`;\n const existingKey = jwtSecrets.get(fingerprintKey);\n if (existingKey) {\n issues.push(`Chiave JWT condivisa tra \"${existingKey}\" e \"${tenant.id}\": usare segreti distinti.`);\n } else {\n jwtSecrets.set(fingerprintKey, tenant.id);\n }\n\n const issuer = tenant.options.jwtOptions.issuer ?? '(assente)';\n const audience = Array.isArray(tenant.options.jwtOptions.audience)\n ? tenant.options.jwtOptions.audience.join(',')\n : (tenant.options.jwtOptions.audience ?? '(assente)');\n const issuerKey = `${issuer}|${audience}`;\n const existingIssuer = issuerAudience.get(issuerKey);\n if (existingIssuer && options.allowSharedIssuer !== true) {\n warnings.push(`Issuer/audience \"${issuerKey}\" condivisi tra \"${existingIssuer}\" e \"${tenant.id}\".`);\n } else {\n issuerAudience.set(issuerKey, tenant.id);\n }\n }\n\n const enabled = tenants.filter((tenant) => tenant.enabled);\n if (enabled.length > 1) {\n const defaultTenant = enabled.find((tenant) => tenant.hosts.length === 0 && tenant.path === '/');\n if (defaultTenant) {\n issues.push(`Tenant \"${defaultTenant.id}\" intercetta tutte le richieste (path \"/\") insieme ad altri tenant.`);\n }\n }\n\n if (issues.length > 0) throw new AccessiTenantConfigurationError(issues);\n\n const registry: AccessiTenantRegistry = {\n directory,\n tenants,\n warnings,\n allowHeader: options.allowHeader === true,\n byId,\n resolve(input, resolveOptions) {\n const allowHeader = resolveOptions?.allowHeader ?? registry.allowHeader;\n const host = input.host ? normalizeHost(input.host.split(':')[0] ?? input.host) : undefined;\n if (host) {\n const byHost = enabled.find((tenant) => matchesHost(tenant, host));\n if (byHost) return { tenant: byHost, matchedBy: 'host' };\n }\n\n const requestPath = input.path ?? '/';\n const byPath = enabled\n .filter((tenant) => matchesPath(tenant, requestPath))\n .sort((left, right) => (right.path?.length ?? 0) - (left.path?.length ?? 0))[0];\n if (byPath && byPath.path !== '/') return { tenant: byPath, matchedBy: 'path' };\n\n if (allowHeader && input.header) {\n const headerId = input.header.trim().toLowerCase();\n const tenant = byId.get(headerId);\n if (tenant?.enabled) return { tenant, matchedBy: 'header' };\n }\n\n if (byPath) return { tenant: byPath, matchedBy: 'path' };\n return undefined;\n },\n summary() {\n return tenants.map(tenantSummary);\n },\n };\n\n return registry;\n}\n\n"]}
|
|
@@ -246,8 +246,12 @@ export interface AccessiOptions {
|
|
|
246
246
|
systemSeeds?: SystemSeedsOptions;
|
|
247
247
|
/** Bootstrap del primo admin tramite token casuale stampato a ogni avvio. */
|
|
248
248
|
adminBootstrap?: AdminBootstrapOptions;
|
|
249
|
-
/**
|
|
249
|
+
/**
|
|
250
|
+
* Audit persistente: sink, retention ed endpoint amministrativi di query/export.
|
|
251
|
+
*/
|
|
250
252
|
audit?: AccessiAuditOptions;
|
|
253
|
+
/** Identificativo del tenant quando la configurazione arriva da una cartella multi-tenant. */
|
|
254
|
+
tenantId?: string;
|
|
251
255
|
/** Base URL pubblica usata solo per loggare gli indirizzi (console, OpenAPI, API). */
|
|
252
256
|
publicBaseUrl?: string;
|
|
253
257
|
/** Base URL API per la console quando UI e API sono su domini diversi. */
|
|
@@ -50,9 +50,11 @@ let AccessiModule = AccessiModule_1 = class AccessiModule {
|
|
|
50
50
|
* La configurazione viene registrata con il token DI `ACCESSI_OPTIONS` ed e condivisa dai servizi.
|
|
51
51
|
*/
|
|
52
52
|
static forRoot(options) {
|
|
53
|
+
var _a, _b, _c;
|
|
53
54
|
// Il servizio email e obbligatorio: reset password, 2FA e creazione utente ne dipendono.
|
|
54
55
|
(0, emailConfiguration_1.assertEmailConfigured)(options);
|
|
55
|
-
(0, audit_1.configureAccessiAudit)(options.
|
|
56
|
+
(0, audit_1.configureAccessiAudit)(options.tenantId
|
|
57
|
+
? Object.assign(Object.assign({}, ((_a = options.audit) !== null && _a !== void 0 ? _a : {})), { tenantId: (_c = (_b = options.audit) === null || _b === void 0 ? void 0 : _b.tenantId) !== null && _c !== void 0 ? _c : options.tenantId }) : options.audit);
|
|
56
58
|
return {
|
|
57
59
|
module: AccessiModule_1,
|
|
58
60
|
providers: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccessiModule.js","sourceRoot":"","sources":["../../src/accessi-module/AccessiModule.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,mFAAgF;AAQhF,2CAA+D;AAU/D,oEAAiE;AACjE,uEAAoE;AACpE,sFAAmF;AACnF,oEAAiE;AACjE,mEAAgE;AAChE,iEAA8D;AAC9D,6EAA0E;AAC1E,iEAA8D;AAC9D,0EAAuE;AACvE,qEAAkE;AAClE,iFAA8E;AAC9E,4FAAyF;AACzF,qDAAoD;AACpD,kEAAsE;AACtE,sFAAmF;AACnF,gFAA6E;AAC7E,sFAAmF;AACnF,sGAAmG;AACnG,qFAAkF;AAClF,iFAA8E;AAC9E,iFAA8E;AAC9E,4FAAyF;AACzF,oEAAiE;AACjE,qEAAkE;AAClE,iGAA8F;AAC9F,4FAAyF;AACzF,kGAA+F;AAC/F,4DAAuE;AACvE,sEAAsE;AACtE,4CAAyD;AACzD,iFAA8E;AAiTvE,IAAM,aAAa,qBAAnB,MAAM,aAAa;IACxB;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,CAAC,OAAuB;QACpC,yFAAyF;QACzF,IAAA,0CAAqB,EAAC,OAAO,CAAC,CAAC;QAC/B,IAAA,6BAAqB,EAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACrC,OAAO;YACL,MAAM,EAAE,eAAa;YACrB,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,iBAAiB;oBAC1B,QAAQ,EAAE,OAAO;iBAClB;gBACD,yBAAW;gBACX,mCAAgB;gBAChB,yBAAW;gBACX,2BAAY;gBACZ,qCAAiB;gBACjB,6BAAa;gBACb,yCAAmB;gBACnB,6BAAc;gBACd,wCAAsB;gBACtB,+CAAsB;gBACtB,2CAAoB;gBACpB,yCAAmB;gBACnB,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,EAAE,2CAA2B;aAAC;YAC7F,OAAO,EAAE;gBACP,iBAAiB;gBACjB,yBAAW;gBACX,yBAAW;gBACX,2BAAY;gBACZ,qCAAiB;gBACjB,6BAAa;gBACb,yCAAmB;gBACnB,6BAAc;gBACd,wCAAsB;gBACtB,2CAAoB;gBACpB,yCAAmB;gBACnB,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB;aAAC;SACjE,CAAC;IACJ,CAAC;CACF,CAAA;AA/CY,sCAAa;wBAAb,aAAa;IArBzB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,WAAW,EAAE;YACX,iCAAe;YACf,+BAAc;YACd,2CAAoB;YACpB,+BAAc;YACd,mCAAgB;YAChB,+CAAsB;YACtB,iDAAuB;YACvB,mDAAwB;YACxB,+CAAsB;YACtB,iEAA+B;YAC/B,+CAAsB;YACtB,mCAAgB;YAChB,+DAA8B;YAC9B,+CAAsB;SACvB;QACD,SAAS,EAAE,CAAC,yBAAW,EAAE,mCAAgB,EAAE,yBAAW,EAAE,2BAAY,EAAE,qCAAiB,EAAE,6BAAa,EAAE,yCAAmB,EAAE,6BAAc,EAAE,wCAAsB,EAAE,+CAAsB,EAAE,2CAAoB,EAAE,yCAAmB,EAAE,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,EAAE,2CAA2B,CAAC;QACnU,OAAO,EAAE,CAAC,yBAAW,EAAE,yBAAW,EAAE,2BAAY,EAAE,qCAAiB,EAAE,6BAAa,EAAE,yCAAmB,EAAE,6BAAc,EAAE,wCAAsB,EAAE,2CAAoB,EAAE,yCAAmB,EAAE,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,CAAC;KAC3P,CAAC;GACW,aAAa,CA+CzB","sourcesContent":["import { TwoFactorService } from './Services/TwoFactorService/TwoFactorService';\n/**\n * Modulo che gestisce le operazioni di accesso degli utenti, incluse le rotte, il controller e il modello.\n *\n * @module AccessiModule\n * @author mttdev382\n */\nimport { Options } from 'node-firebird';\nimport { DynamicModule, Global, Module } from '@nestjs/common';\nimport type { INestApplication, LoggerService } from '@nestjs/common';\nimport type { OpenApiSetupOptions } from '../openapi/OpenApiConfig';\nimport type { AccessiAccessTokenAlgorithm, AccessiUnknownKeyIdEvent } from './security/jwtTokenOptions';\nimport type { ConsoleEntryStore } from './security/consoleEntry';\nimport type { RateLimitStore } from './security/rateLimitStore';\nimport type { AccessiEdgeRateLimitOptions } from './security/edgeRateLimit';\nimport type { SessionRevocationOptions } from './security/sessionRevocation';\nimport type { AccessTokenRevocationOptions } from './security/accessTokenRevocation';\nimport type { RefreshTokenRotationOptions } from './security/refreshTokenRotation';\nimport { AuthService } from './Services/AuthService/AuthService';\nimport { EmailService } from './Services/EmailService/EmailService';\nimport { PermissionService } from './Services/PermissionService/PermissionService';\nimport { UserService } from './Services/UserService/UserService';\nimport { EmailController } from './Controllers/EmailController';\nimport { AuthController } from './Controllers/AuthController';\nimport { PermissionController } from './Controllers/PermissionController';\nimport { UserController } from './Controllers/UserController';\nimport { FiltriService } from './Services/FiltriService/FiltriService';\nimport { FiltriController } from './Controllers/FiltriController';\nimport { ConfiguratorController } from './Controllers/ConfiguratorController';\nimport { ConfiguratorService } from './Services/ConfiguratorService/ConfiguratorService';\nimport { JwtSimpleGuard } from './jwt/jwt.strategy';\nimport { AuthenticateGenService } from './middleware/authenticateGen';\nimport { AccessiDatabaseUpdater } from './database-updates/AccessiDatabaseUpdater';\nimport { FederatedAuthService } from './federated-auth/FederatedAuthService';\nimport { FederatedAuthController } from './federated-auth/FederatedAuthController';\nimport { FederatedAuthExchangeController } from './federated-auth/FederatedAuthExchangeController';\nimport { AccessiConsoleController } from './Controllers/AccessiConsoleController';\nimport { ConsoleEntryController } from './Controllers/ConsoleEntryController';\nimport { ServiceTokenController } from './Controllers/ServiceTokenController';\nimport { ServiceTokenService } from './Services/ServiceTokenService/ServiceTokenService';\nimport { ServiceTokenGuard } from './security/serviceTokenGuard';\nimport { SchemaController } from './Controllers/SchemaController';\nimport { AccessiIntrospectionController } from './Controllers/AccessiIntrospectionController';\nimport { SchemaExportService } from './Services/SchemaExportService/SchemaExportService';\nimport { AdminBootstrapService } from './Services/AdminBootstrapService/AdminBootstrapService';\nimport { AccessiJwksBootstrapService } from './security/jwksBootstrap';\nimport { assertEmailConfigured } from './security/emailConfiguration';\nimport { configureAccessiAudit } from './security/audit';\nimport { AccessiAuditController } from './Controllers/AccessiAuditController';\n\n/** JWT emesso da Accessi dopo un login locale o SSO. Non riutilizzare il segreto del provider SSO. */\nexport interface JwtOptions {\n /** Segreto simmetrico per firmare e verificare i JWT Accessi; deve essere unico per ambiente. */\n secret: string;\n /** Issuer (`iss`) opzionale degli access token. Consigliato in una topologia multi-servizio. */\n issuer?: string;\n /** Audience (`aud`) opzionale degli access token. */\n audience?: string | string[];\n /** Algoritmo degli access token. Default `HS256` per retrocompatibilita. */\n algorithm?: AccessiAccessTokenAlgorithm;\n /** Chiave privata PEM per RS/ES. Obbligatoria quando `algorithm` non e HS*. */\n privateKey?: string;\n /** Chiave pubblica PEM per la verifica RS/ES. */\n publicKey?: string;\n /** Key id pubblicato nella JWKS, per rotazione chiavi. */\n keyId?: string;\n /** Path pubblico della JWKS. Default `accessi/.well-known/jwks.json` con algoritmi asimmetrici. */\n jwksPath?: string;\n /**\n * Chiavi pubbliche precedenti, esposte in JWKS e accettate in verifica\n * durante la rotazione. Se `keyId` e assente si usa il fingerprint della\n * chiave, lo stesso calcolato dalla JWKS.\n */\n previousPublicKeys?: Array<{\n publicKey: string;\n keyId?: string;\n algorithm?: AccessiAccessTokenAlgorithm;\n }>;\n /**\n * Alert quando un token presenta un `kid` assente da chiave corrente e\n * chiavi precedenti (tipico di una rotazione non completata).\n */\n onUnknownKeyId?: (event: AccessiUnknownKeyIdEvent) => void;\n /** Durata compatibile con `jsonwebtoken`, per esempio `24h` o `3600s`. */\n expiresIn: string;\n /**\n * Durata del refresh token. Se assente il refresh token non viene emesso.\n * Consigliato: `30d`.\n */\n refreshExpiresIn?: string;\n}\n\n/** Configurazione dei ticket di ingresso alla console. */\nexport interface ConsoleEntryOptions {\n /** Durata del ticket in millisecondi. Default 60000. */\n ttlMs?: number;\n /** Store condiviso (es. Redis) per deployment multi-istanza. Default: memoria. */\n store?: ConsoleEntryStore;\n}\n\nimport type { AccessiSecurityHeadersSetting } from './security/securityHeaders';\nimport type { AccessiAuthCookieOptions } from './security/authCookies';\nimport type { AccessiMetricsOptions } from './observability/metrics';\nimport type { AccessiAuditOptions } from './security/audit';\n\n/** Impostazioni HTTP del servizio Accessi. */\nexport interface AccessiHttpOptions {\n /** Opzioni CORS Nest. `false` disabilita CORS. Default: exposedHeaders ['X-Total-Count']. */\n cors?: Parameters<INestApplication['enableCors']>[0] | false;\n /** Trust proxy per IP reali dietro load balancer/reverse proxy. */\n trustProxy?: boolean | number | string;\n /** Header di sicurezza di base su console/OpenAPI. Default: true. */\n securityHeaders?: AccessiSecurityHeadersSetting;\n}\n\n/** Parametri SMTP usati esclusivamente per impostazione e reset della password. */\nexport interface EmailOptions {\n host: string;\n port: number;\n secure: boolean;\n requireTLS : boolean;\n tls: {\n rejectUnauthorized: boolean,\n }\n from: string;\n auth: {\n user: string;\n pass: string;\n };\n}\n\n/** Configura campi applicativi aggiuntivi restituiti insieme al profilo Accessi. */\nexport interface ExtensionFieldsOptions {\n /** Connessione al database contenente la tabella estesa. */\n databaseOptions: Options;\n /** Chiave con cui i dati saranno esposti nella risposta. */\n objectKey: string;\n /** Tabella applicativa contenente i dati estesi. */\n tableName: string;\n /** Colonne consentite; non valorizzarle mai con input HTTP. */\n tableFields: string[];\n /** Colonna correlata a `UTENTI.CODUTE`. */\n tableJoinFieldName: string;\n}\n\n/** Limite per una singola categoria di endpoint pubblici. */\nexport interface PublicAuthRateLimitRuleOptions {\n windowMs: number;\n maxAttempts: number;\n}\n\n/** Protezione degli endpoint pubblici. Default in memoria; in cluster usare uno store condiviso. */\nexport interface PublicAuthRateLimitOptions {\n enabled?: boolean;\n /** Store condiviso (es. Redis). Default: memoria del processo. */\n store?: RateLimitStore;\n login?: PublicAuthRateLimitRuleOptions;\n register?: PublicAuthRateLimitRuleOptions;\n passwordResetEmail?: PublicAuthRateLimitRuleOptions;\n passwordResetConfirm?: PublicAuthRateLimitRuleOptions;\n refresh?: PublicAuthRateLimitRuleOptions;\n getUserByToken?: PublicAuthRateLimitRuleOptions;\n twoFactorVerify?: PublicAuthRateLimitRuleOptions;\n twoFactorResend?: PublicAuthRateLimitRuleOptions;\n adminBootstrap?: PublicAuthRateLimitRuleOptions;\n consoleEntry?: PublicAuthRateLimitRuleOptions;\n}\n\n/**\n * Bootstrap del primo admin. Se abilitato, Accessi genera un token casuale a ogni avvio del\n * processo, lo stampa nel log e lo accetta sull'endpoint pubblico `POST /accessi/auth/bootstrap-admin`.\n * Serve a creare il primo utente admin senza email di conferma e senza un admin gia esistente.\n */\nexport interface AdminBootstrapOptions {\n /** Abilita il bootstrap admin. Default `false`. */\n enabled?: boolean;\n /** Crea l'utente come superutente oltre che admin. Default `true`. */\n superUser?: boolean;\n}\n\n/** Controls the legacy public username/password registration endpoint. Disabled by default. */\nexport interface PublicRegistrationOptions {\n /** Enables POST /accessi/user/register. Applications that used it before must opt in explicitly. */\n enabled?: boolean;\n}\n\n/** Configurazione dei token di servizio (macchina-a-macchina, senza utente). */\nexport interface ServiceTokenOptions {\n /** Abilita gli endpoint amministrativi dei service token. Default `true`. */\n enabled?: boolean;\n /** Durata predefinita in giorni quando non e indicata una scadenza esplicita. Default: nessuna scadenza. */\n defaultTtlDays?: number;\n /** Store condiviso per il rate limit dei service token. Default: memoria. */\n rateLimitStore?: RateLimitStore;\n}\n\n/** Cosa fare quando uno script gia applicato presenta un checksum diverso (file modificato). */\nexport type CatalogScriptChecksumPolicy = 'error' | 'warn' | 'reapply';\n\n/**\n * Catalog migrations applicative (menu, ruoli, tipi, cataloghi). Gli script `.sql` vivono nel\n * backend ospitante (versionati in repository) e vengono eseguiti da Accessi dopo la\n * riconciliazione dello schema. Ogni script e tracciato nella tabella `ACCESSI_CATALOG_SCRIPT`,\n * quindi viene applicato una sola volta per database (idempotenza lato DB).\n */\nexport interface SystemSeedsOptions {\n /** `false` disabilita il seed automatico, ad esempio per ambienti gestiti manualmente. */\n enabled?: boolean;\n}\n\nexport interface CatalogScriptsOptions {\n /** Abilita l'esecuzione automatica al bootstrap. Default `false`. */\n enabled?: boolean;\n /** Cartella contenente gli script `.sql` (assoluta o relativa alla working directory). */\n folder: string;\n /** Cosa fare se il checksum di uno script applicato e cambiato. Default `error`. */\n onChecksumMismatch?: CatalogScriptChecksumPolicy;\n /** Se `true` (default) un errore interrompe l'esecuzione e non avanza allo script successivo. */\n stopOnError?: boolean;\n /** Globi/estensioni inclusi. Default: tutti i `.sql`. */\n include?: string[];\n /** Numero massimo di script eseguiti in un singolo run (protezione operativa). Default: illimitato. */\n maxScriptsPerRun?: number;\n /**\n * Permette statement DDL/schema negli script. Default `false`: le catalog migrations possono\n * modificare solo righe. Sconsigliato: lo schema e di competenza di `db:update:accessi`.\n */\n allowDdl?: boolean;\n}\n\n/**\n * Enables Accessi's provider-agnostic federated identity capability.\n *\n * Accessi never validates Azure, Google, Apple, SAML, or other provider tokens.\n * The hosting backend must validate its own SSO flow and pass only a verified\n * `{ provider, subject }` identity to FederatedAuthService.\n */\nexport interface FederatedAuthenticationOptions {\n /** Attiva le API SSO generiche; con `false` il comportamento password storico rimane invariato. */\n enabled?: boolean;\n /** @deprecated Schema management is centralized in autoUpdateDatabase. This legacy flag no longer starts separate SSO migrations. */\n autoUpdateSchema?: boolean;\n /** Allows a backend to create a user from a verified external identity without a master user. Default false. */\n allowSelfRegistration?: boolean;\n}\n\n/**\n * Configurazione completa del modulo Accessi, fornita esclusivamente dal backend ospitante.\n * Segreti JWT, SMTP, database e le configurazioni tecniche dei provider SSO non devono mai raggiungere il browser.\n */\nexport interface AccessiOptions {\n /** Connessione Firebird alla base dati Accessi. */\n databaseOptions: Options;\n /**\n * Basepath del sito es: 'http://www.il-mio-sito.it/nome-progetto(se c'è)'\n */\n confirmationEmailUrl: string;\n /**\n * Percorso della pagina di reset personalizzata es. http://localhost:4200/#/admin/reset-password\n * N.B si sostituisce al confirmationMailURl\n */\n customResetPage?: string;\n /** URL di ritorno della pagina standard di reset dopo il completamento. */\n confirmationEmailReturnUrl: string;\n /** Prefisso applicativo facoltativo trasmesso alla pagina standard di reset. */\n confirmationEmailPrefix?: string;\n /** Chiave riservata alla compatibilita e alla migrazione delle password legacy cifrate. */\n encryptionKey: string;\n /** Abilita gli utenti fittizi `admin` e `demo` solo fuori produzione. */\n mockDemoUser: boolean;\n /** Attiva il controllo della scadenza password durante il login locale. */\n passwordExpiration?: boolean;\n /** Giorni di validita della password; quando assente Accessi usa 90 giorni. */\n passwordExpirationDays?: number;\n /** Esegue al bootstrap la conversione non distruttiva delle password legacy. Default `true`. */\n legacyPasswordMigrationOnStartup?: boolean;\n /** Riconcilia lo schema al bootstrap (default `true`). Con `false` verifica comunque la compatibilita in sola lettura e blocca avvii incompatibili. */\n autoUpdateDatabase?: boolean;\n /** Parametri JWT dei token emessi da Accessi. */\n jwtOptions: JwtOptions;\n /** Parametri SMTP per reset e attivazione password. */\n emailOptions: EmailOptions;\n publicAuthRateLimit?: PublicAuthRateLimitOptions;\n /** Public local registration is deliberately opt-in. Existing applications must set enabled: true to retain it. */\n publicRegistration?: PublicRegistrationOptions;\n /** Optional provider-agnostic SSO support. Provider-specific configuration remains in the hosting backend. */\n federatedAuthentication?: FederatedAuthenticationOptions;\n extensionFieldsOptions?: ExtensionFieldsOptions[];\n /** Token di servizio per chiamate tecniche macchina-a-macchina. Abilitati di default. */\n serviceTokens?: ServiceTokenOptions;\n /** Catalog migrations applicative eseguite dopo la riconciliazione dello schema. */\n catalogScripts?: CatalogScriptsOptions;\n /** Seed di sistema eseguiti dopo la riconciliazione dello schema. Default: abilitati. */\n systemSeeds?: SystemSeedsOptions;\n /** Bootstrap del primo admin tramite token casuale stampato a ogni avvio. */\n adminBootstrap?: AdminBootstrapOptions;\n /** Audit persistente: sink, retention ed endpoint amministrativi di query/export. */\n audit?: AccessiAuditOptions;\n /** Base URL pubblica usata solo per loggare gli indirizzi (console, OpenAPI, API). */\n publicBaseUrl?: string;\n /** Base URL API per la console quando UI e API sono su domini diversi. */\n consoleApiBaseUrl?: string;\n /**\n * Documentazione OpenAPI del modulo. Default invariati:\n * UI `accessi/openapi`, JSON `accessi/openapi.json`.\n */\n openapi?: OpenApiSetupOptions;\n /** Logger Nest usato dal sotto-app Accessi. Se assente Nest usa il proprio logger. */\n logger?: LoggerService;\n /** Buffer dei log di bootstrap del sotto-app. Default `true` (comportamento storico). */\n bufferLogs?: boolean;\n /** Ticket di ingresso console: TTL e store condiviso per deployment multi-istanza. */\n consoleEntry?: ConsoleEntryOptions;\n /** Limite grossolano per IP davanti alle rotte Accessi. Opzionale. */\n edgeRateLimit?: AccessiEdgeRateLimitOptions;\n /** CORS, trust proxy e header di sicurezza del servizio. */\n http?: AccessiHttpOptions;\n /**\n * Autenticazione opzionale via cookie httpOnly. Bearer resta il default:\n * la modalita si abilita passando questo oggetto (o `enabled: true`).\n */\n authCookie?: AccessiAuthCookieOptions;\n /** Metriche di dominio (login, 2FA, refresh, rate limit, store, ...). */\n metrics?: AccessiMetricsOptions;\n /** Revoca sessioni per utente tramite store memory/Redis. */\n sessionRevocation?: SessionRevocationOptions;\n /** Revoca dei singoli access token tramite `jti`, memory/Redis. */\n accessTokenRevocation?: AccessTokenRevocationOptions;\n /** Refresh token monouso tramite rotazione memory/Redis; rileva il riuso e revoca la famiglia. */\n refreshTokenRotation?: RefreshTokenRotationOptions;\n}\n\n@Global()\n@Module({\n controllers: [\n EmailController,\n AuthController,\n PermissionController,\n UserController,\n FiltriController,\n ConfiguratorController,\n FederatedAuthController,\n AccessiConsoleController,\n ConsoleEntryController,\n FederatedAuthExchangeController,\n ServiceTokenController,\n SchemaController,\n AccessiIntrospectionController,\n AccessiAuditController,\n ],\n providers: [AuthService, TwoFactorService, UserService, EmailService, PermissionService, FiltriService, ConfiguratorService, JwtSimpleGuard, AuthenticateGenService, AccessiDatabaseUpdater, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, SchemaExportService, AdminBootstrapService, AccessiJwksBootstrapService],\n exports: [AuthService, UserService, EmailService, PermissionService, FiltriService, ConfiguratorService, JwtSimpleGuard, AuthenticateGenService, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, SchemaExportService, AdminBootstrapService],\n})\nexport class AccessiModule {\n /**\n * Registra Accessi nel contenitore Nest del backend ospitante.\n *\n * Usare questo metodo una sola volta nella composizione dell'applicazione. Per un backend\n * Express esistente usare invece `initializeAccessiModule`, che crea e monta l'host Nest interno.\n * La configurazione viene registrata con il token DI `ACCESSI_OPTIONS` ed e condivisa dai servizi.\n */\n static forRoot(options: AccessiOptions): DynamicModule {\n // Il servizio email e obbligatorio: reset password, 2FA e creazione utente ne dipendono.\n assertEmailConfigured(options);\n configureAccessiAudit(options.audit);\n return {\n module: AccessiModule,\n providers: [\n {\n provide: 'ACCESSI_OPTIONS',\n useValue: options,\n },\n AuthService,\n TwoFactorService,\n UserService,\n EmailService,\n PermissionService,\n FiltriService,\n ConfiguratorService,\n JwtSimpleGuard,\n AuthenticateGenService,\n AccessiDatabaseUpdater,\n FederatedAuthService,\n ServiceTokenService,\n ServiceTokenGuard, SchemaExportService, AdminBootstrapService, AccessiJwksBootstrapService],\n exports: [\n 'ACCESSI_OPTIONS',\n AuthService,\n UserService,\n EmailService,\n PermissionService,\n FiltriService,\n ConfiguratorService,\n JwtSimpleGuard,\n AuthenticateGenService,\n FederatedAuthService,\n ServiceTokenService,\n ServiceTokenGuard, SchemaExportService, AdminBootstrapService],\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"AccessiModule.js","sourceRoot":"","sources":["../../src/accessi-module/AccessiModule.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,mFAAgF;AAQhF,2CAA+D;AAU/D,oEAAiE;AACjE,uEAAoE;AACpE,sFAAmF;AACnF,oEAAiE;AACjE,mEAAgE;AAChE,iEAA8D;AAC9D,6EAA0E;AAC1E,iEAA8D;AAC9D,0EAAuE;AACvE,qEAAkE;AAClE,iFAA8E;AAC9E,4FAAyF;AACzF,qDAAoD;AACpD,kEAAsE;AACtE,sFAAmF;AACnF,gFAA6E;AAC7E,sFAAmF;AACnF,sGAAmG;AACnG,qFAAkF;AAClF,iFAA8E;AAC9E,iFAA8E;AAC9E,4FAAyF;AACzF,oEAAiE;AACjE,qEAAkE;AAClE,iGAA8F;AAC9F,4FAAyF;AACzF,kGAA+F;AAC/F,4DAAuE;AACvE,sEAAsE;AACtE,4CAAyD;AACzD,iFAA8E;AAqTvE,IAAM,aAAa,qBAAnB,MAAM,aAAa;IACxB;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,CAAC,OAAuB;;QACpC,yFAAyF;QACzF,IAAA,0CAAqB,EAAC,OAAO,CAAC,CAAC;QAC/B,IAAA,6BAAqB,EACnB,OAAO,CAAC,QAAQ;YACd,CAAC,iCAAM,CAAC,MAAA,OAAO,CAAC,KAAK,mCAAI,EAAE,CAAC,KAAE,QAAQ,EAAE,MAAA,MAAA,OAAO,CAAC,KAAK,0CAAE,QAAQ,mCAAI,OAAO,CAAC,QAAQ,IACnF,CAAC,CAAC,OAAO,CAAC,KAAK,CAClB,CAAC;QACF,OAAO;YACL,MAAM,EAAE,eAAa;YACrB,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,iBAAiB;oBAC1B,QAAQ,EAAE,OAAO;iBAClB;gBACD,yBAAW;gBACX,mCAAgB;gBAChB,yBAAW;gBACX,2BAAY;gBACZ,qCAAiB;gBACjB,6BAAa;gBACb,yCAAmB;gBACnB,6BAAc;gBACd,wCAAsB;gBACtB,+CAAsB;gBACtB,2CAAoB;gBACpB,yCAAmB;gBACnB,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,EAAE,2CAA2B;aAAC;YAC7F,OAAO,EAAE;gBACP,iBAAiB;gBACjB,yBAAW;gBACX,yBAAW;gBACX,2BAAY;gBACZ,qCAAiB;gBACjB,6BAAa;gBACb,yCAAmB;gBACnB,6BAAc;gBACd,wCAAsB;gBACtB,2CAAoB;gBACpB,yCAAmB;gBACnB,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB;aAAC;SACjE,CAAC;IACJ,CAAC;CACF,CAAA;AAnDY,sCAAa;wBAAb,aAAa;IArBzB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,WAAW,EAAE;YACX,iCAAe;YACf,+BAAc;YACd,2CAAoB;YACpB,+BAAc;YACd,mCAAgB;YAChB,+CAAsB;YACtB,iDAAuB;YACvB,mDAAwB;YACxB,+CAAsB;YACtB,iEAA+B;YAC/B,+CAAsB;YACtB,mCAAgB;YAChB,+DAA8B;YAC9B,+CAAsB;SACvB;QACD,SAAS,EAAE,CAAC,yBAAW,EAAE,mCAAgB,EAAE,yBAAW,EAAE,2BAAY,EAAE,qCAAiB,EAAE,6BAAa,EAAE,yCAAmB,EAAE,6BAAc,EAAE,wCAAsB,EAAE,+CAAsB,EAAE,2CAAoB,EAAE,yCAAmB,EAAE,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,EAAE,2CAA2B,CAAC;QACnU,OAAO,EAAE,CAAC,yBAAW,EAAE,yBAAW,EAAE,2BAAY,EAAE,qCAAiB,EAAE,6BAAa,EAAE,yCAAmB,EAAE,6BAAc,EAAE,wCAAsB,EAAE,2CAAoB,EAAE,yCAAmB,EAAE,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,CAAC;KAC3P,CAAC;GACW,aAAa,CAmDzB","sourcesContent":["import { TwoFactorService } from './Services/TwoFactorService/TwoFactorService';\n/**\n * Modulo che gestisce le operazioni di accesso degli utenti, incluse le rotte, il controller e il modello.\n *\n * @module AccessiModule\n * @author mttdev382\n */\nimport { Options } from 'node-firebird';\nimport { DynamicModule, Global, Module } from '@nestjs/common';\nimport type { INestApplication, LoggerService } from '@nestjs/common';\nimport type { OpenApiSetupOptions } from '../openapi/OpenApiConfig';\nimport type { AccessiAccessTokenAlgorithm, AccessiUnknownKeyIdEvent } from './security/jwtTokenOptions';\nimport type { ConsoleEntryStore } from './security/consoleEntry';\nimport type { RateLimitStore } from './security/rateLimitStore';\nimport type { AccessiEdgeRateLimitOptions } from './security/edgeRateLimit';\nimport type { SessionRevocationOptions } from './security/sessionRevocation';\nimport type { AccessTokenRevocationOptions } from './security/accessTokenRevocation';\nimport type { RefreshTokenRotationOptions } from './security/refreshTokenRotation';\nimport { AuthService } from './Services/AuthService/AuthService';\nimport { EmailService } from './Services/EmailService/EmailService';\nimport { PermissionService } from './Services/PermissionService/PermissionService';\nimport { UserService } from './Services/UserService/UserService';\nimport { EmailController } from './Controllers/EmailController';\nimport { AuthController } from './Controllers/AuthController';\nimport { PermissionController } from './Controllers/PermissionController';\nimport { UserController } from './Controllers/UserController';\nimport { FiltriService } from './Services/FiltriService/FiltriService';\nimport { FiltriController } from './Controllers/FiltriController';\nimport { ConfiguratorController } from './Controllers/ConfiguratorController';\nimport { ConfiguratorService } from './Services/ConfiguratorService/ConfiguratorService';\nimport { JwtSimpleGuard } from './jwt/jwt.strategy';\nimport { AuthenticateGenService } from './middleware/authenticateGen';\nimport { AccessiDatabaseUpdater } from './database-updates/AccessiDatabaseUpdater';\nimport { FederatedAuthService } from './federated-auth/FederatedAuthService';\nimport { FederatedAuthController } from './federated-auth/FederatedAuthController';\nimport { FederatedAuthExchangeController } from './federated-auth/FederatedAuthExchangeController';\nimport { AccessiConsoleController } from './Controllers/AccessiConsoleController';\nimport { ConsoleEntryController } from './Controllers/ConsoleEntryController';\nimport { ServiceTokenController } from './Controllers/ServiceTokenController';\nimport { ServiceTokenService } from './Services/ServiceTokenService/ServiceTokenService';\nimport { ServiceTokenGuard } from './security/serviceTokenGuard';\nimport { SchemaController } from './Controllers/SchemaController';\nimport { AccessiIntrospectionController } from './Controllers/AccessiIntrospectionController';\nimport { SchemaExportService } from './Services/SchemaExportService/SchemaExportService';\nimport { AdminBootstrapService } from './Services/AdminBootstrapService/AdminBootstrapService';\nimport { AccessiJwksBootstrapService } from './security/jwksBootstrap';\nimport { assertEmailConfigured } from './security/emailConfiguration';\nimport { configureAccessiAudit } from './security/audit';\nimport { AccessiAuditController } from './Controllers/AccessiAuditController';\n\n/** JWT emesso da Accessi dopo un login locale o SSO. Non riutilizzare il segreto del provider SSO. */\nexport interface JwtOptions {\n /** Segreto simmetrico per firmare e verificare i JWT Accessi; deve essere unico per ambiente. */\n secret: string;\n /** Issuer (`iss`) opzionale degli access token. Consigliato in una topologia multi-servizio. */\n issuer?: string;\n /** Audience (`aud`) opzionale degli access token. */\n audience?: string | string[];\n /** Algoritmo degli access token. Default `HS256` per retrocompatibilita. */\n algorithm?: AccessiAccessTokenAlgorithm;\n /** Chiave privata PEM per RS/ES. Obbligatoria quando `algorithm` non e HS*. */\n privateKey?: string;\n /** Chiave pubblica PEM per la verifica RS/ES. */\n publicKey?: string;\n /** Key id pubblicato nella JWKS, per rotazione chiavi. */\n keyId?: string;\n /** Path pubblico della JWKS. Default `accessi/.well-known/jwks.json` con algoritmi asimmetrici. */\n jwksPath?: string;\n /**\n * Chiavi pubbliche precedenti, esposte in JWKS e accettate in verifica\n * durante la rotazione. Se `keyId` e assente si usa il fingerprint della\n * chiave, lo stesso calcolato dalla JWKS.\n */\n previousPublicKeys?: Array<{\n publicKey: string;\n keyId?: string;\n algorithm?: AccessiAccessTokenAlgorithm;\n }>;\n /**\n * Alert quando un token presenta un `kid` assente da chiave corrente e\n * chiavi precedenti (tipico di una rotazione non completata).\n */\n onUnknownKeyId?: (event: AccessiUnknownKeyIdEvent) => void;\n /** Durata compatibile con `jsonwebtoken`, per esempio `24h` o `3600s`. */\n expiresIn: string;\n /**\n * Durata del refresh token. Se assente il refresh token non viene emesso.\n * Consigliato: `30d`.\n */\n refreshExpiresIn?: string;\n}\n\n/** Configurazione dei ticket di ingresso alla console. */\nexport interface ConsoleEntryOptions {\n /** Durata del ticket in millisecondi. Default 60000. */\n ttlMs?: number;\n /** Store condiviso (es. Redis) per deployment multi-istanza. Default: memoria. */\n store?: ConsoleEntryStore;\n}\n\nimport type { AccessiSecurityHeadersSetting } from './security/securityHeaders';\nimport type { AccessiAuthCookieOptions } from './security/authCookies';\nimport type { AccessiMetricsOptions } from './observability/metrics';\nimport type { AccessiAuditOptions } from './security/audit';\n\n/** Impostazioni HTTP del servizio Accessi. */\nexport interface AccessiHttpOptions {\n /** Opzioni CORS Nest. `false` disabilita CORS. Default: exposedHeaders ['X-Total-Count']. */\n cors?: Parameters<INestApplication['enableCors']>[0] | false;\n /** Trust proxy per IP reali dietro load balancer/reverse proxy. */\n trustProxy?: boolean | number | string;\n /** Header di sicurezza di base su console/OpenAPI. Default: true. */\n securityHeaders?: AccessiSecurityHeadersSetting;\n}\n\n/** Parametri SMTP usati esclusivamente per impostazione e reset della password. */\nexport interface EmailOptions {\n host: string;\n port: number;\n secure: boolean;\n requireTLS : boolean;\n tls: {\n rejectUnauthorized: boolean,\n }\n from: string;\n auth: {\n user: string;\n pass: string;\n };\n}\n\n/** Configura campi applicativi aggiuntivi restituiti insieme al profilo Accessi. */\nexport interface ExtensionFieldsOptions {\n /** Connessione al database contenente la tabella estesa. */\n databaseOptions: Options;\n /** Chiave con cui i dati saranno esposti nella risposta. */\n objectKey: string;\n /** Tabella applicativa contenente i dati estesi. */\n tableName: string;\n /** Colonne consentite; non valorizzarle mai con input HTTP. */\n tableFields: string[];\n /** Colonna correlata a `UTENTI.CODUTE`. */\n tableJoinFieldName: string;\n}\n\n/** Limite per una singola categoria di endpoint pubblici. */\nexport interface PublicAuthRateLimitRuleOptions {\n windowMs: number;\n maxAttempts: number;\n}\n\n/** Protezione degli endpoint pubblici. Default in memoria; in cluster usare uno store condiviso. */\nexport interface PublicAuthRateLimitOptions {\n enabled?: boolean;\n /** Store condiviso (es. Redis). Default: memoria del processo. */\n store?: RateLimitStore;\n login?: PublicAuthRateLimitRuleOptions;\n register?: PublicAuthRateLimitRuleOptions;\n passwordResetEmail?: PublicAuthRateLimitRuleOptions;\n passwordResetConfirm?: PublicAuthRateLimitRuleOptions;\n refresh?: PublicAuthRateLimitRuleOptions;\n getUserByToken?: PublicAuthRateLimitRuleOptions;\n twoFactorVerify?: PublicAuthRateLimitRuleOptions;\n twoFactorResend?: PublicAuthRateLimitRuleOptions;\n adminBootstrap?: PublicAuthRateLimitRuleOptions;\n consoleEntry?: PublicAuthRateLimitRuleOptions;\n}\n\n/**\n * Bootstrap del primo admin. Se abilitato, Accessi genera un token casuale a ogni avvio del\n * processo, lo stampa nel log e lo accetta sull'endpoint pubblico `POST /accessi/auth/bootstrap-admin`.\n * Serve a creare il primo utente admin senza email di conferma e senza un admin gia esistente.\n */\nexport interface AdminBootstrapOptions {\n /** Abilita il bootstrap admin. Default `false`. */\n enabled?: boolean;\n /** Crea l'utente come superutente oltre che admin. Default `true`. */\n superUser?: boolean;\n}\n\n/** Controls the legacy public username/password registration endpoint. Disabled by default. */\nexport interface PublicRegistrationOptions {\n /** Enables POST /accessi/user/register. Applications that used it before must opt in explicitly. */\n enabled?: boolean;\n}\n\n/** Configurazione dei token di servizio (macchina-a-macchina, senza utente). */\nexport interface ServiceTokenOptions {\n /** Abilita gli endpoint amministrativi dei service token. Default `true`. */\n enabled?: boolean;\n /** Durata predefinita in giorni quando non e indicata una scadenza esplicita. Default: nessuna scadenza. */\n defaultTtlDays?: number;\n /** Store condiviso per il rate limit dei service token. Default: memoria. */\n rateLimitStore?: RateLimitStore;\n}\n\n/** Cosa fare quando uno script gia applicato presenta un checksum diverso (file modificato). */\nexport type CatalogScriptChecksumPolicy = 'error' | 'warn' | 'reapply';\n\n/**\n * Catalog migrations applicative (menu, ruoli, tipi, cataloghi). Gli script `.sql` vivono nel\n * backend ospitante (versionati in repository) e vengono eseguiti da Accessi dopo la\n * riconciliazione dello schema. Ogni script e tracciato nella tabella `ACCESSI_CATALOG_SCRIPT`,\n * quindi viene applicato una sola volta per database (idempotenza lato DB).\n */\nexport interface SystemSeedsOptions {\n /** `false` disabilita il seed automatico, ad esempio per ambienti gestiti manualmente. */\n enabled?: boolean;\n}\n\nexport interface CatalogScriptsOptions {\n /** Abilita l'esecuzione automatica al bootstrap. Default `false`. */\n enabled?: boolean;\n /** Cartella contenente gli script `.sql` (assoluta o relativa alla working directory). */\n folder: string;\n /** Cosa fare se il checksum di uno script applicato e cambiato. Default `error`. */\n onChecksumMismatch?: CatalogScriptChecksumPolicy;\n /** Se `true` (default) un errore interrompe l'esecuzione e non avanza allo script successivo. */\n stopOnError?: boolean;\n /** Globi/estensioni inclusi. Default: tutti i `.sql`. */\n include?: string[];\n /** Numero massimo di script eseguiti in un singolo run (protezione operativa). Default: illimitato. */\n maxScriptsPerRun?: number;\n /**\n * Permette statement DDL/schema negli script. Default `false`: le catalog migrations possono\n * modificare solo righe. Sconsigliato: lo schema e di competenza di `db:update:accessi`.\n */\n allowDdl?: boolean;\n}\n\n/**\n * Enables Accessi's provider-agnostic federated identity capability.\n *\n * Accessi never validates Azure, Google, Apple, SAML, or other provider tokens.\n * The hosting backend must validate its own SSO flow and pass only a verified\n * `{ provider, subject }` identity to FederatedAuthService.\n */\nexport interface FederatedAuthenticationOptions {\n /** Attiva le API SSO generiche; con `false` il comportamento password storico rimane invariato. */\n enabled?: boolean;\n /** @deprecated Schema management is centralized in autoUpdateDatabase. This legacy flag no longer starts separate SSO migrations. */\n autoUpdateSchema?: boolean;\n /** Allows a backend to create a user from a verified external identity without a master user. Default false. */\n allowSelfRegistration?: boolean;\n}\n\n/**\n * Configurazione completa del modulo Accessi, fornita esclusivamente dal backend ospitante.\n * Segreti JWT, SMTP, database e le configurazioni tecniche dei provider SSO non devono mai raggiungere il browser.\n */\nexport interface AccessiOptions {\n /** Connessione Firebird alla base dati Accessi. */\n databaseOptions: Options;\n /**\n * Basepath del sito es: 'http://www.il-mio-sito.it/nome-progetto(se c'è)'\n */\n confirmationEmailUrl: string;\n /**\n * Percorso della pagina di reset personalizzata es. http://localhost:4200/#/admin/reset-password\n * N.B si sostituisce al confirmationMailURl\n */\n customResetPage?: string;\n /** URL di ritorno della pagina standard di reset dopo il completamento. */\n confirmationEmailReturnUrl: string;\n /** Prefisso applicativo facoltativo trasmesso alla pagina standard di reset. */\n confirmationEmailPrefix?: string;\n /** Chiave riservata alla compatibilita e alla migrazione delle password legacy cifrate. */\n encryptionKey: string;\n /** Abilita gli utenti fittizi `admin` e `demo` solo fuori produzione. */\n mockDemoUser: boolean;\n /** Attiva il controllo della scadenza password durante il login locale. */\n passwordExpiration?: boolean;\n /** Giorni di validita della password; quando assente Accessi usa 90 giorni. */\n passwordExpirationDays?: number;\n /** Esegue al bootstrap la conversione non distruttiva delle password legacy. Default `true`. */\n legacyPasswordMigrationOnStartup?: boolean;\n /** Riconcilia lo schema al bootstrap (default `true`). Con `false` verifica comunque la compatibilita in sola lettura e blocca avvii incompatibili. */\n autoUpdateDatabase?: boolean;\n /** Parametri JWT dei token emessi da Accessi. */\n jwtOptions: JwtOptions;\n /** Parametri SMTP per reset e attivazione password. */\n emailOptions: EmailOptions;\n publicAuthRateLimit?: PublicAuthRateLimitOptions;\n /** Public local registration is deliberately opt-in. Existing applications must set enabled: true to retain it. */\n publicRegistration?: PublicRegistrationOptions;\n /** Optional provider-agnostic SSO support. Provider-specific configuration remains in the hosting backend. */\n federatedAuthentication?: FederatedAuthenticationOptions;\n extensionFieldsOptions?: ExtensionFieldsOptions[];\n /** Token di servizio per chiamate tecniche macchina-a-macchina. Abilitati di default. */\n serviceTokens?: ServiceTokenOptions;\n /** Catalog migrations applicative eseguite dopo la riconciliazione dello schema. */\n catalogScripts?: CatalogScriptsOptions;\n /** Seed di sistema eseguiti dopo la riconciliazione dello schema. Default: abilitati. */\n systemSeeds?: SystemSeedsOptions;\n /** Bootstrap del primo admin tramite token casuale stampato a ogni avvio. */\n adminBootstrap?: AdminBootstrapOptions;\n /**\n * Audit persistente: sink, retention ed endpoint amministrativi di query/export.\n */\n audit?: AccessiAuditOptions;\n /** Identificativo del tenant quando la configurazione arriva da una cartella multi-tenant. */\n tenantId?: string;\n /** Base URL pubblica usata solo per loggare gli indirizzi (console, OpenAPI, API). */\n publicBaseUrl?: string;\n /** Base URL API per la console quando UI e API sono su domini diversi. */\n consoleApiBaseUrl?: string;\n /**\n * Documentazione OpenAPI del modulo. Default invariati:\n * UI `accessi/openapi`, JSON `accessi/openapi.json`.\n */\n openapi?: OpenApiSetupOptions;\n /** Logger Nest usato dal sotto-app Accessi. Se assente Nest usa il proprio logger. */\n logger?: LoggerService;\n /** Buffer dei log di bootstrap del sotto-app. Default `true` (comportamento storico). */\n bufferLogs?: boolean;\n /** Ticket di ingresso console: TTL e store condiviso per deployment multi-istanza. */\n consoleEntry?: ConsoleEntryOptions;\n /** Limite grossolano per IP davanti alle rotte Accessi. Opzionale. */\n edgeRateLimit?: AccessiEdgeRateLimitOptions;\n /** CORS, trust proxy e header di sicurezza del servizio. */\n http?: AccessiHttpOptions;\n /**\n * Autenticazione opzionale via cookie httpOnly. Bearer resta il default:\n * la modalita si abilita passando questo oggetto (o `enabled: true`).\n */\n authCookie?: AccessiAuthCookieOptions;\n /** Metriche di dominio (login, 2FA, refresh, rate limit, store, ...). */\n metrics?: AccessiMetricsOptions;\n /** Revoca sessioni per utente tramite store memory/Redis. */\n sessionRevocation?: SessionRevocationOptions;\n /** Revoca dei singoli access token tramite `jti`, memory/Redis. */\n accessTokenRevocation?: AccessTokenRevocationOptions;\n /** Refresh token monouso tramite rotazione memory/Redis; rileva il riuso e revoca la famiglia. */\n refreshTokenRotation?: RefreshTokenRotationOptions;\n}\n\n@Global()\n@Module({\n controllers: [\n EmailController,\n AuthController,\n PermissionController,\n UserController,\n FiltriController,\n ConfiguratorController,\n FederatedAuthController,\n AccessiConsoleController,\n ConsoleEntryController,\n FederatedAuthExchangeController,\n ServiceTokenController,\n SchemaController,\n AccessiIntrospectionController,\n AccessiAuditController,\n ],\n providers: [AuthService, TwoFactorService, UserService, EmailService, PermissionService, FiltriService, ConfiguratorService, JwtSimpleGuard, AuthenticateGenService, AccessiDatabaseUpdater, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, SchemaExportService, AdminBootstrapService, AccessiJwksBootstrapService],\n exports: [AuthService, UserService, EmailService, PermissionService, FiltriService, ConfiguratorService, JwtSimpleGuard, AuthenticateGenService, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, SchemaExportService, AdminBootstrapService],\n})\nexport class AccessiModule {\n /**\n * Registra Accessi nel contenitore Nest del backend ospitante.\n *\n * Usare questo metodo una sola volta nella composizione dell'applicazione. Per un backend\n * Express esistente usare invece `initializeAccessiModule`, che crea e monta l'host Nest interno.\n * La configurazione viene registrata con il token DI `ACCESSI_OPTIONS` ed e condivisa dai servizi.\n */\n static forRoot(options: AccessiOptions): DynamicModule {\n // Il servizio email e obbligatorio: reset password, 2FA e creazione utente ne dipendono.\n assertEmailConfigured(options);\n configureAccessiAudit(\n options.tenantId\n ? { ...(options.audit ?? {}), tenantId: options.audit?.tenantId ?? options.tenantId }\n : options.audit,\n );\n return {\n module: AccessiModule,\n providers: [\n {\n provide: 'ACCESSI_OPTIONS',\n useValue: options,\n },\n AuthService,\n TwoFactorService,\n UserService,\n EmailService,\n PermissionService,\n FiltriService,\n ConfiguratorService,\n JwtSimpleGuard,\n AuthenticateGenService,\n AccessiDatabaseUpdater,\n FederatedAuthService,\n ServiceTokenService,\n ServiceTokenGuard, SchemaExportService, AdminBootstrapService, AccessiJwksBootstrapService],\n exports: [\n 'ACCESSI_OPTIONS',\n AuthService,\n UserService,\n EmailService,\n PermissionService,\n FiltriService,\n ConfiguratorService,\n JwtSimpleGuard,\n AuthenticateGenService,\n FederatedAuthService,\n ServiceTokenService,\n ServiceTokenGuard, SchemaExportService, AdminBootstrapService],\n };\n }\n}\n"]}
|