emilsoftware-utilities 2.0.0-dev.67 → 2.0.0-dev.69
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/resource-server/errors.d.ts +1 -1
- package/dist/accessi/resource-server/errors.js.map +1 -1
- package/dist/accessi/resource-server/express.js +21 -0
- package/dist/accessi/resource-server/express.js.map +1 -1
- package/dist/accessi/resource-server/nest.js +13 -0
- package/dist/accessi/resource-server/nest.js.map +1 -1
- package/dist/accessi/resource-server/runtime.js +11 -1
- package/dist/accessi/resource-server/runtime.js.map +1 -1
- package/dist/accessi/resource-server/types.d.ts +7 -0
- package/dist/accessi/resource-server/types.js.map +1 -1
- 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/standalone.d.ts +41 -0
- package/dist/accessi/server/standalone.js +264 -0
- package/dist/accessi/server/standalone.js.map +1 -0
- package/dist/accessi-module/AccessiModule.d.ts +53 -2
- package/dist/accessi-module/AccessiModule.js +5 -2
- package/dist/accessi-module/AccessiModule.js.map +1 -1
- package/dist/accessi-module/Console/accessiFetch.d.ts +5 -0
- package/dist/accessi-module/Console/accessiFetch.js +7 -4
- package/dist/accessi-module/Console/accessiFetch.js.map +1 -1
- package/dist/accessi-module/Console/console.js +21 -7
- package/dist/accessi-module/Console/generated/emilsoftware-utilities.d.ts +31 -1
- package/dist/accessi-module/Console/generated/emilsoftware-utilities.js +28 -3
- package/dist/accessi-module/Console/generated/emilsoftware-utilities.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/exchangeFederatedIdentityRequest.d.ts +12 -0
- package/dist/accessi-module/Console/generated/model/exchangeFederatedIdentityRequest.js +9 -0
- package/dist/accessi-module/Console/generated/model/exchangeFederatedIdentityRequest.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/federatedAuthenticationResultDto.d.ts +15 -0
- package/dist/accessi-module/Console/generated/model/federatedAuthenticationResultDto.js +3 -0
- package/dist/accessi-module/Console/generated/model/federatedAuthenticationResultDto.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/federatedExchangeResponse.d.ts +14 -0
- package/dist/accessi-module/Console/generated/model/federatedExchangeResponse.js +3 -0
- package/dist/accessi-module/Console/generated/model/federatedExchangeResponse.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/index.d.ts +6 -0
- package/dist/accessi-module/Console/generated/model/index.js +6 -0
- package/dist/accessi-module/Console/generated/model/index.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/loginResult.d.ts +2 -0
- package/dist/accessi-module/Console/generated/model/loginResult.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/refreshTokenRequest.d.ts +10 -0
- package/dist/accessi-module/Console/generated/model/refreshTokenRequest.js +9 -0
- package/dist/accessi-module/Console/generated/model/refreshTokenRequest.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/refreshTokenResponse.d.ts +14 -0
- package/dist/accessi-module/Console/generated/model/refreshTokenResponse.js +3 -0
- package/dist/accessi-module/Console/generated/model/refreshTokenResponse.js.map +1 -0
- package/dist/accessi-module/Console/generated/model/refreshTokenResult.d.ts +13 -0
- package/dist/accessi-module/Console/generated/model/refreshTokenResult.js +3 -0
- package/dist/accessi-module/Console/generated/model/refreshTokenResult.js.map +1 -0
- package/dist/accessi-module/Console/wiki.js +16 -3
- package/dist/accessi-module/Console/wiki.js.map +1 -1
- package/dist/accessi-module/Controllers/AccessiConsoleController.d.ts +6 -1
- package/dist/accessi-module/Controllers/AccessiConsoleController.js +24 -1
- package/dist/accessi-module/Controllers/AccessiConsoleController.js.map +1 -1
- package/dist/accessi-module/Controllers/AuthController.d.ts +3 -1
- package/dist/accessi-module/Controllers/AuthController.js +90 -8
- package/dist/accessi-module/Controllers/AuthController.js.map +1 -1
- package/dist/accessi-module/Controllers/ConsoleEntryController.d.ts +2 -0
- package/dist/accessi-module/Controllers/ConsoleEntryController.js +11 -3
- package/dist/accessi-module/Controllers/ConsoleEntryController.js.map +1 -1
- package/dist/accessi-module/Controllers/EmailController.js +1 -1
- package/dist/accessi-module/Controllers/EmailController.js.map +1 -1
- package/dist/accessi-module/Controllers/UserController.js +1 -1
- package/dist/accessi-module/Controllers/UserController.js.map +1 -1
- package/dist/accessi-module/Dtos/FederatedExchangeDtos.d.ts +21 -0
- package/dist/accessi-module/Dtos/FederatedExchangeDtos.js +74 -0
- package/dist/accessi-module/Dtos/FederatedExchangeDtos.js.map +1 -0
- package/dist/accessi-module/Dtos/LoginResponse.d.ts +1 -0
- package/dist/accessi-module/Dtos/LoginResponse.js +6 -0
- package/dist/accessi-module/Dtos/LoginResponse.js.map +1 -1
- package/dist/accessi-module/Dtos/RefreshTokenDtos.d.ts +12 -0
- package/dist/accessi-module/Dtos/RefreshTokenDtos.js +50 -0
- package/dist/accessi-module/Dtos/RefreshTokenDtos.js.map +1 -0
- package/dist/accessi-module/Dtos/index.d.ts +2 -0
- package/dist/accessi-module/Dtos/index.js +2 -0
- package/dist/accessi-module/Dtos/index.js.map +1 -1
- package/dist/accessi-module/Services/AuthService/AuthService.d.ts +14 -0
- package/dist/accessi-module/Services/AuthService/AuthService.js +99 -6
- package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js +8 -16
- package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js.map +1 -1
- package/dist/accessi-module/federated-auth/FederatedAuthExchangeController.d.ts +15 -0
- package/dist/accessi-module/federated-auth/FederatedAuthExchangeController.js +85 -0
- package/dist/accessi-module/federated-auth/FederatedAuthExchangeController.js.map +1 -0
- package/dist/accessi-module/federated-auth/FederatedAuthService.js +7 -1
- package/dist/accessi-module/federated-auth/FederatedAuthService.js.map +1 -1
- package/dist/accessi-module/index.d.ts +17 -1
- package/dist/accessi-module/index.js +71 -5
- package/dist/accessi-module/index.js.map +1 -1
- package/dist/accessi-module/jwt/jwt.strategy.js +4 -0
- package/dist/accessi-module/jwt/jwt.strategy.js.map +1 -1
- package/dist/accessi-module/middleware/authenticateGen.js +4 -0
- package/dist/accessi-module/middleware/authenticateGen.js.map +1 -1
- package/dist/accessi-module/security/audit.d.ts +7 -0
- package/dist/accessi-module/security/audit.js +14 -0
- package/dist/accessi-module/security/audit.js.map +1 -0
- package/dist/accessi-module/security/authenticatedToken.d.ts +1 -1
- package/dist/accessi-module/security/authenticatedToken.js +3 -3
- package/dist/accessi-module/security/authenticatedToken.js.map +1 -1
- package/dist/accessi-module/security/consoleEntry.d.ts +58 -11
- package/dist/accessi-module/security/consoleEntry.js +181 -31
- package/dist/accessi-module/security/consoleEntry.js.map +1 -1
- package/dist/accessi-module/security/edgeRateLimit.d.ts +33 -0
- package/dist/accessi-module/security/edgeRateLimit.js +81 -0
- package/dist/accessi-module/security/edgeRateLimit.js.map +1 -0
- package/dist/accessi-module/security/jwks.d.ts +33 -0
- package/dist/accessi-module/security/jwks.js +83 -0
- package/dist/accessi-module/security/jwks.js.map +1 -0
- package/dist/accessi-module/security/jwksBootstrap.d.ts +15 -0
- package/dist/accessi-module/security/jwksBootstrap.js +44 -0
- package/dist/accessi-module/security/jwksBootstrap.js.map +1 -0
- package/dist/accessi-module/security/jwtTokenOptions.d.ts +9 -2
- package/dist/accessi-module/security/jwtTokenOptions.js +18 -4
- package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -1
- package/dist/accessi-module/security/passwordResetToken.d.ts +1 -1
- package/dist/accessi-module/security/passwordResetToken.js.map +1 -1
- package/dist/accessi-module/security/publicAuthRateLimit.d.ts +9 -7
- package/dist/accessi-module/security/publicAuthRateLimit.js +55 -78
- package/dist/accessi-module/security/publicAuthRateLimit.js.map +1 -1
- package/dist/accessi-module/security/rateLimitStore.d.ts +34 -0
- package/dist/accessi-module/security/rateLimitStore.js +145 -0
- package/dist/accessi-module/security/rateLimitStore.js.map +1 -0
- package/dist/accessi-module/security/refreshTokenRotation.d.ts +40 -0
- package/dist/accessi-module/security/refreshTokenRotation.js +91 -0
- package/dist/accessi-module/security/refreshTokenRotation.js.map +1 -0
- package/dist/accessi-module/security/serviceTokenGuard.d.ts +3 -1
- package/dist/accessi-module/security/serviceTokenGuard.js +11 -6
- package/dist/accessi-module/security/serviceTokenGuard.js.map +1 -1
- package/dist/accessi-module/security/serviceTokenRateLimit.d.ts +5 -4
- package/dist/accessi-module/security/serviceTokenRateLimit.js +33 -29
- package/dist/accessi-module/security/serviceTokenRateLimit.js.map +1 -1
- package/dist/accessi-module/security/sessionRevocation.d.ts +52 -0
- package/dist/accessi-module/security/sessionRevocation.js +124 -0
- package/dist/accessi-module/security/sessionRevocation.js.map +1 -0
- package/dist/accessi-module/security/validateOptions.d.ts +19 -0
- package/dist/accessi-module/security/validateOptions.js +115 -0
- package/dist/accessi-module/security/validateOptions.js.map +1 -0
- package/dist/emilsoftware-utilities-2.0.0-dev.69.tgz +0 -0
- package/dist/initEmilsoftwareModule.js +39 -3
- package/dist/initEmilsoftwareModule.js.map +1 -1
- package/dist/scripts/startAccessiService.d.ts +1 -0
- package/dist/scripts/startAccessiService.js +11 -0
- package/dist/scripts/startAccessiService.js.map +1 -0
- package/docs/accessi-openapi-fingerprint.json +190 -0
- package/docs/gateway-rate-limit.md +98 -0
- package/docs/standalone-service.md +176 -0
- package/integration-prompt.md +299 -8
- package/package.json +11 -5
- package/dist/emilsoftware-utilities-2.0.0-dev.67.tgz +0 -0
package/integration-prompt.md
CHANGED
|
@@ -74,6 +74,60 @@ const { nestApp, document } = await initializeAccessiModule(app, options);
|
|
|
74
74
|
|
|
75
75
|
Aprire la porta HTTP solo dopo l'await.
|
|
76
76
|
|
|
77
|
+
### Avvio standalone
|
|
78
|
+
|
|
79
|
+
Per un servizio Accessi autonomo:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { startAccessiService } from "emilsoftware-utilities/accessi/server";
|
|
83
|
+
|
|
84
|
+
await startAccessiService({
|
|
85
|
+
port: Number(process.env.PORT ?? 3000),
|
|
86
|
+
healthPath: "/health",
|
|
87
|
+
metrics: true,
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Oppure:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm run accessi:serve
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Endpoint:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
GET /health/live
|
|
101
|
+
GET /health/ready
|
|
102
|
+
GET /health/metrics
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`/health/ready` controlla bootstrap, database, schema, store Redis configurati e `readinessChecks` custom. `startAccessiService` registra SIGTERM/SIGINT e chiude Nest e i pool Firebird.
|
|
106
|
+
|
|
107
|
+
In produzione il servizio viene eseguito come processo Node con systemd/PM2/supervisor:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npm run accessi:serve:dist
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Dettagli e esempio systemd: `docs/standalone-service.md`.
|
|
114
|
+
|
|
115
|
+
### Validazione configurazione
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import {
|
|
119
|
+
assertValidAccessiOptions,
|
|
120
|
+
validateAccessiOptions,
|
|
121
|
+
} from "emilsoftware-utilities/accessi/server";
|
|
122
|
+
|
|
123
|
+
const validation = validateAccessiOptions(options);
|
|
124
|
+
// validation.errors / validation.warnings
|
|
125
|
+
|
|
126
|
+
assertValidAccessiOptions(options); // fail-fast
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`createAccessiService` la applica automaticamente. Controlla JWT/key, DB, encryption key, URL email, rate limit, console entry e `http`.
|
|
130
|
+
|
|
77
131
|
### Bootstrap Nest
|
|
78
132
|
|
|
79
133
|
```ts
|
|
@@ -150,6 +204,151 @@ const merged = mergeOpenApiDocuments(hostDocument, accessiDocument, {
|
|
|
150
204
|
});
|
|
151
205
|
```
|
|
152
206
|
|
|
207
|
+
### HTTP, CORS e proxy
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
http: {
|
|
211
|
+
cors: {
|
|
212
|
+
origin: ["https://app.example.com"],
|
|
213
|
+
credentials: false,
|
|
214
|
+
},
|
|
215
|
+
trustProxy: 1, // load balancer/reverse proxy
|
|
216
|
+
securityHeaders: true,
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- `cors: false` disabilita CORS.
|
|
221
|
+
- `trustProxy` e obbligatorio per rate limit corretti dietro proxy.
|
|
222
|
+
- `securityHeaders` aggiunge `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`.
|
|
223
|
+
- Con Bearer token non serve CSRF. Se usi cookie httpOnly, aggiungi CSRF e cookie same-site.
|
|
224
|
+
|
|
225
|
+
### JWKS e rotazione chiavi
|
|
226
|
+
|
|
227
|
+
Con RS256/ES256 Accessi pubblica automaticamente:
|
|
228
|
+
|
|
229
|
+
```text
|
|
230
|
+
GET /accessi/.well-known/jwks.json
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Configurazione:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
jwtOptions: {
|
|
237
|
+
algorithm: "RS256",
|
|
238
|
+
privateKey: process.env.ACCESSI_JWT_PRIVATE_KEY,
|
|
239
|
+
publicKey: process.env.ACCESSI_JWT_PUBLIC_KEY,
|
|
240
|
+
keyId: "accessi-2026-02",
|
|
241
|
+
jwksPath: "accessi/.well-known/jwks.json",
|
|
242
|
+
previousPublicKeys: [
|
|
243
|
+
{
|
|
244
|
+
publicKey: process.env.ACCESSI_JWT_PUBLIC_KEY_2026_01!,
|
|
245
|
+
keyId: "accessi-2026-01",
|
|
246
|
+
algorithm: "RS256",
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Rotazione:
|
|
253
|
+
|
|
254
|
+
1. generare la nuova coppia
|
|
255
|
+
2. pubblicare la nuova chiave mantenendo la precedente in `previousPublicKeys`
|
|
256
|
+
3. firmare con la nuova `keyId`
|
|
257
|
+
4. attendere la scadenza massima dei token + il TTL cache della JWKS (default 5 minuti)
|
|
258
|
+
5. rimuovere la chiave precedente
|
|
259
|
+
|
|
260
|
+
Per un'app Nest esistente si può usare `setupAccessiJwks(app.getHttpAdapter().getInstance(), options)`.
|
|
261
|
+
|
|
262
|
+
### Store ticket console
|
|
263
|
+
|
|
264
|
+
Il default è in memoria, adatto a una sola istanza. In multi-istanza configurare uno store condiviso:
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
import { createRedisConsoleEntryStore } from "emilsoftware-utilities/accessi/server";
|
|
268
|
+
|
|
269
|
+
consoleEntry: {
|
|
270
|
+
ttlMs: 60_000,
|
|
271
|
+
store: createRedisConsoleEntryStore({
|
|
272
|
+
client: redis, // ioredis o client compatibile con eval(script, numKeys, ...args)
|
|
273
|
+
encryptionKey: process.env.ACCESSI_CONSOLE_ENTRY_KEY!,
|
|
274
|
+
keyPrefix: "accessi:console-entry:",
|
|
275
|
+
}),
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Lo store Redis:
|
|
280
|
+
|
|
281
|
+
- cifra il JWT con AES-256-GCM
|
|
282
|
+
- usa `SET ... PX ... NX` con TTL
|
|
283
|
+
- consuma con `GET` + `DEL` atomici in Lua
|
|
284
|
+
- rende il ticket monouso anche tra istanze diverse
|
|
285
|
+
|
|
286
|
+
Se lo store condiviso non è raggiungibile, la console deve fallire chiusa.
|
|
287
|
+
|
|
288
|
+
### Rate limit condiviso
|
|
289
|
+
|
|
290
|
+
Con più istanze anche i rate limit devono usare uno store condiviso:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import { createRedisRateLimitStore } from "emilsoftware-utilities/accessi/server";
|
|
294
|
+
|
|
295
|
+
const rateLimitStore = createRedisRateLimitStore({
|
|
296
|
+
client: redis, // ioredis o client compatibile con eval(script, numKeys, ...args)
|
|
297
|
+
keyPrefix: "accessi:rate:",
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
publicAuthRateLimit: {
|
|
301
|
+
enabled: true,
|
|
302
|
+
store: rateLimitStore,
|
|
303
|
+
login: { windowMs: 15 * 60_000, maxAttempts: 10 },
|
|
304
|
+
twoFactorVerify: { windowMs: 15 * 60_000, maxAttempts: 20 },
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
serviceTokens: {
|
|
308
|
+
enabled: true,
|
|
309
|
+
rateLimitStore: rateLimitStore,
|
|
310
|
+
},
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Lo store Redis usa `INCR` + `PEXPIRE` atomici:
|
|
314
|
+
|
|
315
|
+
- conta tentativi per IP e per soggetto (email, challenge, ticket, tokenId);
|
|
316
|
+
- il limite è globale tra le istanze;
|
|
317
|
+
- non viene azzerato da un restart;
|
|
318
|
+
- se Redis non è raggiungibile il controllo fallisce chiuso: l’endpoint risponde con errore, non autorizza.
|
|
319
|
+
|
|
320
|
+
Default: store in memoria. Va bene per una singola istanza o per ambienti di sviluppo.
|
|
321
|
+
|
|
322
|
+
### Edge rate limit per IP
|
|
323
|
+
|
|
324
|
+
Per una soglia grossolana davanti alle rotte Accessi:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
edgeRateLimit: {
|
|
328
|
+
enabled: true,
|
|
329
|
+
windowMs: 60_000,
|
|
330
|
+
maxAttempts: 300,
|
|
331
|
+
store: rateLimitStore, // stesso store Redis, opzionale
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Viene applicato prima del dispatcher Accessi e copre `/api/accessi/*`, OpenAPI e JWKS. In caso di store non disponibile risponde `503 ACCESSI_RATE_LIMIT_UNAVAILABLE`; quando la soglia è superata risponde `429 ACCESSI_EDGE_RATE_LIMITED` con `Retry-After`.
|
|
336
|
+
|
|
337
|
+
Il gateway/API gateway resta consigliato per:
|
|
338
|
+
|
|
339
|
+
- limiti di connessione e burst;
|
|
340
|
+
- bot protection e WAF;
|
|
341
|
+
- rate limit per IP a livello di rete;
|
|
342
|
+
- blocco geografico o reputazionale.
|
|
343
|
+
|
|
344
|
+
Configurazioni di riferimento: `docs/gateway-rate-limit.md`.
|
|
345
|
+
|
|
346
|
+
### Audit
|
|
347
|
+
|
|
348
|
+
Resource server ed edge rate limit producono eventi strutturati tramite `auditAccessi`. I campi sensibili vengono redatti dal Logger. Disattivabile con `audit: false` nelle rispettive opzioni.
|
|
349
|
+
|
|
350
|
+
Nel servizio standalone le metriche Prometheus sono su `/health/metrics`.
|
|
351
|
+
|
|
153
352
|
---
|
|
154
353
|
|
|
155
354
|
## 4. Backend fratello — Express
|
|
@@ -190,7 +389,9 @@ Dopo il middleware:
|
|
|
190
389
|
- **HS256**: usare `jwt.serverSecret` con `ACCESSI_JWT_SECRET`. Il resource server deriva la chiave access con la stessa KDF del server.
|
|
191
390
|
- **RS256/ES256**: usare `jwt.publicKey` PEM oppure `jwt.jwks.url`. Consigliato in multi-servizio.
|
|
192
391
|
- Il server Accessi supporta `jwtOptions.algorithm`, `privateKey`, `publicKey`, `keyId`.
|
|
193
|
-
- Accessi
|
|
392
|
+
- Accessi espone la JWKS su `jwtOptions.jwksPath` (default `accessi/.well-known/jwks.json`) quando usa RS/ES.
|
|
393
|
+
- La JWKS include la chiave corrente e le `previousPublicKeys`, per la rotazione senza downtime.
|
|
394
|
+
- Configurare il resource server con `jwks: { url: "https://accessi.example.com/accessi/.well-known/jwks.json" }`.
|
|
194
395
|
|
|
195
396
|
### Errori
|
|
196
397
|
|
|
@@ -274,6 +475,60 @@ Risposta:
|
|
|
274
475
|
- Il resource server cachea i risultati attivi per `cacheTtlMs` (default 30s). I risultati inattivi non sono cachati.
|
|
275
476
|
- Fail closed: se Accessi Service non risponde, la richiesta non viene autorizzata.
|
|
276
477
|
|
|
478
|
+
### SSO exchange service-to-service
|
|
479
|
+
|
|
480
|
+
Se un backend fratello valida un provider SSO proprio, non deve duplicare utenti o grant. Dopo aver verificato firma, issuer, audience e subject del provider, chiama:
|
|
481
|
+
|
|
482
|
+
```http
|
|
483
|
+
POST /api/accessi/federated-auth/exchange
|
|
484
|
+
Authorization: Bearer <service-token>
|
|
485
|
+
Content-Type: application/json
|
|
486
|
+
|
|
487
|
+
{ "provider": "azure-ad-acme-produzione", "subject": "<oid o sub>" }
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Scope richiesto: `accessi:federated:exchange`.
|
|
491
|
+
|
|
492
|
+
La risposta contiene:
|
|
493
|
+
|
|
494
|
+
- `codiceUtente`
|
|
495
|
+
- `login` con profilo, grant e token
|
|
496
|
+
- `token` assente quando è richiesta la verifica 2FA
|
|
497
|
+
|
|
498
|
+
Accessi non riceve il token del provider: riceve solo l'identità esterna già verificata.
|
|
499
|
+
|
|
500
|
+
### Revoca sessioni
|
|
501
|
+
|
|
502
|
+
Configurazione:
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
import { createRedisSessionRevocationStore } from "emilsoftware-utilities/accessi/server";
|
|
506
|
+
|
|
507
|
+
sessionRevocation: {
|
|
508
|
+
store: createRedisSessionRevocationStore({
|
|
509
|
+
client: redis,
|
|
510
|
+
ttlMs: 30 * 24 * 60 * 60 * 1000, // maggiore della durata massima dei token
|
|
511
|
+
}),
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Endpoint utente:
|
|
516
|
+
|
|
517
|
+
```http
|
|
518
|
+
POST /api/accessi/auth/revoke-sessions
|
|
519
|
+
Authorization: Bearer <access-token>
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Invalida i token emessi prima della chiamata. I token emessi dopo restano validi. Per i backend fratelli configurare lo stesso store anche nel resource server:
|
|
523
|
+
|
|
524
|
+
```ts
|
|
525
|
+
revocation: {
|
|
526
|
+
store: revocationStore,
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Senza store condiviso la revoca vale solo nel processo che la esegue.
|
|
531
|
+
|
|
277
532
|
---
|
|
278
533
|
|
|
279
534
|
## 7. Frontend
|
|
@@ -303,6 +558,29 @@ configureAccessiClient({
|
|
|
303
558
|
});
|
|
304
559
|
```
|
|
305
560
|
|
|
561
|
+
Se `jwtOptions.refreshExpiresIn` è configurato, la risposta contiene anche `refreshToken`. Conservarlo separatamente dall’access token e usarlo su:
|
|
562
|
+
|
|
563
|
+
```http
|
|
564
|
+
POST /api/accessi/auth/refresh
|
|
565
|
+
Content-Type: application/json
|
|
566
|
+
|
|
567
|
+
{ "refreshToken": "<refresh-token>" }
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
La risposta contiene un nuovo `token` e un nuovo `refreshToken`. Il refresh token ha `typ: "refresh"` e non va mai usato come Bearer sulle API.
|
|
571
|
+
|
|
572
|
+
Per rendere il refresh token monouso anche in multi-istanza:
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
import { createRedisRefreshTokenRotationStore } from "emilsoftware-utilities/accessi/server";
|
|
576
|
+
|
|
577
|
+
refreshTokenRotation: {
|
|
578
|
+
store: createRedisRefreshTokenRotationStore({ client: redis }),
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Ogni refresh consuma il vecchio `jti` in modo atomico. Due refresh concorrenti dello stesso token producono un solo successo.
|
|
583
|
+
|
|
306
584
|
Per token che cambiano nel tempo:
|
|
307
585
|
|
|
308
586
|
```ts
|
|
@@ -332,6 +610,7 @@ const consoleUrl = buildAccessiConsoleUrl("https://accessi.example.com", {
|
|
|
332
610
|
- Il ticket e monouso, dura 60 secondi.
|
|
333
611
|
- Per il 2FA handoff usare `twoFactorChallengeId`.
|
|
334
612
|
- Se la console deve stare sullo stesso dominio, fare reverse proxy di `/api/accessi/*` verso Accessi Service, non copiare gli asset.
|
|
613
|
+
- Se la UI della console è su un dominio diverso dall’API, configurare `consoleApiBaseUrl`: Accessi inietta `window.__ACCESSI_API_BASE_URL__` nella pagina servita.
|
|
335
614
|
|
|
336
615
|
### Web component
|
|
337
616
|
|
|
@@ -362,6 +641,7 @@ Per chiamare l'introspection o API tecniche:
|
|
|
362
641
|
2. Creare un token dalla console o con `POST /api/accessi/service-token`.
|
|
363
642
|
3. Assegnare lo scope minimo necessario:
|
|
364
643
|
- `accessi:introspect` per l'introspection
|
|
644
|
+
- `accessi:federated:exchange` per scambiare un'identita SSO verificata
|
|
365
645
|
- altri scope applicativi secondo necessita
|
|
366
646
|
4. Conservarlo come secret nel backend fratello.
|
|
367
647
|
5. Ruotarlo periodicamente; il segreto e mostrato una sola volta.
|
|
@@ -397,6 +677,7 @@ ACCESSI_DB_PASSWORD=
|
|
|
397
677
|
ACCESSI_ENCRYPTION_KEY=
|
|
398
678
|
ACCESSI_JWT_SECRET=
|
|
399
679
|
ACCESSI_JWT_EXPIRES=24h
|
|
680
|
+
ACCESSI_JWT_REFRESH_EXPIRES=30d
|
|
400
681
|
ACCESSI_JWT_ISSUER=accessi
|
|
401
682
|
ACCESSI_JWT_AUDIENCE=backend-app
|
|
402
683
|
# Opzionale RS256
|
|
@@ -404,6 +685,9 @@ ACCESSI_JWT_ALGORITHM=RS256
|
|
|
404
685
|
ACCESSI_JWT_PRIVATE_KEY=
|
|
405
686
|
ACCESSI_JWT_PUBLIC_KEY=
|
|
406
687
|
ACCESSI_JWT_KEY_ID=
|
|
688
|
+
ACCESSI_JWKS_PATH=accessi/.well-known/jwks.json
|
|
689
|
+
ACCESSI_CONSOLE_ENTRY_TTL_MS=60000
|
|
690
|
+
ACCESSI_CONSOLE_ENTRY_KEY=
|
|
407
691
|
SMTP_HOST=
|
|
408
692
|
SMTP_PORT=587
|
|
409
693
|
SMTP_USER=
|
|
@@ -428,6 +712,7 @@ Frontend:
|
|
|
428
712
|
```env
|
|
429
713
|
ACCESSI_API_BASE_URL=https://accessi.example.com
|
|
430
714
|
ACCESSI_CONSOLE_URL=https://accessi.example.com/api/accessi/console
|
|
715
|
+
ACCESSI_JWKS_URL=https://accessi.example.com/accessi/.well-known/jwks.json
|
|
431
716
|
```
|
|
432
717
|
|
|
433
718
|
---
|
|
@@ -443,16 +728,22 @@ Checklist:
|
|
|
443
728
|
5. Spostare la console una sola volta.
|
|
444
729
|
6. Passare i backend fratelli a `resource-server`.
|
|
445
730
|
7. Configurare un service token con `accessi:introspect`.
|
|
446
|
-
8.
|
|
731
|
+
8. Pubblicare la JWKS e configurare i resource server su `jwks.url`.
|
|
732
|
+
9. In multi-istanza configurare `consoleEntry.store`, `publicAuthRateLimit.store` e `serviceTokens.rateLimitStore` con Redis.
|
|
733
|
+
10. Testare:
|
|
447
734
|
- login
|
|
448
735
|
- token valido
|
|
449
736
|
- token scaduto
|
|
450
737
|
- utente disabilitato
|
|
451
738
|
- revoca grants
|
|
452
739
|
- Accessi Service spento (fail closed)
|
|
453
|
-
- console entry ticket
|
|
454
|
-
|
|
455
|
-
|
|
740
|
+
- console entry ticket su istanze diverse
|
|
741
|
+
- rate limit condiviso tra istanze
|
|
742
|
+
- rotazione JWKS con vecchia chiave ancora valida
|
|
743
|
+
11. Aggiornare CORS se i frontend cambiano dominio.
|
|
744
|
+
12. Aggiornare monitoraggio e log senza includere token.
|
|
745
|
+
13. Eseguire `npm run check:accessi-openapi` e `npm run typecheck` prima del rilascio.
|
|
746
|
+
14. Dopo una modifica intenzionale al contratto, aggiornare il fingerprint con `npm run accessi:update-openapi-fingerprint` e includerlo nel commit.
|
|
456
747
|
|
|
457
748
|
---
|
|
458
749
|
|
|
@@ -474,8 +765,8 @@ Checklist:
|
|
|
474
765
|
|
|
475
766
|
## 14. Limiti attuali da conoscere
|
|
476
767
|
|
|
477
|
-
- I ticket di ingresso console
|
|
478
|
-
- I rate limit
|
|
479
|
-
- Accessi non espone ancora un endpoint JWKS pronto: il resource server sa verificare una JWKS, ma la chiave pubblica va pubblicata dall'host.
|
|
768
|
+
- I ticket di ingresso console usano lo store in memoria per default: in multi-istanza configurare `consoleEntry.store` con Redis. Lo store Redis e cifrato e monouso, ma richiede un client Redis compatibile.
|
|
769
|
+
- I rate limit usano lo store in memoria per default: in multi-istanza configurare `publicAuthRateLimit.store` e `serviceTokens.rateLimitStore` con Redis. Il gateway resta comunque consigliato per i limiti grossolani per IP.
|
|
480
770
|
- Il client generato e completo (include endpoint amministrativi): non distribuirlo a frontend non amministrativi senza valutare gli endpoint esposti. Per un client pubblico ridotto, generare un sottoinsieme per tag con Orval.
|
|
481
771
|
- I DTO in `accessi/contracts/dtos` dipendono da Nest/class-validator: non sono pensati per bundle frontend minimali.
|
|
772
|
+
- La JWKS viene pubblicata solo con algoritmi asimmetrici: con HS256 non esiste una chiave pubblica da distribuire.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emilsoftware-utilities",
|
|
3
|
-
"version": "2.0.0-dev.
|
|
3
|
+
"version": "2.0.0-dev.69",
|
|
4
4
|
"description": "Utilities for EmilSoftware",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -87,7 +87,9 @@
|
|
|
87
87
|
},
|
|
88
88
|
"scripts": {
|
|
89
89
|
"release": "semantic-release",
|
|
90
|
-
"build": "npm run generate:accessi-openapi && npm run generate:accessi-client && npm run generate:accessi-wiki && tsc && tsc -p tsconfig.console.json --noEmit && npm run build:accessi-console && npm run copy-views",
|
|
90
|
+
"build": "npm run generate:accessi-openapi && npm run check:accessi-openapi && npm run generate:accessi-client && npm run generate:accessi-wiki && tsc && tsc -p tsconfig.console.json --noEmit && npm run build:accessi-console && npm run copy-views",
|
|
91
|
+
"check:accessi-openapi": "node scripts/check-accessi-openapi.cjs",
|
|
92
|
+
"accessi:update-openapi-fingerprint": "node scripts/check-accessi-openapi.cjs --update",
|
|
91
93
|
"generate:accessi-openapi": "ts-node --transpile-only src/scripts/generateAccessiOpenApi.ts",
|
|
92
94
|
"generate:accessi-client": "orval --config orval.config.ts",
|
|
93
95
|
"generate:accessi-wiki": "ts-node --transpile-only src/scripts/generateAccessiWiki.ts",
|
|
@@ -95,14 +97,17 @@
|
|
|
95
97
|
"copy-views": "node copy-views.js",
|
|
96
98
|
"db:update:accessi": "ts-node src/accessi-module/database-updates/runAccessiDbUpdate.ts",
|
|
97
99
|
"db:update:accessi:dist": "npm run build && node dist/accessi-module/database-updates/runAccessiDbUpdate.js",
|
|
100
|
+
"accessi:serve": "ts-node src/scripts/startAccessiService.ts",
|
|
101
|
+
"accessi:serve:dist": "node dist/scripts/startAccessiService.js",
|
|
102
|
+
"accessi:generate-jwt-keys": "node scripts/generate-accessi-jwt-keys.cjs",
|
|
98
103
|
"db:catalog:accessi": "ts-node src/accessi-module/database-updates/runAccessiCatalogMigrations.ts",
|
|
99
104
|
"db:catalog:accessi:check": "ts-node src/accessi-module/database-updates/runAccessiCatalogMigrations.ts --check",
|
|
100
105
|
"prepublishOnly": "npm run build",
|
|
101
106
|
"typecheck": "tsc --noEmit",
|
|
102
107
|
"lint": "node scripts/lint.cjs",
|
|
103
108
|
"test": "npm run typecheck && npm run lint && npm run test:unit",
|
|
104
|
-
"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",
|
|
105
|
-
"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",
|
|
109
|
+
"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",
|
|
110
|
+
"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",
|
|
106
111
|
"test:accessi": "npm run test:unit",
|
|
107
112
|
"test:all": "npm test && npm run test:integration",
|
|
108
113
|
"test:integration": "node test/integration/run-firebird.cjs 5",
|
|
@@ -183,7 +188,8 @@
|
|
|
183
188
|
},
|
|
184
189
|
"files": [
|
|
185
190
|
"dist",
|
|
186
|
-
"integration-prompt.md"
|
|
191
|
+
"integration-prompt.md",
|
|
192
|
+
"docs"
|
|
187
193
|
],
|
|
188
194
|
"config": {
|
|
189
195
|
"commitizen": {
|
|
Binary file
|