emilsoftware-utilities 2.0.0-dev.69 → 2.0.0-dev.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/accessi/resource-server/runtime.js +51 -10
  2. package/dist/accessi/resource-server/runtime.js.map +1 -1
  3. package/dist/accessi/resource-server/types.d.ts +9 -2
  4. package/dist/accessi/resource-server/types.js.map +1 -1
  5. package/dist/accessi/server/standalone.js +3 -2
  6. package/dist/accessi/server/standalone.js.map +1 -1
  7. package/dist/accessi-module/AccessiModule.d.ts +4 -1
  8. package/dist/accessi-module/AccessiModule.js.map +1 -1
  9. package/dist/accessi-module/Console/console.js +5 -3
  10. package/dist/accessi-module/Console/generated/emilsoftware-utilities.d.ts +11 -1
  11. package/dist/accessi-module/Console/generated/emilsoftware-utilities.js +12 -4
  12. package/dist/accessi-module/Console/generated/emilsoftware-utilities.js.map +1 -1
  13. package/dist/accessi-module/Console/generated/model/index.d.ts +1 -0
  14. package/dist/accessi-module/Console/generated/model/index.js +1 -0
  15. package/dist/accessi-module/Console/generated/model/index.js.map +1 -1
  16. package/dist/accessi-module/Console/generated/model/revokeTokenRequest.d.ts +10 -0
  17. package/dist/accessi-module/Console/generated/model/revokeTokenRequest.js +9 -0
  18. package/dist/accessi-module/Console/generated/model/revokeTokenRequest.js.map +1 -0
  19. package/dist/accessi-module/Console/wiki.js +5 -3
  20. package/dist/accessi-module/Console/wiki.js.map +1 -1
  21. package/dist/accessi-module/Controllers/AuthController.d.ts +2 -1
  22. package/dist/accessi-module/Controllers/AuthController.js +63 -0
  23. package/dist/accessi-module/Controllers/AuthController.js.map +1 -1
  24. package/dist/accessi-module/Dtos/RevokeTokenRequest.d.ts +3 -0
  25. package/dist/accessi-module/Dtos/RevokeTokenRequest.js +27 -0
  26. package/dist/accessi-module/Dtos/RevokeTokenRequest.js.map +1 -0
  27. package/dist/accessi-module/Dtos/index.d.ts +1 -0
  28. package/dist/accessi-module/Dtos/index.js +1 -0
  29. package/dist/accessi-module/Dtos/index.js.map +1 -1
  30. package/dist/accessi-module/Services/AuthService/AuthService.d.ts +22 -2
  31. package/dist/accessi-module/Services/AuthService/AuthService.js +115 -19
  32. package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
  33. package/dist/accessi-module/Services/UserService/UserService.d.ts +6 -0
  34. package/dist/accessi-module/Services/UserService/UserService.js +61 -5
  35. package/dist/accessi-module/Services/UserService/UserService.js.map +1 -1
  36. package/dist/accessi-module/federated-auth/FederatedAuthService.js +5 -0
  37. package/dist/accessi-module/federated-auth/FederatedAuthService.js.map +1 -1
  38. package/dist/accessi-module/index.d.ts +5 -3
  39. package/dist/accessi-module/index.js +13 -2
  40. package/dist/accessi-module/index.js.map +1 -1
  41. package/dist/accessi-module/security/accessTokenRevocation.d.ts +47 -0
  42. package/dist/accessi-module/security/accessTokenRevocation.js +143 -0
  43. package/dist/accessi-module/security/accessTokenRevocation.js.map +1 -0
  44. package/dist/accessi-module/security/refreshTokenRotation.d.ts +42 -2
  45. package/dist/accessi-module/security/refreshTokenRotation.js +123 -10
  46. package/dist/accessi-module/security/refreshTokenRotation.js.map +1 -1
  47. package/dist/accessi-module/security/sessionRevocation.d.ts +21 -6
  48. package/dist/accessi-module/security/sessionRevocation.js +51 -5
  49. package/dist/accessi-module/security/sessionRevocation.js.map +1 -1
  50. package/dist/emilsoftware-utilities-2.0.0-dev.70.tgz +0 -0
  51. package/docs/accessi-openapi-fingerprint.json +2 -0
  52. package/package.json +3 -3
  53. package/dist/emilsoftware-utilities-2.0.0-dev.69.tgz +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"refreshTokenRotation.js","sourceRoot":"","sources":["../../../src/accessi-module/security/refreshTokenRotation.ts"],"names":[],"mappings":";;;;;;;;;;;AA+BA,sFAmBC;AAkBD,oFA6BC;AAID,oEAMC;AAGD,8EAEC;AAvFD,MAAM,cAAc,GAAG;;;;CAItB,CAAC;AAEF,SAAgB,qCAAqC;IACnD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,OAAO;QACC,OAAO,CAAC,GAAG,EAAE,WAAW;;gBAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC/B,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,GAAG,GAAG,EAAE,CAAC;oBAC7C,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;gBAC3B,OAAO,IAAI,CAAC;YACd,CAAC;SAAA;QACK,WAAW;;gBACf,OAAO,IAAI,CAAC;YACd,CAAC;SAAA;QACD,KAAK;YACH,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC;AAYD;;;;;GAKG;AACH,SAAgB,oCAAoC,CAClD,OAA8C;;IAE9C,IAAI,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,MAAM,CAAA,IAAI,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,MAAA,OAAO,CAAC,SAAS,mCAAI,uBAAuB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAErF,OAAO;QACC,OAAO,CAAC,GAAG,EAAE,WAAW;;gBAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;gBAChE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CACtC,cAAc,EACd,CAAC,EACD,GAAG,SAAS,GAAG,GAAG,EAAE,EACpB,KAAK,CACN,CAAC;gBACF,OAAO,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,CAAC;YACrE,CAAC;SAAA;QACK,WAAW;;gBACf,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE,CAAC,CAAC,CAAC;oBACzE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;gBACjD,CAAC;gBAAC,WAAM,CAAC;oBACP,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;SAAA;KACF,CAAC;AACJ,CAAC;AAED,MAAM,kBAAkB,GAAG,qCAAqC,EAAE,CAAC;AAEnE,SAAgB,4BAA4B,CAC1C,OAAuB;;IAEvB,IAAI,CAAA,MAAA,OAAO,CAAC,oBAAoB,0CAAE,OAAO,MAAK,KAAK;QAAE,OAAO,SAAS,CAAC;IACtE,IAAI,MAAA,OAAO,CAAC,oBAAoB,0CAAE,KAAK;QAAE,OAAO,OAAO,CAAC,oBAAoB,CAAC,KAAK,CAAC;IACnF,OAAO,CAAA,MAAA,OAAO,CAAC,oBAAoB,0CAAE,OAAO,MAAK,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;AACzF,CAAC;AAED,4DAA4D;AAC5D,SAAgB,iCAAiC;IAC/C,kBAAkB,CAAC,KAAK,EAAE,CAAC;AAC7B,CAAC","sourcesContent":["import type { AccessiOptions } from '../AccessiModule';\n\n/**\n * Store di rotazione refresh token.\n *\n * `consume` e atomico: restituisce true una sola volta per `jti`, poi il token\n * e considerato usato fino alla sua scadenza. In multi-istanza usare Redis.\n */\nexport interface RefreshTokenRotationStore {\n consume(jti: string, expiresAtMs: number): Promise<boolean>;\n /** Health check opzionale per readiness del servizio. */\n healthCheck?(): Promise<boolean>;\n}\n\nexport interface RefreshTokenRotationOptions {\n /** Default: disabilitata se non viene fornito uno store. */\n enabled?: boolean;\n /** Store condiviso (es. Redis) per il monouso tra istanze. */\n store?: RefreshTokenRotationStore;\n}\n\nexport interface MemoryRefreshTokenRotationStore extends RefreshTokenRotationStore {\n clear(): void;\n}\n\nconst CONSUME_SCRIPT = `\nlocal ttl = tonumber(ARGV[2])\nif not ttl or ttl <= 0 then return nil end\nreturn redis.call('SET', KEYS[1], '1', 'PX', ttl, 'NX')\n`;\n\nexport function createMemoryRefreshTokenRotationStore(): MemoryRefreshTokenRotationStore {\n const used = new Map<string, number>();\n return {\n async consume(jti, expiresAtMs) {\n const now = Date.now();\n const existing = used.get(jti);\n if (existing !== undefined && existing > now) {\n return false;\n }\n used.set(jti, expiresAtMs);\n return true;\n },\n async healthCheck() {\n return true;\n },\n clear() {\n used.clear();\n },\n };\n}\n\nexport interface RefreshTokenRotationRedisClient {\n eval(script: string, numKeys: number, ...args: Array<string | number>): Promise<unknown>;\n}\n\nexport interface RedisRefreshTokenRotationStoreOptions {\n client: RefreshTokenRotationRedisClient;\n /** Default: `accessi:refresh-used:`. */\n keyPrefix?: string;\n}\n\n/**\n * Store Redis per refresh token monouso.\n *\n * Usa `SET ... PX ... NX` atomico: due refresh concorrenti dello stesso token\n * producono un solo successo.\n */\nexport function createRedisRefreshTokenRotationStore(\n options: RedisRefreshTokenRotationStoreOptions,\n): RefreshTokenRotationStore {\n if (!options?.client || typeof options.client.eval !== 'function') {\n throw new Error('Redis refresh rotation store: client.eval non configurato.');\n }\n\n const keyPrefix = (options.keyPrefix ?? 'accessi:refresh-used:').replace(/:+$/, ':');\n\n return {\n async consume(jti, expiresAtMs) {\n const ttlMs = Math.max(1, Math.trunc(expiresAtMs - Date.now()));\n const result = await options.client.eval(\n CONSUME_SCRIPT,\n 1,\n `${keyPrefix}${jti}`,\n ttlMs,\n );\n return result !== null && result !== undefined && result !== false;\n },\n async healthCheck() {\n try {\n const result = await options.client.eval(\"return redis.call('PING')\", 0);\n return String(result).toUpperCase() === 'PONG';\n } catch {\n return false;\n }\n },\n };\n}\n\nconst defaultMemoryStore = createMemoryRefreshTokenRotationStore();\n\nexport function getRefreshTokenRotationStore(\n options: AccessiOptions,\n): RefreshTokenRotationStore | undefined {\n if (options.refreshTokenRotation?.enabled === false) return undefined;\n if (options.refreshTokenRotation?.store) return options.refreshTokenRotation.store;\n return options.refreshTokenRotation?.enabled === true ? defaultMemoryStore : undefined;\n}\n\n/** Solo per test: azzera lo store in memoria di default. */\nexport function clearDefaultRefreshTokenRotations(): void {\n defaultMemoryStore.clear();\n}\n"]}
