# Base de données persistante en localStorage

Lors de l'étape précédente, nous avions ajouté une étape de pré-dérivation du mot de passe sur la méthode d'authentification pour assurer la sécurité de la protection par mot de passe de l'identité Seald d'un utilisateur.

Nous sommes toujours confrontés au problème que quand un utilisateur ouvre un nouvel onglet de l'application, bien qu'ayant une session authentifié, l'utilisateur doit retaper son mot de passe pour récupérer son identité Seald.

Lors de cette étape, nous ajouterons une mise en cache de l'identité en localStorage pour que l'utilisateur puisse ouvrir un nouvel onglet.

La branche sur laquelle est basée cette étape est 2-pre-derivation (opens new window) , le résultat final est 3-localstorage (opens new window) .

# Explication

Dans le mécanisme implémenté dans les étapes précédentes, l'identité Seald d'un utilisateur est récupérée depuis SSKS puis déchiffrée à l'aide du mot de passe de l'utilisateur. Ainsi, cette identité n'est conservée qu'en mémoire côté client.

Il est pourtant souhaitable que quand un utilisateur actualise la page, ou ouvre un nouvel onglet, il ait accès à son identité Seald sans retaper de mot de passe.

La solution que nous préconisons est de mettre la base de donnée en cache dans le navigateur dans le localStorage, et par mesure de précaution, nous préconisons de protéger cette base de données en localStorage par une clé stockée en backend appelée databaseKey.

TIP

La base de donnée de Seald utilise nedb (opens new window), qui dans le navigateur, n'utilise pas forcément le localStorage, mais choisit (via localForage (opens new window)) la meilleure méthode de stockage entre IndexedDB, WebSQL ou localStorage selon votre navigateur.

uml diagram

Lorsque l'utilisateur va rouvrir l'application, la databaseKey sera récupérée depuis le backend via une session authentifiée, et cette clé sera utilisée pour déchiffrer l'encryptedDB.

uml diagram

Tout cela se fait sans aucune interaction utilisateur.

# Stockage de la base de données

Pour stocker la base de données, nous allons modifier la fonction instantiateSealdSDK pour sauvegarder la base de données en localStorage de façon chiffrée.

Pour cela, on ajoute un argument databasePath pour enregistrer la base de données, et un argument databaseKey pour la chiffrer.

TIP

On rajoute sessionID dans le databasePath afin que celui-ci soit unique pour chaque session.

En effet, si un même utilisateur se déconnecte, puis se reconnecte, le serveur aura généré une nouvelle databaseKey. On ne peut donc pas utiliser la même base de données : il faut que la base de données soit unique pour chaque session, on fait donc varier le databasePath en fonction du sessionID.

Lorsque l'on appelle la fonction createIdentity, l'identité est à la fois sauvegardée sur SSKS par le mot de passe connu de l'utilisateur, et en localStorage protégée avec la databaseKey.

De même, quand on appelle retrieveIdentity, l'identitée récupérée sur SSKS est stockée en localStorage protégée avec la databaseKey.

Nous verrons par la suite comment gérer cette databaseKey.

TIP

Il est possible de créer une sous-identité différente pour la protection par mot de passe et pour la base de données locale.

Ici, par souci de simplicité, la même identité sera dans la base de donnée locale et protégée par mot de passe.

# Récupération de l'identité

Pour récupérer l'identité depuis le localStorage, on va créer une fonction retrieveIdentityFromLocalStorage qui instancie le SDK et vérifie que la base de données est dans l'état attendu :

Lorsque que l'on appelle la fonction retrieveIdentityFromLocalStorage, la base de données est récupérée depuis le localStorage, déchiffrée avec la databaseKey et chargée dans le SDK. Nous verrons par la suite comment gérer cette databaseKey.

# Gestion de databaseKey

La databaseKey est un secret qui protège la base de donnée enregistrée en localStorage, et donc l'identité de l'utilisateur. Elle doit être récupérable par l'utilisateur de façon authentifiée.

Nous allons la générer depuis le backend, la stocker en session, et modifier le client API du frontend en conséquence.

# Modification de l'API en backend

Pour cela, nous allons modifier les routes suivantes dans le fichier backend/routes/account.js :

  • de création de compte : POST /account/ pour générer, stocker et rendre la databaseKey et le sessionID ;
  • de connexion : POST /account/login pour générer, stocker et rendre la databaseKey et le sessionID ;
  • de déconnexion : GET /account/logout pour supprimer la databaseKey ;
  • de statut : GET /account/ pour rendre la databaseKey et le sessionID.

Avant de modifier les routes, il faut importer le module crypto pour générer des databaseKey aléatoires avec fonction  :

Dans les routes de création de compte et de connexion, on génère et rend la databaseKey :

Dans la route de statut, on ne fait que rendre la databaseKey :

Dans la route de déconnexion, on supprime la databaseKey de la session :

# Modification du client API en frontend

Nous allons répercuter ces modifications dans le client d'API frontend/services/api.js.

Pour cela il faut :

  • stocker databaseKey et sessionID en propriétés de la classe User
  • les récupérer après les appels API dans les méthodes statiques User.createAccount, User.login et User.updateCurrentUser.

# Récupération depuis le localStorage à l'initialisation

La dernière étape est d'appeler retrieveIdentityFromLocalStorage au chargement de l'application dans la fonction init de frontend/App.js.

Il faut :

  • tenter un appel GET /account/ pour vérifier si le navigateur dispose d'une session authentifiée, et récupérer databaseKey et sessionID ;
  • tenter de récupérer et déchiffrer la base de données stockée en localStorage.

En cas d'erreur sur l'une de ces deux étapes, on définit le currentUser à null ce qui va emmener l'utilisateur sur l'étape de connexion.

# Conclusion

L'identité est désormais mise en cache, un utilisateur connecté peut donc fermer la fenêtre de chat et la rouvrir sans devoir retaper son mot de passe.

Il reste encore une dernière étape pour un résultat satisfaisant : générer le jeton de licence sur le backend.