emilsoftware-utilities 2.0.0-dev.78 → 2.0.0-dev.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/accessi/client/generated/admin.d.ts +21 -1
  2. package/dist/accessi/client/generated/admin.js +35 -1
  3. package/dist/accessi/client/generated/admin.js.map +1 -1
  4. package/dist/accessi-module/AccessiModule.d.ts +3 -0
  5. package/dist/accessi-module/AccessiModule.js +4 -0
  6. package/dist/accessi-module/AccessiModule.js.map +1 -1
  7. package/dist/accessi-module/Console/console.js +1 -0
  8. package/dist/accessi-module/Console/generated/emilsoftware-utilities.d.ts +21 -1
  9. package/dist/accessi-module/Console/generated/emilsoftware-utilities.js +35 -1
  10. package/dist/accessi-module/Console/generated/emilsoftware-utilities.js.map +1 -1
  11. package/dist/accessi-module/Console/generated/model/exportAuditOrder.d.ts +11 -0
  12. package/dist/accessi-module/Console/generated/model/exportAuditOrder.js +15 -0
  13. package/dist/accessi-module/Console/generated/model/exportAuditOrder.js.map +1 -0
  14. package/dist/accessi-module/Console/generated/model/exportAuditParams.d.ts +37 -0
  15. package/dist/accessi-module/Console/generated/model/exportAuditParams.js +3 -0
  16. package/dist/accessi-module/Console/generated/model/exportAuditParams.js.map +1 -0
  17. package/dist/accessi-module/Console/generated/model/index.d.ts +4 -0
  18. package/dist/accessi-module/Console/generated/model/index.js +4 -0
  19. package/dist/accessi-module/Console/generated/model/index.js.map +1 -1
  20. package/dist/accessi-module/Console/generated/model/queryAuditOrder.d.ts +11 -0
  21. package/dist/accessi-module/Console/generated/model/queryAuditOrder.js +15 -0
  22. package/dist/accessi-module/Console/generated/model/queryAuditOrder.js.map +1 -0
  23. package/dist/accessi-module/Console/generated/model/queryAuditParams.d.ts +37 -0
  24. package/dist/accessi-module/Console/generated/model/queryAuditParams.js +3 -0
  25. package/dist/accessi-module/Console/generated/model/queryAuditParams.js.map +1 -0
  26. package/dist/accessi-module/Console/wiki.js +1 -0
  27. package/dist/accessi-module/Console/wiki.js.map +1 -1
  28. package/dist/accessi-module/Controllers/AccessiAuditController.d.ts +16 -0
  29. package/dist/accessi-module/Controllers/AccessiAuditController.js +155 -0
  30. package/dist/accessi-module/Controllers/AccessiAuditController.js.map +1 -0
  31. package/dist/accessi-module/Controllers/AuthController.js +15 -0
  32. package/dist/accessi-module/Controllers/AuthController.js.map +1 -1
  33. package/dist/accessi-module/Controllers/ConsoleEntryController.js +6 -0
  34. package/dist/accessi-module/Controllers/ConsoleEntryController.js.map +1 -1
  35. package/dist/accessi-module/Dtos/AuditDtos.d.ts +10 -0
  36. package/dist/accessi-module/Dtos/AuditDtos.js +68 -0
  37. package/dist/accessi-module/Dtos/AuditDtos.js.map +1 -0
  38. package/dist/accessi-module/Dtos/index.d.ts +1 -0
  39. package/dist/accessi-module/Dtos/index.js +1 -0
  40. package/dist/accessi-module/Dtos/index.js.map +1 -1
  41. package/dist/accessi-module/index.d.ts +5 -1
  42. package/dist/accessi-module/index.js +14 -3
  43. package/dist/accessi-module/index.js.map +1 -1
  44. package/dist/accessi-module/security/audit.d.ts +68 -3
  45. package/dist/accessi-module/security/audit.js +177 -4
  46. package/dist/accessi-module/security/audit.js.map +1 -1
  47. package/dist/accessi-module/security/auditSinks.d.ts +55 -0
  48. package/dist/accessi-module/security/auditSinks.js +228 -0
  49. package/dist/accessi-module/security/auditSinks.js.map +1 -0
  50. package/dist/accessi-module/security/jwtTokenOptions.js +2 -0
  51. package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -1
  52. package/dist/accessi-module/security/publicAuthRateLimit.js +7 -0
  53. package/dist/accessi-module/security/publicAuthRateLimit.js.map +1 -1
  54. package/dist/accessi-module/security/sessionRevocation.js.map +1 -1
  55. package/dist/emilsoftware-utilities-2.0.0-dev.79.tgz +0 -0
  56. package/docs/audit.md +136 -0
  57. package/package.json +3 -3
  58. package/dist/emilsoftware-utilities-2.0.0-dev.78.tgz +0 -0