1
+ {"version":3,"file":"refreshTokenRotation.js","sourceRoot":"","sources":["../../../src/accessi-module/security/refreshTokenRotation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccessiOptions } from '../AccessiModule';\n\n/**\n * Store di rotazione refresh token.\n *\n * `consume` e atomico: restituisce true una sola volta per `jti`, poi il token\n * e considerato usato fino alla sua scadenza. In multi-istanza usare Redis.\n */\nexport interface RefreshTokenRotationStore {\n consume(jti: string, expiresAtMs: number): Promise<boolean>;\n /**\n * Come `consume`, ma restituisce anche quando il token era gia stato\n * consumato: serve a distinguere un retry concorrente da un possibile furto.\n */\n consumeWithMetadata?(jti: string, expiresAtMs: number): Promise<RefreshTokenConsumeResult>;\n /** Revoca l'intera famiglia di refresh token (stesso login). */\n revokeFamily?(familyId: string, expiresAtMs: number): Promise<void>;\n /** True se la famiglia e stata revocata per riuso sospetto. */\n isFamilyRevoked?(familyId: string): Promise<boolean>;\n /** Health check opzionale per readiness del servizio. */\n healthCheck?(): Promise<boolean>;\n}\n\n/** Esito del consumo atomico di un refresh token. */\nexport interface RefreshTokenConsumeResult {\n consumed: boolean;\n /** Quando il token era gia stato consumato, il timestamp del primo consumo. */\n consumedAtMs?: number;\n}\n\nexport interface RefreshTokenRotationOptions {\n /** Default: disabilitata se non viene fornito uno store. */\n enabled?: boolean;\n /** Store condiviso (es. Redis) per il monouso tra istanze. */\n store?: RefreshTokenRotationStore;\n /**\n * Finestra entro cui il riuso di un token appena consumato e trattato come\n * retry concorrente (nessuna revoca famiglia). Default: 10000 ms.\n */\n reuseGraceMs?: number;\n /**\n * Se true, dopo un riuso rilevato revoca anche tutte le sessioni utente.\n * Default: false, per non disconnettere dispositivi legittimi.\n */\n revokeUserSessionsOnReuse?: boolean;\n}\n\nexport interface MemoryRefreshTokenRotationStore extends RefreshTokenRotationStore {\n clear(): void;\n}\n\nconst CONSUME_SCRIPT = `\nlocal ttl = tonumber(ARGV[2])\nif not ttl or ttl <= 0 then return nil end\nlocal stored = redis.call('GET', KEYS[1])\nif not stored then\n redis.call('SET', KEYS[1], ARGV[1], 'PX', ttl)\n return {1, 0}\nend\nreturn {0, tonumber(stored) or 0}\n`;\nconst REVOKE_FAMILY_SCRIPT =\n \"if tonumber(ARGV[1]) > 0 then return redis.call('SET', KEYS[1], '1', 'PX', ARGV[1]) else return redis.call('SET', KEYS[1], '1') end\";\nconst IS_FAMILY_REVOKED_SCRIPT = \"return redis.call('EXISTS', KEYS[1])\";\n\nfunction parseConsumeResult(result: unknown): RefreshTokenConsumeResult {\n if (Array.isArray(result)) {\n const consumed = Number(result[0]) === 1 || result[0] === true;\n const consumedAtMs = Number(result[1]);\n return {\n consumed,\n ...(Number.isFinite(consumedAtMs) && consumedAtMs > 0 ? { consumedAtMs } : {}),\n };\n }\n // Client minimali (o store precedenti) rispondono 'OK' / null.\n return { consumed: result !== null && result !== undefined && result !== false };\n}\n\nexport function createMemoryRefreshTokenRotationStore(): MemoryRefreshTokenRotationStore {\n const used = new Map<string, { expiresAtMs: number; consumedAtMs: number }>();\n const revokedFamilies = new Map<string, number>();\n\n const prune = (now: number) => {\n for (const [jti, entry] of used) {\n if (entry.expiresAtMs <= now) used.delete(jti);\n }\n for (const [familyId, expiresAt] of revokedFamilies) {\n if (expiresAt <= now) revokedFamilies.delete(familyId);\n }\n };\n\n const consumeWithMetadata = async (\n jti: string,\n expiresAtMs: number,\n ): Promise<RefreshTokenConsumeResult> => {\n const now = Date.now();\n prune(now);\n const existing = used.get(jti);\n if (existing && existing.expiresAtMs > now) {\n return { consumed: false, consumedAtMs: existing.consumedAtMs };\n }\n used.set(jti, { expiresAtMs, consumedAtMs: now });\n return { consumed: true };\n };\n\n return {\n async consume(jti, expiresAtMs) {\n return (await consumeWithMetadata(jti, expiresAtMs)).consumed;\n },\n consumeWithMetadata,\n async revokeFamily(familyId, expiresAtMs) {\n revokedFamilies.set(familyId, Math.max(expiresAtMs, Date.now() + 1));\n },\n async isFamilyRevoked(familyId) {\n const expiresAt = revokedFamilies.get(familyId);\n if (expiresAt === undefined) return false;\n if (expiresAt <= Date.now()) {\n revokedFamilies.delete(familyId);\n return false;\n }\n return true;\n },\n async healthCheck() {\n return true;\n },\n clear() {\n used.clear();\n revokedFamilies.clear();\n },\n };\n}\n\nexport interface RefreshTokenRotationRedisClient {\n eval(script: string, numKeys: number, ...args: Array<string | number>): Promise<unknown>;\n}\n\nexport interface RedisRefreshTokenRotationStoreOptions {\n client: RefreshTokenRotationRedisClient;\n /** Default: `accessi:refresh-used:`. */\n keyPrefix?: string;\n /** Default: `accessi:refresh-family-revoked:`. */\n familyKeyPrefix?: string;\n}\n\n/**\n * Store Redis per refresh token monouso.\n *\n * Il consumo e atomico: due refresh concorrenti dello stesso token producono\n * un solo successo. La famiglia viene revocata con una chiave separata che\n * scade insieme ai token che ne fanno parte.\n */\nexport function createRedisRefreshTokenRotationStore(\n options: RedisRefreshTokenRotationStoreOptions,\n): RefreshTokenRotationStore {\n if (!options?.client || typeof options.client.eval !== 'function') {\n throw new Error('Redis refresh rotation store: client.eval non configurato.');\n }\n\n const keyPrefix = (options.keyPrefix ?? 'accessi:refresh-used:').replace(/:+$/, ':');\n const familyKeyPrefix = (options.familyKeyPrefix ?? 'accessi:refresh-family-revoked:').replace(/:+$/, ':');\n\n return {\n async consume(jti, expiresAtMs) {\n const ttlMs = Math.max(1, Math.trunc(expiresAtMs - Date.now()));\n const result = await options.client.eval(\n CONSUME_SCRIPT,\n 1,\n `${keyPrefix}${jti}`,\n Date.now(),\n ttlMs,\n );\n return parseConsumeResult(result).consumed;\n },\n\n async consumeWithMetadata(jti, expiresAtMs) {\n const ttlMs = Math.max(1, Math.trunc(expiresAtMs - Date.now()));\n const result = await options.client.eval(\n CONSUME_SCRIPT,\n 1,\n `${keyPrefix}${jti}`,\n Date.now(),\n ttlMs,\n );\n return parseConsumeResult(result);\n },\n\n async revokeFamily(familyId, expiresAtMs) {\n const ttlMs = Math.max(0, Math.trunc(expiresAtMs - Date.now()));\n await options.client.eval(REVOKE_FAMILY_SCRIPT, 1, `${familyKeyPrefix}${familyId}`, ttlMs);\n },\n\n async isFamilyRevoked(familyId) {\n const result = await options.client.eval(IS_FAMILY_REVOKED_SCRIPT, 1, `${familyKeyPrefix}${familyId}`);\n return Number(result) > 0;\n },\n\n async healthCheck() {\n try {\n const result = await options.client.eval(\"return redis.call('PING')\", 0);\n return String(result).toUpperCase() === 'PONG';\n } catch {\n return false;\n }\n },\n };\n}\n\nconst defaultMemoryStore = createMemoryRefreshTokenRotationStore();\n\nexport function getRefreshTokenRotationStore(\n options: AccessiOptions,\n): RefreshTokenRotationStore | undefined {\n if (options.refreshTokenRotation?.enabled === false) return undefined;\n if (options.refreshTokenRotation?.store) return options.refreshTokenRotation.store;\n return options.refreshTokenRotation?.enabled === true ? defaultMemoryStore : undefined;\n}\n\n/**\n * True se la famiglia di refresh token e stata revocata. Ritorna false se lo\n * store non supporta le famiglie (implementazioni legacy).\n */\nexport async function isRefreshTokenFamilyRevoked(\n options: AccessiOptions,\n familyId: unknown,\n): Promise<boolean> {\n if (typeof familyId !== 'string' || familyId.trim() === '') return false;\n const store = getRefreshTokenRotationStore(options);\n if (!store?.isFamilyRevoked) return false;\n return store.isFamilyRevoked(familyId);\n}\n\n/**\n * Revoca la famiglia di refresh token fino alla scadenza indicata. Ritorna\n * false se lo store non supporta le famiglie.\n */\nexport async function revokeRefreshTokenFamily(\n options: AccessiOptions,\n familyId: string,\n expiresAtMs: number,\n): Promise<boolean> {\n const store = getRefreshTokenRotationStore(options);\n if (!store?.revokeFamily) return false;\n await store.revokeFamily(familyId, expiresAtMs);\n return true;\n}\n\n/** Consumo atomico con metadati, se lo store li supporta. */\nexport async function consumeRefreshToken(\n options: AccessiOptions,\n jti: string,\n expiresAtMs: number,\n): Promise<RefreshTokenConsumeResult | undefined> {\n const store = getRefreshTokenRotationStore(options);\n if (!store) return undefined;\n if (store.consumeWithMetadata) {\n return store.consumeWithMetadata(jti, expiresAtMs);\n }\n return { consumed: await store.consume(jti, expiresAtMs) };\n}\n\n/** Solo per test: azzera lo store in memoria di default. */\nexport function clearDefaultRefreshTokenRotations(): void {\n defaultMemoryStore.clear();\n}\n"]}
@@ -2,10 +2,11 @@ import type { AccessiOptions } from '../AccessiModule';
2
2
  /**
3
3
  * Revoca delle sessioni per utente.
4
4
  *
5
- * La revoca e un timestamp: un token con `iat` precedente al cutoff viene
6
- * rifiutato. I token emessi dopo il cutoff restano validi. La granularita di
7
- * `iat` e in secondi, quindi un token emesso nello stesso secondo della revoca
8
- * puo sopravvivere: e una finestra massima di 1 secondo.
5
+ * La revoca e un timestamp: un token emesso prima del cutoff viene rifiutato.
6
+ * I token emessi dopo il cutoff restano validi. I token includono `iatMs`
7
+ * (millisecondi) per rendere la revoca deterministica anche quando il cambio
8
+ * password e il login avvengono nello stesso secondo; per i token legacy senza
9
+ * `iatMs` si usa `iat` in secondi.
9
10
  */
