@fluxo-dev/api-client-base 26.4.1 → 26.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxo-dev/api-client-base",
3
- "version": "26.4.1",
3
+ "version": "26.5.0",
4
4
  "type": "commonjs",
5
5
  "peerDependencies": {
6
6
  "react": "^18.3.1",
@@ -8,7 +8,7 @@
8
8
  "react-router-dom": "^6.11.2",
9
9
  "react-redux": "^8.0.5",
10
10
  "@reduxjs/toolkit": "^1.9.1",
11
- "@fluxo-dev/state": "^26.4.0"
11
+ "@fluxo-dev/state": "^26.5.0"
12
12
  },
13
13
  "main": "./index.cjs.js",
14
14
  "types": "./index.cjs.d.ts"
package/src/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export * from './lib/security/user';
13
13
  export * from './lib/security/apikey';
14
14
  export * from './lib/security/domain-role';
15
15
  export * from './lib/security/permission';
16
+ export * from './lib/security/policy';
16
17
  export * from './lib/auth';
17
18
  export * from './lib/communication';
18
19
  export * from './lib/korebi';
@@ -0,0 +1,73 @@
1
+ export interface ActivityTimeoutConfig {
2
+ /** Inattività dopo cui la sessione viene chiusa */
3
+ timeoutMs: number;
4
+ /** Anticipo con cui mostrare l'avviso */
5
+ warningMs: number;
6
+ /** Chiamato una sola volta allo scadere del timeout */
7
+ onTimeout: () => void;
8
+ }
9
+ export interface ActivitySnapshot {
10
+ /** Ultima attività dell'utente in una qualsiasi tab (ms epoch), 0 se il tracker non è attivo */
11
+ lastActivityAt: number;
12
+ /** Secondi alla chiusura per inattività mentre l'avviso è in corso, altrimenti null */
13
+ warningSecondsLeft: number | null;
14
+ }
15
+ /**
16
+ * Rileva l'attività dell'utente (mouse, tastiera, touch, scroll) condividendola tra le tab tramite localStorage:
17
+ * l'attività in una tab tiene viva la sessione in tutte. I tempi si misurano sull'orologio, non contando i tick
18
+ * dei timer, perché le tab in background li rallentano e il computer può andare in sospensione.
19
+ * Segnala periodicamente l'attività al server, che chiude le sessioni inattive anche a browser chiuso.
20
+ */
21
+ export declare class ActivityTracker {
22
+ private static _instance;
23
+ private running;
24
+ private localLastActivity;
25
+ private lastStorageWrite;
26
+ private pingInFlight;
27
+ private checkInterval;
28
+ private timeoutConfig;
29
+ private snapshot;
30
+ private listeners;
31
+ private readonly onUserActivity;
32
+ private readonly onVisibilityChange;
33
+ static get Instance(): ActivityTracker;
34
+ start(): void;
35
+ stop(): void;
36
+ /**
37
+ * Attiva (o disattiva con null) la chiusura per inattività. Senza configurazione il tracker continua
38
+ * comunque a rilevare l'attività e a segnalarla al server.
39
+ */
40
+ configureTimeout(config: ActivityTimeoutConfig | null): void;
41
+ /**
42
+ * Registra un'attività dell'utente. Con `immediate` (es. "Resta connesso", login) la condivide subito con le altre tab.
43
+ */
44
+ registerActivity(immediate?: boolean): void;
45
+ /**
46
+ * Tiene viva la sessione finché non si chiama la funzione restituita: registra subito un'attività
47
+ * e poi una ogni 30 secondi. Vale anche per il server, quindi va usato solo per operazioni avviate
48
+ * dall'utente in cui lui aspetta senza interagire (upload, elaborazioni lunghe), mai per tenere aperta una pagina.
49
+ * @returns la funzione che interrompe il keep-alive
50
+ */
51
+ keepAlive(): () => void;
52
+ /**
53
+ * Tiene viva la sessione finché l'operazione non termina, con successo o con errore.
54
+ * @example await ActivityTracker.Instance.keepAliveWhile(uploadService.upload(file));
55
+ */
56
+ keepAliveWhile<T>(operation: Promise<T>): Promise<T>;
57
+ /**
58
+ * Dimentica l'attività della sessione chiusa, così non conta per il login successivo.
59
+ */
60
+ clear(): void;
61
+ getSnapshot(): ActivitySnapshot;
62
+ /**
63
+ * L'utente è inattivo da almeno `thresholdMs` (false se il tracker non è attivo).
64
+ */
65
+ isIdle(thresholdMs: number): boolean;
66
+ subscribe(listener: () => void): () => void;
67
+ private check;
68
+ /**
69
+ * Segnala al server l'attività nuova, al massimo una volta al minuto per tutte le tab insieme.
70
+ */
71
+ private pingIfNeeded;
72
+ private setSnapshot;
73
+ }
@@ -1,4 +1,23 @@
1
1
  import { SecurityPolicyInfo } from '../security/user';
