Aller au contenu

PD-174 : Auto-lock Configurable + Effacement Cache

Vue d'ensemble

Cette fonctionnalité implémente un système de verrouillage automatique de l'application ProbatioVault après une période d'inactivité configurable, avec effacement automatique des données sensibles en mémoire.

Objectifs de sécurité

  • Protéger les données sensibles en cas d'abandon de l'appareil
  • Effacer les clés cryptographiques et mots de passe de la mémoire
  • Permettre un déverrouillage rapide par mot de passe ou biométrie
  • Offrir une expérience utilisateur configurable

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    AppNavigator.tsx                          │
│  ┌────────────────────────────────────────────────────────┐ │
│  │              useAutoLock() hook                         │ │
│  │  ┌─────────────┐    ┌──────────────┐                   │ │
│  │  │ Timer       │    │ AppState     │                   │ │
│  │  │ Inactivité  │    │ Listener     │                   │ │
│  │  └──────┬──────┘    └──────┬───────┘                   │ │
│  │         │                   │                           │ │
│  │         └───────┬───────────┘                           │ │
│  │                 ▼                                       │ │
│  │      ┌──────────────────┐                               │ │
│  │      │  performLock()   │                               │ │
│  │      └────────┬─────────┘                               │ │
│  │               ▼                                         │ │
│  │      ┌──────────────────┐                               │ │
│  │      │ clearSensitive   │                               │ │
│  │      │ Cache()          │                               │ │
│  │      └────────┬─────────┘                               │ │
│  │               ▼                                         │ │
│  │      ┌──────────────────┐                               │ │
│  │      │ useSecurityStore │                               │ │
│  │      │ .lock()          │                               │ │
│  │      └──────────────────┘                               │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌────────────────────────────────────────────────────────┐ │
│  │              LockScreen Overlay                         │ │
│  │     (affiché quand isLocked === true)                   │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Composants

1. Constants (src/constants/security.ts)

Définit les constantes de sécurité :

// Options de délai disponibles
export type AutoLockTimeout = 30 | 60 | 120 | 300 | 600; // secondes

// Configuration par défaut
export const SECURITY_DEFAULTS = {
  AUTO_LOCK_TIMEOUT: 60,      // 1 minute
  LOCK_ON_BACKGROUND: true,   // Verrouiller immédiatement en background
  AUTO_LOCK_ENABLED: true,    // Actif par défaut
  PIN_LENGTH: 6,
  MAX_FAILED_ATTEMPTS: 5,
  LOCKOUT_DURATION_MS: 300000, // 5 minutes
};

2. Security Store (src/store/useSecurityStore.ts)

Store Zustand persisté avec expo-secure-store :

interface SecurityState {
  autoLockEnabled: boolean;
  autoLockTimeout: AutoLockTimeout;
  lockOnBackground: boolean;
  isLocked: boolean;
  lastActivityTimestamp: number | null;

  // Actions
  setAutoLockEnabled(enabled: boolean): Promise<void>;
  setAutoLockTimeout(timeout: AutoLockTimeout): Promise<void>;
  setLockOnBackground(enabled: boolean): Promise<void>;
  lock(): void;
  unlock(): void;
  hydrate(): Promise<void>;
}

3. Cache Cleaner (src/services/cacheCleaner.ts)

Service d'effacement des données sensibles :

// Efface uniquement les données en mémoire (sur lock)
clearSensitiveCache(): Promise<ClearResult>
  - Mot de passe en mémoire (AuthStore)
  - Cache du vault (VaultStore)
  - Fichiers temporaires

// Efface tout (sur logout)
clearAllOnLogout(): Promise<ClearResult>
  - Tout ce qui précède
  - Mot de passe biométrique stocké
  - Clés cryptographiques persistées

// Recharge après déverrouillage
rehydrateVaultAfterUnlock(): Promise<RehydrateResult>

4. Auto-Lock Hook (src/hooks/useAutoLock.ts)

Hook principal orchestrant le verrouillage :

useAutoLock(options?: {
  disabled?: boolean;
  onBeforeLock?: () => void;
  onAfterLock?: () => void;
}): {
  isLocked: boolean;
  timeRemainingSeconds: number;
  handleTouch: () => void;      // Reset le timer
  handleScroll: () => void;     // Reset le timer
  resetTimer: () => void;
  lockNow: () => Promise<void>;
  unlock: () => void;
}

5. Composants UI

PinInput (src/components/security/PinInput.tsx)

  • Clavier numérique personnalisé
  • Animation shake sur erreur
  • Support biométrie optionnel
  • Accessibilité complète