10
11
  export interface SessionRevocationStore {
11
12
  revokeUser(codiceUtente: number, revokedAtMs: number): Promise<void>;
@@ -44,9 +45,23 @@ export interface RedisSessionRevocationStoreOptions {
44
45
  export declare function createRedisSessionRevocationStore(options: RedisSessionRevocationStoreOptions): SessionRevocationStore;
45
46
  /** Store di default in memoria, usato quando `sessionRevocation.store` non e configurato ma la revoca e abilitata. */
46
47
  export declare function getSessionRevocationStore(options: AccessiOptions): SessionRevocationStore | undefined;
47
- /** True se il token e stato emesso prima dell'ultima revoca sessioni dell'utente. */
48
+ /**
49
+ * Issued-at in millisecondi. I token emessi da questa versione includono `iatMs`;
50
+ * per quelli legacy si ricade su `iat` (secondi) per restare compatibili.
51
+ */
52
+ export declare function resolveAccessiTokenIssuedAtMs(payload: Record<string, unknown>): number | null;
53
+ /**
54
+ * True se il token e stato revocato: famiglia refresh revocata, `jti` revocato
55
+ * oppure sessione utente revocata prima dell'emissione del token.
56
+ */
48
57
  export declare function isAccessiTokenRevoked(options: AccessiOptions, payload: Record<string, unknown>, codiceUtente: number): Promise<boolean>;
49
58
  /** Revoca tutte le sessioni dell'utente. Richiede uno store configurato. */
50
- export declare function revokeAccessiUserSessions(options: AccessiOptions, codiceUtente: number, revokedAtMs?: number): Promise<void>;
59
+ export declare function revokeAccessiUserSessions(options: AccessiOptions, codiceUtente: number, revokedAtMs?: number, reason?: string): Promise<void>;
60
+ /**
61
+ * Variante per i flussi applicativi (cambio password, blocco, cancellazione):
62
+ * se la revoca non e configurata non fallisce, perche quei flussi devono
63
+ * continuare a funzionare anche senza store condiviso.
64
+ */
65
+ export declare function revokeAccessiUserSessionsIfConfigured(options: AccessiOptions, codiceUtente: number, reason?: string, revokedAtMs?: number): Promise<boolean>;
51
66
  /** Solo per test: azzera lo store in memoria di default. */
52
67
  export declare function clearDefaultSessionRevocations(): void;
@@ -12,9 +12,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.createMemorySessionRevocationStore = createMemorySessionRevocationStore;
13
13
  exports.createRedisSessionRevocationStore = createRedisSessionRevocationStore;
14
14
  exports.getSessionRevocationStore = getSessionRevocationStore;
15
+ exports.resolveAccessiTokenIssuedAtMs = resolveAccessiTokenIssuedAtMs;
15
16
  exports.isAccessiTokenRevoked = isAccessiTokenRevoked;
16
17
  exports.revokeAccessiUserSessions = revokeAccessiUserSessions;
18
+ exports.revokeAccessiUserSessionsIfConfigured = revokeAccessiUserSessionsIfConfigured;
17
19
  exports.clearDefaultSessionRevocations = clearDefaultSessionRevocations;
20
+ const audit_1 = require("./audit");
21
+ const accessTokenRevocation_1 = require("./accessTokenRevocation");
22
+ const refreshTokenRotation_1 = require("./refreshTokenRotation");
18
23
  const REVOKE_USER_SCRIPT = "if tonumber(ARGV[2]) > 0 then return redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2]) else return redis.call('SET', KEYS[1], ARGV[1]) end";
19
24
  const GET_USER_SCRIPT = "return redis.call('GET', KEYS[1])";
20
25
  function createMemorySessionRevocationStore() {
@@ -92,29 +97,70 @@ function getSessionRevocationStore(options) {
92
97
  return options.sessionRevocation.store;
93
98
  return ((_c = options.sessionRevocation) === null || _c === void 0 ? void 0 : _c.enabled) === true ? defaultMemoryStore : undefined;
94
99
  }
95
- /** True se il token e stato emesso prima dell'ultima revoca sessioni dell'utente. */
100
+ /**
101
+ * Issued-at in millisecondi. I token emessi da questa versione includono `iatMs`;
102
+ * per quelli legacy si ricade su `iat` (secondi) per restare compatibili.
103
+ */
104
+ function resolveAccessiTokenIssuedAtMs(payload) {
105
+ const issuedAtMs = Number(payload.iatMs);
106
+ if (Number.isFinite(issuedAtMs) && issuedAtMs > 0)
107
+ return issuedAtMs;
108
+ const issuedAt = Number(payload.iat);
109
+ if (!Number.isFinite(issuedAt))
110
+ return null;
111
+ return issuedAt * 1000;
112
+ }
113
+ /**
114
+ * True se il token e stato revocato: famiglia refresh revocata, `jti` revocato
115
+ * oppure sessione utente revocata prima dell'emissione del token.
116
+ */
96
117
  function isAccessiTokenRevoked(options, payload, codiceUtente) {
97
118
  return __awaiter(this, void 0, void 0, function* () {
119
+ if (yield (0, refreshTokenRotation_1.isRefreshTokenFamilyRevoked)(options, payload.fid))
120
+ return true;
121
+ if (yield (0, accessTokenRevocation_1.isAccessiAccessTokenRevoked)(options, payload))
122
+ return true;
98
123
  const store = getSessionRevocationStore(options);
99
124
  if (!store)
100
125
  return false;
101
- const issuedAt = Number(payload.iat);
102
- if (!Number.isFinite(issuedAt))
126
+ const issuedAtMs = resolveAccessiTokenIssuedAtMs(payload);
127
+ if (issuedAtMs === null)
103
128
  return true;
104
129
  const revokedAt = yield store.getUserRevocation(codiceUtente);
105
130
  if (revokedAt === null)
106
131
  return false;
107
- return issuedAt < Math.floor(revokedAt / 1000);
132
+ // I token emessi da questa versione hanno `iatMs`: un token emesso nello
133
+ // stesso millisecondo della revoca e considerato revocato. Per i token
134
+ // legacy (solo `iat`, secondi) si mantiene il confronto per secondo.
135
+ const preciseIssuedAtMs = Number(payload.iatMs);
136
+ if (Number.isFinite(preciseIssuedAtMs) && preciseIssuedAtMs > 0) {
137
+ return issuedAtMs <= revokedAt;
138
+ }
139
+ return issuedAtMs < Math.floor(revokedAt / 1000) * 1000;
108
140
  });
109
141
  }
110
142
  /** Revoca tutte le sessioni dell'utente. Richiede uno store configurato. */
111
143
  function revokeAccessiUserSessions(options_1, codiceUtente_1) {
112
- return __awaiter(this, arguments, void 0, function* (options, codiceUtente, revokedAtMs = Date.now()) {
144
+ return __awaiter(this, arguments, void 0, function* (options, codiceUtente, revokedAtMs = Date.now(), reason = 'manual') {
113
145
  const store = getSessionRevocationStore(options);
114
146
  if (!store) {
115
147
  throw new Error('Session revocation store non configurato.');
116
148
  }
117
149
  yield store.revokeUser(codiceUtente, revokedAtMs);
150
+ (0, audit_1.auditAccessi)('accessi_sessions_revoked', { codiceUtente, revokedAtMs, reason });
151
+ });
152
+ }
153
+ /**
154
+ * Variante per i flussi applicativi (cambio password, blocco, cancellazione):
155
+ * se la revoca non e configurata non fallisce, perche quei flussi devono
156
+ * continuare a funzionare anche senza store condiviso.
157
+ */
158
+ function revokeAccessiUserSessionsIfConfigured(options_1, codiceUtente_1) {
159
+ return __awaiter(this, arguments, void 0, function* (options, codiceUtente, reason = 'manual', revokedAtMs = Date.now()) {
160
+ if (!getSessionRevocationStore(options))
161
+ return false;
162
+ yield revokeAccessiUserSessions(options, codiceUtente, revokedAtMs, reason);
163
+ return true;
118
164
  });
119
165
  }
120
166
  /** Solo per test: azzera lo store in memoria di default. */
@@ -1 +1 @@
1
- {"version":3,"file":"sessionRevocation.js","sourceRoot":"","sources":["../../../src/accessi-module/security/sessionRevocation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccessiOptions } from '../AccessiModule';\n\n/**\n * Revoca delle sessioni per utente.\n *\n * La revoca e un timestamp: un token con `iat` precedente al cutoff viene\n * rifiutato. I token emessi dopo il cutoff restano validi. La granularita di\n * `iat` e in secondi, quindi un token emesso nello stesso secondo della revoca\n * puo sopravvivere: e una finestra massima di 1 secondo.\n */\nexport interface SessionRevocationStore {\n revokeUser(codiceUtente: number, revokedAtMs: number): Promise<void>;\n getUserRevocation(codiceUtente: number): Promise<number | null>;\n /** Health check opzionale per readiness del servizio. */\n healthCheck?(): Promise<boolean>;\n}\n\nexport interface SessionRevocationOptions {\n /** Default: disabilitata se non viene fornito uno store. */\n enabled?: boolean;\n /** Store condiviso (es. Redis) per invalidare le sessioni tra istanze. */\n store?: SessionRevocationStore;\n /** TTL delle chiavi Redis. Default: 365 giorni. */\n ttlMs?: number;\n}\n\nexport interface MemorySessionRevocationStore extends SessionRevocationStore {\n clear(): void;\n}\n\nconst REVOKE_USER_SCRIPT =\n \"if tonumber(ARGV[2]) > 0 then return redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2]) else return redis.call('SET', KEYS[1], ARGV[1]) end\";\nconst GET_USER_SCRIPT = \"return redis.call('GET', KEYS[1])\";\n\nexport function createMemorySessionRevocationStore(): MemorySessionRevocationStore {\n const revokedAt = new Map<number, number>();\n return {\n async revokeUser(codiceUtente, timestamp) {\n revokedAt.set(codiceUtente, timestamp);\n },\n async getUserRevocation(codiceUtente) {\n return revokedAt.get(codiceUtente) ?? null;\n },\n async healthCheck() {\n return true;\n },\n clear() {\n revokedAt.clear();\n },\n };\n}\n\nexport interface SessionRevocationRedisClient {\n eval(script: string, numKeys: number, ...args: Array<string | number>): Promise<unknown>;\n}\n\nexport interface RedisSessionRevocationStoreOptions {\n client: SessionRevocationRedisClient;\n /** Default: `accessi:revoked-user:`. */\n keyPrefix?: string;\n /** Default: 365 giorni. */\n ttlMs?: number;\n}\n\n/**\n * Store Redis per la revoca sessioni.\n *\n * `revokeUser` scrive un timestamp con TTL; `getUserRevocation` lo legge.\n * Il TTL deve essere maggiore della durata massima dei token.\n */\nexport function createRedisSessionRevocationStore(\n options: RedisSessionRevocationStoreOptions,\n): SessionRevocationStore {\n if (!options?.client || typeof options.client.eval !== 'function') {\n throw new Error('Redis session revocation store: client.eval non configurato.');\n }\n\n const keyPrefix = (options.keyPrefix ?? 'accessi:revoked-user:').replace(/:+$/, ':');\n const ttlMs = Math.max(0, Math.trunc(options.ttlMs ?? 365 * 24 * 60 * 60 * 1000));\n\n return {\n async revokeUser(codiceUtente, revokedAtMs) {\n await options.client.eval(\n REVOKE_USER_SCRIPT,\n 1,\n `${keyPrefix}${codiceUtente}`,\n String(revokedAtMs),\n ttlMs,\n );\n },\n\n async getUserRevocation(codiceUtente) {\n const value = await options.client.eval(\n GET_USER_SCRIPT,\n 1,\n `${keyPrefix}${codiceUtente}`,\n );\n if (typeof value !== 'string' && typeof value !== 'number') return null;\n const timestamp = Number(value);\n return Number.isFinite(timestamp) ? timestamp : null;\n },\n\n async healthCheck() {\n try {\n const result = await options.client.eval(\"return redis.call('PING')\", 0);\n return String(result).toUpperCase() === 'PONG';\n } catch {\n return false;\n }\n },\n };\n}\n\nconst defaultMemoryStore = createMemorySessionRevocationStore();\n\n/** Store di default in memoria, usato quando `sessionRevocation.store` non e configurato ma la revoca e abilitata. */\nexport function getSessionRevocationStore(options: AccessiOptions): SessionRevocationStore | undefined {\n if (options.sessionRevocation?.enabled === false) return undefined;\n if (options.sessionRevocation?.store) return options.sessionRevocation.store;\n return options.sessionRevocation?.enabled === true ? defaultMemoryStore : undefined;\n}\n\n/** True se il token e stato emesso prima dell'ultima revoca sessioni dell'utente. */\nexport async function isAccessiTokenRevoked(\n options: AccessiOptions,\n payload: Record<string, unknown>,\n codiceUtente: number,\n): Promise<boolean> {\n const store = getSessionRevocationStore(options);\n if (!store) return false;\n\n const issuedAt = Number(payload.iat);\n if (!Number.isFinite(issuedAt)) return true;\n\n const revokedAt = await store.getUserRevocation(codiceUtente);\n if (revokedAt === null) return false;\n\n return issuedAt < Math.floor(revokedAt / 1000);\n}\n\n/** Revoca tutte le sessioni dell'utente. Richiede uno store configurato. */\nexport async function revokeAccessiUserSessions(\n options: AccessiOptions,\n codiceUtente: number,\n revokedAtMs: number = Date.now(),\n): Promise<void> {\n const store = getSessionRevocationStore(options);\n if (!store) {\n throw new Error('Session revocation store non configurato.');\n }\n await store.revokeUser(codiceUtente, revokedAtMs);\n}\n\n/** Solo per test: azzera lo store in memoria di default. */\nexport function clearDefaultSessionRevocations(): void {\n defaultMemoryStore.clear();\n}\n"]}
1
+ {"version":3,"file":"sessionRevocation.js","sourceRoot":"","sources":["../../../src/accessi-module/security/sessionRevocation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccessiOptions } from '../AccessiModule';\nimport { auditAccessi } from './audit';\nimport { isAccessiAccessTokenRevoked } from './accessTokenRevocation';\nimport { isRefreshTokenFamilyRevoked } from './refreshTokenRotation';\n\n/**\n * Revoca delle sessioni per utente.\n *\n * La revoca e un timestamp: un token emesso prima del cutoff viene rifiutato.\n * I token emessi dopo il cutoff restano validi. I token includono `iatMs`\n * (millisecondi) per rendere la revoca deterministica anche quando il cambio\n * password e il login avvengono nello stesso secondo; per i token legacy senza\n * `iatMs` si usa `iat` in secondi.\n */\nexport interface SessionRevocationStore {\n revokeUser(codiceUtente: number, revokedAtMs: number): Promise<void>;\n getUserRevocation(codiceUtente: number): Promise<number | null>;\n /** Health check opzionale per readiness del servizio. */\n healthCheck?(): Promise<boolean>;\n}\n\nexport interface SessionRevocationOptions {\n /** Default: disabilitata se non viene fornito uno store. */\n enabled?: boolean;\n /** Store condiviso (es. Redis) per invalidare le sessioni tra istanze. */\n store?: SessionRevocationStore;\n /** TTL delle chiavi Redis. Default: 365 giorni. */\n ttlMs?: number;\n}\n\nexport interface MemorySessionRevocationStore extends SessionRevocationStore {\n clear(): void;\n}\n\nconst REVOKE_USER_SCRIPT =\n \"if tonumber(ARGV[2]) > 0 then return redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2]) else return redis.call('SET', KEYS[1], ARGV[1]) end\";\nconst GET_USER_SCRIPT = \"return redis.call('GET', KEYS[1])\";\n\nexport function createMemorySessionRevocationStore(): MemorySessionRevocationStore {\n const revokedAt = new Map<number, number>();\n return {\n async revokeUser(codiceUtente, timestamp) {\n revokedAt.set(codiceUtente, timestamp);\n },\n async getUserRevocation(codiceUtente) {\n return revokedAt.get(codiceUtente) ?? null;\n },\n async healthCheck() {\n return true;\n },\n clear() {\n revokedAt.clear();\n },\n };\n}\n\nexport interface SessionRevocationRedisClient {\n eval(script: string, numKeys: number, ...args: Array<string | number>): Promise<unknown>;\n}\n\nexport interface RedisSessionRevocationStoreOptions {\n client: SessionRevocationRedisClient;\n /** Default: `accessi:revoked-user:`. */\n keyPrefix?: string;\n /** Default: 365 giorni. */\n ttlMs?: number;\n}\n\n/**\n * Store Redis per la revoca sessioni.\n *\n * `revokeUser` scrive un timestamp con TTL; `getUserRevocation` lo legge.\n * Il TTL deve essere maggiore della durata massima dei token.\n */\nexport function createRedisSessionRevocationStore(\n options: RedisSessionRevocationStoreOptions,\n): SessionRevocationStore {\n if (!options?.client || typeof options.client.eval !== 'function') {\n throw new Error('Redis session revocation store: client.eval non configurato.');\n }\n\n const keyPrefix = (options.keyPrefix ?? 'accessi:revoked-user:').replace(/:+$/, ':');\n const ttlMs = Math.max(0, Math.trunc(options.ttlMs ?? 365 * 24 * 60 * 60 * 1000));\n\n return {\n async revokeUser(codiceUtente, revokedAtMs) {\n await options.client.eval(\n REVOKE_USER_SCRIPT,\n 1,\n `${keyPrefix}${codiceUtente}`,\n String(revokedAtMs),\n ttlMs,\n );\n },\n\n async getUserRevocation(codiceUtente) {\n const value = await options.client.eval(\n GET_USER_SCRIPT,\n 1,\n `${keyPrefix}${codiceUtente}`,\n );\n if (typeof value !== 'string' && typeof value !== 'number') return null;\n const timestamp = Number(value);\n return Number.isFinite(timestamp) ? timestamp : null;\n },\n\n async healthCheck() {\n try {\n const result = await options.client.eval(\"return redis.call('PING')\", 0);\n return String(result).toUpperCase() === 'PONG';\n } catch {\n return false;\n }\n },\n };\n}\n\nconst defaultMemoryStore = createMemorySessionRevocationStore();\n\n/** Store di default in memoria, usato quando `sessionRevocation.store` non e configurato ma la revoca e abilitata. */\nexport function getSessionRevocationStore(options: AccessiOptions): SessionRevocationStore | undefined {\n if (options.sessionRevocation?.enabled === false) return undefined;\n if (options.sessionRevocation?.store) return options.sessionRevocation.store;\n return options.sessionRevocation?.enabled === true ? defaultMemoryStore : undefined;\n}\n\n/**\n * Issued-at in millisecondi. I token emessi da questa versione includono `iatMs`;\n * per quelli legacy si ricade su `iat` (secondi) per restare compatibili.\n */\nexport function resolveAccessiTokenIssuedAtMs(payload: Record<string, unknown>): number | null {\n const issuedAtMs = Number(payload.iatMs);\n if (Number.isFinite(issuedAtMs) && issuedAtMs > 0) return issuedAtMs;\n\n const issuedAt = Number(payload.iat);\n if (!Number.isFinite(issuedAt)) return null;\n return issuedAt * 1000;\n}\n\n/**\n * True se il token e stato revocato: famiglia refresh revocata, `jti` revocato\n * oppure sessione utente revocata prima dell'emissione del token.\n */\nexport async function isAccessiTokenRevoked(\n options: AccessiOptions,\n payload: Record<string, unknown>,\n codiceUtente: number,\n): Promise<boolean> {\n if (await isRefreshTokenFamilyRevoked(options, payload.fid)) return true;\n if (await isAccessiAccessTokenRevoked(options, payload)) return true;\n\n const store = getSessionRevocationStore(options);\n if (!store) return false;\n\n const issuedAtMs = resolveAccessiTokenIssuedAtMs(payload);\n if (issuedAtMs === null) return true;\n\n const revokedAt = await store.getUserRevocation(codiceUtente);\n if (revokedAt === null) return false;\n\n // I token emessi da questa versione hanno `iatMs`: un token emesso nello\n // stesso millisecondo della revoca e considerato revocato. Per i token\n // legacy (solo `iat`, secondi) si mantiene il confronto per secondo.\n const preciseIssuedAtMs = Number(payload.iatMs);\n if (Number.isFinite(preciseIssuedAtMs) && preciseIssuedAtMs > 0) {\n return issuedAtMs <= revokedAt;\n }\n return issuedAtMs < Math.floor(revokedAt / 1000) * 1000;\n}\n\n/** Revoca tutte le sessioni dell'utente. Richiede uno store configurato. */\nexport async function revokeAccessiUserSessions(\n options: AccessiOptions,\n codiceUtente: number,\n revokedAtMs: number = Date.now(),\n reason: string = 'manual',\n): Promise<void> {\n const store = getSessionRevocationStore(options);\n if (!store) {\n throw new Error('Session revocation store non configurato.');\n }\n await store.revokeUser(codiceUtente, revokedAtMs);\n auditAccessi('accessi_sessions_revoked', { codiceUtente, revokedAtMs, reason });\n}\n\n/**\n * Variante per i flussi applicativi (cambio password, blocco, cancellazione):\n * se la revoca non e configurata non fallisce, perche quei flussi devono\n * continuare a funzionare anche senza store condiviso.\n */\nexport async function revokeAccessiUserSessionsIfConfigured(\n options: AccessiOptions,\n codiceUtente: number,\n reason: string = 'manual',\n revokedAtMs: number = Date.now(),\n): Promise<boolean> {\n if (!getSessionRevocationStore(options)) return false;\n await revokeAccessiUserSessions(options, codiceUtente, revokedAtMs, reason);\n return true;\n}\n\n/** Solo per test: azzera lo store in memoria di default. */\nexport function clearDefaultSessionRevocations(): void {\n defaultMemoryStore.clear();\n}\n"]}
@@ -42,6 +42,7 @@
42
42
  "POST /api/accessi/auth/login login",
43
43
  "POST /api/accessi/auth/refresh refreshAccessToken",
44
44
  "POST /api/accessi/auth/revoke-sessions revokeSessions",
45
+ "POST /api/accessi/auth/revoke-token revokeToken",
45
46
  "POST /api/accessi/auth/two-factor/resend resendTwoFactor",
46
47
  "POST /api/accessi/auth/two-factor/verify verifyTwoFactor",
47
48
  "POST /api/accessi/console/entry createConsoleEntryTicket",
@@ -155,6 +156,7 @@
155
156
  "RegisterRequest",
156
157
  "RegisterResponse",
157
158
  "ResendTwoFactorRequest",
159
+ "RevokeTokenRequest",
158
160
  "Role",
159
161
  "RuoliMenu",
160
162
  "SchemaDdlResult",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emilsoftware-utilities",
3
- "version": "2.0.0-dev.69",
3
+ "version": "2.0.0-dev.70",
4
4
  "description": "Utilities for EmilSoftware",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -106,8 +106,8 @@
106
106
  "typecheck": "tsc --noEmit",
107
107
  "lint": "node scripts/lint.cjs",
108
108
  "test": "npm run typecheck && npm run lint && npm run test:unit",
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",
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 test/token-revocation.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 test/token-revocation.cjs",
111
111
  "test:accessi": "npm run test:unit",
112
112
  "test:all": "npm test && npm run test:integration",
113
113
  "test:integration": "node test/integration/run-firebird.cjs 5",