2
+ import { SessionEndReason } from './session-end-reason';
3
+ /**
4
+ * Motivo per cui il login chiede di impostare una nuova password prima di aprire la sessione.
5
+ */
6
+ export declare enum PasswordChangeReason {
7
+ Expired = 1,
8
+ RequiredByAdmin = 2
9
+ }
10
+ /**
11
+ * Esito di un passaggio del login. Se è richiesto un altro passaggio, `challengeToken` va passato
12
+ * a verify2FA (require2FA) o a completeRequiredPasswordChange (requirePasswordChange).
13
+ */
14
+ export interface LoginAttemptResult {
15
+ success: boolean;
16
+ require2FA: boolean;
17
+ requirePasswordChange: boolean;
18
+ passwordChangeReason: PasswordChangeReason | null;
19
+ challengeToken: string | null;
20
+ }
2
21
  export interface FluxoUserProfile {
3
22
  userId: string;
4
23
  firstname: string;
@@ -15,19 +34,37 @@ export interface FluxoUserProfile {
15
34
  canHandle2FA: boolean;
16
35
  canHandleAccessTokens: boolean;
17
36
  canDisable2FA: boolean;
37
+ /** Escluso dal logout per inattività */
38
+ idleTimeoutExempt: boolean;
39
+ /** Il logout per inattività si applica a questo utente: calcolato dal server, è il valore da seguire */
40
+ idleTimeoutApplies: boolean;
41
+ /** Scadenza della password (UTC), null se per l'utente non scade */
42
+ passwordExpiresAt: string | null;
18
43
  }
19
44
  export declare class AuthService {
20
- private isRefreshingToken;
45
+ private refreshInFlight;
21
46
  constructor();
22
47
  setup(): Promise<void>;
23
48
  getProfile(): Promise<FluxoUserProfile | null>;
24
49
  private tryAutoLogin;
25
- login(username: string, password: string): Promise<{
50
+ /**
51
+ * Primo passaggio del login. Se servono altri passaggi (2FA, nuova password) restituisce il challengeToken.
52
+ */
53
+ login(username: string, password: string): Promise<LoginAttemptResult>;
54
+ /**
55
+ * @param challengeToken restituito da login() quando require2FA è true (valido 5 minuti)
56
+ */
57
+ verify2FA(code: string, challengeToken: string): Promise<LoginAttemptResult>;
58
+ /**
59
+ * Imposta la nuova password quando il login la richiede (password scaduta o da cambiare) e apre la sessione.
60
+ * @param challengeToken restituito da login() o verify2FA() con requirePasswordChange (valido 5 minuti)
61
+ * @returns gli errori di validazione della nuova password, vuoti se il cambio è riuscito
62
+ */
63
+ completeRequiredPasswordChange(challengeToken: string, newPassword: string): Promise<{
26
64
  success: boolean;
27
- require2FA: boolean;
28
- userId: string;
65
+ errors: string[];
29
66
  }>;
30
- verify2FA(code: string, userId: string): Promise<boolean>;
67
+ private handleLoginResponse;
31
68
  enable2FA(userId: string): Promise<{
32
69
  qrCodeUri: string;
33
70
  sharedKey: string;
@@ -37,7 +74,21 @@ export declare class AuthService {
37
74
  }>;
38
75
  disable2FA(userId: string): Promise<boolean>;
39
76
  updateSecurityPolicyInfo(userId: string, info: SecurityPolicyInfo): Promise<void>;
40
- logout(): void;
77
+ /**
78
+ * @param reason motivo da mostrare nella pagina di login quando la chiusura non è stata chiesta dall'utente
79
+ */
80
+ logout(reason?: SessionEndReason): void;
81
+ /**
82
+ * Chiusura della sessione decisa dal server (revoca, inattività, password scaduta) o da un amministratore.
83
+ * Ignorata se la sessione è già chiusa, così più notifiche dello stesso evento non si sovrappongono.
84
+ */
85
+ endSession(reason: SessionEndReason): void;
86
+ /**
87
+ * Chiude la sessione solo in questa tab, senza chiamare il server né notificare le altre tab.
88
+ */
89
+ private clearLocalSession;
41
90
  refreshToken(): Promise<boolean>;
91
+ private executeRefreshToken;
92
+ private postRefreshToken;
42
93
  static getJwtTokenRemainingLifetime(): number | null;
43
94
  }
@@ -1 +1,3 @@
1
- export { FluxoUserProfile } from './auth-service';
1
+ export { FluxoUserProfile, PasswordChangeReason, LoginAttemptResult } from './auth-service';
2
+ export * from './session-end-reason';
3
+ export * from './activity-tracker';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Motivo per cui la sessione è stata chiusa senza un logout esplicito dell'utente.
3
+ * `session_*` e `password_expired` arrivano dal server (error_description dell'header WWW-Authenticate).
4
+ */
5
+ export declare const SessionEndReason: {
6
+ readonly Revoked: "session_revoked";
7
+ readonly Idle: "session_idle";
8
+ readonly PasswordExpired: "password_expired";
9
+ readonly ForcedLogout: "forced_logout";
10
+ };
11
+ export type SessionEndReason = typeof SessionEndReason[keyof typeof SessionEndReason];
12
+ /**
13
+ * Estrae il motivo dall'header WWW-Authenticate di una risposta 401
14
+ * (es. `Bearer error="invalid_token", error_description="session_revoked"`).
15
+ * Restituisce null per i 401 che non riguardano la sessione.
16
+ */
17
+ export declare function parseSessionEndReason(wwwAuthenticate: string | null | undefined): SessionEndReason | null;
18
+ /**
19
+ * Motivo dell'ultima chiusura di sessione, conservato nella tab finché la pagina di login non lo mostra.
20
+ */
21
+ export declare const LogoutReasonStore: {
22
+ set(reason: SessionEndReason): void;
23
+ consume(): SessionEndReason | null;
24
+ };
@@ -1,15 +1,17 @@
1
+ import { SessionEndReason } from "./session-end-reason";
1
2
  export declare class TabSyncAuthManager {
2
3
  private static _instance;
3
4
  private broadcastChannel;
4
5
  private tabId;
5
6
  private refreshTokenAction;
6
- private onErrorCallback;
7
+ private onLogoutCallback;
8
+ private refreshWaiters;
7
9
  private refreshTimeout;
8
10
  private heartbeatInterval;
9
11
  private lockCleanupInterval;
10
12
  private isDestroyed;
11
13
  private constructor();
12
- static Init(refreshAction: () => Promise<boolean>, onErrorCallback: () => void): void;
14
+ static Init(refreshAction: () => Promise<boolean>, onLogoutCallback: (reason?: SessionEndReason) => void): void;
13
15
  static get Instance(): TabSyncAuthManager;
14
16
  private generateTabId;
15
17
  private initBroadcastChannel;
@@ -18,12 +20,25 @@ export declare class TabSyncAuthManager {
18
20
  private handleTabMessage;
19
21
  private handleTokenRefreshed;
20
22
  private handleTokenRefreshFailed;
23
+ private handleLogout;
24
+ /**
25
+ * Notifica alle altre tab che la sessione è stata chiusa. Va chiamato solo dalla tab che esegue il logout:
26
+ * senza, le altre tab continuerebbero a rinnovare il token con il refresh token condiviso.
27
+ */
28
+ notifyLogout(reason?: SessionEndReason): void;
21
29
  private syncWithStoredToken;
22
30
  startRefreshTimer(timeoutMs: number): Promise<void>;
23
31
  stopRefreshTimer(): void;
24
32
  start(timeoutMs: number): void;
25
33
  stop(): void;
26
- private coordinatedRefresh;
34
+ /**
35
+ * Refresh coordinato tra le tab: lo esegue solo la tab che ottiene il lock. Le altre attendono l'esito
36
+ * invece di usare in parallelo lo stesso refresh token (il server lo ha già ruotato).
37
+ * Usato dal timer e all'avvio quando il token in localStorage è già scaduto.
38
+ */
39
+ refreshNow(): Promise<boolean>;
40
+ private waitForOtherTabRefresh;
41
+ private resolveRefreshWaiters;
27
42
  private acquireLock;
28
43
  private tryAcquireLockOnce;
29
44
  private releaseLock;
@@ -1,10 +1,16 @@
1
1
  import { AxiosInstance } from "axios";
2
2
  import { AuthenticationType } from "@fluxo-dev/state";
3
+ import { SessionEndReason } from "../auth/session-end-reason";
3
4
  export declare class HttpClient {
4
5
  private static _instance;
6
+ private static _sessionEndedHandler;
5
7
  private _axiosInstance;
6
8
  get client(): AxiosInstance;
7
9
  constructor(authenticationType: AuthenticationType, baseUrl: string);
10
+ /**
11
+ * Registra chi chiude la sessione quando il server rifiuta una richiesta perché la sessione non è più valida.
12
+ */
13
+ static onSessionEnded(handler: (reason: SessionEndReason) => void): void;
8
14
  static Instance(): HttpClient;
9
15
  static Init(authenticationType: AuthenticationType, baseUrl: string, apiKeyOrAccessToken: string): void;
10
16
  }
@@ -0,0 +1,2 @@
1
+ export * from './security-policy';
2
+ export * from './security-policy-service';
@@ -0,0 +1,28 @@
1
+ import { SecurityPolicy, SecurityPolicySettings, UpdateSecurityPolicySettings } from "./security-policy";
2
+ export declare class SecurityPolicyService {
3
+ private cachedPolicy;
4
+ private changeListeners;
5
+ /**
6
+ * Policy pubblica di sicurezza. Viene letta una sola volta e riusata da tutte le schermate:
7
+ * in caso di errore la cache si svuota e la richiesta successiva riprova.
8
+ */
9
+ getSecurityPolicy(): Promise<SecurityPolicy>;
10
+ /**
11
+ * Registra chi deve rileggere la policy quando cambia (es. il gestore dell'inattività).
12
+ * @returns la funzione per annullare la registrazione
13
+ */
14
+ onChanged(listener: () => void): () => void;
15
+ /**
16
+ * La policy (globale o le eccezioni dell'utente) è cambiata sul server: svuota la cache e avvisa i listener.
17
+ * Chiamato alla notifica `SecurityPolicyChanged` dell'hub di sessione.
18
+ */
19
+ notifyChanged(): void;
20
+ /**
21
+ * Policy completa (solo amministratori).
22
+ */
23
+ getSettings(): Promise<SecurityPolicySettings>;
24
+ /**
25
+ * Aggiorna la policy (solo amministratori). Le altre istanze del server la applicano entro 30 secondi.
26
+ */
27
+ updateSettings(settings: UpdateSecurityPolicySettings): Promise<SecurityPolicySettings>;
28
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Policy di sicurezza pubblica esposta da `GET /identity/security-policy` (leggibile anche senza login).
3
+ */
4
+ export interface SecurityPolicy {
5
+ password: PasswordPolicy;
6
+ session: SessionPolicy;
7
+ passwordExpiration: PasswordExpirationPolicy;
8
+ }
9
+ /**
10
+ * Criteri effettivi di ASP.NET Identity per le nuove password.
11
+ */
12
+ export interface PasswordPolicy {
13
+ requiredLength: number;
14
+ requiredUniqueChars: number;
15
+ requireDigit: boolean;
16
+ requireLowercase: boolean;
17
+ requireUppercase: boolean;
18
+ requireNonAlphanumeric: boolean;
19
+ /** Password precedenti non riutilizzabili (0 se lo storico è disattivato): verificabile solo dal server */
20
+ historySize: number;
21
+ }
22
+ export interface SessionPolicy {
23
+ idleTimeoutEnabled: boolean;
24
+ idleTimeoutMinutes: number;
25
+ idleWarningSeconds: number;
26
+ }
27
+ export declare enum PasswordExpirationScope {
28
+ UsersWithout2FA = 1,
29
+ AllUsers = 2
30
+ }
31
+ export interface PasswordExpirationPolicy {
32
+ enabled: boolean;
33
+ days: number;
34
+ scope: PasswordExpirationScope;
35
+ warningDays: number;
36
+ }
37
+ /**
38
+ * Policy completa modificabile dagli amministratori (`/identity/security-policy/settings`).
39
+ */
40
+ export interface UpdateSecurityPolicySettings {
41
+ idleTimeoutEnabled: boolean;
42
+ idleTimeoutMinutes: number;
43
+ idleWarningSeconds: number;
44
+ /** Applica il logout per inattività anche agli utenti esterni */
45
+ idleTimeoutIncludeExternalUsers: boolean;
46
+ passwordExpirationEnabled: boolean;
47
+ passwordExpirationDays: number;
48
+ passwordExpirationScope: PasswordExpirationScope;
49
+ passwordExpiryWarningDays: number;
50
+ /** Applica la scadenza della password anche agli utenti esterni */
51
+ passwordExpirationIncludeExternalUsers: boolean;
52
+ passwordHistoryEnabled: boolean;
53
+ passwordHistorySize: number;
54
+ lockoutEnabled: boolean;
55
+ maxFailedAccessAttempts: number;
56
+ lockoutMinutes: number;
57
+ }
58
+ export interface SecurityPolicySettings extends UpdateSecurityPolicySettings {
59
+ updatedAt: string | null;
60
+ updatedBy: string | null;
61
+ }
62
+ export interface PasswordRequirementCheck {
63
+ key: 'length' | 'lowercase' | 'uppercase' | 'digit' | 'nonAlphanumeric' | 'uniqueChars';
64
+ label: string;
65
+ satisfied: boolean;
66
+ }
67
+ export interface PasswordPolicyCheck {
68
+ isValid: boolean;
69
+ requirements: PasswordRequirementCheck[];
70
+ }
71
+ /**
72
+ * Verifica una password contro i criteri della policy, replicando le regole del server.
73
+ * Solo un aiuto per l'utente: il server resta l'unico a validare (anche con eventuali validator personalizzati).
74
+ */
75
+ export declare function checkPasswordPolicy(password: string, policy: PasswordPolicy): PasswordPolicyCheck;
@@ -2,6 +2,7 @@ import { UserService } from "./user/user-service";
2
2
  import { DomainRoleService } from "./domain-role";
3
3
  import { ApiKeyService } from "./apikey";
4
4
  import { PermissionService } from "./permission/permission.service";
5
+ import { SecurityPolicyService } from "./policy/security-policy-service";
5
6
  import { CurrentUser } from "@fluxo-dev/api-client-base";
6
7
  export declare class SecurityFacade {
7
8
  private readonly _userService;
@@ -9,7 +10,9 @@ export declare class SecurityFacade {
9
10
  private readonly _apiKeyService;
10
11
  private readonly _domainRoleService;
11
12
  private readonly _permissionService;
13
+ private readonly _policyService;
12
14
  constructor();
15
+ get policy(): SecurityPolicyService;
13
16
  get user(): UserService;
14
17
  get currentUser(): CurrentUser;
15
18
  get permission(): PermissionService;
@@ -6,6 +6,8 @@ export interface NewUser {
6
6
  firstPassword?: string;
7
7
  accessType?: NewUserAccessType;
8
8
  sendEmailWithCredentials: boolean;
9
+ /** Obbliga l'utente a scegliere una nuova password al primo accesso */
10
+ requirePasswordChange?: boolean;
9
11
  }
10
12
  export interface NewUserUser {
11
13
  firstName: string;
@@ -1,4 +1,6 @@
1
1
  export interface ResetPassword {
2
2
  userId: string;
3
3
  password?: string;
4
+ /** Obbliga l'utente a scegliere una nuova password al prossimo accesso */
5
+ requirePasswordChange?: boolean;
4
6
  }
@@ -18,6 +18,10 @@ export declare class UserService {
18
18
  forgotPassword(email: string): Promise<IdentityOperationResult>;
19
19
  forgotPasswordConfirm(forgotPassword: ForgotPassword): Promise<IdentityOperationResult>;
20
20
  disableUserLogin(userId: string): Promise<IdentityOperationResult>;
21
+ /**
22
+ * Revoca tutte le sessioni dell'utente (solo amministratori): dovrà rifare il login su ogni dispositivo.
23
+ */
24
+ revokeSessions(userId: string): Promise<IdentityOperationResult>;
21
25
  enableUserLogin(userId: string): Promise<IdentityOperationResult>;
22
26
  resetPassword(command: ResetPassword): Promise<IdentityOperationResult>;
23
27
  changeEmail(command: ChangeEmail): Promise<IdentityOperationResult>;
@@ -12,9 +12,24 @@ export interface User {
12
12
  lockoutEnd: string | null;
13
13
  lockoutEnabled: boolean;
14
14
  isLockedOut: boolean;
15
+ accessFailedCount: number;
16
+ lockoutReason: UserLockoutReason;
17
+ passwordChangedAt: string;
18
+ /** Deve scegliere una nuova password al prossimo accesso */
19
+ mustChangePassword: boolean;
20
+ /** Scadenza della password (UTC), null se per l'utente non scade */
21
+ passwordExpiresAt: string | null;
15
22
  twoFactorEnabled: boolean;
16
23
  securityPolicyInfo: SecurityPolicyInfo;
17
24
  }
25
+ /**
26
+ * Motivo del blocco: tentativi di accesso falliti (temporaneo) o accesso disattivato dall'amministratore.
27
+ */
28
+ export declare enum UserLockoutReason {
29
+ None = 0,
30
+ FailedAttempts = 1,
31
+ Disabled = 2
32
+ }
18
33
  export interface UserData {
19
34
  FirstName: string;
20
35
  LastName: string;
@@ -24,4 +39,8 @@ export interface SecurityPolicyInfo {
24
39
  canHandle2FA: boolean;
25
40
  canHandleAccessTokens: boolean;
26
41
  canDisable2FA: boolean;
42
+ /** Escluso dal logout per inattività (es. postazioni kiosk) */
43
+ idleTimeoutExempt: boolean;
44
+ /** Esclusa dalla scadenza periodica della password (es. account tecnici) */
45
+ passwordNeverExpires: boolean;
27
46
  }