emilsoftware-utilities 2.0.0-dev.82 → 2.0.0-dev.84
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 +4 -0
- package/dist/accessi/server/index.js +7 -1
- package/dist/accessi/server/index.js.map +1 -1
- package/dist/accessi/server/tenantRouter.d.ts +35 -0
- package/dist/accessi/server/tenantRouter.js +71 -0
- package/dist/accessi/server/tenantRouter.js.map +1 -0
- package/dist/accessi/server/tenantSupervisor.d.ts +60 -0
- package/dist/accessi/server/tenantSupervisor.js +296 -0
- package/dist/accessi/server/tenantSupervisor.js.map +1 -0
- package/dist/accessi/server/tenants.js +8 -0
- package/dist/accessi/server/tenants.js.map +1 -1
- package/dist/accessi-module/AccessiModule.js.map +1 -1
- package/dist/accessi-module/Console/console.js +1 -1
- package/dist/accessi-module/index.d.ts +1 -1
- package/dist/accessi-module/index.js +4 -3
- package/dist/accessi-module/index.js.map +1 -1
- package/dist/accessi-module/observability/metrics.js +13 -4
- package/dist/accessi-module/observability/metrics.js.map +1 -1
- package/dist/accessi-module/security/audit.d.ts +14 -4
- package/dist/accessi-module/security/audit.js +73 -48
- package/dist/accessi-module/security/audit.js.map +1 -1
- package/dist/accessi-module/tenancy/tenantContext.d.ts +15 -0
- package/dist/accessi-module/tenancy/tenantContext.js +26 -0
- package/dist/accessi-module/tenancy/tenantContext.js.map +1 -0
- package/dist/emilsoftware-utilities-2.0.0-dev.84.tgz +0 -0
- package/docs/adr/0002-multi-tenancy.md +9 -7
- package/docs/multi-tenancy.md +69 -3
- package/package.json +3 -3
- package/dist/emilsoftware-utilities-2.0.0-dev.82.tgz +0 -0
|
@@ -10,6 +10,7 @@ exports.resetAccessiMetrics = resetAccessiMetrics;
|
|
|
10
10
|
exports.accessiMetricName = accessiMetricName;
|
|
11
11
|
exports.countAccessiMetric = countAccessiMetric;
|
|
12
12
|
exports.observeAccessiMetric = observeAccessiMetric;
|
|
13
|
+
const tenantContext_1 = require("../tenancy/tenantContext");
|
|
13
14
|
const HISTOGRAM_BUCKETS_MS = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000];
|
|
14
15
|
const METRIC_NAME = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;
|
|
15
16
|
const LABEL_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
@@ -178,13 +179,19 @@ function getAccessiMetricsSink(options) {
|
|
|
178
179
|
return undefined;
|
|
179
180
|
return (_c = (_b = options === null || options === void 0 ? void 0 : options.metrics) === null || _b === void 0 ? void 0 : _b.sink) !== null && _c !== void 0 ? _c : defaultRegistry;
|
|
180
181
|
}
|
|
182
|
+
function withTenantContext(labels) {
|
|
183
|
+
const tenant = (0, tenantContext_1.getAccessiTenantId)();
|
|
184
|
+
if (!tenant || (labels === null || labels === void 0 ? void 0 : labels.tenant) !== undefined)
|
|
185
|
+
return labels;
|
|
186
|
+
return Object.assign({ tenant }, labels);
|
|
187
|
+
}
|
|
181
188
|
/** Registra una metrica sul registry globale (usato da store e moduli senza options). */
|
|
182
189
|
function recordAccessiMetric(name, value = 1, labels) {
|
|
183
|
-
defaultRegistry.increment(name, value, labels);
|
|
190
|
+
defaultRegistry.increment(name, value, withTenantContext(labels));
|
|
184
191
|
}
|
|
185
192
|
/** Registra una durata in millisecondi sul registry globale. */
|
|
186
193
|
function recordAccessiDuration(name, valueMs, labels) {
|
|
187
|
-
defaultRegistry.observe(name, valueMs, labels);
|
|
194
|
+
defaultRegistry.observe(name, valueMs, withTenantContext(labels));
|
|
188
195
|
}
|
|
189
196
|
/** Render Prometheus del registry globale. */
|
|
190
197
|
function renderAccessiMetrics() {
|
|
@@ -206,14 +213,16 @@ function accessiMetricName(options, name) {
|
|
|
206
213
|
function countAccessiMetric(options, name, labels, value = 1) {
|
|
207
214
|
var _a;
|
|
208
215
|
const withTenant = (options === null || options === void 0 ? void 0 : options.tenantId)
|
|
209
|
-
? Object.assign({ tenant: options.tenantId }, labels)
|
|
216
|
+
? withTenantContext(Object.assign({ tenant: options.tenantId }, labels))
|
|
217
|
+
: withTenantContext(labels);
|
|
210
218
|
(_a = getAccessiMetricsSink(options)) === null || _a === void 0 ? void 0 : _a.increment(accessiMetricName(options, name), value, withTenant);
|
|
211
219
|
}
|
|
212
220
|
/** Registra una durata di dominio in millisecondi. */
|
|
213
221
|
function observeAccessiMetric(options, name, valueMs, labels) {
|
|
214
222
|
var _a;
|
|
215
223
|
const withTenant = (options === null || options === void 0 ? void 0 : options.tenantId)
|
|
216
|
-
? Object.assign({ tenant: options.tenantId }, labels)
|
|
224
|
+
? withTenantContext(Object.assign({ tenant: options.tenantId }, labels))
|
|
225
|
+
: withTenantContext(labels);
|
|
217
226
|
const sink = getAccessiMetricsSink(options);
|
|
218
227
|
(_a = sink === null || sink === void 0 ? void 0 : sink.observe) === null || _a === void 0 ? void 0 : _a.call(sink, accessiMetricName(options, name), valueMs, withTenant);
|
|
219
228
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/accessi-module/observability/metrics.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccessiOptions } from '../AccessiModule';\n\n/** Valore ammesso per un label Prometheus. */\nexport type AccessiMetricLabelValue = string | number | boolean | undefined;\nexport type AccessiMetricLabels = Record<string, AccessiMetricLabelValue>;\n\n/**\n * Destinazione delle metriche Accessi.\n *\n * L'implementazione predefinita e il registry in memoria renderizzato in\n * formato Prometheus. Un host puo passare un sink custom (OpenTelemetry,\n * StatsD, ...) tramite `metrics.sink`.\n */\nexport interface AccessiMetricsSink {\n increment(name: string, value?: number, labels?: AccessiMetricLabels): void;\n gauge?(name: string, value: number, labels?: AccessiMetricLabels): void;\n observe?(name: string, valueMs: number, labels?: AccessiMetricLabels): void;\n}\n\n/** Opzioni di osservabilita di Accessi. */\nexport interface AccessiMetricsOptions {\n /** `false` disabilita le metriche di dominio. Default: abilitate. */\n enabled?: boolean;\n /** Sink custom. Default: registry in memoria. */\n sink?: AccessiMetricsSink;\n /** Prefisso dei nomi. Default `accessi`. */\n prefix?: string;\n}\n\nexport interface AccessiMetricsRegistry extends AccessiMetricsSink {\n observe(name: string, valueMs: number, labels?: AccessiMetricLabels): void;\n gauge(name: string, value: number, labels?: AccessiMetricLabels): void;\n renderPrometheus(): string;\n snapshot(): AccessiMetricsSnapshot;\n reset(): void;\n}\n\nexport interface AccessiMetricsSnapshot {\n counters: Array<{ name: string; labels: Record<string, string>; value: number }>;\n gauges: Array<{ name: string; labels: Record<string, string>; value: number }>;\n histograms: Array<{\n name: string;\n labels: Record<string, string>;\n count: number;\n sum: number;\n buckets: Array<{ le: number; count: number }>;\n }>;\n}\n\nconst HISTOGRAM_BUCKETS_MS = [5, 10, 25, 50, 100, 250, 500, 1_000, 2_500, 5_000, 10_000];\nconst METRIC_NAME = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;\nconst LABEL_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\nfunction normalizeValue(value: AccessiMetricLabelValue): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n return String(value);\n}\n\nfunction normalizeLabels(labels: AccessiMetricLabels | undefined): Record<string, string> {\n const normalized: Record<string, string> = {};\n for (const [name, value] of Object.entries(labels ?? {})) {\n const text = normalizeValue(value);\n if (text !== undefined && LABEL_NAME.test(name)) normalized[name] = text;\n }\n return normalized;\n}\n\nfunction labelKey(labels: Record<string, string>): string {\n return Object.keys(labels)\n .sort()\n .map((name) => `${name}=${labels[name]}`)\n .join(',');\n}\n\nfunction escapeLabel(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n');\n}\n\nfunction renderLabels(labels: Record<string, string>, extra?: [string, string]): string {\n const entries = Object.entries(labels).sort(([left], [right]) => left.localeCompare(right));\n if (extra) entries.push(extra);\n if (entries.length === 0) return '';\n return `{${entries.map(([name, value]) => `${name}=\"${escapeLabel(value)}\"`).join(',')}}`;\n}\n\nfunction metricName(name: string): string {\n return METRIC_NAME.test(name) ? name : name.replace(/[^a-zA-Z0-9_:]/g, '_');\n}\n\n/**\n * Registry in memoria con rendering Prometheus.\n *\n * Non usa dipendenze: la stessa struttura e usata dal servizio standalone e\n * dai test. `reset()` serve per isolare i test.\n */\nexport function createAccessiMetricsRegistry(): AccessiMetricsRegistry {\n const counters = new Map<string, { name: string; labels: Record<string, string>; value: number }>();\n const gauges = new Map<string, { name: string; labels: Record<string, string>; value: number }>();\n const histograms = new Map<string, {\n name: string;\n labels: Record<string, string>;\n counts: number[];\n sum: number;\n count: number;\n }>();\n\n const entryKey = (name: string, labels: Record<string, string>) => `${metricName(name)}|${labelKey(labels)}`;\n\n return {\n increment(name, value = 1, labels) {\n const normalizedName = metricName(name);\n const normalizedLabels = normalizeLabels(labels);\n const key = entryKey(normalizedName, normalizedLabels);\n const current = counters.get(key);\n if (current) current.value += value;\n else counters.set(key, { name: normalizedName, labels: normalizedLabels, value });\n },\n gauge(name, value, labels) {\n const normalizedName = metricName(name);\n const normalizedLabels = normalizeLabels(labels);\n gauges.set(entryKey(normalizedName, normalizedLabels), {\n name: normalizedName,\n labels: normalizedLabels,\n value,\n });\n },\n observe(name, valueMs, labels) {\n if (!Number.isFinite(valueMs) || valueMs < 0) return;\n const normalizedName = metricName(name);\n const normalizedLabels = normalizeLabels(labels);\n const key = entryKey(normalizedName, normalizedLabels);\n let entry = histograms.get(key);\n if (!entry) {\n entry = {\n name: normalizedName,\n labels: normalizedLabels,\n counts: HISTOGRAM_BUCKETS_MS.map(() => 0),\n sum: 0,\n count: 0,\n };\n histograms.set(key, entry);\n }\n entry.sum += valueMs;\n entry.count += 1;\n const histogram = entry;\n HISTOGRAM_BUCKETS_MS.forEach((bucket, index) => {\n if (valueMs <= bucket) histogram.counts[index] = (histogram.counts[index] ?? 0) + 1;\n });\n },\n renderPrometheus() {\n const lines: string[] = [];\n const grouped = <T extends { name: string }>(entries: T[]): Array<[string, T[]]> => {\n const byName = new Map<string, T[]>();\n for (const entry of entries) {\n const bucket = byName.get(entry.name);\n if (bucket) bucket.push(entry);\n else byName.set(entry.name, [entry]);\n }\n return [...byName.entries()].sort(([left], [right]) => left.localeCompare(right));\n };\n const sorted = <T extends { name: string; labels: Record<string, string> }>(entries: T[]) =>\n [...entries].sort((left, right) => labelKey(left.labels).localeCompare(labelKey(right.labels)));\n\n for (const [name, entries] of grouped([...counters.values()])) {\n lines.push(`# HELP ${name} Accessi metric.`);\n lines.push(`# TYPE ${name} counter`);\n for (const entry of sorted(entries)) lines.push(`${name}${renderLabels(entry.labels)} ${entry.value}`);\n }\n\n for (const [name, entries] of grouped([...gauges.values()])) {\n lines.push(`# HELP ${name} Accessi metric.`);\n lines.push(`# TYPE ${name} gauge`);\n for (const entry of sorted(entries)) lines.push(`${name}${renderLabels(entry.labels)} ${entry.value}`);\n }\n\n for (const [name, entries] of grouped([...histograms.values()])) {\n lines.push(`# HELP ${name} Accessi metric.`);\n lines.push(`# TYPE ${name} histogram`);\n for (const entry of sorted(entries)) {\n HISTOGRAM_BUCKETS_MS.forEach((bucket, index) => {\n lines.push(`${name}_bucket${renderLabels(entry.labels, ['le', String(bucket)])} ${entry.counts[index]}`);\n });\n lines.push(`${name}_bucket${renderLabels(entry.labels, ['le', '+Inf'])} ${entry.count}`);\n lines.push(`${name}_sum${renderLabels(entry.labels)} ${entry.sum}`);\n lines.push(`${name}_count${renderLabels(entry.labels)} ${entry.count}`);\n }\n }\n\n return lines.length > 0 ? `${lines.join('\\n')}\\n` : '';\n },\n snapshot() {\n return {\n counters: [...counters.values()].map((entry) => ({ ...entry, labels: { ...entry.labels } })),\n gauges: [...gauges.values()].map((entry) => ({ ...entry, labels: { ...entry.labels } })),\n histograms: [...histograms.values()].map((entry) => ({\n name: entry.name,\n labels: { ...entry.labels },\n count: entry.count,\n sum: entry.sum,\n buckets: HISTOGRAM_BUCKETS_MS.map((le, index) => ({ le, count: entry.counts[index] ?? 0 })),\n })),\n };\n },\n reset() {\n counters.clear();\n gauges.clear();\n histograms.clear();\n },\n };\n}\n\nconst defaultRegistry = createAccessiMetricsRegistry();\n\n/** Registry globale usato da Accessi e dal servizio standalone. */\nexport function getAccessiMetricsRegistry(): AccessiMetricsRegistry {\n return defaultRegistry;\n}\n\n/** Sink configurato per l'istanza, oppure registry globale. */\nexport function getAccessiMetricsSink(options: AccessiOptions | undefined): AccessiMetricsSink | undefined {\n if (options?.metrics?.enabled === false) return undefined;\n return options?.metrics?.sink ?? defaultRegistry;\n}\n\n/** Registra una metrica sul registry globale (usato da store e moduli senza options). */\nexport function recordAccessiMetric(\n name: string,\n value = 1,\n labels?: AccessiMetricLabels,\n): void {\n defaultRegistry.increment(name, value, labels);\n}\n\n/** Registra una durata in millisecondi sul registry globale. */\nexport function recordAccessiDuration(\n name: string,\n valueMs: number,\n labels?: AccessiMetricLabels,\n): void {\n defaultRegistry.observe(name, valueMs, labels);\n}\n\n/** Render Prometheus del registry globale. */\nexport function renderAccessiMetrics(): string {\n return defaultRegistry.renderPrometheus();\n}\n\n/** Azzera il registry globale (test e reset espliciti). */\nexport function resetAccessiMetrics(): void {\n defaultRegistry.reset();\n}\n\n/** Prefisso configurato, con normalizzazione per evitare nomi non validi. */\nexport function accessiMetricName(options: AccessiOptions | undefined, name: string): string {\n const prefix = options?.metrics?.prefix ?? 'accessi';\n if (!prefix.trim()) return name;\n return `${prefix.trim().replace(/[^a-zA-Z0-9_:]/g, '_')}_${name}`;\n}\n\n/** Incrementa un contatore di dominio sull'istanza (o sul registry globale). */\nexport function countAccessiMetric(\n options: AccessiOptions | undefined,\n name: string,\n labels?: AccessiMetricLabels,\n value = 1,\n): void {\n const withTenant = options?.tenantId\n ? { tenant: options.tenantId, ...labels }\n : labels;\n getAccessiMetricsSink(options)?.increment(accessiMetricName(options, name), value, withTenant);\n}\n\n/** Registra una durata di dominio in millisecondi. */\nexport function observeAccessiMetric(\n options: AccessiOptions | undefined,\n name: string,\n valueMs: number,\n labels?: AccessiMetricLabels,\n): void {\n const withTenant = options?.tenantId\n ? { tenant: options.tenantId, ...labels }\n : labels;\n const sink = getAccessiMetricsSink(options);\n sink?.observe?.(accessiMetricName(options, name), valueMs, withTenant);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/accessi-module/observability/metrics.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccessiOptions } from '../AccessiModule';\nimport { getAccessiTenantId } from '../tenancy/tenantContext';\n\n/** Valore ammesso per un label Prometheus. */\nexport type AccessiMetricLabelValue = string | number | boolean | undefined;\nexport type AccessiMetricLabels = Record<string, AccessiMetricLabelValue>;\n\n/**\n * Destinazione delle metriche Accessi.\n *\n * L'implementazione predefinita e il registry in memoria renderizzato in\n * formato Prometheus. Un host puo passare un sink custom (OpenTelemetry,\n * StatsD, ...) tramite `metrics.sink`.\n */\nexport interface AccessiMetricsSink {\n increment(name: string, value?: number, labels?: AccessiMetricLabels): void;\n gauge?(name: string, value: number, labels?: AccessiMetricLabels): void;\n observe?(name: string, valueMs: number, labels?: AccessiMetricLabels): void;\n}\n\n/** Opzioni di osservabilita di Accessi. */\nexport interface AccessiMetricsOptions {\n /** `false` disabilita le metriche di dominio. Default: abilitate. */\n enabled?: boolean;\n /** Sink custom. Default: registry in memoria. */\n sink?: AccessiMetricsSink;\n /** Prefisso dei nomi. Default `accessi`. */\n prefix?: string;\n}\n\nexport interface AccessiMetricsRegistry extends AccessiMetricsSink {\n observe(name: string, valueMs: number, labels?: AccessiMetricLabels): void;\n gauge(name: string, value: number, labels?: AccessiMetricLabels): void;\n renderPrometheus(): string;\n snapshot(): AccessiMetricsSnapshot;\n reset(): void;\n}\n\nexport interface AccessiMetricsSnapshot {\n counters: Array<{ name: string; labels: Record<string, string>; value: number }>;\n gauges: Array<{ name: string; labels: Record<string, string>; value: number }>;\n histograms: Array<{\n name: string;\n labels: Record<string, string>;\n count: number;\n sum: number;\n buckets: Array<{ le: number; count: number }>;\n }>;\n}\n\nconst HISTOGRAM_BUCKETS_MS = [5, 10, 25, 50, 100, 250, 500, 1_000, 2_500, 5_000, 10_000];\nconst METRIC_NAME = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;\nconst LABEL_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\nfunction normalizeValue(value: AccessiMetricLabelValue): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n return String(value);\n}\n\nfunction normalizeLabels(labels: AccessiMetricLabels | undefined): Record<string, string> {\n const normalized: Record<string, string> = {};\n for (const [name, value] of Object.entries(labels ?? {})) {\n const text = normalizeValue(value);\n if (text !== undefined && LABEL_NAME.test(name)) normalized[name] = text;\n }\n return normalized;\n}\n\nfunction labelKey(labels: Record<string, string>): string {\n return Object.keys(labels)\n .sort()\n .map((name) => `${name}=${labels[name]}`)\n .join(',');\n}\n\nfunction escapeLabel(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n');\n}\n\nfunction renderLabels(labels: Record<string, string>, extra?: [string, string]): string {\n const entries = Object.entries(labels).sort(([left], [right]) => left.localeCompare(right));\n if (extra) entries.push(extra);\n if (entries.length === 0) return '';\n return `{${entries.map(([name, value]) => `${name}=\"${escapeLabel(value)}\"`).join(',')}}`;\n}\n\nfunction metricName(name: string): string {\n return METRIC_NAME.test(name) ? name : name.replace(/[^a-zA-Z0-9_:]/g, '_');\n}\n\n/**\n * Registry in memoria con rendering Prometheus.\n *\n * Non usa dipendenze: la stessa struttura e usata dal servizio standalone e\n * dai test. `reset()` serve per isolare i test.\n */\nexport function createAccessiMetricsRegistry(): AccessiMetricsRegistry {\n const counters = new Map<string, { name: string; labels: Record<string, string>; value: number }>();\n const gauges = new Map<string, { name: string; labels: Record<string, string>; value: number }>();\n const histograms = new Map<string, {\n name: string;\n labels: Record<string, string>;\n counts: number[];\n sum: number;\n count: number;\n }>();\n\n const entryKey = (name: string, labels: Record<string, string>) => `${metricName(name)}|${labelKey(labels)}`;\n\n return {\n increment(name, value = 1, labels) {\n const normalizedName = metricName(name);\n const normalizedLabels = normalizeLabels(labels);\n const key = entryKey(normalizedName, normalizedLabels);\n const current = counters.get(key);\n if (current) current.value += value;\n else counters.set(key, { name: normalizedName, labels: normalizedLabels, value });\n },\n gauge(name, value, labels) {\n const normalizedName = metricName(name);\n const normalizedLabels = normalizeLabels(labels);\n gauges.set(entryKey(normalizedName, normalizedLabels), {\n name: normalizedName,\n labels: normalizedLabels,\n value,\n });\n },\n observe(name, valueMs, labels) {\n if (!Number.isFinite(valueMs) || valueMs < 0) return;\n const normalizedName = metricName(name);\n const normalizedLabels = normalizeLabels(labels);\n const key = entryKey(normalizedName, normalizedLabels);\n let entry = histograms.get(key);\n if (!entry) {\n entry = {\n name: normalizedName,\n labels: normalizedLabels,\n counts: HISTOGRAM_BUCKETS_MS.map(() => 0),\n sum: 0,\n count: 0,\n };\n histograms.set(key, entry);\n }\n entry.sum += valueMs;\n entry.count += 1;\n const histogram = entry;\n HISTOGRAM_BUCKETS_MS.forEach((bucket, index) => {\n if (valueMs <= bucket) histogram.counts[index] = (histogram.counts[index] ?? 0) + 1;\n });\n },\n renderPrometheus() {\n const lines: string[] = [];\n const grouped = <T extends { name: string }>(entries: T[]): Array<[string, T[]]> => {\n const byName = new Map<string, T[]>();\n for (const entry of entries) {\n const bucket = byName.get(entry.name);\n if (bucket) bucket.push(entry);\n else byName.set(entry.name, [entry]);\n }\n return [...byName.entries()].sort(([left], [right]) => left.localeCompare(right));\n };\n const sorted = <T extends { name: string; labels: Record<string, string> }>(entries: T[]) =>\n [...entries].sort((left, right) => labelKey(left.labels).localeCompare(labelKey(right.labels)));\n\n for (const [name, entries] of grouped([...counters.values()])) {\n lines.push(`# HELP ${name} Accessi metric.`);\n lines.push(`# TYPE ${name} counter`);\n for (const entry of sorted(entries)) lines.push(`${name}${renderLabels(entry.labels)} ${entry.value}`);\n }\n\n for (const [name, entries] of grouped([...gauges.values()])) {\n lines.push(`# HELP ${name} Accessi metric.`);\n lines.push(`# TYPE ${name} gauge`);\n for (const entry of sorted(entries)) lines.push(`${name}${renderLabels(entry.labels)} ${entry.value}`);\n }\n\n for (const [name, entries] of grouped([...histograms.values()])) {\n lines.push(`# HELP ${name} Accessi metric.`);\n lines.push(`# TYPE ${name} histogram`);\n for (const entry of sorted(entries)) {\n HISTOGRAM_BUCKETS_MS.forEach((bucket, index) => {\n lines.push(`${name}_bucket${renderLabels(entry.labels, ['le', String(bucket)])} ${entry.counts[index]}`);\n });\n lines.push(`${name}_bucket${renderLabels(entry.labels, ['le', '+Inf'])} ${entry.count}`);\n lines.push(`${name}_sum${renderLabels(entry.labels)} ${entry.sum}`);\n lines.push(`${name}_count${renderLabels(entry.labels)} ${entry.count}`);\n }\n }\n\n return lines.length > 0 ? `${lines.join('\\n')}\\n` : '';\n },\n snapshot() {\n return {\n counters: [...counters.values()].map((entry) => ({ ...entry, labels: { ...entry.labels } })),\n gauges: [...gauges.values()].map((entry) => ({ ...entry, labels: { ...entry.labels } })),\n histograms: [...histograms.values()].map((entry) => ({\n name: entry.name,\n labels: { ...entry.labels },\n count: entry.count,\n sum: entry.sum,\n buckets: HISTOGRAM_BUCKETS_MS.map((le, index) => ({ le, count: entry.counts[index] ?? 0 })),\n })),\n };\n },\n reset() {\n counters.clear();\n gauges.clear();\n histograms.clear();\n },\n };\n}\n\nconst defaultRegistry = createAccessiMetricsRegistry();\n\n/** Registry globale usato da Accessi e dal servizio standalone. */\nexport function getAccessiMetricsRegistry(): AccessiMetricsRegistry {\n return defaultRegistry;\n}\n\n/** Sink configurato per l'istanza, oppure registry globale. */\nexport function getAccessiMetricsSink(options: AccessiOptions | undefined): AccessiMetricsSink | undefined {\n if (options?.metrics?.enabled === false) return undefined;\n return options?.metrics?.sink ?? defaultRegistry;\n}\n\nfunction withTenantContext(labels?: AccessiMetricLabels): AccessiMetricLabels | undefined {\n const tenant = getAccessiTenantId();\n if (!tenant || labels?.tenant !== undefined) return labels;\n return { tenant, ...labels };\n}\n\n/** Registra una metrica sul registry globale (usato da store e moduli senza options). */\nexport function recordAccessiMetric(\n name: string,\n value = 1,\n labels?: AccessiMetricLabels,\n): void {\n defaultRegistry.increment(name, value, withTenantContext(labels));\n}\n\n/** Registra una durata in millisecondi sul registry globale. */\nexport function recordAccessiDuration(\n name: string,\n valueMs: number,\n labels?: AccessiMetricLabels,\n): void {\n defaultRegistry.observe(name, valueMs, withTenantContext(labels));\n}\n\n/** Render Prometheus del registry globale. */\nexport function renderAccessiMetrics(): string {\n return defaultRegistry.renderPrometheus();\n}\n\n/** Azzera il registry globale (test e reset espliciti). */\nexport function resetAccessiMetrics(): void {\n defaultRegistry.reset();\n}\n\n/** Prefisso configurato, con normalizzazione per evitare nomi non validi. */\nexport function accessiMetricName(options: AccessiOptions | undefined, name: string): string {\n const prefix = options?.metrics?.prefix ?? 'accessi';\n if (!prefix.trim()) return name;\n return `${prefix.trim().replace(/[^a-zA-Z0-9_:]/g, '_')}_${name}`;\n}\n\n/** Incrementa un contatore di dominio sull'istanza (o sul registry globale). */\nexport function countAccessiMetric(\n options: AccessiOptions | undefined,\n name: string,\n labels?: AccessiMetricLabels,\n value = 1,\n): void {\n const withTenant = options?.tenantId\n ? withTenantContext({ tenant: options.tenantId, ...labels })\n : withTenantContext(labels);\n getAccessiMetricsSink(options)?.increment(accessiMetricName(options, name), value, withTenant);\n}\n\n/** Registra una durata di dominio in millisecondi. */\nexport function observeAccessiMetric(\n options: AccessiOptions | undefined,\n name: string,\n valueMs: number,\n labels?: AccessiMetricLabels,\n): void {\n const withTenant = options?.tenantId\n ? withTenantContext({ tenant: options.tenantId, ...labels })\n : withTenantContext(labels);\n const sink = getAccessiMetricsSink(options);\n sink?.observe?.(accessiMetricName(options, name), valueMs, withTenant);\n}\n"]}
|
|
@@ -55,16 +55,26 @@ export interface AccessiAuditOptions {
|
|
|
55
55
|
*/
|
|
56
56
|
export declare function redactAccessiAuditValue(value: unknown, depth?: number): unknown;
|
|
57
57
|
/**
|
|
58
|
-
* Configura i sink di audit
|
|
58
|
+
* Configura i sink di audit.
|
|
59
59
|
*
|
|
60
|
-
* Chiamato da `AccessiModule.forRoot`.
|
|
61
|
-
*
|
|
60
|
+
* Chiamato da `AccessiModule.forRoot`. Con `tenantId` la configurazione vale
|
|
61
|
+
* solo per quel tenant e non sovrascrive quella degli altri: e il presupposto
|
|
62
|
+
* per il router multi-tenant in-process. Senza `tenantId` aggiorna la
|
|
63
|
+
* configurazione di default (comportamento single-tenant).
|
|
62
64
|
*/
|
|
63
65
|
export declare function configureAccessiAudit(options: AccessiAuditOptions | undefined): void;
|
|
66
|
+
/**
|
|
67
|
+
* Configura audit e sink per un tenant specifico (router in-process).
|
|
68
|
+
*
|
|
69
|
+
* La configurazione vale quando la richiesta e dentro
|
|
70
|
+
* `runWithAccessiTenant`/`createAccessiTenantRouter`; gli eventi fuori da un
|
|
71
|
+
* contesto tenant usano la configurazione di default del processo.
|
|
72
|
+
*/
|
|
73
|
+
export declare function configureAccessiTenantAudit(tenantId: string, options: AccessiAuditOptions | undefined): void;
|
|
64
74
|
/** Ripristina la configurazione iniziale (logger attivo, nessun sink). */
|
|
65
75
|
export declare function resetAccessiAudit(): void;
|
|
66
76
|
/** Sink interrogabile configurato, usato dagli endpoint amministrativi. */
|
|
67
|
-
export declare function getAccessiAuditStore(): AccessiAuditQueryableSink | undefined;
|
|
77
|
+
export declare function getAccessiAuditStore(tenantId?: string): AccessiAuditQueryableSink | undefined;
|
|
68
78
|
/**
|
|
69
79
|
* Registra un evento di sicurezza.
|
|
70
80
|
*
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.redactAccessiAuditValue = redactAccessiAuditValue;
|
|
4
4
|
exports.configureAccessiAudit = configureAccessiAudit;
|
|
5
|
+
exports.configureAccessiTenantAudit = configureAccessiTenantAudit;
|
|
5
6
|
exports.resetAccessiAudit = resetAccessiAudit;
|
|
6
7
|
exports.getAccessiAuditStore = getAccessiAuditStore;
|
|
7
8
|
exports.auditAccessi = auditAccessi;
|
|
8
9
|
const Logger_1 = require("../../Logger");
|
|
9
10
|
const metrics_1 = require("../observability/metrics");
|
|
11
|
+
const tenantContext_1 = require("../tenancy/tenantContext");
|
|
10
12
|
const REDACTED = '[REDACTED]';
|
|
11
13
|
const TRUNCATED = '[TRUNCATED]';
|
|
12
14
|
const DEFAULT_REDACTED_KEYS = new Set([
|
|
@@ -35,12 +37,30 @@ const MAX_ARRAY_LENGTH = 50;
|
|
|
35
37
|
const MAX_OBJECT_KEYS = 100;
|
|
36
38
|
const MAX_DEPTH = 4;
|
|
37
39
|
const PURGE_INTERVAL_MS = 60 * 60 * 1000;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
function resolveConfig(options) {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
const redactedKeys = new Set(DEFAULT_REDACTED_KEYS);
|
|
43
|
+
for (const key of (_a = options === null || options === void 0 ? void 0 : options.redact) !== null && _a !== void 0 ? _a : [])
|
|
44
|
+
redactedKeys.add(normalizedKey(key));
|
|
45
|
+
const retentionDays = Number(options === null || options === void 0 ? void 0 : options.retentionDays);
|
|
46
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({ sinks: (options === null || options === void 0 ? void 0 : options.enabled) === false
|
|
47
|
+
? []
|
|
48
|
+
: [
|
|
49
|
+
...((options === null || options === void 0 ? void 0 : options.store) ? [options.store] : []),
|
|
50
|
+
...((_b = options === null || options === void 0 ? void 0 : options.sinks) !== null && _b !== void 0 ? _b : []).filter((sink) => sink !== (options === null || options === void 0 ? void 0 : options.store)),
|
|
51
|
+
], redactedKeys }, (Number.isFinite(retentionDays) && retentionDays > 0 ? { retentionDays } : {})), { loggerEnabled: (options === null || options === void 0 ? void 0 : options.logger) !== false }), ((options === null || options === void 0 ? void 0 : options.tenantId) ? { tenantId: options.tenantId } : {})), { lastPurgeAt: 0 });
|
|
52
|
+
}
|
|
53
|
+
let defaultConfig = resolveConfig(undefined);
|
|
54
|
+
const tenantConfigs = new Map();
|
|
55
|
+
function currentConfig() {
|
|
56
|
+
const tenantId = (0, tenantContext_1.getAccessiTenantId)();
|
|
57
|
+
if (tenantId) {
|
|
58
|
+
const tenantConfig = tenantConfigs.get(tenantId);
|
|
59
|
+
if (tenantConfig)
|
|
60
|
+
return tenantConfig;
|
|
61
|
+
}
|
|
62
|
+
return defaultConfig;
|
|
63
|
+
}
|
|
44
64
|
function normalizedKey(key) {
|
|
45
65
|
return key.toLowerCase().replace(/[-_\s]/g, '');
|
|
46
66
|
}
|
|
@@ -51,6 +71,9 @@ function normalizedKey(key) {
|
|
|
51
71
|
* che un payload applicativo finisca per intero nell'audit.
|
|
52
72
|
*/
|
|
53
73
|
function redactAccessiAuditValue(value, depth = 0) {
|
|
74
|
+
return redactValue(value, depth, currentConfig().redactedKeys);
|
|
75
|
+
}
|
|
76
|
+
function redactValue(value, depth, keys) {
|
|
54
77
|
if (depth > MAX_DEPTH)
|
|
55
78
|
return TRUNCATED;
|
|
56
79
|
if (value === null || value === undefined)
|
|
@@ -65,17 +88,17 @@ function redactAccessiAuditValue(value, depth = 0) {
|
|
|
65
88
|
if (value instanceof Date)
|
|
66
89
|
return value.toISOString();
|
|
67
90
|
if (value instanceof Error) {
|
|
68
|
-
return { name: value.name, message:
|
|
91
|
+
return { name: value.name, message: redactValue(value.message, depth + 1, keys) };
|
|
69
92
|
}
|
|
70
93
|
if (Array.isArray(value)) {
|
|
71
|
-
return value.slice(0, MAX_ARRAY_LENGTH).map((item) =>
|
|
94
|
+
return value.slice(0, MAX_ARRAY_LENGTH).map((item) => redactValue(item, depth + 1, keys));
|
|
72
95
|
}
|
|
73
96
|
if (typeof value === 'object') {
|
|
74
97
|
const result = {};
|
|
75
98
|
for (const [key, entry] of Object.entries(value).slice(0, MAX_OBJECT_KEYS)) {
|
|
76
|
-
result[key] =
|
|
99
|
+
result[key] = keys.has(normalizedKey(key))
|
|
77
100
|
? REDACTED
|
|
78
|
-
:
|
|
101
|
+
: redactValue(entry, depth + 1, keys);
|
|
79
102
|
}
|
|
80
103
|
return result;
|
|
81
104
|
}
|
|
@@ -105,15 +128,15 @@ function writeToSink(sink, record) {
|
|
|
105
128
|
reportSinkError(sink, error);
|
|
106
129
|
}
|
|
107
130
|
}
|
|
108
|
-
function maybePurge() {
|
|
109
|
-
if (!retentionDays || retentionDays <= 0)
|
|
131
|
+
function maybePurge(config) {
|
|
132
|
+
if (!config.retentionDays || config.retentionDays <= 0)
|
|
110
133
|
return;
|
|
111
134
|
const now = Date.now();
|
|
112
|
-
if (now - lastPurgeAt < PURGE_INTERVAL_MS)
|
|
135
|
+
if (now - config.lastPurgeAt < PURGE_INTERVAL_MS)
|
|
113
136
|
return;
|
|
114
|
-
lastPurgeAt = now;
|
|
115
|
-
const before = new Date(now - retentionDays * 24 * 60 * 60 * 1000);
|
|
116
|
-
for (const sink of sinks) {
|
|
137
|
+
config.lastPurgeAt = now;
|
|
138
|
+
const before = new Date(now - config.retentionDays * 24 * 60 * 60 * 1000);
|
|
139
|
+
for (const sink of config.sinks) {
|
|
117
140
|
if (typeof sink.purge !== 'function')
|
|
118
141
|
continue;
|
|
119
142
|
try {
|
|
@@ -128,43 +151,42 @@ function maybePurge() {
|
|
|
128
151
|
}
|
|
129
152
|
}
|
|
130
153
|
/**
|
|
131
|
-
* Configura i sink di audit
|
|
154
|
+
* Configura i sink di audit.
|
|
132
155
|
*
|
|
133
|
-
* Chiamato da `AccessiModule.forRoot`.
|
|
134
|
-
*
|
|
156
|
+
* Chiamato da `AccessiModule.forRoot`. Con `tenantId` la configurazione vale
|
|
157
|
+
* solo per quel tenant e non sovrascrive quella degli altri: e il presupposto
|
|
158
|
+
* per il router multi-tenant in-process. Senza `tenantId` aggiorna la
|
|
159
|
+
* configurazione di default (comportamento single-tenant).
|
|
135
160
|
*/
|
|
136
161
|
function configureAccessiAudit(options) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
sinks.push(options.store);
|
|
151
|
-
for (const sink of (_b = options === null || options === void 0 ? void 0 : options.sinks) !== null && _b !== void 0 ? _b : []) {
|
|
152
|
-
if (!sinks.includes(sink))
|
|
153
|
-
sinks.push(sink);
|
|
154
|
-
}
|
|
155
|
-
const days = Number(options === null || options === void 0 ? void 0 : options.retentionDays);
|
|
156
|
-
if (Number.isFinite(days) && days > 0)
|
|
157
|
-
retentionDays = days;
|
|
162
|
+
defaultConfig = resolveConfig(options);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Configura audit e sink per un tenant specifico (router in-process).
|
|
166
|
+
*
|
|
167
|
+
* La configurazione vale quando la richiesta e dentro
|
|
168
|
+
* `runWithAccessiTenant`/`createAccessiTenantRouter`; gli eventi fuori da un
|
|
169
|
+
* contesto tenant usano la configurazione di default del processo.
|
|
170
|
+
*/
|
|
171
|
+
function configureAccessiTenantAudit(tenantId, options) {
|
|
172
|
+
if (!tenantId)
|
|
173
|
+
throw new Error('configureAccessiTenantAudit: tenantId obbligatorio.');
|
|
174
|
+
tenantConfigs.set(tenantId, resolveConfig(Object.assign(Object.assign({}, (options !== null && options !== void 0 ? options : {})), { tenantId })));
|
|
158
175
|
}
|
|
159
176
|
/** Ripristina la configurazione iniziale (logger attivo, nessun sink). */
|
|
160
177
|
function resetAccessiAudit() {
|
|
161
|
-
|
|
178
|
+
defaultConfig = resolveConfig(undefined);
|
|
179
|
+
tenantConfigs.clear();
|
|
162
180
|
}
|
|
163
181
|
/** Sink interrogabile configurato, usato dagli endpoint amministrativi. */
|
|
164
|
-
function getAccessiAuditStore() {
|
|
165
|
-
|
|
182
|
+
function getAccessiAuditStore(tenantId) {
|
|
183
|
+
var _a;
|
|
184
|
+
const resolvedTenant = tenantId !== null && tenantId !== void 0 ? tenantId : (0, tenantContext_1.getAccessiTenantId)();
|
|
185
|
+
const config = resolvedTenant
|
|
186
|
+
? (_a = tenantConfigs.get(resolvedTenant)) !== null && _a !== void 0 ? _a : defaultConfig
|
|
187
|
+
: defaultConfig;
|
|
188
|
+
return config.sinks.find((sink) => typeof sink.query === 'function'
|
|
166
189
|
&& typeof sink.purge === 'function');
|
|
167
|
-
return configured;
|
|
168
190
|
}
|
|
169
191
|
/**
|
|
170
192
|
* Registra un evento di sicurezza.
|
|
@@ -173,8 +195,11 @@ function getAccessiAuditStore() {
|
|
|
173
195
|
* non interrompe mai il flusso applicativo.
|
|
174
196
|
*/
|
|
175
197
|
function auditAccessi(event, details = {}) {
|
|
176
|
-
const
|
|
177
|
-
|
|
198
|
+
const config = currentConfig();
|
|
199
|
+
const contextTenant = (0, tenantContext_1.getAccessiTenantId)();
|
|
200
|
+
const tenantId = contextTenant !== null && contextTenant !== void 0 ? contextTenant : config.tenantId;
|
|
201
|
+
const record = Object.assign(Object.assign({ event, timestamp: new Date().toISOString() }, (tenantId ? { tenant: tenantId } : {})), redactDetails(details !== null && details !== void 0 ? details : {}));
|
|
202
|
+
if (config.loggerEnabled) {
|
|
178
203
|
try {
|
|
179
204
|
new Logger_1.Logger('AccessiAudit').warning(JSON.stringify(record));
|
|
180
205
|
}
|
|
@@ -182,8 +207,8 @@ function auditAccessi(event, details = {}) {
|
|
|
182
207
|
// Il logging non deve mai interrompere l'operazione applicativa.
|
|
183
208
|
}
|
|
184
209
|
}
|
|
185
|
-
for (const sink of sinks)
|
|
210
|
+
for (const sink of config.sinks)
|
|
186
211
|
writeToSink(sink, record);
|
|
187
|
-
maybePurge();
|
|
212
|
+
maybePurge(config);
|
|
188
213
|
}
|
|
189
214
|
//# sourceMappingURL=audit.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/accessi-module/security/audit.ts"],"names":[],"mappings":"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","sourcesContent":["import { Logger } from '../../Logger';\nimport { recordAccessiMetric } from '../observability/metrics';\n\n/** Record di audit normalizzato e gia redatto. */\nexport interface AccessiAuditRecord {\n event: string;\n timestamp: string;\n [key: string]: unknown;\n}\n\n/** Destinazione di un record di audit. */\nexport interface AccessiAuditSink {\n /** Nome usato nelle metriche di errore del sink. */\n name?: string;\n write(record: AccessiAuditRecord): void | Promise<void>;\n}\n\nexport interface AccessiAuditQuery {\n event?: string;\n from?: string;\n to?: string;\n codiceUtente?: number;\n limit?: number;\n offset?: number;\n order?: 'asc' | 'desc';\n}\n\nexport interface AccessiAuditPage {\n records: AccessiAuditRecord[];\n total: number;\n limit: number;\n offset: number;\n}\n\n/** Sink interrogabile dagli endpoint amministrativi e dalla retention. */\nexport interface AccessiAuditQueryableSink extends AccessiAuditSink {\n query(filter?: AccessiAuditQuery): Promise<AccessiAuditPage>;\n purge(before: Date): Promise<number>;\n}\n\n/** Opzioni di audit del modulo. */\nexport interface AccessiAuditOptions {\n /** `false` disattiva i sink ma lascia il log strutturato. Default: abilitati. */\n enabled?: boolean;\n /** Sink aggiuntivi (HTTP, DB, OpenTelemetry). */\n sinks?: AccessiAuditSink[];\n /** Sink interrogabile usato da `/api/accessi/audit`. Default: primo sink queryable. */\n store?: AccessiAuditQueryableSink;\n /** Giorni di retention applicati ai sink queryable. Default: nessuna scadenza automatica. */\n retentionDays?: number;\n /** Chiavi aggiuntive da redigere, oltre a quelle predefinite. */\n redact?: string[];\n /** Etichetta tenant aggiunta a ogni record (multi-tenancy). */\n tenantId?: string;\n /** Scrivi anche sul Logger strutturato. Default: true. */\n logger?: boolean;\n}\n\nconst REDACTED = '[REDACTED]';\nconst TRUNCATED = '[TRUNCATED]';\n\nconst DEFAULT_REDACTED_KEYS = new Set([\n 'token',\n 'accesstoken',\n 'refreshtoken',\n 'idtoken',\n 'bearer',\n 'jwt',\n 'authorization',\n 'cookie',\n 'setcookie',\n 'password',\n 'passwordhash',\n 'hashedpassword',\n 'resettoken',\n 'secret',\n 'clientsecret',\n 'privatekey',\n 'apikey',\n 'csrf',\n 'csrftoken',\n]);\n\nconst MAX_STRING_LENGTH = 512;\nconst MAX_ARRAY_LENGTH = 50;\nconst MAX_OBJECT_KEYS = 100;\nconst MAX_DEPTH = 4;\nconst PURGE_INTERVAL_MS = 60 * 60 * 1000;\n\nlet sinks: AccessiAuditSink[] = [];\nlet redactedKeys = new Set(DEFAULT_REDACTED_KEYS);\nlet retentionDays: number | undefined;\nlet loggerEnabled = true;\nlet auditTenantId: string | undefined;\nlet lastPurgeAt = 0;\n\nfunction normalizedKey(key: string): string {\n return key.toLowerCase().replace(/[-_\\s]/g, '');\n}\n\n/**\n * Redige ricorsivamente token, segreti, cookie e header sensibili.\n *\n * Limita profondita, lunghezza delle stringhe e numero di elementi per evitare\n * che un payload applicativo finisca per intero nell'audit.\n */\nexport function redactAccessiAuditValue(value: unknown, depth = 0): unknown {\n if (depth > MAX_DEPTH) return TRUNCATED;\n if (value === null || value === undefined) return value;\n if (typeof value === 'string') {\n return value.length > MAX_STRING_LENGTH ? `${value.slice(0, MAX_STRING_LENGTH - 3)}...` : value;\n }\n if (typeof value === 'number' || typeof value === 'boolean') return value;\n if (typeof value === 'bigint') return value.toString();\n if (value instanceof Date) return value.toISOString();\n if (value instanceof Error) {\n return { name: value.name, message: redactAccessiAuditValue(value.message, depth + 1) };\n }\n if (Array.isArray(value)) {\n return value.slice(0, MAX_ARRAY_LENGTH).map((item) => redactAccessiAuditValue(item, depth + 1));\n }\n if (typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value as Record<string, unknown>).slice(0, MAX_OBJECT_KEYS)) {\n result[key] = redactedKeys.has(normalizedKey(key))\n ? REDACTED\n : redactAccessiAuditValue(entry, depth + 1);\n }\n return result;\n }\n return String(value);\n}\n\nfunction redactDetails(details: Record<string, unknown>): Record<string, unknown> {\n return redactAccessiAuditValue(details) as Record<string, unknown>;\n}\n\nfunction reportSinkError(sink: AccessiAuditSink, error: unknown): void {\n recordAccessiMetric('accessi_audit_sink_errors_total', 1, { sink: sink.name ?? 'unknown' });\n try {\n new Logger('AccessiAudit').error(\n `Audit sink non disponibile${sink.name ? ` (${sink.name})` : ''}: ${error instanceof Error ? error.message : String(error)}`,\n );\n } catch {\n // Il logging non deve mai interrompere l'operazione applicativa.\n }\n}\n\nfunction writeToSink(sink: AccessiAuditSink, record: AccessiAuditRecord): void {\n try {\n const result = sink.write(record);\n if (result && typeof (result as Promise<void>).catch === 'function') {\n (result as Promise<void>).catch((error) => reportSinkError(sink, error));\n }\n } catch (error) {\n reportSinkError(sink, error);\n }\n}\n\nfunction maybePurge(): void {\n if (!retentionDays || retentionDays <= 0) return;\n const now = Date.now();\n if (now - lastPurgeAt < PURGE_INTERVAL_MS) return;\n lastPurgeAt = now;\n const before = new Date(now - retentionDays * 24 * 60 * 60 * 1000);\n for (const sink of sinks) {\n if (typeof (sink as AccessiAuditQueryableSink).purge !== 'function') continue;\n try {\n const result = (sink as AccessiAuditQueryableSink).purge(before);\n if (result && typeof (result as Promise<number>).catch === 'function') {\n (result as Promise<number>).catch((error) => reportSinkError(sink, error));\n }\n } catch (error) {\n reportSinkError(sink, error);\n }\n }\n}\n\n/**\n * Configura i sink di audit del processo.\n *\n * Chiamato da `AccessiModule.forRoot`. Le opzioni sono globali: un solo modulo\n * Accessi per processo e la configurazione consigliata.\n */\nexport function configureAccessiAudit(options: AccessiAuditOptions | undefined): void {\n sinks = [];\n redactedKeys = new Set(DEFAULT_REDACTED_KEYS);\n retentionDays = undefined;\n loggerEnabled = true;\n auditTenantId = options?.tenantId;\n lastPurgeAt = 0;\n\n for (const key of options?.redact ?? []) redactedKeys.add(normalizedKey(key));\n loggerEnabled = options?.logger !== false;\n if (options?.enabled === false) return;\n\n if (options?.store) sinks.push(options.store);\n for (const sink of options?.sinks ?? []) {\n if (!sinks.includes(sink)) sinks.push(sink);\n }\n const days = Number(options?.retentionDays);\n if (Number.isFinite(days) && days > 0) retentionDays = days;\n}\n\n/** Ripristina la configurazione iniziale (logger attivo, nessun sink). */\nexport function resetAccessiAudit(): void {\n configureAccessiAudit(undefined);\n}\n\n/** Sink interrogabile configurato, usato dagli endpoint amministrativi. */\nexport function getAccessiAuditStore(): AccessiAuditQueryableSink | undefined {\n const configured = sinks.find((sink): sink is AccessiAuditQueryableSink =>\n typeof (sink as AccessiAuditQueryableSink).query === 'function'\n && typeof (sink as AccessiAuditQueryableSink).purge === 'function',\n );\n return configured;\n}\n\n/**\n * Registra un evento di sicurezza.\n *\n * Il record viene redatto prima di raggiungere logger e sink. Un sink guasto\n * non interrompe mai il flusso applicativo.\n */\nexport function auditAccessi(event: string, details: Record<string, unknown> = {}): void {\n const record: AccessiAuditRecord = {\n event,\n timestamp: new Date().toISOString(),\n ...(auditTenantId ? { tenant: auditTenantId } : {}),\n ...redactDetails(details ?? {}),\n };\n\n if (loggerEnabled) {\n try {\n new Logger('AccessiAudit').warning(JSON.stringify(record));\n } catch {\n // Il logging non deve mai interrompere l'operazione applicativa.\n }\n }\n\n for (const sink of sinks) writeToSink(sink, record);\n maybePurge();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/accessi-module/security/audit.ts"],"names":[],"mappings":"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","sourcesContent":["import { Logger } from '../../Logger';\nimport { recordAccessiMetric } from '../observability/metrics';\nimport { getAccessiTenantId } from '../tenancy/tenantContext';\n\n/** Record di audit normalizzato e gia redatto. */\nexport interface AccessiAuditRecord {\n event: string;\n timestamp: string;\n [key: string]: unknown;\n}\n\n/** Destinazione di un record di audit. */\nexport interface AccessiAuditSink {\n /** Nome usato nelle metriche di errore del sink. */\n name?: string;\n write(record: AccessiAuditRecord): void | Promise<void>;\n}\n\nexport interface AccessiAuditQuery {\n event?: string;\n from?: string;\n to?: string;\n codiceUtente?: number;\n limit?: number;\n offset?: number;\n order?: 'asc' | 'desc';\n}\n\nexport interface AccessiAuditPage {\n records: AccessiAuditRecord[];\n total: number;\n limit: number;\n offset: number;\n}\n\n/** Sink interrogabile dagli endpoint amministrativi e dalla retention. */\nexport interface AccessiAuditQueryableSink extends AccessiAuditSink {\n query(filter?: AccessiAuditQuery): Promise<AccessiAuditPage>;\n purge(before: Date): Promise<number>;\n}\n\n/** Opzioni di audit del modulo. */\nexport interface AccessiAuditOptions {\n /** `false` disattiva i sink ma lascia il log strutturato. Default: abilitati. */\n enabled?: boolean;\n /** Sink aggiuntivi (HTTP, DB, OpenTelemetry). */\n sinks?: AccessiAuditSink[];\n /** Sink interrogabile usato da `/api/accessi/audit`. Default: primo sink queryable. */\n store?: AccessiAuditQueryableSink;\n /** Giorni di retention applicati ai sink queryable. Default: nessuna scadenza automatica. */\n retentionDays?: number;\n /** Chiavi aggiuntive da redigere, oltre a quelle predefinite. */\n redact?: string[];\n /** Etichetta tenant aggiunta a ogni record (multi-tenancy). */\n tenantId?: string;\n /** Scrivi anche sul Logger strutturato. Default: true. */\n logger?: boolean;\n}\n\nconst REDACTED = '[REDACTED]';\nconst TRUNCATED = '[TRUNCATED]';\n\nconst DEFAULT_REDACTED_KEYS = new Set([\n 'token',\n 'accesstoken',\n 'refreshtoken',\n 'idtoken',\n 'bearer',\n 'jwt',\n 'authorization',\n 'cookie',\n 'setcookie',\n 'password',\n 'passwordhash',\n 'hashedpassword',\n 'resettoken',\n 'secret',\n 'clientsecret',\n 'privatekey',\n 'apikey',\n 'csrf',\n 'csrftoken',\n]);\n\nconst MAX_STRING_LENGTH = 512;\nconst MAX_ARRAY_LENGTH = 50;\nconst MAX_OBJECT_KEYS = 100;\nconst MAX_DEPTH = 4;\nconst PURGE_INTERVAL_MS = 60 * 60 * 1000;\n\ninterface ResolvedAccessiAuditConfig {\n sinks: AccessiAuditSink[];\n redactedKeys: Set<string>;\n retentionDays?: number;\n loggerEnabled: boolean;\n tenantId?: string;\n lastPurgeAt: number;\n}\n\nfunction resolveConfig(options: AccessiAuditOptions | undefined): ResolvedAccessiAuditConfig {\n const redactedKeys = new Set(DEFAULT_REDACTED_KEYS);\n for (const key of options?.redact ?? []) redactedKeys.add(normalizedKey(key));\n const retentionDays = Number(options?.retentionDays);\n return {\n sinks: options?.enabled === false\n ? []\n : [\n ...(options?.store ? [options.store] : []),\n ...(options?.sinks ?? []).filter((sink) => sink !== options?.store),\n ],\n redactedKeys,\n ...(Number.isFinite(retentionDays) && retentionDays > 0 ? { retentionDays } : {}),\n loggerEnabled: options?.logger !== false,\n ...(options?.tenantId ? { tenantId: options.tenantId } : {}),\n lastPurgeAt: 0,\n };\n}\n\nlet defaultConfig = resolveConfig(undefined);\nconst tenantConfigs = new Map<string, ResolvedAccessiAuditConfig>();\n\nfunction currentConfig(): ResolvedAccessiAuditConfig {\n const tenantId = getAccessiTenantId();\n if (tenantId) {\n const tenantConfig = tenantConfigs.get(tenantId);\n if (tenantConfig) return tenantConfig;\n }\n return defaultConfig;\n}\n\nfunction normalizedKey(key: string): string {\n return key.toLowerCase().replace(/[-_\\s]/g, '');\n}\n\n/**\n * Redige ricorsivamente token, segreti, cookie e header sensibili.\n *\n * Limita profondita, lunghezza delle stringhe e numero di elementi per evitare\n * che un payload applicativo finisca per intero nell'audit.\n */\nexport function redactAccessiAuditValue(value: unknown, depth = 0): unknown {\n return redactValue(value, depth, currentConfig().redactedKeys);\n}\n\nfunction redactValue(value: unknown, depth: number, keys: Set<string>): unknown {\n if (depth > MAX_DEPTH) return TRUNCATED;\n if (value === null || value === undefined) return value;\n if (typeof value === 'string') {\n return value.length > MAX_STRING_LENGTH ? `${value.slice(0, MAX_STRING_LENGTH - 3)}...` : value;\n }\n if (typeof value === 'number' || typeof value === 'boolean') return value;\n if (typeof value === 'bigint') return value.toString();\n if (value instanceof Date) return value.toISOString();\n if (value instanceof Error) {\n return { name: value.name, message: redactValue(value.message, depth + 1, keys) };\n }\n if (Array.isArray(value)) {\n return value.slice(0, MAX_ARRAY_LENGTH).map((item) => redactValue(item, depth + 1, keys));\n }\n if (typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value as Record<string, unknown>).slice(0, MAX_OBJECT_KEYS)) {\n result[key] = keys.has(normalizedKey(key))\n ? REDACTED\n : redactValue(entry, depth + 1, keys);\n }\n return result;\n }\n return String(value);\n}\n\nfunction redactDetails(details: Record<string, unknown>): Record<string, unknown> {\n return redactAccessiAuditValue(details) as Record<string, unknown>;\n}\n\nfunction reportSinkError(sink: AccessiAuditSink, error: unknown): void {\n recordAccessiMetric('accessi_audit_sink_errors_total', 1, { sink: sink.name ?? 'unknown' });\n try {\n new Logger('AccessiAudit').error(\n `Audit sink non disponibile${sink.name ? ` (${sink.name})` : ''}: ${error instanceof Error ? error.message : String(error)}`,\n );\n } catch {\n // Il logging non deve mai interrompere l'operazione applicativa.\n }\n}\n\nfunction writeToSink(sink: AccessiAuditSink, record: AccessiAuditRecord): void {\n try {\n const result = sink.write(record);\n if (result && typeof (result as Promise<void>).catch === 'function') {\n (result as Promise<void>).catch((error) => reportSinkError(sink, error));\n }\n } catch (error) {\n reportSinkError(sink, error);\n }\n}\n\nfunction maybePurge(config: ResolvedAccessiAuditConfig): void {\n if (!config.retentionDays || config.retentionDays <= 0) return;\n const now = Date.now();\n if (now - config.lastPurgeAt < PURGE_INTERVAL_MS) return;\n config.lastPurgeAt = now;\n const before = new Date(now - config.retentionDays * 24 * 60 * 60 * 1000);\n for (const sink of config.sinks) {\n if (typeof (sink as AccessiAuditQueryableSink).purge !== 'function') continue;\n try {\n const result = (sink as AccessiAuditQueryableSink).purge(before);\n if (result && typeof (result as Promise<number>).catch === 'function') {\n (result as Promise<number>).catch((error) => reportSinkError(sink, error));\n }\n } catch (error) {\n reportSinkError(sink, error);\n }\n }\n}\n\n/**\n * Configura i sink di audit.\n *\n * Chiamato da `AccessiModule.forRoot`. Con `tenantId` la configurazione vale\n * solo per quel tenant e non sovrascrive quella degli altri: e il presupposto\n * per il router multi-tenant in-process. Senza `tenantId` aggiorna la\n * configurazione di default (comportamento single-tenant).\n */\nexport function configureAccessiAudit(options: AccessiAuditOptions | undefined): void {\n defaultConfig = resolveConfig(options);\n}\n\n/**\n * Configura audit e sink per un tenant specifico (router in-process).\n *\n * La configurazione vale quando la richiesta e dentro\n * `runWithAccessiTenant`/`createAccessiTenantRouter`; gli eventi fuori da un\n * contesto tenant usano la configurazione di default del processo.\n */\nexport function configureAccessiTenantAudit(\n tenantId: string,\n options: AccessiAuditOptions | undefined,\n): void {\n if (!tenantId) throw new Error('configureAccessiTenantAudit: tenantId obbligatorio.');\n tenantConfigs.set(tenantId, resolveConfig({ ...(options ?? {}), tenantId }));\n}\n\n/** Ripristina la configurazione iniziale (logger attivo, nessun sink). */\nexport function resetAccessiAudit(): void {\n defaultConfig = resolveConfig(undefined);\n tenantConfigs.clear();\n}\n\n/** Sink interrogabile configurato, usato dagli endpoint amministrativi. */\nexport function getAccessiAuditStore(tenantId?: string): AccessiAuditQueryableSink | undefined {\n const resolvedTenant = tenantId ?? getAccessiTenantId();\n const config = resolvedTenant\n ? tenantConfigs.get(resolvedTenant) ?? defaultConfig\n : defaultConfig;\n return config.sinks.find((sink): sink is AccessiAuditQueryableSink =>\n typeof (sink as AccessiAuditQueryableSink).query === 'function'\n && typeof (sink as AccessiAuditQueryableSink).purge === 'function',\n );\n}\n\n/**\n * Registra un evento di sicurezza.\n *\n * Il record viene redatto prima di raggiungere logger e sink. Un sink guasto\n * non interrompe mai il flusso applicativo.\n */\nexport function auditAccessi(event: string, details: Record<string, unknown> = {}): void {\n const config = currentConfig();\n const contextTenant = getAccessiTenantId();\n const tenantId = contextTenant ?? config.tenantId;\n const record: AccessiAuditRecord = {\n event,\n timestamp: new Date().toISOString(),\n ...(tenantId ? { tenant: tenantId } : {}),\n ...redactDetails(details ?? {}),\n };\n\n if (config.loggerEnabled) {\n try {\n new Logger('AccessiAudit').warning(JSON.stringify(record));\n } catch {\n // Il logging non deve mai interrompere l'operazione applicativa.\n }\n }\n\n for (const sink of config.sinks) writeToSink(sink, record);\n maybePurge(config);\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Contesto di richiesta per il router multi-tenant in-process. */
|
|
2
|
+
export interface AccessiTenantContext {
|
|
3
|
+
tenantId: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Esegue `callback` con il tenant corrente.
|
|
7
|
+
*
|
|
8
|
+
* Usare il router (`createAccessiTenantRouter`) per impostarlo automaticamente;
|
|
9
|
+
* le API pubbliche servono a host che montano un proprio dispatcher.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runWithAccessiTenant<T>(tenantId: string, callback: () => T): T;
|
|
12
|
+
/** Tenant corrente, se la richiesta e dentro `runWithAccessiTenant`/router. */
|
|
13
|
+
export declare function getAccessiTenantContext(): AccessiTenantContext | undefined;
|
|
14
|
+
/** Scorciatoia per il solo identificativo del tenant corrente. */
|
|
15
|
+
export declare function getAccessiTenantId(): string | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runWithAccessiTenant = runWithAccessiTenant;
|
|
4
|
+
exports.getAccessiTenantContext = getAccessiTenantContext;
|
|
5
|
+
exports.getAccessiTenantId = getAccessiTenantId;
|
|
6
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
7
|
+
const storage = new node_async_hooks_1.AsyncLocalStorage();
|
|
8
|
+
/**
|
|
9
|
+
* Esegue `callback` con il tenant corrente.
|
|
10
|
+
*
|
|
11
|
+
* Usare il router (`createAccessiTenantRouter`) per impostarlo automaticamente;
|
|
12
|
+
* le API pubbliche servono a host che montano un proprio dispatcher.
|
|
13
|
+
*/
|
|
14
|
+
function runWithAccessiTenant(tenantId, callback) {
|
|
15
|
+
return storage.run({ tenantId }, callback);
|
|
16
|
+
}
|
|
17
|
+
/** Tenant corrente, se la richiesta e dentro `runWithAccessiTenant`/router. */
|
|
18
|
+
function getAccessiTenantContext() {
|
|
19
|
+
return storage.getStore();
|
|
20
|
+
}
|
|
21
|
+
/** Scorciatoia per il solo identificativo del tenant corrente. */
|
|
22
|
+
function getAccessiTenantId() {
|
|
23
|
+
var _a;
|
|
24
|
+
return (_a = storage.getStore()) === null || _a === void 0 ? void 0 : _a.tenantId;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=tenantContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenantContext.js","sourceRoot":"","sources":["../../../src/accessi-module/tenancy/tenantContext.ts"],"names":[],"mappings":";;AAeA,oDAEC;AAGD,0DAEC;AAGD,gDAEC;AA3BD,uDAAqD;AAOrD,MAAM,OAAO,GAAG,IAAI,oCAAiB,EAAwB,CAAC;AAE9D;;;;;GAKG;AACH,SAAgB,oBAAoB,CAAI,QAAgB,EAAE,QAAiB;IACzE,OAAO,OAAO,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC7C,CAAC;AAED,+EAA+E;AAC/E,SAAgB,uBAAuB;IACrC,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,kEAAkE;AAClE,SAAgB,kBAAkB;;IAChC,OAAO,MAAA,OAAO,CAAC,QAAQ,EAAE,0CAAE,QAAQ,CAAC;AACtC,CAAC","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\n/** Contesto di richiesta per il router multi-tenant in-process. */\nexport interface AccessiTenantContext {\n tenantId: string;\n}\n\nconst storage = new AsyncLocalStorage<AccessiTenantContext>();\n\n/**\n * Esegue `callback` con il tenant corrente.\n *\n * Usare il router (`createAccessiTenantRouter`) per impostarlo automaticamente;\n * le API pubbliche servono a host che montano un proprio dispatcher.\n */\nexport function runWithAccessiTenant<T>(tenantId: string, callback: () => T): T {\n return storage.run({ tenantId }, callback);\n}\n\n/** Tenant corrente, se la richiesta e dentro `runWithAccessiTenant`/router. */\nexport function getAccessiTenantContext(): AccessiTenantContext | undefined {\n return storage.getStore();\n}\n\n/** Scorciatoia per il solo identificativo del tenant corrente. */\nexport function getAccessiTenantId(): string | undefined {\n return storage.getStore()?.tenantId;\n}\n"]}
|
|
Binary file
|
|
@@ -144,7 +144,7 @@ tenant.
|
|
|
144
144
|
| Schema per tenant | Firebird non offre schema/search_path; sarebbe un database separato con altro nome. |
|
|
145
145
|
| Due `AccessiModule` nello stesso processo | La configurazione Nest è globale; audit/metriche/logger condividono stato. |
|
|
146
146
|
| Selezione tenant da header non fidato | Permetterebbe a un client di cambiare tenant e tentare leakage; consentito solo dietro proxy che sovrascrive l'header. |
|
|
147
|
-
| Hot reload dei file `.environment.ts` | Nest non smonta moduli a caldo; il
|
|
147
|
+
| Hot reload dei file `.environment.ts` | Nest non smonta moduli a caldo; il supervisore riavvia i processi dei tenant modificati, non l'intero servizio. |
|
|
148
148
|
|
|
149
149
|
## Conseguenze
|
|
150
150
|
|
|
@@ -152,7 +152,7 @@ Positive:
|
|
|
152
152
|
|
|
153
153
|
- isolamento forte: database, chiavi e processi separati;
|
|
154
154
|
- nessuna migrazione dello schema esistente;
|
|
155
|
-
- aggiunta tenant = nuovo file `.environment.ts`
|
|
155
|
+
- aggiunta tenant = nuovo file `.environment.ts` (il supervisore con `--watch` lo avvia senza riavvio manuale);
|
|
156
156
|
- rollback per tenant indipendente.
|
|
157
157
|
|
|
158
158
|
Negative:
|
|
@@ -166,16 +166,18 @@ Negative:
|
|
|
166
166
|
## Piano
|
|
167
167
|
|
|
168
168
|
1. **Fase 1 (questa)**: loader cartella, validazione isolamento, CLI
|
|
169
|
-
`accessi:tenants` (`--check`, `--list`, `--resolve`, `--serve`),
|
|
170
|
-
e test.
|
|
169
|
+
`accessi:tenants` (`--check`, `--list`, `--resolve`, `--serve`, `--watch`),
|
|
170
|
+
helper store Redis, documentazione e test.
|
|
171
171
|
2. **Fase 2**: template systemd/docker e reverse proxy host/path; health check
|
|
172
172
|
per tenant nel supervisore.
|
|
173
173
|
3. **Fase 3**: migrazione del deployment single-tenant esistente: creare
|
|
174
174
|
`tenants/<id>.environment.ts` dalla configurazione attuale, avviare in parallelo, spostare il DNS,
|
|
175
175
|
spegnere il vecchio servizio.
|
|
176
|
-
4. **Fase 4 (
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
4. **Fase 4 (opt-in, implementata come building block)**: router in-process
|
|
177
|
+
`createAccessiTenantRouter` con contesto `runWithAccessiTenant`, audit e
|
|
178
|
+
metriche per tenant. Le app Accessi restano indipendenti e il
|
|
179
|
+
processo-per-tenant resta il default; le API globali (`authorizeAccessi`)
|
|
180
|
+
non sono tenant-aware e non vanno usate in questa modalità.
|
|
179
181
|
|
|
180
182
|
## Migrazione
|
|
181
183
|
|