package/docs/audit.md ADDED
@@ -0,0 +1,136 @@
1
+ # Audit persistente
2
+
3
+ `auditAccessi` scrive un record strutturato per ogni evento di sicurezza. Di
4
+ default il record finisce nel log (`AccessiAudit`); con `audit.sinks` può essere
5
+ persistito su Redis, inoltrato a un collector HTTP o scritto su database e
6
+ OpenTelemetry tramite una callback.
7
+
8
+ ```ts
9
+ const accessiOptions: AccessiOptions = {
10
+ audit: {
11
+ retentionDays: 90,
12
+ logger: true,
13
+ store: createRedisAccessiAuditSink({
14
+ client: redisClient, // qualunque client con eval (ioredis, node-redis)
15
+ maxRecords: 100_000,
16
+ retentionMs: 90 * 24 * 60 * 60 * 1000,
17
+ }),
18
+ sinks: [
19
+ createHttpAccessiAuditSink({
20
+ url: 'https://siem.example.com/accessi/audit',
21
+ headers: { Authorization: `Bearer ${process.env.SIEM_TOKEN}` },
22
+ }),
23
+ ],
24
+ },
25
+ };
26
+ ```
27
+
28
+ `store` è il sink interrogabile usato dagli endpoint amministrativi. Se non è
29
+ impostato, viene usato il primo sink con `query`/`purge` presente in `sinks`.
30
+ Senza sink interrogabile gli endpoint rispondono `501 ACCESSI_AUDIT_STORE_DISABLED`
31
+ e il log strutturato continua a funzionare.
32
+
33
+ ## Cosa viene registrato
34
+
35
+ | Evento | Dettagli | Dove |
36
+ | --- | --- | --- |
37
+ | `login_succeeded` / `login_failed` | `codiceUtente`, `ip`, motivo | AuthController |
38
+ | `two_factor_succeeded` / `two_factor_failed` | `codiceUtente`, `ip` | AuthController |
39
+ | `refresh_succeeded` / `refresh_failed` | `ip`, motivo (`reuse`, `invalid`, ...) | AuthController |
40
+ | `accessi_refresh_token_reuse` | `codiceUtente`, `jti`, `familyId` | AuthService |
41
+ | `access_token_revoked` | utente, `jti`, self | AuthController |
42
+ | `accessi_sessions_revoked` | utente, cutoff, motivo | sessionRevocation |
43
+ | `public_rate_limited` / `edge_rate_limited` | scope, IP, retry | rate limit |
44
+ | `console_ticket_issued` / `console_ticket_consumed` / `console_ticket_failed` | utente o esito | ConsoleEntryController |
45
+ | `jwt_unknown_key_id` | `kid`, `purpose`, algoritmo | verifica JWT |
46
+
47
+ Non vengono registrati token, password, header `Authorization`, cookie o chiavi
48
+ private: la redazione è applicata prima del logger e di ogni sink.
49
+
50
+ ## Redazione
51
+
52
+ `redactAccessiAuditValue` normalizza le chiavi (case-insensitive, ignora `-`,
53
+ `_` e spazi) e sostituisce con `[REDACTED]`:
54
+
55
+ `token`, `accessToken`, `refreshToken`, `idToken`, `bearer`, `jwt`,
56
+ `authorization`, `cookie`, `set-cookie`, `password`, `passwordHash`,
57
+ `hashedPassword`, `resetToken`, `secret`, `clientSecret`, `privateKey`,
58
+ `apiKey`, `csrf`, `csrfToken`.
59
+
60
+ Inoltre:
61
+
62
+ - le stringhe oltre 512 caratteri vengono troncate;
63
+ - gli array oltre 50 elementi e gli oggetti oltre 100 chiavi vengono limitati;
64
+ - oltre 4 livelli di profondità il valore diventa `[TRUNCATED]`;
65
+ - gli `Error` diventano `{ name, message }` senza stack.
66
+
67
+ Aggiungere chiavi specifiche dell'host:
68
+
69
+ ```ts
70
+ audit: { redact: ['cardNumber', 'iban'] }
71
+ ```
72
+
73
+ ## Retention
74
+
75
+ `retentionDays` abilita la pulizia periodica (al massimo una volta all'ora,
76
+ pigra, al primo evento successivo) dei sink che implementano `purge`. I sink
77
+ in memoria hanno anche `maxRecords`. Il sink Redis usa `LTRIM` più `PEXPIRE`:
78
+
79
+ ```ts
80
+ createRedisAccessiAuditSink({
81
+ client: redisClient,
82
+ maxRecords: 100_000,
83
+ retentionMs: 90 * 24 * 60 * 60 * 1000,
84
+ });
85
+ ```
86
+
87
+ La retention deve essere almeno pari agli obblighi di legge applicabili e alla
88
+ durata massima dei token, così un incidente resta ricostruibile.
89
+
90
+ ## Endpoint amministrativi
91
+
92
+ Richiedono un JWT Accessi di un superutente o admin:
93
+
94
+ ```http
95
+ GET /api/accessi/audit?event=login_failed&from=2026-01-01T00:00:00Z&limit=100
96
+ GET /api/accessi/audit/export?event=login_failed&from=2026-01-01T00:00:00Z
97
+ ```
98
+
99
+ La query restituisce `{ records, total, limit, offset }`. L'export produce
100
+ NDJSON (`application/x-ndjson`) con gli stessi filtri, senza paginazione, fino a
101
+ 100000 record per richiesta. I record sono già redatti.
102
+
103
+ ## Sink disponibili
104
+
105
+ | Factory | Uso |
106
+ | --- | --- |
107
+ | `createMemoryAccessiAuditSink` | test, sviluppo, singola istanza |
108
+ | `createRedisAccessiAuditSink` | persistenza condivisa multi-istanza |
109
+ | `createHttpAccessiAuditSink` | SIEM/collector HTTP |
110
+ | `createCallbackAccessiAuditSink` | database applicativo, OpenTelemetry, coda |
111
+
112
+ Esempio database/OTel:
113
+
114
+ ```ts
115
+ const dbSink = createCallbackAccessiAuditSink(async (record) => {
116
+ await db.insert('AUDIT_EVENTS', {
117
+ EVENT: record.event,
118
+ OCCURRED_AT: record.timestamp,
119
+ PAYLOAD: JSON.stringify(record),
120
+ });
121
+ }, 'database');
122
+ ```
123
+
124
+ La callback deve gestire i propri errori: un errore viene contato in
125
+ `accessi_audit_sink_errors_total{sink}` e non blocca la richiesta.
126
+
127
+ ## Note
128
+
129
+ - I sink sono globali per processo: configurare un solo modulo Accessi per
130
+ processo, come già avviene per il logger Accessi.
131
+ - `audit.enabled: false` disattiva i sink ma **non** il log strutturato; usare
132
+ `logger: false` per silenziare anche quello.
133
+ - Non inserire payload applicativi completi nei dettagli degli eventi: la
134
+ redazione è una rete di sicurezza, non un invito a loggare dati sensibili.
135
+ - L'audit non sostituisce le metriche: usare `docs/metrics.md` per gli alert in
136
+ tempo reale.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emilsoftware-utilities",
3
- "version": "2.0.0-dev.78",
3
+ "version": "2.0.0-dev.79",
4
4
  "description": "Utilities for EmilSoftware",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -139,8 +139,8 @@
