Files
parenthese/backend/src/lib/share-password.ts
T
IliesAllaliandClaude Opus 5.5 92a4e5e1b9 Invitation de la famille : mot de passe toujours à jour, message prêt à copier
Le panneau du propriétaire affichait la copie gardée par le navigateur où le
mot de passe avait été défini : changé depuis un autre appareil, il montrait
l'ancien. Le serveur garde maintenant le mot de passe de partage chiffré
(AES-256-GCM, clé dérivée de JWT_SECRET) dans une table à part, pour ne pas
toucher la version des accès déjà ouverts.

- GET /trees/:id/access/passwords : le mot de passe en vigueur, admin seulement
- POST .../remember : arbres d'avant, le propriétaire saisit celui déjà donné,
  vérifié contre les hash, retenu sans rien changer aux accès
- rotation à deux mots de passe distincts : plus de valeur lisible
- anciennes copies locales effacées à l'ouverture du panneau
- message d'invitation (lien, mot de passe, une phrase) modifiable et à copier

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 17:18:18 +02:00

36 lines
1.4 KiB
TypeScript

import { createCipheriv, createDecipheriv, hkdfSync, randomBytes } from 'node:crypto'
import { env } from '../config/env.js'
// Le mot de passe de partage est fait pour être donné à la famille : le propriétaire doit pouvoir le relire.
// Chiffré en AES-256-GCM, clé dérivée de JWT_SECRET (changer ce secret rend les valeurs illisibles,
// le panneau repasse alors en « à saisir », sans rien casser pour la famille).
const VERSION = 'v1'
function getKey(): Buffer {
return Buffer.from(hkdfSync('sha256', env.JWT_SECRET, 'parenthese', 'share-password-v1', 32))
}
export function encryptSharePassword(value: string): string {
const iv = randomBytes(12)
const cipher = createCipheriv('aes-256-gcm', getKey(), iv)
const encrypted = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()])
const tag = cipher.getAuthTag()
return `${VERSION}:${Buffer.concat([iv, tag, encrypted]).toString('base64')}`
}
export function decryptSharePassword(stored: string | null | undefined): string | null {
if (!stored || !stored.startsWith(`${VERSION}:`)) {
return null
}
try {
const raw = Buffer.from(stored.slice(VERSION.length + 1), 'base64')
const decipher = createDecipheriv('aes-256-gcm', getKey(), raw.subarray(0, 12))
decipher.setAuthTag(raw.subarray(12, 28))
return Buffer.concat([decipher.update(raw.subarray(28)), decipher.final()]).toString('utf8')
} catch {
return null
}
}