AutoLockPicker (src/components/security/AutoLockPicker.tsx)

  • Sélection du délai d'inactivité
  • Liste des options avec indicateur de sélection

LockScreen (src/screens/security/LockScreen.tsx)

  • Écran de verrouillage complet
  • Saisie mot de passe
  • Bouton biométrie
  • Gestion du lockout après X tentatives

SecuritySettingsScreen (src/screens/profile/SecuritySettingsScreen.tsx)

  • Toggle auto-lock
  • Sélection du délai
  • Toggle lock on background
  • État biométrie

Flux de données

Verrouillage automatique

1. useAutoLock démarre un timer (setTimeout)
2. À chaque activité utilisateur (touch/scroll), le timer est reset
3. Si le timer expire :
   - clearSensitiveCache() est appelé
   - useSecurityStore.lock() est appelé
   - isLocked devient true
   - LockScreen est affiché en overlay

Verrouillage sur background

1. AppState change de "active" à "background"
2. Si lockOnBackground === true :
   - Verrouillage immédiat (même flux que ci-dessus)

Déverrouillage

1. Utilisateur saisit mot de passe / utilise biométrie
2. Vérification du mot de passe
3. Si OK :
   - useSecurityStore.unlock()
   - rehydrateVaultAfterUnlock()
   - Timer redémarre
4. Si KO :
   - Compteur tentatives++
   - Si max atteint → lockout temporaire

Configuration i18n

Trois nouveaux namespaces de traduction :

  • profile.json : Écran profil
  • security.json : Paramètres de sécurité
  • lock.json : Écran de verrouillage

Tests

Les tests unitaires couvrent :

  • useAutoLock.test.ts : Hook principal (27 tests - timer, AppState, callbacks, cache clearing)
  • useSecurityStore.test.ts : Store de sécurité
  • cacheCleaner.test.ts : Service d'effacement
  • PinInput.test.tsx : Composant de saisie PIN
  • AutoLockPicker.test.tsx : Sélection du délai

Nouvelles routes ajoutées à RootStackParamList :

Profile: undefined;
SecuritySettings: undefined;

Accessibles depuis HomeScreen → icône profil.

Considérations de sécurité

  1. Le mot de passe n'est JAMAIS persisté - Stocké uniquement en mémoire et effacé au lock
  2. Clés dérivées effacées - K_master, K_files, etc. sont purgées
  3. Données vault en cache effacées - Liste dossiers/documents purgée
  4. Fichiers temporaires supprimés - Cache filesystem nettoyé
  5. Pas de persistence de l'état verrouillé - Évite les attaques par manipulation du storage

Fichiers créés

src/
├── constants/
│   └── security.ts
├── store/
│   └── useSecurityStore.ts
├── services/
│   └── cacheCleaner.ts
├── hooks/
│   ├── useAutoLock.ts
│   └── useUserActivity.ts
├── components/
│   └── security/
│       ├── PinInput.tsx
│       └── AutoLockPicker.tsx
├── screens/
│   ├── profile/
│   │   ├── ProfileScreen.tsx
│   │   └── SecuritySettingsScreen.tsx
│   └── security/
│       └── LockScreen.tsx
└── i18n/
    └── locales/
        ├── fr/
        │   ├── profile.json
        │   ├── security.json
        │   └── lock.json
        └── en/
            ├── profile.json
            ├── security.json
            └── lock.json

__tests__/
├── store/
│   └── useSecurityStore.test.ts
├── components/
│   └── security/
│       └── PinInput.test.tsx
└── services/
    └── cacheCleaner.test.ts

Fichiers modifiés

  • src/navigation/AppNavigator.tsx : Intégration du lock overlay
  • src/i18n/index.ts : Ajout des namespaces profile, security, lock
  • src/i18n/locales/*/navigation.json : Ajout des clés profile/securitySettings

Utilisation

// Dans AppNavigator ou tout composant racine
function AppNavigator() {
  const { isLocked, handleTouch } = useAutoLock();
  const { hydrate, _hasHydrated } = useSecurityStore();

  useEffect(() => {
    if (!_hasHydrated) hydrate();
  }, []);

  return (
    <View onTouchStart={handleTouch}>
      <Stack.Navigator>
        {/* ... routes ... */}
      </Stack.Navigator>

      {isLocked && (
        <View style={StyleSheet.absoluteFillObject}>
          <LockScreen />
        </View>
      )}
    </View>
  );
}