139
139
  "typecheck": "tsc --noEmit",
140
140
  "lint": "node scripts/lint.cjs",
141
141
  "test": "npm run typecheck && npm run lint && npm run test:unit",
142
- "test:unit": "node --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs test/openapi-config.cjs test/resource-server.cjs test/accessi-security-standards.cjs test/accessi-service.cjs test/refresh-token.cjs test/token-revocation.cjs test/accessi-client.cjs test/accessi-client-scopes.cjs test/openapi-semantic-diff.cjs test/proxy-ip.cjs test/readiness.cjs test/jwt-key-rotation.cjs test/security-headers.cjs test/auth-cookies.cjs test/metrics.cjs",
143
- "test:coverage": "node --experimental-test-coverage --test-coverage-lines=50 --test-coverage-functions=60 --test-coverage-branches=60 --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs test/openapi-config.cjs test/resource-server.cjs test/accessi-security-standards.cjs test/accessi-service.cjs test/refresh-token.cjs test/token-revocation.cjs test/accessi-client.cjs test/accessi-client-scopes.cjs test/openapi-semantic-diff.cjs test/proxy-ip.cjs test/readiness.cjs test/jwt-key-rotation.cjs test/security-headers.cjs test/auth-cookies.cjs test/metrics.cjs",
142
+ "test:unit": "node --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs test/openapi-config.cjs test/resource-server.cjs test/accessi-security-standards.cjs test/accessi-service.cjs test/refresh-token.cjs test/token-revocation.cjs test/accessi-client.cjs test/accessi-client-scopes.cjs test/openapi-semantic-diff.cjs test/proxy-ip.cjs test/readiness.cjs test/jwt-key-rotation.cjs test/security-headers.cjs test/auth-cookies.cjs test/metrics.cjs test/audit.cjs",
143
+ "test:coverage": "node --experimental-test-coverage --test-coverage-lines=50 --test-coverage-functions=60 --test-coverage-branches=60 --test test/angular-components.cjs test/menus-catalog.mjs test/menu-scopes.cjs test/accessi-regressions.cjs test/password-reset-regressions.cjs test/two-factor.cjs test/accessi-schema.cjs test/utilities-regressions.cjs test/service-token-regressions.cjs test/accessi-email-config.cjs test/accessi-catalog-scripts.cjs test/openapi-config.cjs test/resource-server.cjs test/accessi-security-standards.cjs test/accessi-service.cjs test/refresh-token.cjs test/token-revocation.cjs test/accessi-client.cjs test/accessi-client-scopes.cjs test/openapi-semantic-diff.cjs test/proxy-ip.cjs test/readiness.cjs test/jwt-key-rotation.cjs test/security-headers.cjs test/auth-cookies.cjs test/metrics.cjs test/audit.cjs",
144
144
  "test:accessi": "npm run test:unit",
145
145
  "test:all": "npm test && npm run test:integration && npm run test:integration:redis",
146
146
  "test:integration": "node test/integration/run-firebird.cjs 5",