emilsoftware-utilities 2.0.0-dev.75 → 2.0.0-dev.77
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-module/AccessiModule.d.ts +8 -1
- package/dist/accessi-module/AccessiModule.js.map +1 -1
- package/dist/accessi-module/Console/accessiFetch.d.ts +7 -0
- package/dist/accessi-module/Console/accessiFetch.js +45 -7
- package/dist/accessi-module/Console/accessiFetch.js.map +1 -1
- package/dist/accessi-module/Console/console.js +84 -24
- package/dist/accessi-module/Console/console.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/getUserByTokenRequest.d.ts +2 -2
- package/dist/accessi-module/Console/generated/model/getUserByTokenRequest.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/refreshTokenRequest.d.ts +2 -2
- package/dist/accessi-module/Console/generated/model/refreshTokenRequest.js.map +1 -1
- package/dist/accessi-module/Console/index.html +1 -1
- package/dist/accessi-module/Console/wiki.js +3 -2
- package/dist/accessi-module/Console/wiki.js.map +1 -1
- package/dist/accessi-module/Controllers/AccessiConsoleController.d.ts +5 -3
- package/dist/accessi-module/Controllers/AccessiConsoleController.js +47 -12
- package/dist/accessi-module/Controllers/AccessiConsoleController.js.map +1 -1
- package/dist/accessi-module/Controllers/AuthController.js +35 -7
- package/dist/accessi-module/Controllers/AuthController.js.map +1 -1
- package/dist/accessi-module/Controllers/ConsoleEntryController.js +8 -0
- package/dist/accessi-module/Controllers/ConsoleEntryController.js.map +1 -1
- package/dist/accessi-module/Dtos/GetUserByTokenRequest.d.ts +1 -1
- package/dist/accessi-module/Dtos/GetUserByTokenRequest.js +3 -2
- package/dist/accessi-module/Dtos/GetUserByTokenRequest.js.map +1 -1
- package/dist/accessi-module/Dtos/RefreshTokenDtos.d.ts +1 -1
- package/dist/accessi-module/Dtos/RefreshTokenDtos.js +4 -1
- package/dist/accessi-module/Dtos/RefreshTokenDtos.js.map +1 -1
- package/dist/accessi-module/index.d.ts +4 -0
- package/dist/accessi-module/index.js +30 -14
- package/dist/accessi-module/index.js.map +1 -1
- package/dist/accessi-module/security/authCookies.d.ts +104 -0
- package/dist/accessi-module/security/authCookies.js +240 -0
- package/dist/accessi-module/security/authCookies.js.map +1 -0
- package/dist/accessi-module/security/securityHeaders.d.ts +89 -0
- package/dist/accessi-module/security/securityHeaders.js +155 -0
- package/dist/accessi-module/security/securityHeaders.js.map +1 -0
- package/dist/accessi-module/security/validateOptions.js +34 -0
- package/dist/accessi-module/security/validateOptions.js.map +1 -1
- package/dist/emilsoftware-utilities-2.0.0-dev.77.tgz +0 -0
- package/dist/initEmilsoftwareModule.js +13 -11
- package/dist/initEmilsoftwareModule.js.map +1 -1
- package/docs/auth-cookies.md +123 -0
- package/docs/security-headers.md +151 -0
- package/package.json +3 -3
- package/dist/emilsoftware-utilities-2.0.0-dev.75.tgz +0 -0
|
@@ -63,6 +63,8 @@ export interface ConsoleEntryOptions {
|
|
|
63
63
|
/** Store condiviso (es. Redis) per deployment multi-istanza. Default: memoria. */
|
|
64
64
|
store?: ConsoleEntryStore;
|
|
65
65
|
}
|
|
66
|
+
import type { AccessiSecurityHeadersSetting } from './security/securityHeaders';
|
|
67
|
+
import type { AccessiAuthCookieOptions } from './security/authCookies';
|
|
66
68
|
/** Impostazioni HTTP del servizio Accessi. */
|
|
67
69
|
export interface AccessiHttpOptions {
|
|
68
70
|
/** Opzioni CORS Nest. `false` disabilita CORS. Default: exposedHeaders ['X-Total-Count']. */
|
|
@@ -70,7 +72,7 @@ export interface AccessiHttpOptions {
|
|
|
70
72
|
/** Trust proxy per IP reali dietro load balancer/reverse proxy. */
|
|
71
73
|
trustProxy?: boolean | number | string;
|
|
72
74
|
/** Header di sicurezza di base su console/OpenAPI. Default: true. */
|
|
73
|
-
securityHeaders?:
|
|
75
|
+
securityHeaders?: AccessiSecurityHeadersSetting;
|
|
74
76
|
}
|
|
75
77
|
/** Parametri SMTP usati esclusivamente per impostazione e reset della password. */
|
|
76
78
|
export interface EmailOptions {
|
|
@@ -261,6 +263,11 @@ export interface AccessiOptions {
|
|
|
261
263
|
edgeRateLimit?: AccessiEdgeRateLimitOptions;
|
|
262
264
|
/** CORS, trust proxy e header di sicurezza del servizio. */
|
|
263
265
|
http?: AccessiHttpOptions;
|
|
266
|
+
/**
|
|
267
|
+
* Autenticazione opzionale via cookie httpOnly. Bearer resta il default:
|
|
268
|
+
* la modalita si abilita passando questo oggetto (o `enabled: true`).
|
|
269
|
+
*/
|
|
270
|
+
authCookie?: AccessiAuthCookieOptions;
|
|
264
271
|
/** Revoca sessioni per utente tramite store memory/Redis. */
|
|
265
272
|
sessionRevocation?: SessionRevocationOptions;
|
|
266
273
|
/** Revoca dei singoli access token tramite `jti`, memory/Redis. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccessiModule.js","sourceRoot":"","sources":["../../src/accessi-module/AccessiModule.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,mFAAgF;AAQhF,2CAA+D;AAU/D,oEAAiE;AACjE,uEAAoE;AACpE,sFAAmF;AACnF,oEAAiE;AACjE,mEAAgE;AAChE,iEAA8D;AAC9D,6EAA0E;AAC1E,iEAA8D;AAC9D,0EAAuE;AACvE,qEAAkE;AAClE,iFAA8E;AAC9E,4FAAyF;AACzF,qDAAoD;AACpD,kEAAsE;AACtE,sFAAmF;AACnF,gFAA6E;AAC7E,sFAAmF;AACnF,sGAAmG;AACnG,qFAAkF;AAClF,iFAA8E;AAC9E,iFAA8E;AAC9E,4FAAyF;AACzF,oEAAiE;AACjE,qEAAkE;AAClE,iGAA8F;AAC9F,4FAAyF;AACzF,kGAA+F;AAC/F,4DAAuE;AACvE,sEAAsE;AAkS/D,IAAM,aAAa,qBAAnB,MAAM,aAAa;IACxB;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,CAAC,OAAuB;QACpC,yFAAyF;QACzF,IAAA,0CAAqB,EAAC,OAAO,CAAC,CAAC;QAC/B,OAAO;YACL,MAAM,EAAE,eAAa;YACrB,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,iBAAiB;oBAC1B,QAAQ,EAAE,OAAO;iBAClB;gBACD,yBAAW;gBACX,mCAAgB;gBAChB,yBAAW;gBACX,2BAAY;gBACZ,qCAAiB;gBACjB,6BAAa;gBACb,yCAAmB;gBACnB,6BAAc;gBACd,wCAAsB;gBACtB,+CAAsB;gBACtB,2CAAoB;gBACpB,yCAAmB;gBACnB,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,EAAE,2CAA2B;aAAC;YAC7F,OAAO,EAAE;gBACP,iBAAiB;gBACjB,yBAAW;gBACX,yBAAW;gBACX,2BAAY;gBACZ,qCAAiB;gBACjB,6BAAa;gBACb,yCAAmB;gBACnB,6BAAc;gBACd,wCAAsB;gBACtB,2CAAoB;gBACpB,yCAAmB;gBACnB,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB;aAAC;SACjE,CAAC;IACJ,CAAC;CACF,CAAA;AA9CY,sCAAa;wBAAb,aAAa;IApBzB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,WAAW,EAAE;YACX,iCAAe;YACf,+BAAc;YACd,2CAAoB;YACpB,+BAAc;YACd,mCAAgB;YAChB,+CAAsB;YACtB,iDAAuB;YACvB,mDAAwB;YACxB,+CAAsB;YACtB,iEAA+B;YAC/B,+CAAsB;YACtB,mCAAgB;YAChB,+DAA8B;SAC/B;QACD,SAAS,EAAE,CAAC,yBAAW,EAAE,mCAAgB,EAAE,yBAAW,EAAE,2BAAY,EAAE,qCAAiB,EAAE,6BAAa,EAAE,yCAAmB,EAAE,6BAAc,EAAE,wCAAsB,EAAE,+CAAsB,EAAE,2CAAoB,EAAE,yCAAmB,EAAE,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,EAAE,2CAA2B,CAAC;QACnU,OAAO,EAAE,CAAC,yBAAW,EAAE,yBAAW,EAAE,2BAAY,EAAE,qCAAiB,EAAE,6BAAa,EAAE,yCAAmB,EAAE,6BAAc,EAAE,wCAAsB,EAAE,2CAAoB,EAAE,yCAAmB,EAAE,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,CAAC;KAC3P,CAAC;GACW,aAAa,CA8CzB","sourcesContent":["import { TwoFactorService } from './Services/TwoFactorService/TwoFactorService';\n/**\n * Modulo che gestisce le operazioni di accesso degli utenti, incluse le rotte, il controller e il modello.\n *\n * @module AccessiModule\n * @author mttdev382\n */\nimport { Options } from 'node-firebird';\nimport { DynamicModule, Global, Module } from '@nestjs/common';\nimport type { INestApplication, LoggerService } from '@nestjs/common';\nimport type { OpenApiSetupOptions } from '../openapi/OpenApiConfig';\nimport type { AccessiAccessTokenAlgorithm, AccessiUnknownKeyIdEvent } from './security/jwtTokenOptions';\nimport type { ConsoleEntryStore } from './security/consoleEntry';\nimport type { RateLimitStore } from './security/rateLimitStore';\nimport type { AccessiEdgeRateLimitOptions } from './security/edgeRateLimit';\nimport type { SessionRevocationOptions } from './security/sessionRevocation';\nimport type { AccessTokenRevocationOptions } from './security/accessTokenRevocation';\nimport type { RefreshTokenRotationOptions } from './security/refreshTokenRotation';\nimport { AuthService } from './Services/AuthService/AuthService';\nimport { EmailService } from './Services/EmailService/EmailService';\nimport { PermissionService } from './Services/PermissionService/PermissionService';\nimport { UserService } from './Services/UserService/UserService';\nimport { EmailController } from './Controllers/EmailController';\nimport { AuthController } from './Controllers/AuthController';\nimport { PermissionController } from './Controllers/PermissionController';\nimport { UserController } from './Controllers/UserController';\nimport { FiltriService } from './Services/FiltriService/FiltriService';\nimport { FiltriController } from './Controllers/FiltriController';\nimport { ConfiguratorController } from './Controllers/ConfiguratorController';\nimport { ConfiguratorService } from './Services/ConfiguratorService/ConfiguratorService';\nimport { JwtSimpleGuard } from './jwt/jwt.strategy';\nimport { AuthenticateGenService } from './middleware/authenticateGen';\nimport { AccessiDatabaseUpdater } from './database-updates/AccessiDatabaseUpdater';\nimport { FederatedAuthService } from './federated-auth/FederatedAuthService';\nimport { FederatedAuthController } from './federated-auth/FederatedAuthController';\nimport { FederatedAuthExchangeController } from './federated-auth/FederatedAuthExchangeController';\nimport { AccessiConsoleController } from './Controllers/AccessiConsoleController';\nimport { ConsoleEntryController } from './Controllers/ConsoleEntryController';\nimport { ServiceTokenController } from './Controllers/ServiceTokenController';\nimport { ServiceTokenService } from './Services/ServiceTokenService/ServiceTokenService';\nimport { ServiceTokenGuard } from './security/serviceTokenGuard';\nimport { SchemaController } from './Controllers/SchemaController';\nimport { AccessiIntrospectionController } from './Controllers/AccessiIntrospectionController';\nimport { SchemaExportService } from './Services/SchemaExportService/SchemaExportService';\nimport { AdminBootstrapService } from './Services/AdminBootstrapService/AdminBootstrapService';\nimport { AccessiJwksBootstrapService } from './security/jwksBootstrap';\nimport { assertEmailConfigured } from './security/emailConfiguration';\n\n/** JWT emesso da Accessi dopo un login locale o SSO. Non riutilizzare il segreto del provider SSO. */\nexport interface JwtOptions {\n /** Segreto simmetrico per firmare e verificare i JWT Accessi; deve essere unico per ambiente. */\n secret: string;\n /** Issuer (`iss`) opzionale degli access token. Consigliato in una topologia multi-servizio. */\n issuer?: string;\n /** Audience (`aud`) opzionale degli access token. */\n audience?: string | string[];\n /** Algoritmo degli access token. Default `HS256` per retrocompatibilita. */\n algorithm?: AccessiAccessTokenAlgorithm;\n /** Chiave privata PEM per RS/ES. Obbligatoria quando `algorithm` non e HS*. */\n privateKey?: string;\n /** Chiave pubblica PEM per la verifica RS/ES. */\n publicKey?: string;\n /** Key id pubblicato nella JWKS, per rotazione chiavi. */\n keyId?: string;\n /** Path pubblico della JWKS. Default `accessi/.well-known/jwks.json` con algoritmi asimmetrici. */\n jwksPath?: string;\n /**\n * Chiavi pubbliche precedenti, esposte in JWKS e accettate in verifica\n * durante la rotazione. Se `keyId` e assente si usa il fingerprint della\n * chiave, lo stesso calcolato dalla JWKS.\n */\n previousPublicKeys?: Array<{\n publicKey: string;\n keyId?: string;\n algorithm?: AccessiAccessTokenAlgorithm;\n }>;\n /**\n * Alert quando un token presenta un `kid` assente da chiave corrente e\n * chiavi precedenti (tipico di una rotazione non completata).\n */\n onUnknownKeyId?: (event: AccessiUnknownKeyIdEvent) => void;\n /** Durata compatibile con `jsonwebtoken`, per esempio `24h` o `3600s`. */\n expiresIn: string;\n /**\n * Durata del refresh token. Se assente il refresh token non viene emesso.\n * Consigliato: `30d`.\n */\n refreshExpiresIn?: string;\n}\n\n/** Configurazione dei ticket di ingresso alla console. */\nexport interface ConsoleEntryOptions {\n /** Durata del ticket in millisecondi. Default 60000. */\n ttlMs?: number;\n /** Store condiviso (es. Redis) per deployment multi-istanza. Default: memoria. */\n store?: ConsoleEntryStore;\n}\n\n/** Impostazioni HTTP del servizio Accessi. */\nexport interface AccessiHttpOptions {\n /** Opzioni CORS Nest. `false` disabilita CORS. Default: exposedHeaders ['X-Total-Count']. */\n cors?: Parameters<INestApplication['enableCors']>[0] | false;\n /** Trust proxy per IP reali dietro load balancer/reverse proxy. */\n trustProxy?: boolean | number | string;\n /** Header di sicurezza di base su console/OpenAPI. Default: true. */\n securityHeaders?: boolean;\n}\n\n/** Parametri SMTP usati esclusivamente per impostazione e reset della password. */\nexport interface EmailOptions {\n host: string;\n port: number;\n secure: boolean;\n requireTLS : boolean;\n tls: {\n rejectUnauthorized: boolean,\n }\n from: string;\n auth: {\n user: string;\n pass: string;\n };\n}\n\n/** Configura campi applicativi aggiuntivi restituiti insieme al profilo Accessi. */\nexport interface ExtensionFieldsOptions {\n /** Connessione al database contenente la tabella estesa. */\n databaseOptions: Options;\n /** Chiave con cui i dati saranno esposti nella risposta. */\n objectKey: string;\n /** Tabella applicativa contenente i dati estesi. */\n tableName: string;\n /** Colonne consentite; non valorizzarle mai con input HTTP. */\n tableFields: string[];\n /** Colonna correlata a `UTENTI.CODUTE`. */\n tableJoinFieldName: string;\n}\n\n/** Limite per una singola categoria di endpoint pubblici. */\nexport interface PublicAuthRateLimitRuleOptions {\n windowMs: number;\n maxAttempts: number;\n}\n\n/** Protezione degli endpoint pubblici. Default in memoria; in cluster usare uno store condiviso. */\nexport interface PublicAuthRateLimitOptions {\n enabled?: boolean;\n /** Store condiviso (es. Redis). Default: memoria del processo. */\n store?: RateLimitStore;\n login?: PublicAuthRateLimitRuleOptions;\n register?: PublicAuthRateLimitRuleOptions;\n passwordResetEmail?: PublicAuthRateLimitRuleOptions;\n passwordResetConfirm?: PublicAuthRateLimitRuleOptions;\n refresh?: PublicAuthRateLimitRuleOptions;\n getUserByToken?: PublicAuthRateLimitRuleOptions;\n twoFactorVerify?: PublicAuthRateLimitRuleOptions;\n twoFactorResend?: PublicAuthRateLimitRuleOptions;\n adminBootstrap?: PublicAuthRateLimitRuleOptions;\n consoleEntry?: PublicAuthRateLimitRuleOptions;\n}\n\n/**\n * Bootstrap del primo admin. Se abilitato, Accessi genera un token casuale a ogni avvio del\n * processo, lo stampa nel log e lo accetta sull'endpoint pubblico `POST /accessi/auth/bootstrap-admin`.\n * Serve a creare il primo utente admin senza email di conferma e senza un admin gia esistente.\n */\nexport interface AdminBootstrapOptions {\n /** Abilita il bootstrap admin. Default `false`. */\n enabled?: boolean;\n /** Crea l'utente come superutente oltre che admin. Default `true`. */\n superUser?: boolean;\n}\n\n/** Controls the legacy public username/password registration endpoint. Disabled by default. */\nexport interface PublicRegistrationOptions {\n /** Enables POST /accessi/user/register. Applications that used it before must opt in explicitly. */\n enabled?: boolean;\n}\n\n/** Configurazione dei token di servizio (macchina-a-macchina, senza utente). */\nexport interface ServiceTokenOptions {\n /** Abilita gli endpoint amministrativi dei service token. Default `true`. */\n enabled?: boolean;\n /** Durata predefinita in giorni quando non e indicata una scadenza esplicita. Default: nessuna scadenza. */\n defaultTtlDays?: number;\n /** Store condiviso per il rate limit dei service token. Default: memoria. */\n rateLimitStore?: RateLimitStore;\n}\n\n/** Cosa fare quando uno script gia applicato presenta un checksum diverso (file modificato). */\nexport type CatalogScriptChecksumPolicy = 'error' | 'warn' | 'reapply';\n\n/**\n * Catalog migrations applicative (menu, ruoli, tipi, cataloghi). Gli script `.sql` vivono nel\n * backend ospitante (versionati in repository) e vengono eseguiti da Accessi dopo la\n * riconciliazione dello schema. Ogni script e tracciato nella tabella `ACCESSI_CATALOG_SCRIPT`,\n * quindi viene applicato una sola volta per database (idempotenza lato DB).\n */\nexport interface SystemSeedsOptions {\n /** `false` disabilita il seed automatico, ad esempio per ambienti gestiti manualmente. */\n enabled?: boolean;\n}\n\nexport interface CatalogScriptsOptions {\n /** Abilita l'esecuzione automatica al bootstrap. Default `false`. */\n enabled?: boolean;\n /** Cartella contenente gli script `.sql` (assoluta o relativa alla working directory). */\n folder: string;\n /** Cosa fare se il checksum di uno script applicato e cambiato. Default `error`. */\n onChecksumMismatch?: CatalogScriptChecksumPolicy;\n /** Se `true` (default) un errore interrompe l'esecuzione e non avanza allo script successivo. */\n stopOnError?: boolean;\n /** Globi/estensioni inclusi. Default: tutti i `.sql`. */\n include?: string[];\n /** Numero massimo di script eseguiti in un singolo run (protezione operativa). Default: illimitato. */\n maxScriptsPerRun?: number;\n /**\n * Permette statement DDL/schema negli script. Default `false`: le catalog migrations possono\n * modificare solo righe. Sconsigliato: lo schema e di competenza di `db:update:accessi`.\n */\n allowDdl?: boolean;\n}\n\n/**\n * Enables Accessi's provider-agnostic federated identity capability.\n *\n * Accessi never validates Azure, Google, Apple, SAML, or other provider tokens.\n * The hosting backend must validate its own SSO flow and pass only a verified\n * `{ provider, subject }` identity to FederatedAuthService.\n */\nexport interface FederatedAuthenticationOptions {\n /** Attiva le API SSO generiche; con `false` il comportamento password storico rimane invariato. */\n enabled?: boolean;\n /** @deprecated Schema management is centralized in autoUpdateDatabase. This legacy flag no longer starts separate SSO migrations. */\n autoUpdateSchema?: boolean;\n /** Allows a backend to create a user from a verified external identity without a master user. Default false. */\n allowSelfRegistration?: boolean;\n}\n\n/**\n * Configurazione completa del modulo Accessi, fornita esclusivamente dal backend ospitante.\n * Segreti JWT, SMTP, database e le configurazioni tecniche dei provider SSO non devono mai raggiungere il browser.\n */\nexport interface AccessiOptions {\n /** Connessione Firebird alla base dati Accessi. */\n databaseOptions: Options;\n /**\n * Basepath del sito es: 'http://www.il-mio-sito.it/nome-progetto(se c'è)'\n */\n confirmationEmailUrl: string;\n /**\n * Percorso della pagina di reset personalizzata es. http://localhost:4200/#/admin/reset-password\n * N.B si sostituisce al confirmationMailURl\n */\n customResetPage?: string;\n /** URL di ritorno della pagina standard di reset dopo il completamento. */\n confirmationEmailReturnUrl: string;\n /** Prefisso applicativo facoltativo trasmesso alla pagina standard di reset. */\n confirmationEmailPrefix?: string;\n /** Chiave riservata alla compatibilita e alla migrazione delle password legacy cifrate. */\n encryptionKey: string;\n /** Abilita gli utenti fittizi `admin` e `demo` solo fuori produzione. */\n mockDemoUser: boolean;\n /** Attiva il controllo della scadenza password durante il login locale. */\n passwordExpiration?: boolean;\n /** Giorni di validita della password; quando assente Accessi usa 90 giorni. */\n passwordExpirationDays?: number;\n /** Esegue al bootstrap la conversione non distruttiva delle password legacy. Default `true`. */\n legacyPasswordMigrationOnStartup?: boolean;\n /** Riconcilia lo schema al bootstrap (default `true`). Con `false` verifica comunque la compatibilita in sola lettura e blocca avvii incompatibili. */\n autoUpdateDatabase?: boolean;\n /** Parametri JWT dei token emessi da Accessi. */\n jwtOptions: JwtOptions;\n /** Parametri SMTP per reset e attivazione password. */\n emailOptions: EmailOptions;\n publicAuthRateLimit?: PublicAuthRateLimitOptions;\n /** Public local registration is deliberately opt-in. Existing applications must set enabled: true to retain it. */\n publicRegistration?: PublicRegistrationOptions;\n /** Optional provider-agnostic SSO support. Provider-specific configuration remains in the hosting backend. */\n federatedAuthentication?: FederatedAuthenticationOptions;\n extensionFieldsOptions?: ExtensionFieldsOptions[];\n /** Token di servizio per chiamate tecniche macchina-a-macchina. Abilitati di default. */\n serviceTokens?: ServiceTokenOptions;\n /** Catalog migrations applicative eseguite dopo la riconciliazione dello schema. */\n catalogScripts?: CatalogScriptsOptions;\n /** Seed di sistema eseguiti dopo la riconciliazione dello schema. Default: abilitati. */\n systemSeeds?: SystemSeedsOptions;\n /** Bootstrap del primo admin tramite token casuale stampato a ogni avvio. */\n adminBootstrap?: AdminBootstrapOptions;\n /** Base URL pubblica usata solo per loggare gli indirizzi (console, OpenAPI, API). */\n publicBaseUrl?: string;\n /** Base URL API per la console quando UI e API sono su domini diversi. */\n consoleApiBaseUrl?: string;\n /**\n * Documentazione OpenAPI del modulo. Default invariati:\n * UI `accessi/openapi`, JSON `accessi/openapi.json`.\n */\n openapi?: OpenApiSetupOptions;\n /** Logger Nest usato dal sotto-app Accessi. Se assente Nest usa il proprio logger. */\n logger?: LoggerService;\n /** Buffer dei log di bootstrap del sotto-app. Default `true` (comportamento storico). */\n bufferLogs?: boolean;\n /** Ticket di ingresso console: TTL e store condiviso per deployment multi-istanza. */\n consoleEntry?: ConsoleEntryOptions;\n /** Limite grossolano per IP davanti alle rotte Accessi. Opzionale. */\n edgeRateLimit?: AccessiEdgeRateLimitOptions;\n /** CORS, trust proxy e header di sicurezza del servizio. */\n http?: AccessiHttpOptions;\n /** Revoca sessioni per utente tramite store memory/Redis. */\n sessionRevocation?: SessionRevocationOptions;\n /** Revoca dei singoli access token tramite `jti`, memory/Redis. */\n accessTokenRevocation?: AccessTokenRevocationOptions;\n /** Refresh token monouso tramite rotazione memory/Redis; rileva il riuso e revoca la famiglia. */\n refreshTokenRotation?: RefreshTokenRotationOptions;\n}\n\n@Global()\n@Module({\n controllers: [\n EmailController,\n AuthController,\n PermissionController,\n UserController,\n FiltriController,\n ConfiguratorController,\n FederatedAuthController,\n AccessiConsoleController,\n ConsoleEntryController,\n FederatedAuthExchangeController,\n ServiceTokenController,\n SchemaController,\n AccessiIntrospectionController,\n ],\n providers: [AuthService, TwoFactorService, UserService, EmailService, PermissionService, FiltriService, ConfiguratorService, JwtSimpleGuard, AuthenticateGenService, AccessiDatabaseUpdater, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, SchemaExportService, AdminBootstrapService, AccessiJwksBootstrapService],\n exports: [AuthService, UserService, EmailService, PermissionService, FiltriService, ConfiguratorService, JwtSimpleGuard, AuthenticateGenService, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, SchemaExportService, AdminBootstrapService],\n})\nexport class AccessiModule {\n /**\n * Registra Accessi nel contenitore Nest del backend ospitante.\n *\n * Usare questo metodo una sola volta nella composizione dell'applicazione. Per un backend\n * Express esistente usare invece `initializeAccessiModule`, che crea e monta l'host Nest interno.\n * La configurazione viene registrata con il token DI `ACCESSI_OPTIONS` ed e condivisa dai servizi.\n */\n static forRoot(options: AccessiOptions): DynamicModule {\n // Il servizio email e obbligatorio: reset password, 2FA e creazione utente ne dipendono.\n assertEmailConfigured(options);\n return {\n module: AccessiModule,\n providers: [\n {\n provide: 'ACCESSI_OPTIONS',\n useValue: options,\n },\n AuthService,\n TwoFactorService,\n UserService,\n EmailService,\n PermissionService,\n FiltriService,\n ConfiguratorService,\n JwtSimpleGuard,\n AuthenticateGenService,\n AccessiDatabaseUpdater,\n FederatedAuthService,\n ServiceTokenService,\n ServiceTokenGuard, SchemaExportService, AdminBootstrapService, AccessiJwksBootstrapService],\n exports: [\n 'ACCESSI_OPTIONS',\n AuthService,\n UserService,\n EmailService,\n PermissionService,\n FiltriService,\n ConfiguratorService,\n JwtSimpleGuard,\n AuthenticateGenService,\n FederatedAuthService,\n ServiceTokenService,\n ServiceTokenGuard, SchemaExportService, AdminBootstrapService],\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"AccessiModule.js","sourceRoot":"","sources":["../../src/accessi-module/AccessiModule.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,mFAAgF;AAQhF,2CAA+D;AAU/D,oEAAiE;AACjE,uEAAoE;AACpE,sFAAmF;AACnF,oEAAiE;AACjE,mEAAgE;AAChE,iEAA8D;AAC9D,6EAA0E;AAC1E,iEAA8D;AAC9D,0EAAuE;AACvE,qEAAkE;AAClE,iFAA8E;AAC9E,4FAAyF;AACzF,qDAAoD;AACpD,kEAAsE;AACtE,sFAAmF;AACnF,gFAA6E;AAC7E,sFAAmF;AACnF,sGAAmG;AACnG,qFAAkF;AAClF,iFAA8E;AAC9E,iFAA8E;AAC9E,4FAAyF;AACzF,oEAAiE;AACjE,qEAAkE;AAClE,iGAA8F;AAC9F,4FAAyF;AACzF,kGAA+F;AAC/F,4DAAuE;AACvE,sEAAsE;AA0S/D,IAAM,aAAa,qBAAnB,MAAM,aAAa;IACxB;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,CAAC,OAAuB;QACpC,yFAAyF;QACzF,IAAA,0CAAqB,EAAC,OAAO,CAAC,CAAC;QAC/B,OAAO;YACL,MAAM,EAAE,eAAa;YACrB,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,iBAAiB;oBAC1B,QAAQ,EAAE,OAAO;iBAClB;gBACD,yBAAW;gBACX,mCAAgB;gBAChB,yBAAW;gBACX,2BAAY;gBACZ,qCAAiB;gBACjB,6BAAa;gBACb,yCAAmB;gBACnB,6BAAc;gBACd,wCAAsB;gBACtB,+CAAsB;gBACtB,2CAAoB;gBACpB,yCAAmB;gBACnB,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,EAAE,2CAA2B;aAAC;YAC7F,OAAO,EAAE;gBACP,iBAAiB;gBACjB,yBAAW;gBACX,yBAAW;gBACX,2BAAY;gBACZ,qCAAiB;gBACjB,6BAAa;gBACb,yCAAmB;gBACnB,6BAAc;gBACd,wCAAsB;gBACtB,2CAAoB;gBACpB,yCAAmB;gBACnB,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB;aAAC;SACjE,CAAC;IACJ,CAAC;CACF,CAAA;AA9CY,sCAAa;wBAAb,aAAa;IApBzB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,WAAW,EAAE;YACX,iCAAe;YACf,+BAAc;YACd,2CAAoB;YACpB,+BAAc;YACd,mCAAgB;YAChB,+CAAsB;YACtB,iDAAuB;YACvB,mDAAwB;YACxB,+CAAsB;YACtB,iEAA+B;YAC/B,+CAAsB;YACtB,mCAAgB;YAChB,+DAA8B;SAC/B;QACD,SAAS,EAAE,CAAC,yBAAW,EAAE,mCAAgB,EAAE,yBAAW,EAAE,2BAAY,EAAE,qCAAiB,EAAE,6BAAa,EAAE,yCAAmB,EAAE,6BAAc,EAAE,wCAAsB,EAAE,+CAAsB,EAAE,2CAAoB,EAAE,yCAAmB,EAAE,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,EAAE,2CAA2B,CAAC;QACnU,OAAO,EAAE,CAAC,yBAAW,EAAE,yBAAW,EAAE,2BAAY,EAAE,qCAAiB,EAAE,6BAAa,EAAE,yCAAmB,EAAE,6BAAc,EAAE,wCAAsB,EAAE,2CAAoB,EAAE,yCAAmB,EAAE,qCAAiB,EAAE,yCAAmB,EAAE,6CAAqB,CAAC;KAC3P,CAAC;GACW,aAAa,CA8CzB","sourcesContent":["import { TwoFactorService } from './Services/TwoFactorService/TwoFactorService';\n/**\n * Modulo che gestisce le operazioni di accesso degli utenti, incluse le rotte, il controller e il modello.\n *\n * @module AccessiModule\n * @author mttdev382\n */\nimport { Options } from 'node-firebird';\nimport { DynamicModule, Global, Module } from '@nestjs/common';\nimport type { INestApplication, LoggerService } from '@nestjs/common';\nimport type { OpenApiSetupOptions } from '../openapi/OpenApiConfig';\nimport type { AccessiAccessTokenAlgorithm, AccessiUnknownKeyIdEvent } from './security/jwtTokenOptions';\nimport type { ConsoleEntryStore } from './security/consoleEntry';\nimport type { RateLimitStore } from './security/rateLimitStore';\nimport type { AccessiEdgeRateLimitOptions } from './security/edgeRateLimit';\nimport type { SessionRevocationOptions } from './security/sessionRevocation';\nimport type { AccessTokenRevocationOptions } from './security/accessTokenRevocation';\nimport type { RefreshTokenRotationOptions } from './security/refreshTokenRotation';\nimport { AuthService } from './Services/AuthService/AuthService';\nimport { EmailService } from './Services/EmailService/EmailService';\nimport { PermissionService } from './Services/PermissionService/PermissionService';\nimport { UserService } from './Services/UserService/UserService';\nimport { EmailController } from './Controllers/EmailController';\nimport { AuthController } from './Controllers/AuthController';\nimport { PermissionController } from './Controllers/PermissionController';\nimport { UserController } from './Controllers/UserController';\nimport { FiltriService } from './Services/FiltriService/FiltriService';\nimport { FiltriController } from './Controllers/FiltriController';\nimport { ConfiguratorController } from './Controllers/ConfiguratorController';\nimport { ConfiguratorService } from './Services/ConfiguratorService/ConfiguratorService';\nimport { JwtSimpleGuard } from './jwt/jwt.strategy';\nimport { AuthenticateGenService } from './middleware/authenticateGen';\nimport { AccessiDatabaseUpdater } from './database-updates/AccessiDatabaseUpdater';\nimport { FederatedAuthService } from './federated-auth/FederatedAuthService';\nimport { FederatedAuthController } from './federated-auth/FederatedAuthController';\nimport { FederatedAuthExchangeController } from './federated-auth/FederatedAuthExchangeController';\nimport { AccessiConsoleController } from './Controllers/AccessiConsoleController';\nimport { ConsoleEntryController } from './Controllers/ConsoleEntryController';\nimport { ServiceTokenController } from './Controllers/ServiceTokenController';\nimport { ServiceTokenService } from './Services/ServiceTokenService/ServiceTokenService';\nimport { ServiceTokenGuard } from './security/serviceTokenGuard';\nimport { SchemaController } from './Controllers/SchemaController';\nimport { AccessiIntrospectionController } from './Controllers/AccessiIntrospectionController';\nimport { SchemaExportService } from './Services/SchemaExportService/SchemaExportService';\nimport { AdminBootstrapService } from './Services/AdminBootstrapService/AdminBootstrapService';\nimport { AccessiJwksBootstrapService } from './security/jwksBootstrap';\nimport { assertEmailConfigured } from './security/emailConfiguration';\n\n/** JWT emesso da Accessi dopo un login locale o SSO. Non riutilizzare il segreto del provider SSO. */\nexport interface JwtOptions {\n /** Segreto simmetrico per firmare e verificare i JWT Accessi; deve essere unico per ambiente. */\n secret: string;\n /** Issuer (`iss`) opzionale degli access token. Consigliato in una topologia multi-servizio. */\n issuer?: string;\n /** Audience (`aud`) opzionale degli access token. */\n audience?: string | string[];\n /** Algoritmo degli access token. Default `HS256` per retrocompatibilita. */\n algorithm?: AccessiAccessTokenAlgorithm;\n /** Chiave privata PEM per RS/ES. Obbligatoria quando `algorithm` non e HS*. */\n privateKey?: string;\n /** Chiave pubblica PEM per la verifica RS/ES. */\n publicKey?: string;\n /** Key id pubblicato nella JWKS, per rotazione chiavi. */\n keyId?: string;\n /** Path pubblico della JWKS. Default `accessi/.well-known/jwks.json` con algoritmi asimmetrici. */\n jwksPath?: string;\n /**\n * Chiavi pubbliche precedenti, esposte in JWKS e accettate in verifica\n * durante la rotazione. Se `keyId` e assente si usa il fingerprint della\n * chiave, lo stesso calcolato dalla JWKS.\n */\n previousPublicKeys?: Array<{\n publicKey: string;\n keyId?: string;\n algorithm?: AccessiAccessTokenAlgorithm;\n }>;\n /**\n * Alert quando un token presenta un `kid` assente da chiave corrente e\n * chiavi precedenti (tipico di una rotazione non completata).\n */\n onUnknownKeyId?: (event: AccessiUnknownKeyIdEvent) => void;\n /** Durata compatibile con `jsonwebtoken`, per esempio `24h` o `3600s`. */\n expiresIn: string;\n /**\n * Durata del refresh token. Se assente il refresh token non viene emesso.\n * Consigliato: `30d`.\n */\n refreshExpiresIn?: string;\n}\n\n/** Configurazione dei ticket di ingresso alla console. */\nexport interface ConsoleEntryOptions {\n /** Durata del ticket in millisecondi. Default 60000. */\n ttlMs?: number;\n /** Store condiviso (es. Redis) per deployment multi-istanza. Default: memoria. */\n store?: ConsoleEntryStore;\n}\n\nimport type { AccessiSecurityHeadersSetting } from './security/securityHeaders';\nimport type { AccessiAuthCookieOptions } from './security/authCookies';\n\n/** Impostazioni HTTP del servizio Accessi. */\nexport interface AccessiHttpOptions {\n /** Opzioni CORS Nest. `false` disabilita CORS. Default: exposedHeaders ['X-Total-Count']. */\n cors?: Parameters<INestApplication['enableCors']>[0] | false;\n /** Trust proxy per IP reali dietro load balancer/reverse proxy. */\n trustProxy?: boolean | number | string;\n /** Header di sicurezza di base su console/OpenAPI. Default: true. */\n securityHeaders?: AccessiSecurityHeadersSetting;\n}\n\n/** Parametri SMTP usati esclusivamente per impostazione e reset della password. */\nexport interface EmailOptions {\n host: string;\n port: number;\n secure: boolean;\n requireTLS : boolean;\n tls: {\n rejectUnauthorized: boolean,\n }\n from: string;\n auth: {\n user: string;\n pass: string;\n };\n}\n\n/** Configura campi applicativi aggiuntivi restituiti insieme al profilo Accessi. */\nexport interface ExtensionFieldsOptions {\n /** Connessione al database contenente la tabella estesa. */\n databaseOptions: Options;\n /** Chiave con cui i dati saranno esposti nella risposta. */\n objectKey: string;\n /** Tabella applicativa contenente i dati estesi. */\n tableName: string;\n /** Colonne consentite; non valorizzarle mai con input HTTP. */\n tableFields: string[];\n /** Colonna correlata a `UTENTI.CODUTE`. */\n tableJoinFieldName: string;\n}\n\n/** Limite per una singola categoria di endpoint pubblici. */\nexport interface PublicAuthRateLimitRuleOptions {\n windowMs: number;\n maxAttempts: number;\n}\n\n/** Protezione degli endpoint pubblici. Default in memoria; in cluster usare uno store condiviso. */\nexport interface PublicAuthRateLimitOptions {\n enabled?: boolean;\n /** Store condiviso (es. Redis). Default: memoria del processo. */\n store?: RateLimitStore;\n login?: PublicAuthRateLimitRuleOptions;\n register?: PublicAuthRateLimitRuleOptions;\n passwordResetEmail?: PublicAuthRateLimitRuleOptions;\n passwordResetConfirm?: PublicAuthRateLimitRuleOptions;\n refresh?: PublicAuthRateLimitRuleOptions;\n getUserByToken?: PublicAuthRateLimitRuleOptions;\n twoFactorVerify?: PublicAuthRateLimitRuleOptions;\n twoFactorResend?: PublicAuthRateLimitRuleOptions;\n adminBootstrap?: PublicAuthRateLimitRuleOptions;\n consoleEntry?: PublicAuthRateLimitRuleOptions;\n}\n\n/**\n * Bootstrap del primo admin. Se abilitato, Accessi genera un token casuale a ogni avvio del\n * processo, lo stampa nel log e lo accetta sull'endpoint pubblico `POST /accessi/auth/bootstrap-admin`.\n * Serve a creare il primo utente admin senza email di conferma e senza un admin gia esistente.\n */\nexport interface AdminBootstrapOptions {\n /** Abilita il bootstrap admin. Default `false`. */\n enabled?: boolean;\n /** Crea l'utente come superutente oltre che admin. Default `true`. */\n superUser?: boolean;\n}\n\n/** Controls the legacy public username/password registration endpoint. Disabled by default. */\nexport interface PublicRegistrationOptions {\n /** Enables POST /accessi/user/register. Applications that used it before must opt in explicitly. */\n enabled?: boolean;\n}\n\n/** Configurazione dei token di servizio (macchina-a-macchina, senza utente). */\nexport interface ServiceTokenOptions {\n /** Abilita gli endpoint amministrativi dei service token. Default `true`. */\n enabled?: boolean;\n /** Durata predefinita in giorni quando non e indicata una scadenza esplicita. Default: nessuna scadenza. */\n defaultTtlDays?: number;\n /** Store condiviso per il rate limit dei service token. Default: memoria. */\n rateLimitStore?: RateLimitStore;\n}\n\n/** Cosa fare quando uno script gia applicato presenta un checksum diverso (file modificato). */\nexport type CatalogScriptChecksumPolicy = 'error' | 'warn' | 'reapply';\n\n/**\n * Catalog migrations applicative (menu, ruoli, tipi, cataloghi). Gli script `.sql` vivono nel\n * backend ospitante (versionati in repository) e vengono eseguiti da Accessi dopo la\n * riconciliazione dello schema. Ogni script e tracciato nella tabella `ACCESSI_CATALOG_SCRIPT`,\n * quindi viene applicato una sola volta per database (idempotenza lato DB).\n */\nexport interface SystemSeedsOptions {\n /** `false` disabilita il seed automatico, ad esempio per ambienti gestiti manualmente. */\n enabled?: boolean;\n}\n\nexport interface CatalogScriptsOptions {\n /** Abilita l'esecuzione automatica al bootstrap. Default `false`. */\n enabled?: boolean;\n /** Cartella contenente gli script `.sql` (assoluta o relativa alla working directory). */\n folder: string;\n /** Cosa fare se il checksum di uno script applicato e cambiato. Default `error`. */\n onChecksumMismatch?: CatalogScriptChecksumPolicy;\n /** Se `true` (default) un errore interrompe l'esecuzione e non avanza allo script successivo. */\n stopOnError?: boolean;\n /** Globi/estensioni inclusi. Default: tutti i `.sql`. */\n include?: string[];\n /** Numero massimo di script eseguiti in un singolo run (protezione operativa). Default: illimitato. */\n maxScriptsPerRun?: number;\n /**\n * Permette statement DDL/schema negli script. Default `false`: le catalog migrations possono\n * modificare solo righe. Sconsigliato: lo schema e di competenza di `db:update:accessi`.\n */\n allowDdl?: boolean;\n}\n\n/**\n * Enables Accessi's provider-agnostic federated identity capability.\n *\n * Accessi never validates Azure, Google, Apple, SAML, or other provider tokens.\n * The hosting backend must validate its own SSO flow and pass only a verified\n * `{ provider, subject }` identity to FederatedAuthService.\n */\nexport interface FederatedAuthenticationOptions {\n /** Attiva le API SSO generiche; con `false` il comportamento password storico rimane invariato. */\n enabled?: boolean;\n /** @deprecated Schema management is centralized in autoUpdateDatabase. This legacy flag no longer starts separate SSO migrations. */\n autoUpdateSchema?: boolean;\n /** Allows a backend to create a user from a verified external identity without a master user. Default false. */\n allowSelfRegistration?: boolean;\n}\n\n/**\n * Configurazione completa del modulo Accessi, fornita esclusivamente dal backend ospitante.\n * Segreti JWT, SMTP, database e le configurazioni tecniche dei provider SSO non devono mai raggiungere il browser.\n */\nexport interface AccessiOptions {\n /** Connessione Firebird alla base dati Accessi. */\n databaseOptions: Options;\n /**\n * Basepath del sito es: 'http://www.il-mio-sito.it/nome-progetto(se c'è)'\n */\n confirmationEmailUrl: string;\n /**\n * Percorso della pagina di reset personalizzata es. http://localhost:4200/#/admin/reset-password\n * N.B si sostituisce al confirmationMailURl\n */\n customResetPage?: string;\n /** URL di ritorno della pagina standard di reset dopo il completamento. */\n confirmationEmailReturnUrl: string;\n /** Prefisso applicativo facoltativo trasmesso alla pagina standard di reset. */\n confirmationEmailPrefix?: string;\n /** Chiave riservata alla compatibilita e alla migrazione delle password legacy cifrate. */\n encryptionKey: string;\n /** Abilita gli utenti fittizi `admin` e `demo` solo fuori produzione. */\n mockDemoUser: boolean;\n /** Attiva il controllo della scadenza password durante il login locale. */\n passwordExpiration?: boolean;\n /** Giorni di validita della password; quando assente Accessi usa 90 giorni. */\n passwordExpirationDays?: number;\n /** Esegue al bootstrap la conversione non distruttiva delle password legacy. Default `true`. */\n legacyPasswordMigrationOnStartup?: boolean;\n /** Riconcilia lo schema al bootstrap (default `true`). Con `false` verifica comunque la compatibilita in sola lettura e blocca avvii incompatibili. */\n autoUpdateDatabase?: boolean;\n /** Parametri JWT dei token emessi da Accessi. */\n jwtOptions: JwtOptions;\n /** Parametri SMTP per reset e attivazione password. */\n emailOptions: EmailOptions;\n publicAuthRateLimit?: PublicAuthRateLimitOptions;\n /** Public local registration is deliberately opt-in. Existing applications must set enabled: true to retain it. */\n publicRegistration?: PublicRegistrationOptions;\n /** Optional provider-agnostic SSO support. Provider-specific configuration remains in the hosting backend. */\n federatedAuthentication?: FederatedAuthenticationOptions;\n extensionFieldsOptions?: ExtensionFieldsOptions[];\n /** Token di servizio per chiamate tecniche macchina-a-macchina. Abilitati di default. */\n serviceTokens?: ServiceTokenOptions;\n /** Catalog migrations applicative eseguite dopo la riconciliazione dello schema. */\n catalogScripts?: CatalogScriptsOptions;\n /** Seed di sistema eseguiti dopo la riconciliazione dello schema. Default: abilitati. */\n systemSeeds?: SystemSeedsOptions;\n /** Bootstrap del primo admin tramite token casuale stampato a ogni avvio. */\n adminBootstrap?: AdminBootstrapOptions;\n /** Base URL pubblica usata solo per loggare gli indirizzi (console, OpenAPI, API). */\n publicBaseUrl?: string;\n /** Base URL API per la console quando UI e API sono su domini diversi. */\n consoleApiBaseUrl?: string;\n /**\n * Documentazione OpenAPI del modulo. Default invariati:\n * UI `accessi/openapi`, JSON `accessi/openapi.json`.\n */\n openapi?: OpenApiSetupOptions;\n /** Logger Nest usato dal sotto-app Accessi. Se assente Nest usa il proprio logger. */\n logger?: LoggerService;\n /** Buffer dei log di bootstrap del sotto-app. Default `true` (comportamento storico). */\n bufferLogs?: boolean;\n /** Ticket di ingresso console: TTL e store condiviso per deployment multi-istanza. */\n consoleEntry?: ConsoleEntryOptions;\n /** Limite grossolano per IP davanti alle rotte Accessi. Opzionale. */\n edgeRateLimit?: AccessiEdgeRateLimitOptions;\n /** CORS, trust proxy e header di sicurezza del servizio. */\n http?: AccessiHttpOptions;\n /**\n * Autenticazione opzionale via cookie httpOnly. Bearer resta il default:\n * la modalita si abilita passando questo oggetto (o `enabled: true`).\n */\n authCookie?: AccessiAuthCookieOptions;\n /** Revoca sessioni per utente tramite store memory/Redis. */\n sessionRevocation?: SessionRevocationOptions;\n /** Revoca dei singoli access token tramite `jti`, memory/Redis. */\n accessTokenRevocation?: AccessTokenRevocationOptions;\n /** Refresh token monouso tramite rotazione memory/Redis; rileva il riuso e revoca la famiglia. */\n refreshTokenRotation?: RefreshTokenRotationOptions;\n}\n\n@Global()\n@Module({\n controllers: [\n EmailController,\n AuthController,\n PermissionController,\n UserController,\n FiltriController,\n ConfiguratorController,\n FederatedAuthController,\n AccessiConsoleController,\n ConsoleEntryController,\n FederatedAuthExchangeController,\n ServiceTokenController,\n SchemaController,\n AccessiIntrospectionController,\n ],\n providers: [AuthService, TwoFactorService, UserService, EmailService, PermissionService, FiltriService, ConfiguratorService, JwtSimpleGuard, AuthenticateGenService, AccessiDatabaseUpdater, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, SchemaExportService, AdminBootstrapService, AccessiJwksBootstrapService],\n exports: [AuthService, UserService, EmailService, PermissionService, FiltriService, ConfiguratorService, JwtSimpleGuard, AuthenticateGenService, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, SchemaExportService, AdminBootstrapService],\n})\nexport class AccessiModule {\n /**\n * Registra Accessi nel contenitore Nest del backend ospitante.\n *\n * Usare questo metodo una sola volta nella composizione dell'applicazione. Per un backend\n * Express esistente usare invece `initializeAccessiModule`, che crea e monta l'host Nest interno.\n * La configurazione viene registrata con il token DI `ACCESSI_OPTIONS` ed e condivisa dai servizi.\n */\n static forRoot(options: AccessiOptions): DynamicModule {\n // Il servizio email e obbligatorio: reset password, 2FA e creazione utente ne dipendono.\n assertEmailConfigured(options);\n return {\n module: AccessiModule,\n providers: [\n {\n provide: 'ACCESSI_OPTIONS',\n useValue: options,\n },\n AuthService,\n TwoFactorService,\n UserService,\n EmailService,\n PermissionService,\n FiltriService,\n ConfiguratorService,\n JwtSimpleGuard,\n AuthenticateGenService,\n AccessiDatabaseUpdater,\n FederatedAuthService,\n ServiceTokenService,\n ServiceTokenGuard, SchemaExportService, AdminBootstrapService, AccessiJwksBootstrapService],\n exports: [\n 'ACCESSI_OPTIONS',\n AuthService,\n UserService,\n EmailService,\n PermissionService,\n FiltriService,\n ConfiguratorService,\n JwtSimpleGuard,\n AuthenticateGenService,\n FederatedAuthService,\n ServiceTokenService,\n ServiceTokenGuard, SchemaExportService, AdminBootstrapService],\n };\n }\n}\n"]}
|
|
@@ -15,6 +15,10 @@ export interface AccessiClientConfig {
|
|
|
15
15
|
declare global {
|
|
16
16
|
interface Window {
|
|
17
17
|
__ACCESSI_API_BASE_URL__?: string;
|
|
18
|
+
/** La console e servita in modalita cookie httpOnly. */
|
|
19
|
+
__ACCESSI_AUTH_COOKIE__?: boolean;
|
|
20
|
+
__ACCESSI_CSRF_COOKIE_NAME__?: string;
|
|
21
|
+
__ACCESSI_CSRF_HEADER_NAME__?: string;
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
/** Emesso quando una richiesta autenticata riceve 401: la console deve riportare al login. */
|
|
@@ -48,5 +52,8 @@ export declare function runWithAccessiClient<T>(config: AccessiClientConfig, cal
|
|
|
48
52
|
/**
|
|
49
53
|
* Trasporto comune del client Orval: aggiunge il Bearer token e converte le
|
|
50
54
|
* risposte di errore Accessi in Error JavaScript leggibili dalla console.
|
|
55
|
+
*
|
|
56
|
+
* In modalita cookie non richiede un token in memoria: invia i cookie
|
|
57
|
+
* same-origin/cross-origin e l'header CSRF double-submit.
|
|
51
58
|
*/
|
|
52
59
|
export declare function accessiFetch<T>(url: string, options: RequestInit): Promise<T>;
|
|
@@ -83,48 +83,86 @@ function payloadString(payload, key) {
|
|
|
83
83
|
const value = payload[key];
|
|
84
84
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
85
85
|
}
|
|
86
|
+
/** Legge un cookie non httpOnly (token CSRF double-submit). */
|
|
87
|
+
function readBrowserCookie(name) {
|
|
88
|
+
if (typeof document === 'undefined' || !document.cookie)
|
|
89
|
+
return null;
|
|
90
|
+
for (const part of document.cookie.split(';')) {
|
|
91
|
+
const separator = part.indexOf('=');
|
|
92
|
+
if (separator < 0)
|
|
93
|
+
continue;
|
|
94
|
+
if (part.slice(0, separator).trim() !== name)
|
|
95
|
+
continue;
|
|
96
|
+
try {
|
|
97
|
+
return decodeURIComponent(part.slice(separator + 1).trim());
|
|
98
|
+
}
|
|
99
|
+
catch (_a) {
|
|
100
|
+
return part.slice(separator + 1).trim();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
function isCookieAuthMode() {
|
|
106
|
+
return typeof window !== 'undefined' && window.__ACCESSI_AUTH_COOKIE__ === true;
|
|
107
|
+
}
|
|
108
|
+
const UNSAFE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
86
109
|
/**
|
|
87
110
|
* Trasporto comune del client Orval: aggiunge il Bearer token e converte le
|
|
88
111
|
* risposte di errore Accessi in Error JavaScript leggibili dalla console.
|
|
112
|
+
*
|
|
113
|
+
* In modalita cookie non richiede un token in memoria: invia i cookie
|
|
114
|
+
* same-origin/cross-origin e l'header CSRF double-submit.
|
|
89
115
|
*/
|
|
90
116
|
function accessiFetch(url, options) {
|
|
91
117
|
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
-
var _a, _b, _c, _d, _e;
|
|
118
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
93
119
|
// Il contesto va letto prima di ogni await: la funzione generata invoca il
|
|
94
120
|
// trasporto in modo sincrono, quindi la configurazione appartiene a questa call.
|
|
95
121
|
const config = scopedConfig;
|
|
122
|
+
const cookieMode = isCookieAuthMode();
|
|
96
123
|
const headers = new Headers(options.headers);
|
|
97
124
|
headers.set('Accept', 'application/json');
|
|
98
125
|
if (options.body !== undefined && !headers.has('Content-Type')) {
|
|
99
126
|
headers.set('Content-Type', 'application/json');
|
|
100
127
|
}
|
|
128
|
+
if (cookieMode) {
|
|
129
|
+
const method = ((_a = options.method) !== null && _a !== void 0 ? _a : 'GET').toUpperCase();
|
|
130
|
+
if (UNSAFE_METHODS.has(method)) {
|
|
131
|
+
const csrfCookie = (_b = window.__ACCESSI_CSRF_COOKIE_NAME__) !== null && _b !== void 0 ? _b : 'accessi_csrf';
|
|
132
|
+
const csrfHeader = (_c = window.__ACCESSI_CSRF_HEADER_NAME__) !== null && _c !== void 0 ? _c : 'x-csrf-token';
|
|
133
|
+
const csrfToken = readBrowserCookie(csrfCookie);
|
|
134
|
+
if (csrfToken)
|
|
135
|
+
headers.set(csrfHeader, csrfToken);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
101
138
|
const resolvedToken = config
|
|
102
|
-
? (config.getToken ? yield config.getToken() : (
|
|
139
|
+
? (config.getToken ? yield config.getToken() : (_d = config.token) !== null && _d !== void 0 ? _d : null)
|
|
103
140
|
: tokenProvider
|
|
104
141
|
? yield tokenProvider()
|
|
105
142
|
: accessToken;
|
|
106
|
-
const
|
|
143
|
+
const isAuthEntryRequest = /\/(?:auth\/(?:login|refresh|two-factor\/(?:verify|resend))|console\/entry\/exchange)\b/.test(url);
|
|
144
|
+
const authenticatedRequest = !isAuthEntryRequest && (cookieMode || (resolvedToken !== null && resolvedToken !== undefined));
|
|
107
145
|
if (resolvedToken)
|
|
108
146
|
headers.set('Authorization', `Bearer ${resolvedToken}`);
|
|
109
147
|
const runtimeBaseUrl = (config === null || config === void 0 ? void 0 : config.baseUrl) !== undefined
|
|
110
148
|
? config.baseUrl
|
|
111
|
-
: baseUrl !== null && baseUrl !== void 0 ? baseUrl : (typeof window !== 'undefined' ? (
|
|
149
|
+
: baseUrl !== null && baseUrl !== void 0 ? baseUrl : (typeof window !== 'undefined' ? (_e = window.__ACCESSI_API_BASE_URL__) !== null && _e !== void 0 ? _e : null : null);
|
|
112
150
|
const requestUrl = runtimeBaseUrl && url.startsWith('/')
|
|
113
151
|
? `${runtimeBaseUrl.replace(/\/+$/, '')}${url}`
|
|
114
152
|
: url;
|
|
115
153
|
notifyNetworkActivity(true);
|
|
116
154
|
try {
|
|
117
|
-
const response = yield fetch(requestUrl, Object.assign(Object.assign({}, options), { headers }));
|
|
155
|
+
const response = yield fetch(requestUrl, Object.assign(Object.assign(Object.assign({}, options), { headers }), (cookieMode ? { credentials: 'include' } : {})));
|
|
118
156
|
const payload = yield response.json().catch(() => undefined);
|
|
119
157
|
if (!response.ok) {
|
|
120
|
-
const message = (
|
|
158
|
+
const message = (_g = (_f = payloadString(payload, 'message')) !== null && _f !== void 0 ? _f : payloadString(payload, 'error')) !== null && _g !== void 0 ? _g : `Errore HTTP ${response.status}`;
|
|
121
159
|
const rawDetails = payload && typeof payload === 'object' ? payload.details : undefined;
|
|
122
160
|
const details = Array.isArray(rawDetails) ? rawDetails.map((item) => String(item)) : undefined;
|
|
123
161
|
// Un 401 su una richiesta autenticata significa sessione scaduta/revocata: il token non e piu valido.
|
|
124
162
|
if (response.status === 401 && authenticatedRequest && typeof window !== 'undefined') {
|
|
125
163
|
window.dispatchEvent(new CustomEvent(exports.ACCESSI_UNAUTHORIZED_EVENT));
|
|
126
164
|
}
|
|
127
|
-
throw new AccessiRequestError(message, response.status, (
|
|
165
|
+
throw new AccessiRequestError(message, response.status, (_h = payloadString(payload, 'code')) !== null && _h !== void 0 ? _h : payloadString(payload, 'error'), details);
|
|
128
166
|
}
|
|
129
167
|
return {
|
|
130
168
|
data: payload,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessiFetch.js","sourceRoot":"","sources":["../../../src/accessi-module/Console/accessiFetch.ts"],"names":[],"mappings":"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","sourcesContent":["let accessToken: string | null = null;\nlet baseUrl: string | null = null;\nlet tokenProvider: (() => string | null | Promise<string | null>) | null = null;\nlet scopedConfig: AccessiClientConfig | null = null;\n\n/**\n * Configurazione di un client Accessi.\n *\n * Usata sia dalla configurazione globale (`configureAccessiClient`) sia dalle\n * istanze isolate create con `createAccessiClient`.\n */\nexport interface AccessiClientConfig {\n /** Base URL di Accessi Service, es. `https://accessi.example.com`. */\n baseUrl?: string | null;\n /** Token statico. Preferire `getToken` per token che cambiano nel tempo. */\n token?: string | null;\n /** Provider asincrono/sincrono del token corrente. */\n getToken?: () => string | null | Promise<string | null>;\n}\n\ndeclare global {\n interface Window {\n __ACCESSI_API_BASE_URL__?: string;\n }\n}\n\nconst loadingEventName = 'accessi-console-network';\n/** Emesso quando una richiesta autenticata riceve 401: la console deve riportare al login. */\nexport const ACCESSI_UNAUTHORIZED_EVENT = 'accessi-console-unauthorized';\n\n/** Errore del client console: conserva status, codice stabile Accessi e dettagli di validazione. */\nexport class AccessiRequestError extends Error {\n constructor(message: string, readonly status: number, readonly code?: string, readonly details?: string[]) {\n super(message);\n this.name = 'AccessiRequestError';\n }\n}\n\n/** Notifies the browser shell without coupling generated Orval calls to the UI implementation. */\nfunction notifyNetworkActivity(active: boolean): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(loadingEventName, { detail: { active } }));\n }\n}\n\n/** Imposta il JWT usato da tutte le funzioni generate da Orval. */\nexport function setAccessiConsoleToken(token: string | null): void {\n accessToken = token;\n}\n\n/** Prefissa le chiamate relative del client generato (es. `https://accessi.example.com`). */\nexport function setAccessiFetchBaseUrl(url: string | null): void {\n baseUrl = url ? url.replace(/\\/+$/, '') : null;\n}\n\n/** Token provider per client esterni alla console (server-to-server o frontend). */\nexport function setAccessiFetchTokenProvider(\n provider: (() => string | null | Promise<string | null>) | null,\n): void {\n tokenProvider = provider;\n}\n\n/** Configurazione isolata attiva in questo momento, se una chiamata e in corso. */\nexport function getAccessiClientConfig(): AccessiClientConfig | null {\n return scopedConfig;\n}\n\n/**\n * Esegue `callback` con una configurazione del client isolata.\n *\n * Le funzioni generate da Orval invocano il trasporto in modo sincrono, quindi\n * la configurazione viene letta all'inizio della richiesta: due client diversi\n * possono chiamare in concorrenza senza sovrascriversi a vicenda.\n *\n * Per un uso continuativo preferire `createAccessiClient`, che applica il\n * contesto a ogni metodo.\n */\nexport function runWithAccessiClient<T>(config: AccessiClientConfig, callback: () => T): T {\n const previous = scopedConfig;\n scopedConfig = config;\n try {\n return callback();\n } finally {\n scopedConfig = previous;\n }\n}\n\n/** Legge una stringa non vuota dal payload di errore Accessi, ignorando tipi inattesi. */\nfunction payloadString(payload: unknown, key: string): string | undefined {\n if (!payload || typeof payload !== 'object') return undefined;\n const value = (payload as Record<string, unknown>)[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * Trasporto comune del client Orval: aggiunge il Bearer token e converte le\n * risposte di errore Accessi in Error JavaScript leggibili dalla console.\n */\nexport async function accessiFetch<T>(url: string, options: RequestInit): Promise<T> {\n // Il contesto va letto prima di ogni await: la funzione generata invoca il\n // trasporto in modo sincrono, quindi la configurazione appartiene a questa call.\n const config = scopedConfig;\n const headers = new Headers(options.headers);\n headers.set('Accept', 'application/json');\n if (options.body !== undefined && !headers.has('Content-Type')) {\n headers.set('Content-Type', 'application/json');\n }\n const resolvedToken = config\n ? (config.getToken ? await config.getToken() : config.token ?? null)\n : tokenProvider\n ? await tokenProvider()\n : accessToken;\n const authenticatedRequest = resolvedToken !== null && resolvedToken !== undefined;\n if (resolvedToken) headers.set('Authorization', `Bearer ${resolvedToken}`);\n const runtimeBaseUrl = config?.baseUrl !== undefined\n ? config.baseUrl\n : baseUrl ?? (typeof window !== 'undefined' ? window.__ACCESSI_API_BASE_URL__ ?? null : null);\n const requestUrl = runtimeBaseUrl && url.startsWith('/')\n ? `${runtimeBaseUrl.replace(/\\/+$/, '')}${url}`\n : url;\n\n notifyNetworkActivity(true);\n try {\n const response = await fetch(requestUrl, { ...options, headers });\n const payload = await response.json().catch(() => undefined);\n if (!response.ok) {\n const message = payloadString(payload, 'message') ?? payloadString(payload, 'error') ?? `Errore HTTP ${response.status}`;\n const rawDetails = payload && typeof payload === 'object' ? (payload as { details?: unknown }).details : undefined;\n const details = Array.isArray(rawDetails) ? rawDetails.map((item) => String(item)) : undefined;\n // Un 401 su una richiesta autenticata significa sessione scaduta/revocata: il token non e piu valido.\n if (response.status === 401 && authenticatedRequest && typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(ACCESSI_UNAUTHORIZED_EVENT));\n }\n throw new AccessiRequestError(message, response.status, payloadString(payload, 'code') ?? payloadString(payload, 'error'), details);\n }\n return {\n data: payload,\n status: response.status,\n headers: response.headers,\n } as T;\n } finally {\n notifyNetworkActivity(false);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"accessiFetch.js","sourceRoot":"","sources":["../../../src/accessi-module/Console/accessiFetch.ts"],"names":[],"mappings":"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","sourcesContent":["let accessToken: string | null = null;\nlet baseUrl: string | null = null;\nlet tokenProvider: (() => string | null | Promise<string | null>) | null = null;\nlet scopedConfig: AccessiClientConfig | null = null;\n\n/**\n * Configurazione di un client Accessi.\n *\n * Usata sia dalla configurazione globale (`configureAccessiClient`) sia dalle\n * istanze isolate create con `createAccessiClient`.\n */\nexport interface AccessiClientConfig {\n /** Base URL di Accessi Service, es. `https://accessi.example.com`. */\n baseUrl?: string | null;\n /** Token statico. Preferire `getToken` per token che cambiano nel tempo. */\n token?: string | null;\n /** Provider asincrono/sincrono del token corrente. */\n getToken?: () => string | null | Promise<string | null>;\n}\n\ndeclare global {\n interface Window {\n __ACCESSI_API_BASE_URL__?: string;\n /** La console e servita in modalita cookie httpOnly. */\n __ACCESSI_AUTH_COOKIE__?: boolean;\n __ACCESSI_CSRF_COOKIE_NAME__?: string;\n __ACCESSI_CSRF_HEADER_NAME__?: string;\n }\n}\n\nconst loadingEventName = 'accessi-console-network';\n/** Emesso quando una richiesta autenticata riceve 401: la console deve riportare al login. */\nexport const ACCESSI_UNAUTHORIZED_EVENT = 'accessi-console-unauthorized';\n\n/** Errore del client console: conserva status, codice stabile Accessi e dettagli di validazione. */\nexport class AccessiRequestError extends Error {\n constructor(message: string, readonly status: number, readonly code?: string, readonly details?: string[]) {\n super(message);\n this.name = 'AccessiRequestError';\n }\n}\n\n/** Notifies the browser shell without coupling generated Orval calls to the UI implementation. */\nfunction notifyNetworkActivity(active: boolean): void {\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(loadingEventName, { detail: { active } }));\n }\n}\n\n/** Imposta il JWT usato da tutte le funzioni generate da Orval. */\nexport function setAccessiConsoleToken(token: string | null): void {\n accessToken = token;\n}\n\n/** Prefissa le chiamate relative del client generato (es. `https://accessi.example.com`). */\nexport function setAccessiFetchBaseUrl(url: string | null): void {\n baseUrl = url ? url.replace(/\\/+$/, '') : null;\n}\n\n/** Token provider per client esterni alla console (server-to-server o frontend). */\nexport function setAccessiFetchTokenProvider(\n provider: (() => string | null | Promise<string | null>) | null,\n): void {\n tokenProvider = provider;\n}\n\n/** Configurazione isolata attiva in questo momento, se una chiamata e in corso. */\nexport function getAccessiClientConfig(): AccessiClientConfig | null {\n return scopedConfig;\n}\n\n/**\n * Esegue `callback` con una configurazione del client isolata.\n *\n * Le funzioni generate da Orval invocano il trasporto in modo sincrono, quindi\n * la configurazione viene letta all'inizio della richiesta: due client diversi\n * possono chiamare in concorrenza senza sovrascriversi a vicenda.\n *\n * Per un uso continuativo preferire `createAccessiClient`, che applica il\n * contesto a ogni metodo.\n */\nexport function runWithAccessiClient<T>(config: AccessiClientConfig, callback: () => T): T {\n const previous = scopedConfig;\n scopedConfig = config;\n try {\n return callback();\n } finally {\n scopedConfig = previous;\n }\n}\n\n/** Legge una stringa non vuota dal payload di errore Accessi, ignorando tipi inattesi. */\nfunction payloadString(payload: unknown, key: string): string | undefined {\n if (!payload || typeof payload !== 'object') return undefined;\n const value = (payload as Record<string, unknown>)[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/** Legge un cookie non httpOnly (token CSRF double-submit). */\nfunction readBrowserCookie(name: string): string | null {\n if (typeof document === 'undefined' || !document.cookie) return null;\n for (const part of document.cookie.split(';')) {\n const separator = part.indexOf('=');\n if (separator < 0) continue;\n if (part.slice(0, separator).trim() !== name) continue;\n try {\n return decodeURIComponent(part.slice(separator + 1).trim());\n } catch {\n return part.slice(separator + 1).trim();\n }\n }\n return null;\n}\n\nfunction isCookieAuthMode(): boolean {\n return typeof window !== 'undefined' && window.__ACCESSI_AUTH_COOKIE__ === true;\n}\n\nconst UNSAFE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);\n\n/**\n * Trasporto comune del client Orval: aggiunge il Bearer token e converte le\n * risposte di errore Accessi in Error JavaScript leggibili dalla console.\n *\n * In modalita cookie non richiede un token in memoria: invia i cookie\n * same-origin/cross-origin e l'header CSRF double-submit.\n */\nexport async function accessiFetch<T>(url: string, options: RequestInit): Promise<T> {\n // Il contesto va letto prima di ogni await: la funzione generata invoca il\n // trasporto in modo sincrono, quindi la configurazione appartiene a questa call.\n const config = scopedConfig;\n const cookieMode = isCookieAuthMode();\n const headers = new Headers(options.headers);\n headers.set('Accept', 'application/json');\n if (options.body !== undefined && !headers.has('Content-Type')) {\n headers.set('Content-Type', 'application/json');\n }\n if (cookieMode) {\n const method = (options.method ?? 'GET').toUpperCase();\n if (UNSAFE_METHODS.has(method)) {\n const csrfCookie = window.__ACCESSI_CSRF_COOKIE_NAME__ ?? 'accessi_csrf';\n const csrfHeader = window.__ACCESSI_CSRF_HEADER_NAME__ ?? 'x-csrf-token';\n const csrfToken = readBrowserCookie(csrfCookie);\n if (csrfToken) headers.set(csrfHeader, csrfToken);\n }\n }\n const resolvedToken = config\n ? (config.getToken ? await config.getToken() : config.token ?? null)\n : tokenProvider\n ? await tokenProvider()\n : accessToken;\n const isAuthEntryRequest = /\\/(?:auth\\/(?:login|refresh|two-factor\\/(?:verify|resend))|console\\/entry\\/exchange)\\b/.test(url);\n const authenticatedRequest = !isAuthEntryRequest && (cookieMode || (resolvedToken !== null && resolvedToken !== undefined));\n if (resolvedToken) headers.set('Authorization', `Bearer ${resolvedToken}`);\n const runtimeBaseUrl = config?.baseUrl !== undefined\n ? config.baseUrl\n : baseUrl ?? (typeof window !== 'undefined' ? window.__ACCESSI_API_BASE_URL__ ?? null : null);\n const requestUrl = runtimeBaseUrl && url.startsWith('/')\n ? `${runtimeBaseUrl.replace(/\\/+$/, '')}${url}`\n : url;\n\n notifyNetworkActivity(true);\n try {\n const response = await fetch(requestUrl, {\n ...options,\n headers,\n ...(cookieMode ? { credentials: 'include' as const } : {}),\n });\n const payload = await response.json().catch(() => undefined);\n if (!response.ok) {\n const message = payloadString(payload, 'message') ?? payloadString(payload, 'error') ?? `Errore HTTP ${response.status}`;\n const rawDetails = payload && typeof payload === 'object' ? (payload as { details?: unknown }).details : undefined;\n const details = Array.isArray(rawDetails) ? rawDetails.map((item) => String(item)) : undefined;\n // Un 401 su una richiesta autenticata significa sessione scaduta/revocata: il token non e piu valido.\n if (response.status === 401 && authenticatedRequest && typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(ACCESSI_UNAUTHORIZED_EVENT));\n }\n throw new AccessiRequestError(message, response.status, payloadString(payload, 'code') ?? payloadString(payload, 'error'), details);\n }\n return {\n data: payload,\n status: response.status,\n headers: response.headers,\n } as T;\n } finally {\n notifyNetworkActivity(false);\n }\n}\n"]}
|
|
@@ -27,21 +27,54 @@ function payloadString(payload, key) {
|
|
|
27
27
|
const value = payload[key];
|
|
28
28
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
29
29
|
}
|
|
30
|
+
function readBrowserCookie(name) {
|
|
31
|
+
if (typeof document === "undefined" || !document.cookie) return null;
|
|
32
|
+
for (const part of document.cookie.split(";")) {
|
|
33
|
+
const separator = part.indexOf("=");
|
|
34
|
+
if (separator < 0) continue;
|
|
35
|
+
if (part.slice(0, separator).trim() !== name) continue;
|
|
36
|
+
try {
|
|
37
|
+
return decodeURIComponent(part.slice(separator + 1).trim());
|
|
38
|
+
} catch {
|
|
39
|
+
return part.slice(separator + 1).trim();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function isCookieAuthMode() {
|
|
45
|
+
return typeof window !== "undefined" && window.__ACCESSI_AUTH_COOKIE__ === true;
|
|
46
|
+
}
|
|
47
|
+
var UNSAFE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
30
48
|
async function accessiFetch(url, options) {
|
|
31
49
|
const config = scopedConfig;
|
|
50
|
+
const cookieMode2 = isCookieAuthMode();
|
|
32
51
|
const headers = new Headers(options.headers);
|
|
33
52
|
headers.set("Accept", "application/json");
|
|
34
53
|
if (options.body !== void 0 && !headers.has("Content-Type")) {
|
|
35
54
|
headers.set("Content-Type", "application/json");
|
|
36
55
|
}
|
|
56
|
+
if (cookieMode2) {
|
|
57
|
+
const method = (options.method ?? "GET").toUpperCase();
|
|
58
|
+
if (UNSAFE_METHODS.has(method)) {
|
|
59
|
+
const csrfCookie = window.__ACCESSI_CSRF_COOKIE_NAME__ ?? "accessi_csrf";
|
|
60
|
+
const csrfHeader = window.__ACCESSI_CSRF_HEADER_NAME__ ?? "x-csrf-token";
|
|
61
|
+
const csrfToken = readBrowserCookie(csrfCookie);
|
|
62
|
+
if (csrfToken) headers.set(csrfHeader, csrfToken);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
37
65
|
const resolvedToken = config ? config.getToken ? await config.getToken() : config.token ?? null : tokenProvider ? await tokenProvider() : accessToken;
|
|
38
|
-
const
|
|
66
|
+
const isAuthEntryRequest = /\/(?:auth\/(?:login|refresh|two-factor\/(?:verify|resend))|console\/entry\/exchange)\b/.test(url);
|
|
67
|
+
const authenticatedRequest = !isAuthEntryRequest && (cookieMode2 || resolvedToken !== null && resolvedToken !== void 0);
|
|
39
68
|
if (resolvedToken) headers.set("Authorization", `Bearer ${resolvedToken}`);
|
|
40
69
|
const runtimeBaseUrl = config?.baseUrl !== void 0 ? config.baseUrl : baseUrl ?? (typeof window !== "undefined" ? window.__ACCESSI_API_BASE_URL__ ?? null : null);
|
|
41
70
|
const requestUrl = runtimeBaseUrl && url.startsWith("/") ? `${runtimeBaseUrl.replace(/\/+$/, "")}${url}` : url;
|
|
42
71
|
notifyNetworkActivity(true);
|
|
43
72
|
try {
|
|
44
|
-
const response = await fetch(requestUrl, {
|
|
73
|
+
const response = await fetch(requestUrl, {
|
|
74
|
+
...options,
|
|
75
|
+
headers,
|
|
76
|
+
...cookieMode2 ? { credentials: "include" } : {}
|
|
77
|
+
});
|
|
45
78
|
const payload = await response.json().catch(() => void 0);
|
|
46
79
|
if (!response.ok) {
|
|
47
80
|
const message = payloadString(payload, "message") ?? payloadString(payload, "error") ?? `Errore HTTP ${response.status}`;
|
|
@@ -78,6 +111,21 @@ var getUserByToken = async (getUserByTokenRequest, options) => {
|
|
|
78
111
|
}
|
|
79
112
|
);
|
|
80
113
|
};
|
|
114
|
+
var getRevokeTokenUrl = () => {
|
|
115
|
+
return `/api/accessi/auth/revoke-token`;
|
|
116
|
+
};
|
|
117
|
+
var revokeToken = async (revokeTokenRequest, options) => {
|
|
118
|
+
return accessiFetch(
|
|
119
|
+
getRevokeTokenUrl(),
|
|
120
|
+
{
|
|
121
|
+
...options,
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: JSON.stringify(
|
|
124
|
+
revokeTokenRequest
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
};
|
|
81
129
|
var getLoginUrl = () => {
|
|
82
130
|
return `/api/accessi/auth/login`;
|
|
83
131
|
};
|
|
@@ -1056,11 +1104,12 @@ var WIKI_SECTIONS = [
|
|
|
1056
1104
|
["passwordExpirationDays", "number", "no", "Valore predefinito: 90 giorni."],
|
|
1057
1105
|
["legacyPasswordMigrationOnStartup", "boolean", "no", "Predefinito true: converte le password legacy."],
|
|
1058
1106
|
["autoUpdateDatabase", "boolean", "no", "Predefinito true: riconcilia lo schema al boot."],
|
|
1059
|
-
["jwtOptions", "{ secret, expiresIn, refreshExpiresIn, algorithm, issuer, audience, privateKey, publicKey, keyId, jwksPath, previousPublicKeys }", "s\xEC", "Firma, durata, refresh token e
|
|
1107
|
+
["jwtOptions", "{ secret, expiresIn, refreshExpiresIn, algorithm, issuer, audience, privateKey, publicKey, keyId, jwksPath, previousPublicKeys, onUnknownKeyId }", "s\xEC", "Firma, durata, refresh token, JWKS e rotazione chiavi. Default HS256; RS/ES consigliati per resource server multi-servizio."],
|
|
1060
1108
|
["emailOptions", "EmailOptions", "s\xEC", "SMTP per reset e codici email."],
|
|
1061
1109
|
["publicAuthRateLimit", "PublicAuthRateLimitOptions", "no", "Limiti per endpoint pubblici; store condiviso opzionale (Redis) per il cluster."],
|
|
1062
1110
|
["edgeRateLimit", "AccessiEdgeRateLimitOptions", "no", "Limite grossolano per IP davanti alle rotte Accessi; opzionale, store condiviso supportato."],
|
|
1063
|
-
["http", "{ cors, trustProxy, securityHeaders }", "no", "CORS, IP reali dietro proxy e header di sicurezza
|
|
1111
|
+
["http", "{ cors, trustProxy, securityHeaders }", "no", "CORS, IP reali dietro proxy e header di sicurezza con CSP a nonce per la console e direttive configurabili."],
|
|
1112
|
+
["authCookie", "{ enabled, secure, sameSite, csrf, ... }", "no", "Cookie HttpOnly e CSRF double-submit al posto del Bearer. Default disattivato."],
|
|
1064
1113
|
["publicRegistration", "{ enabled }", "no", "Registrazione pubblica: disattivata per impostazione predefinita."],
|
|
1065
1114
|
["federatedAuthentication", "FederatedAuthenticationOptions", "no", `Abilita l'SSO generico.`],
|
|
1066
1115
|
["extensionFieldsOptions", "ExtensionFieldsOptions[]", "no", "Tabelle esterne allegate al profilo."],
|
|
@@ -2255,6 +2304,7 @@ var consoleMarkerIndex = window.location.pathname.indexOf(consoleMarker);
|
|
|
2255
2304
|
var consoleBasePath = consoleMarkerIndex >= 0 ? window.location.pathname.slice(0, consoleMarkerIndex + consoleMarker.length) : `${window.location.pathname.replace(/\/$/, "")}`;
|
|
2256
2305
|
var USERS_PAGE_SIZE = 25;
|
|
2257
2306
|
var token = sessionStorage.getItem("accessi-console-token");
|
|
2307
|
+
var cookieMode = typeof window !== "undefined" && window.__ACCESSI_AUTH_COOKIE__ === true;
|
|
2258
2308
|
var currentUser = null;
|
|
2259
2309
|
var federatedAuthenticationAvailable = false;
|
|
2260
2310
|
var usersPage = 0;
|
|
@@ -2708,18 +2758,22 @@ async function completeLoginStep(response) {
|
|
|
2708
2758
|
byId("login-submit").textContent = "Accedi";
|
|
2709
2759
|
return;
|
|
2710
2760
|
}
|
|
2711
|
-
if (
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2761
|
+
if (response.token?.value) {
|
|
2762
|
+
token = response.token.value;
|
|
2763
|
+
sessionStorage.setItem("accessi-console-token", token);
|
|
2764
|
+
setAccessiConsoleToken(token);
|
|
2765
|
+
} else if (!cookieMode) {
|
|
2766
|
+
throw new Error("Risposta di autenticazione incompleta.");
|
|
2767
|
+
}
|
|
2715
2768
|
resetLogin();
|
|
2716
2769
|
await bootstrap();
|
|
2717
2770
|
}
|
|
2718
2771
|
async function bootstrap() {
|
|
2772
|
+
const hadToken = token !== null;
|
|
2719
2773
|
try {
|
|
2720
2774
|
setAccessiConsoleToken(token);
|
|
2721
|
-
if (!token) throw new Error("Inserisci le credenziali amministrative.");
|
|
2722
|
-
const payload = result(await getUserByToken({ token }));
|
|
2775
|
+
if (!token && !cookieMode) throw new Error("Inserisci le credenziali amministrative.");
|
|
2776
|
+
const payload = result(await getUserByToken({ token: token ?? "" }));
|
|
2723
2777
|
currentUser = payload.userData;
|
|
2724
2778
|
requireConsoleAccess();
|
|
2725
2779
|
applyRoleNavigation();
|
|
@@ -2736,19 +2790,20 @@ async function bootstrap() {
|
|
|
2736
2790
|
federatedAuthenticationAvailable = false;
|
|
2737
2791
|
sessionStorage.removeItem("accessi-console-token");
|
|
2738
2792
|
finishBoot("login");
|
|
2739
|
-
byId("login-error").textContent = error instanceof Error ? error.message : "Sessione non valida.";
|
|
2793
|
+
byId("login-error").textContent = cookieMode && !hadToken ? "" : error instanceof Error ? error.message : "Sessione non valida.";
|
|
2740
2794
|
}
|
|
2741
2795
|
}
|
|
2742
2796
|
async function exchangeConsoleEntry(ticket) {
|
|
2743
2797
|
try {
|
|
2744
2798
|
const session = result(await exchangeConsoleEntryTicket({ ticket }));
|
|
2745
2799
|
const value = session.token?.value;
|
|
2746
|
-
if (typeof value
|
|
2800
|
+
if (typeof value === "string" && value !== "") {
|
|
2801
|
+
token = value;
|
|
2802
|
+
sessionStorage.setItem("accessi-console-token", value);
|
|
2803
|
+
setAccessiConsoleToken(value);
|
|
2804
|
+
} else if (!cookieMode) {
|
|
2747
2805
|
throw new Error("Apertura automatica della console non riuscita.");
|
|
2748
2806
|
}
|
|
2749
|
-
token = value;
|
|
2750
|
-
sessionStorage.setItem("accessi-console-token", value);
|
|
2751
|
-
setAccessiConsoleToken(value);
|
|
2752
2807
|
await bootstrap();
|
|
2753
2808
|
} catch (error) {
|
|
2754
2809
|
token = null;
|
|
@@ -4036,13 +4091,18 @@ byId("resend-code").onclick = async () => {
|
|
|
4036
4091
|
}
|
|
4037
4092
|
};
|
|
4038
4093
|
byId("logout").onclick = () => {
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4094
|
+
void (async () => {
|
|
4095
|
+
if (cookieMode) {
|
|
4096
|
+
await revokeToken({}).catch(() => void 0);
|
|
4097
|
+
}
|
|
4098
|
+
resetLogin();
|
|
4099
|
+
token = null;
|
|
4100
|
+
currentUser = null;
|
|
4101
|
+
setAccessiConsoleToken(null);
|
|
4102
|
+
sessionStorage.removeItem("accessi-console-token");
|
|
4103
|
+
window.history.replaceState(null, "", consoleBasePath);
|
|
4104
|
+
void bootstrap();
|
|
4105
|
+
})();
|
|
4046
4106
|
};
|
|
4047
4107
|
byId("nav").onclick = (event) => {
|
|
4048
4108
|
const link = event.target.closest("a[data-view]");
|
|
@@ -4054,7 +4114,7 @@ window.addEventListener("popstate", () => {
|
|
|
4054
4114
|
if (token && currentUser) handleAction(() => show(viewFromLocation()));
|
|
4055
4115
|
});
|
|
4056
4116
|
window.addEventListener(ACCESSI_UNAUTHORIZED_EVENT, () => {
|
|
4057
|
-
if (!token) return;
|
|
4117
|
+
if (!token && !cookieMode) return;
|
|
4058
4118
|
token = null;
|
|
4059
4119
|
currentUser = null;
|
|
4060
4120
|
setAccessiConsoleToken(null);
|
|
@@ -4102,7 +4162,7 @@ if (entryTicket) {
|
|
|
4102
4162
|
window.history.replaceState(null, "", window.location.pathname);
|
|
4103
4163
|
finishBoot("login");
|
|
4104
4164
|
showTwoFactor({ challengeId: ssoChallenge, resendAfterSeconds: 60 });
|
|
4105
|
-
} else if (token) {
|
|
4165
|
+
} else if (token || cookieMode) {
|
|
4106
4166
|
void bootstrap();
|
|
4107
4167
|
} else {
|
|
4108
4168
|
finishBoot("login");
|