ia_dev/projects/lecoffreio/docs/ARCHITECTURE.md
Nicolas Cantu 61cec6f430 Sync ia_dev: token resolution via .secrets/<env>/ia_token, doc updates
**Motivations:**
- Align master with current codebase (token from projects/<id>/.secrets/<env>/ia_token)
- Id resolution by mail To or by API token; no slug

**Root causes:**
- Token moved from conf.json to .secrets/<env>/ia_token; env from directory name

**Correctifs:**
- Server and scripts resolve project+env by scanning all projects and envs

**Evolutions:**
- tickets-fetch-inbox routes by To address; notary-ai agents and API doc updated

**Pages affectées:**
- ai_working_help/server.js, docs, project_config.py, lib/project_config.sh
- projects/README.md, lecoffreio/docs/API.md, gitea-issues/tickets-fetch-inbox.py
2026-03-16 15:00:23 +01:00

1790 lines
78 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Architecture LeCoffre.io
**Dernière mise à jour** : 2026-01-28
**Version** : 2.1.0
**Référence unique (checks de déploiement)** : [`docs/DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique`](./DEPLOYMENT.md#cartographie-des-checks-de-déploiement-source-unique)
Ce document décrit l'architecture complète de LeCoffre.io : infrastructure physique, architecture logicielle, configuration des serveurs, types d'utilisateurs, rôles et permissions, gestion multi-office, et ancrage blockchain.
---
## 📋 Table des Matières
1. [Architecture Physique](#architecture-physique)
2. [Architecture Logicielle (Monorepo)](#architecture-logicielle-monorepo)
3. [Configuration Initiale des Serveurs](#configuration-initiale-des-serveurs)
4. [Environnements](#environnements)
5. [Services Externes](#services-externes)
6. [Types d'Utilisateurs](#types-dutilisateurs)
7. [Architecture Rôles & Règles](#architecture-rôles--règles)
8. [Matrice des Permissions](#matrice-des-permissions)
9. [JWT Multi-Office](#jwt-multi-office)
10. [Évolution planifiée - Documents unifiés](#évolution-planifiée---documents-unifiés)
11. [Partage Inter-Études](#partage-inter-études)
12. [Gestion Multi-Office](#gestion-multi-office)
13. [Ancrage Blockchain](#ancrage-blockchain)
---
## Architecture Physique
### Infrastructure Serveurs
LeCoffre.io est déployé sur une infrastructure **privée** (réseau `192.168.1.x`) avec un **proxy** public en point d'entrée unique.
#### VM Environnements (Application)
| Environnement | IP | Domaine | Répertoire |
|---------------|-----|---------|------------|
| **test** | `192.168.1.101` | `test.lecoffreio.4nkweb.com` | `/srv/4NK/test.lecoffreio.4nkweb.com` |
| **pprod** | `192.168.1.102` | `pprod.lecoffreio.4nkweb.com` | `/srv/4NK/pprod.lecoffreio.4nkweb.com` |
| **prod** | `192.168.1.103` | `prod.lecoffreio.4nkweb.com` | `/srv/4NK/prod.lecoffreio.4nkweb.com` |
**Proxy (point d'entrée)** :
- IP privée : `192.168.1.100`
- Accès externe : `4nk.myftp.biz`
- Rôle : terminaison SSL + routage Nginx vers les **ports applicatifs** des serveurs `101/102/103`.
**Services déployés sur chaque serveur environnement (host-native, systemd)** :
- PostgreSQL (service système)
- ClamAV (service système)
- Backend (systemd)
- Cron (systemd)
- Frontend (systemd)
- Routeur HTTP local (Node) sur le port "service" (systemd)
**Important :**
- Il n'y a **pas de Nginx** ni Certbot sur `101/102/103`.
- Les déploiements "v2" sont gérés par `deploy/scripts_v2/` (orchestrés depuis le proxy).
#### VM Git (Repository)
**Serveur Git** : `git.4nkweb.com`
**Repository** : `git@git.4nkweb.com:4nk/lecoffreio.git`
**Rôle** :
- Stockage du code source (monorepo)
- Versioning Git
- Synchronisation entre machines locales et serveurs de déploiement
**Workflow** :
1. Développement local → commit/push vers Git
2. Script de déploiement → pull depuis Git sur VM environnement
3. Build et déploiement sur VM environnement
#### VM Base de Données
**PostgreSQL externe** :
- **Host** : Variable selon environnement (`DATABASE_HOST` dans `.env.<env>`)
- **Port** : **5432** (standard PostgreSQL en mode host-native)
- **Utilisateurs** : `lecoffre-user-{env}` (un par environnement)
- **Bases** : `bdd-{env}` (une par environnement)
**Accès** :
- Depuis VM environnements sur l'hôte
- Depuis machines locales via SSH tunnel (optionnel)
#### VM API Ancrage Bitcoin (Séparée)
**Serveur** : `dev3.4nkweb.com`
**Host SSH** : `<USER>@31.33.24.235`
**Répertoire** : `/home/<USER>/dev/lecoffre-anchor-api`
**Port** : `3002`
**Health** : `http://31.33.24.235:3002/health`
**Rôle** :
- API d'ancrage blockchain Bitcoin Signet
- Service séparé pour isolation et scalabilité
- Appelée par le backend principal pour les ancrages
### Réseau et Communication
```text
┌─────────────────────────────────────────────────────────────┐
│ Machine Dev (externe) │
│ │
│ ┌──────────────┐ │
│ │ Git Client │ ────SSH───> Proxy (4nk.myftp.biz) │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ Deploy Script│ ────SSH───> Proxy (orchestrateur) │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ SSH (réseau privé)
┌─────────────────────────────────────────────────────────────┐
│ Serveurs Environnements (101/102/103) │
│ │
│ ┌──────────────┐ │
│ │ Git Pull │ <───SSH─── Git (git.4nkweb.com) │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ systemd │ - backend / cron / frontend / router │
│ └──────────────┘ │
│ │ │
│ │ PostgreSQL (port 5432) │
│ ▼ │
│ ┌──────────────┐ │
│ │ PostgreSQL │ (VM Base de Données externe) │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Persistance des Données
| Type | Emplacement | Persistance |
|------|-------------|-------------|
| **Code source** | VM Git (git.4nkweb.com) | ✅ Git repository |
| **Code déployé** | VM Environnement (`/home/debian/sites/...`) | ✅ Host filesystem |
| **Base de données** | VM PostgreSQL externe | ✅ PostgreSQL |
| **Certificats SSL** | VM Environnement (`deploy/nginx/certbot/`) | ✅ Host filesystem (bind mount) |
| **Configuration Nginx** | VM Environnement (`deploy/nginx/nginx-*.conf`) | ✅ Host filesystem (bind mount) |
| **Backups BDD** | VM Environnement (`deploy/backups/`) | ✅ Host filesystem |
| **Backups certificats** | VM Environnement (`deploy/certificats/`) | ✅ Host filesystem |
| **Logs** | VM Environnement (`logs/`) | ✅ Host filesystem (collectés par Promtail) |
| **Build backend/frontend** | VM Environnement | ⚠️ Rebuild à chaque déploiement |
| **node_modules** | VM Environnement (dans le build) | ❌ Non persistant (rebuild) |
---
## Architecture Logicielle (Monorepo)
### Structure du Monorepo
LeCoffre.io est organisé en **monorepo** contenant 4 sous-projets principaux :
```text
lecoffreio/ # Repository global (ce repo)
├── lecoffre-back-main/ # Backend API (Express + TypeScript)
│ ├── src/ # Code source TypeScript
│ │ ├── app/ # Controllers & Middlewares
│ │ │ ├── api/ # Routes API (notary, admin, customer, public)
│ │ │ └── middleware/ # Middlewares (auth, rate limiting, CORS)
│ │ ├── services/ # Business Logic
│ │ │ ├── admin/ # Services admin
│ │ │ ├── common/ # Services communs (IdNot, Annuaire, Stripe, Bitcoin)
│ │ │ ├── customer/ # Services clients
│ │ │ ├── notary/ # Services notaires
│ │ │ └── super-admin/ # Services super-admin
│ │ ├── common/ # Utilities & Repositories
│ │ │ ├── databases/ # Prisma schema & migrations
│ │ │ ├── dtos/ # DTOs validation
│ │ │ ├── emails/ # Services email
│ │ │ ├── repositories/ # Couche d'accès données
│ │ │ └── utils/ # Helpers centralisés
│ │ └── entries/ # Entry points
│ │ └── App.ts # Point d'entrée API
│ ├── dist/ # Build JavaScript (généré)
│ ├── prisma/ # Migrations base de données
│ └── package.json # Dependencies backend
├── lecoffre-front-main/ # Frontend Web (Next.js + React)
│ ├── src/
│ │ ├── pages/ # Pages Next.js (routes)
│ │ │ ├── folders/ # Routes dossiers
│ │ │ ├── admin/ # Routes admin
│ │ │ ├── subscription/ # Routes abonnements
│ │ │ └── ...
│ │ ├── front/ # Components React
│ │ │ ├── Components/ # UI Components
│ │ │ │ ├── DesignSystem/ # Design system (Header, Footer, etc.)
│ │ │ │ ├── Layouts/ # Layouts (Folder, Subscription, etc.)
│ │ │ │ └── Elements/ # Éléments UI (Buttons, Forms, etc.)
│ │ │ ├── Api/ # API Services (59 services)
│ │ │ ├── Hooks/ # Custom React Hooks
│ │ │ ├── Services/ # Frontend Services
│ │ │ ├── Stores/ # State management (MobX)
│ │ │ └── Utils/ # Utilitaires
│ │ └── proxy.ts # Next.js Proxy (auth)
│ ├── public/ # Assets statiques
│ └── package.json # Dependencies frontend
├── lecoffre-ressources-dev/ # Resources TypeScript partagées
│ ├── src/
│ │ ├── Admin/ # Types Admin (22 types)
│ │ ├── Notary/ # Types Notary (29 types)
│ │ ├── Customer/ # Types Customer (15 types)
│ │ └── SuperAdmin/ # Types SuperAdmin (20 types)
│ └── package.json # Dependencies resources
├── lecoffre-anchor-api/ # API d'ancrage Bitcoin (serveur séparé)
│ ├── src/
│ │ ├── config/ # Configuration logger
│ │ ├── controllers/ # Controllers API ancrage
│ │ ├── services/ # Bitcoin + Queue services
│ │ └── types/ # Types ancrage
│ └── package.json # Dependencies anchor API
├── docs/ # Documentation technique
├── todoFix/ # Corrections et fixes documentés
├── IA_agents/ # Règles pour IA
├── deploy/ # Déploiement et outils
│ ├── scripts/ # Scripts de déploiement
│ ├── nginx/ # Configuration Nginx
│ ├── monitoring/ # Configuration Grafana/Loki
│ └── scripts_v2/ # Scripts déploiement host-native
├── logs/ # Logs de déploiement
├── VERSION # Version actuelle
├── CHANGELOG.md # Historique des versions
└── README.md # Documentation principale
```
### Rôles des Sous-Projets
| Sous-Projet | Rôle | Technologies | Port |
|-------------|------|--------------|------|
| **lecoffre-back-main** | API REST, authentification, business logic | Express, TypeScript, Prisma, PostgreSQL | 3001 |
| **lecoffre-front-main** | Interface web responsive, SSR | Next.js 14, React 18, TypeScript, SCSS | 3000 |
| **lecoffre-ressources-dev** | Types TypeScript partagés | TypeScript | - |
| **lecoffre-anchor-api** | API d'ancrage blockchain Bitcoin Signet | Express, TypeScript, Bitcoin RPC | 3002 |
### Dépendances Inter-Projets
```text
lecoffre-back-main
└── lecoffre-ressources-dev (types partagés)
lecoffre-front-main
└── lecoffre-ressources-dev (types partagés)
lecoffre-anchor-api
└── (indépendant)
```
**Build order** :
1. `lecoffre-ressources-dev` : Compilation TypeScript → 87 fichiers `.js`
2. `lecoffre-back-main` : Utilise les types compilés
3. `lecoffre-front-main` : Utilise les types compilés
4. `lecoffre-anchor-api` : Build indépendant (serveur séparé)
### Communication Inter-Services
#### Backend ↔ Frontend
- **Protocole** : HTTP/HTTPS
- **Format** : JSON (REST API)
- **Authentification** : JWT (Bearer token)
- **Endpoints** : `/api/v1/{notary|admin|customer|public}/...`
#### Backend ↔ Anchor API
- **Protocole** : HTTP
- **Format** : JSON
- **Authentification** : API Key (`ANCHORE_API_KEY`)
- **Endpoint** : `http://<DOMAINE_BITCOIN>:3002/anchor`
#### Backend ↔ Services Externes
- **PostgreSQL** : Prisma ORM (port 5432 en host-native)
- **IdNot** : OAuth2 (HTTPS)
- **API Annuaire** : Basic Auth (HTTPS)
- **Stripe** : API Key (HTTPS)
- **Pinata (IPFS)** : API Key (HTTPS)
- **Mailchimp** : API Key (HTTPS)
- **OVH SMS** : API Key (HTTPS)
- **ClamAV** : TCP (port 3310)
---
## Configuration Initiale des Serveurs
### Vue d'ensemble
Le script `setup-all-servers.sh` permet de configurer automatiquement toutes les machines distantes avec :
- Configuration de `/etc/hosts` pour la résolution de noms
- Configuration sudo NOPASSWD pour l'utilisateur `ncantu`
- Installation des outils système (git, curl, wget)
- Installation et configuration des services (Node, PostgreSQL, etc.)
**Serveurs configurés** :
- `test` (192.168.1.101) : Environnement de test
- `pprod` (192.168.1.102) : Pré-production
- `prod` (192.168.1.103) : Production
**Point d'entrée** : Proxy (4nk.myftp.biz) via SSH ProxyJump
### Prérequis
**Sur la machine locale** :
- Accès SSH au proxy (4nk.myftp.biz)
- Clé SSH configurée pour l'utilisateur `ncantu`
- Bash disponible (Git Bash sur Windows)
- Mot de passe sudo des serveurs distants : `picnic1280`
**Sur les serveurs distants** :
- Accès SSH depuis le proxy
- Utilisateur `ncantu` avec droits sudo
- Connexion Internet pour télécharger les paquets
### Script de configuration
**Fichier** : `deploy/scripts/backup/setup-all-servers.sh`
**Description** : Script bash qui se connecte à chaque serveur via SSH (ProxyJump) et exécute un script de configuration distant.
### Fonctionnalités
#### 1. Configuration de `/etc/hosts`
Ajoute les entrées suivantes dans `/etc/hosts` de chaque serveur :
```text
192.168.1.100 proxy
192.168.1.101 test
192.168.1.102 pprod
192.168.1.103 prod
192.168.1.104 services
```
**Avantages** :
- Résolution de noms locale sans dépendre du DNS
- Accès rapide entre serveurs
- Indépendance du réseau externe
#### 2. Configuration sudo NOPASSWD
Configure l'utilisateur `ncantu` pour utiliser sudo sans mot de passe :
```text
ncantu ALL=(ALL) NOPASSWD: ALL
```
**Fichier créé** : `/etc/sudoers.d/ncantu-nopasswd`
**Sécurité** : Cette configuration permet à l'utilisateur `ncantu` d'exécuter toutes les commandes sudo sans mot de passe. À utiliser uniquement sur des serveurs d'infrastructure avec accès SSH sécurisé.
#### 3. Installation des outils système
Installe les outils suivants via `apt-get` (Debian/Ubuntu) ou `yum` (CentOS/RHEL) :
- **git** : Gestion de versions
- **curl** : Téléchargement de fichiers
- **wget** : Téléchargement de fichiers (alternative)
#### 4. Prérequis serveur
Le script vérifie et installe les prérequis selon la distribution (Debian/Ubuntu) : git, curl, wget, Node.js, PostgreSQL, etc. Voir [DEPLOYMENT.md](./DEPLOYMENT.md) et section "Configuration Initiale des Serveurs" ci-dessus.
### Utilisation
**Exécution du script** :
```bash
cd d:\code\lecoffreio
bash deploy/scripts/backup/setup-all-servers.sh
```
**Configuration** :
Le script contient les variables suivantes (modifiables si nécessaire) :
```bash
PROXY_HOST="ncantu@4nk.myftp.biz"
SUDO_PASSWORD="picnic1280"
SERVERS=(
"192.168.1.101:test"
"192.168.1.102:pprod"
"192.168.1.103:prod"
)
```
### Architecture SSH
Le script utilise SSH ProxyJump pour se connecter aux serveurs backend via le proxy :
```text
Client Local → Proxy (4nk.myftp.biz) → Serveur Backend (192.168.1.10x)
```
**Commande SSH** :
```bash
ssh -J ncantu@4nk.myftp.biz ncantu@192.168.1.101
```
### Vérifications post-configuration
**Vérification manuelle** :
```bash
# Se connecter au serveur
ssh -J ncantu@4nk.myftp.biz ncantu@192.168.1.101
# Vérifier /etc/hosts
cat /etc/hosts | grep "192.168.1.10"
# Vérifier sudo NOPASSWD
sudo -n true && echo "OK" || echo "KO"
# Vérifier les outils
which git curl wget node
node --version
```
**Notes importantes** :
- ⚠️ Le script configure sudo NOPASSWD pour **toutes** les commandes
- ⚠️ À utiliser uniquement sur des serveurs d'infrastructure avec accès SSH sécurisé
- ⚠️ Le mot de passe sudo est codé en dur dans le script (à modifier si nécessaire)
- Le script est idempotent : peut être exécuté plusieurs fois sans problème
---
## Environnements
### Environnements Disponibles
| Environnement | Usage | Domaine | Base de Données |
|---------------|-------|---------|-----------------|
| **test** | Tests et développement | `test-lecoffreio.4nkweb.com` | `bdd-test` |
| **pprod** | Pré-production | `pprod-lecoffreio.4nkweb.com` | `bdd-pprod` |
| **prod** | Production | `prod-lecoffreio.4nkweb.com` | `bdd-prod` |
| **demo** | Démonstration | `demo-lecoffreio.4nkweb.com` | `bdd-demo` |
### Configuration par Environnement
Chaque environnement a sa propre configuration (legacy vs scripts_v2) :
```bash
# scripts_v2 (host-native, recommandé)
DEPLOY_SSH_USER=ncantu
DEPLOY_SSH_KEY=~/.ssh/id_ed25519
DEPLOY_SSH_PROXY_HOST=4nk.myftp.biz
# Base de données (souvent locale au host env)
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_USERNAME=lecoffre-user-<env>
DATABASE_PASSWORD=***
DATABASE_NAME=bdd-<env>
```
---
## Services Externes
### Services Utilisés
| Service | Usage | Configuration |
|---------|-------|---------------|
| **PostgreSQL** | Base de données principale | Port 5432 (host-native) |
| **Bitcoin Signet** | Ancrage blockchain | RPC (port 38332) |
| **IPFS Pinata** | Stockage décentralisé fichiers | API Key |
| **ClamAV** | Scan antivirus uploads | Service système (TCP 3310) |
| **Mailchimp** | Emails transactionnels | API Key |
| **OVH SMS** | Notifications SMS | API Key |
| **Stripe** | Gestion paiements | API Key |
| **IdNot** | OAuth notarial | OAuth2 |
| **API Annuaire** | Synchronisation offices/personnes | Basic Auth |
### Configuration Dynamique
Les configurations sensibles sont stockées dans la base de données (`system_configuration`) plutôt que dans les fichiers `.env` :
- **109 configurations** disponibles
- **Injection** : Via `npm run config:import-env -- --env <env>`
- **Lecture** : Via `BackendVariables` (backend)
- **Sécurité** : Masquage des valeurs sensibles dans les logs
Voir [DATABASE_COMPLETE.md](./DATABASE_COMPLETE.md#configuration-systeme-dynamique) pour plus de détails sur `system_configuration`.
---
## Types d'Utilisateurs
Le système LeCoffre gère **4 types d'utilisateurs** avec des mécanismes d'authentification et d'autorisation différents.
### 1. Notaires authentifiés IdNot (avec abonnement)
**Caractéristiques** :
- Authentification via **IdNot OAuth**
- Stockés dans table `users`
- Possèdent un `office_role_uid` (lié à leur rôle dans l'office)
- **Abonnement actif** requis pour accès complet
- Accès via JWT contenant : `userId`, `office_Id`, `role` (global), `rules` (role + office_role), `isSuperAdmin` (boolean, si `users.is_super_admin === true`)
**Rôles possibles** :
- `role` global : `admin`, `super-admin`, `notary`, `default`
- `office_role` : `Notaire`, `Collaborateur` (lié à l'office)
**Règles d'accès** :
- JWT contient **toutes les règles** combinées (`role.rules` + `office_role.rules`)
- Accès complet selon règles assignées
- Exemple : `POST rib`, `GET folders`, `DELETE documents`, etc.
**Création** :
1. Connexion IdNot OAuth
2. Appel API IdNot `/api/pp/v2/rattachements/{profile_idn}`
3. Création automatique de l'`office` si n'existe pas
4. Duplication `office_roles` depuis Office Template (idNot "0000")
5. Assignment automatique `office_role` selon `typeLien.name` IdNot :
- `NOTAIRE_TITULAIRE`, `NOTAIRE_ASSOCIE`, `NOTAIRE_SALARIE` → "Notaire"
- `COLLABORATEUR`, `SUPPLEANT`, `ADMINISTRATEUR`, `CURATEUR` → "Collaborateur"
**À la connexion (mise à jour)** : les données contact (nom, prénom, téléphones, civilité) sont synchronisées depuis IdNot ; **lemail en base nest pas mis à jour** sil existe déjà (les notaires conservent lemail stocké). Implémentation : `IdNotService.updateContactFromRattachement` (condition `!normalizedCurrentEmail`) et `AffiliationSyncContactDataExtractionHelper.buildContactUpdateDataFromComparison` (condition `newEmail && !currentEmail`).
### 2. Notaires visiteurs (authentifiés IdNot, SANS abonnement)
**Caractéristiques** :
- Identiques aux notaires authentifiés IdNot mais **sans abonnement actif**
- `hasActiveSubscription === false` dans le payload JWT
- **Nouveau flag JWT** : `isVisitor = true`
- Le rôle d'office renvoyé au frontend est forcé à `Visiteur`
- L'UI affiche un badge `Visiteur` (OfficeSelector) au lieu de `(invité)`
**Règles d'accès** :
- Filtrage serveur → seuls les endpoints `GET *` et le parcours d'abonnement restent autorisés :
- `GET /api/v1/admin/subscriptions`
- `GET /api/v1/admin/subscription-plans`
- `POST /api/v1/admin/stripe`
- `PUT /api/v1/admin/subscriptions`
- `GET /api/v1/admin/stripe/:uid/client-portal`
- **Toutes les autres règles (POST/PUT/DELETE dossiers, documents, etc.) sont retirées du JWT**
- Les contrôles frontend reposent donc uniquement sur `jwt.rules` (plus de bouton "Créer un dossier")
**Effets frontend** :
- Badge `Visiteur` dans le sélecteur d'étude (`OfficeSelector`)
- Absence du CTA "Créer un dossier" et des actions de modification
- Parcours abonnement toujours visible (composants conditionnés sur `jwt.rules`)
- Lorsqu'un notaire invité ouvre un dossier partagé, l'interface monte automatiquement le template client (`DefaultCustomerDashboard`) pour éviter les états parasites issus des composants notaire (onglets, CTA, hooks permissions) et garantir une lecture seule stricte.
- La liste complète des dossiers invités et la vue détail utilisent maintenant la même expérience que les clients (sections "Documents envoyés / reçus" basées sur `DocumentTables`), sans onglets notaire ni actions d'édition.
**But** :
- Permettre à un notaire sans abonnement actif de **naviguer en lecture seule** et de souscrire
- Faciliter l'onboarding multi-office
### 3. Clients (Customers)
**Caractéristiques** :
- Authentification via **email + password** ou **code 2FA**
- Stockés dans table `customers` (**PAS** dans `users`)
- **Pas de `office_role_uid`** (n'existent pas dans `users`)
- JWT différent : `ICustomerJwtPayload` avec `customerId` et `email`
**Règles d'accès** :
- Endpoints dédiés préfixés `/api/v1/customer/*`
- Authentification via `customerAuthHandler` (différent de `authHandler`)
- Accès limité à :
- Leurs propres dossiers
- Leurs propres documents
- Upload de documents demandés
**Création** :
1. Notaire crée un client via `POST /api/v1/notary/customers`
2. Client reçoit email avec code 2FA
3. Client se connecte via `/customer-login`
4. Définit son mot de passe (première connexion)
### 4. Tiers (Third Parties)
**Caractéristiques** :
- **Quasiment identiques aux clients**
- Authentification via **email + code 2FA** (pas de mot de passe permanent)
- Stockés dans table `folder_third_parties`
- Liés à un dossier spécifique
**Règles d'accès** :
- Endpoints dédiés : `/api/v1/third-party/*`
- Authentification via code 2FA à usage unique
- Accès limité à :
- Le dossier auquel ils sont rattachés
- Upload de documents demandés par le notaire
**Création** :
1. Notaire ajoute un tiers via `POST /api/v1/notary/folders/:folderUid/third-parties`
2. Tiers reçoit email avec code 2FA
3. Tiers se connecte via `/third-party/login`
4. Accès temporaire au dossier
**Accès aux dossiers** :
- Les tiers n'ont **pas d'`activeOfficeUid`** (contrairement aux notaires)
- L'accès au dossier est vérifié via le `folderUid` dans le JWT
- Les contrôleurs doivent gérer ce cas spécial : vérifier `folderUid` au lieu de `activeOfficeUid` pour les tiers
- Exemple : `FolderThirdPartiesController.verifyFolderAccess()` vérifie `permissionContext.folderUid` pour les tiers
**Différence avec clients** :
- Clients : liés à l'office, multiples dossiers possibles
- Tiers : liés à UN dossier spécifique, accès temporaire
### Résumé des Types d'Utilisateurs
| Type | Table | Auth | office_role_uid | Abonnement | Accès |
|------|-------|------|-----------------|------------|-------|
| **Notaire avec abo** | `users` | IdNot OAuth | ✅ | ✅ Requis | Complet selon rules |
| **Notaire invité** | `users` | IdNot OAuth | ✅ | ❌ Non | Limité + Gestion abo |
| **Client** | `customers` | Email/2FA | ❌ | N/A | Ses dossiers seulement |
| **Tiers** | `folder_third_parties` | 2FA | ❌ | N/A | 1 dossier seulement |
| **Template users** | `users` (Office 0000) | N/A | ❌ | N/A | Jamais connectés |
---
## Architecture Rôles & Règles
### Hiérarchie
```text
┌─────────────────────────────────────────────────────────────┐
│ GLOBAL │
├─────────────────────────────────────────────────────────────┤
│ roles (table) │
│ ├─ admin │
│ ├─ super-admin │
│ ├─ notary │
│ └─ default │
│ │
│ _RolesHasRules (liaison) │
│ ├─ role.uid → rules.uid │
│ └─ Ex: super-admin → [ALL RULES] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ PAR OFFICE │
├─────────────────────────────────────────────────────────────┤
│ office_roles (table) │
│ ├─ Notaire (office A) │
│ ├─ Collaborateur (office A) │
│ ├─ Notaire (office B) │
│ └─ Collaborateur (office B) │
│ │
│ _OfficeRolesHasRules (liaison) │
│ ├─ office_role.uid → rules.uid │
│ └─ Ex: Notaire (office A) → [POST rib, GET folders, ...] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ UTILISATEUR │
├─────────────────────────────────────────────────────────────┤
│ users.roles_uid → role (global) │
│ users.office_role_uid → office_role (spécifique office) │
│ │
│ JWT contains: │
│ rules = role.rules + office_role.rules (MERGED) │
│ hasActiveSubscription (boolean) │
│ isVisitor (boolean) │
│ isSuperAdmin (boolean, si users.is_super_admin === true) │
└─────────────────────────────────────────────────────────────┘
```
### Office Template (idNot "0000")
**Rôle** :
- Office de **référence** contenant les données par défaut
- Utilisé pour **dupliquer** `office_roles`, `deed_types`, `document_types` vers nouveaux offices
**Contenu** :
- `office_roles` : `Notaire`, `Collaborateur` avec leurs règles
- `deed_types` : Types d'actes par défaut
- `document_types` : Types de documents par défaut
**Users dans Office Template** :
- 4 users de test avec `idNot` invalides (ex: `rflrefrjf`)
- **N'ont PAS** de `office_role_uid` (c'est **NORMAL**)
- Ne se connectent **jamais** à l'application
- Servent uniquement de données de référence
### Flux d'autorisation
#### Notaires (avec/sans abonnement)
```text
Request → authHandler → ActiveOfficeInjector → ruleHandler → Controller
↓ ↓ ↓
JWT verify office_uid inject check rule
req.user req.activeOfficeUid req.user.rules.includes(requiredRule)
```
**Bypass `ruleHandler`** :
- **Super-admin** : Accès à toutes les routes si `isSuperAdmin === true` (champ séparé dans la table `users.is_super_admin`) **OU** `role === "super-admin"` (rétrocompatibilité)
- Les middlewares (`roleHandler`, `ruleHandler`) et les controllers super-admin vérifient les deux conditions
- Endpoints `/admin/subscriptions`, `/admin/stripe`, `/admin/subscription-plans`**pas de `ruleHandler`**
#### Clients
```text
Request → customerAuthHandler → Customer endpoints only
JWT verify (ICustomerJwtPayload)
req.body.customer
```
#### Tiers
```text
Request → thirdPartyAuthHandler → Third Party endpoints only
2FA code verify
req.body.thirdParty
```
### Règles spéciales
#### Endpoints SANS `ruleHandler`
**Abonnements (accessibles à TOUS les notaires authentifiés)** :
- `GET /api/v1/admin/subscriptions`
- `GET /api/v1/admin/subscription-plans`
- `POST /api/v1/admin/stripe` (création checkout)
- `GET /api/v1/admin/stripe/:uid/client-portal`
**Raison** : Permettre aux notaires invités (sans abonnement) de s'abonner.
#### Endpoints sensibles AVEC `ruleHandler`
**RIB (données bancaires)** :
- `GET /api/v1/notary/rib` → règle `GET rib`
- `POST /api/v1/notary/rib` → règle `POST rib`
- `DELETE /api/v1/notary/rib` → règle `DELETE rib`
**Assignées par défaut à** :
- `office_role` "Notaire" et "Collaborateur" (selon configuration Office Template)
**Téléchargement de fichiers** :
- `POST /api/v1/notary/files/download-multiple` → règle `POST files`
**Assignées par défaut à** :
- `office_role` "Notaire" et "Collaborateur" (héritage des règles documents/fichiers)
### Statut Super-Admin
#### Double mécanisme de vérification
Le système utilise **deux mécanismes** pour identifier un super-admin :
1. **Champ `is_super_admin`** (table `users`) : Statut séparé du rôle, permet de promouvoir un utilisateur en super-admin sans changer son rôle global
2. **Rôle `super-admin`** (table `roles`) : Rôle global classique (rétrocompatibilité)
**Vérification dans le code** :
- Les middlewares (`roleHandler`, `ruleHandler`) vérifient : `isSuperAdmin === true || role === "super-admin"`
- Les controllers super-admin (ex: `HealthTestsController`, `SiteTextsController`) utilisent la même logique
- Le JWT contient `isSuperAdmin: true` si `users.is_super_admin === true`
**Bypass super-admin** :
- **`ruleHandler`** : Les super-admins bypassent la vérification des règles de permissions (accès à toutes les routes)
- **Validation d'accès aux dossiers** : Les super-admins sont **soumis aux mêmes règles** que les notaires et collaborateurs :
- Vérification de l'appartenance au dossier (stakeholder)
- Vérification des partages de dossier (folder_sharing)
- Validation de l'office propriétaire
- **Raison** : Le statut super-admin ne joue pas sur les fonctions de validation d'accès aux dossiers. Un super-admin doit être explicitement stakeholder ou avoir accès via `folder_sharing` pour accéder à un dossier.
- **Correction appliquée (décembre 2025)** : Le bypass super-admin dans `folderAccessHelpers.ensureFolderAccess()` a été supprimé. Les super-admins passent maintenant par les mêmes validations que les autres utilisateurs.
**Avantages** :
- Permet d'avoir un super-admin avec un rôle `notary` ou `admin` tout en conservant les permissions super-admin
- Flexibilité pour la gestion des accès sans modifier la structure des rôles
- Sécurité renforcée : les super-admins ne peuvent pas contourner les validations d'accès aux dossiers
#### Page /offices et synchronisation IdNot
Sur la page super-admin `/offices`, les actions liées aux synchronisations historiques IdNot/Annuaire ne sont plus exposées. Les recherches de confrères et les résolutions par email utilisent des **appels API** (IdNot/Annuaire) au moment de la demande, en complément de la base du site (users/contacts/offices) et des partages (`folder_sharing`).
#### Promotion en super-admin
La promotion se fait via :
- Script : `lecoffre-back-main/src/scripts/promote-super-admin.ts`
- Configuration : Variable `SUPERADMIN_EMAILS` dans la BDD (déploiement avec `--promoteSuperAdmins`)
### Cas particuliers
#### Multer et authHandler
**Problème** : `multer().single()` écrase `req.body` lors du parsing de `multipart/form-data`.
**Solution** :
- `authHandler` stocke le user dans **2 emplacements** :
- `req.body.user` (standard)
- `req.user` (backup, non écrasé par multer)
- `ActiveOfficeInjector` vérifie les 2 emplacements et restaure `req.body.user` si manquant
**Token d'authentification** : le JWT est lu depuis l'en-tête `Authorization` ou, pour les flux SSE uniquement (EventSource sans en-têtes personnalisés), depuis le paramètre de requête `token`. L'URL est nettoyée avant d'être loggée (paramètre `token` masqué). Voir `docs/CODE_STANDARDS.md` (section Sécurité du Code).
#### Multi-office
- Un user peut avoir plusieurs `user_office_affiliations`
- Une seule affiliation est `is_primary = true`
- JWT généré avec l'office PRIMARY
- `ActiveOfficeInjector` injecte `req.body.activeOfficeUid`
- Les routes utilisant `activeOfficeInjector` doivent utiliser `req.body.activeOfficeUid` au lieu de `req.body.user.office_Id` pour supporter les super-admins et le multi-office
**Important - Ordre des routes Express** : Les routes spécifiques (ex: `/folders/blockchain`, `/folders/deleted`) doivent être déclarées **avant** les routes paramétrées (ex: `/folders/:uid`) pour éviter les conflits de routage. Express matche les routes dans l'ordre de déclaration, donc `/folders/:uid` intercepterait `/folders/blockchain` si déclarée en premier.
**Exemple** :
```typescript
// ✅ Correct : route spécifique avant route paramétrée
@Get("/v1/notary/folders/blockchain", [authHandler, activeOfficeInjector, ruleHandler])
@Get("/v1/notary/folders/deleted", [authHandler, ruleHandler])
@Get("/v1/notary/folders/:uid", [authHandler, ruleHandler, folderHandler])
// ❌ Incorrect : route paramétrée avant route spécifique
@Get("/v1/notary/folders/:uid", [authHandler, ruleHandler, folderHandler])
@Get("/v1/notary/folders/blockchain", [authHandler, activeOfficeInjector, ruleHandler]) // Ne sera jamais atteinte
```
### Base de données - Tables clés
```sql
-- Utilisateurs notaires
users {
uid, idNot, roles_uid (global), office_role_uid (spécifique),
office_uid (deprecated, use affiliations), contact_uid,
is_super_admin (boolean, statut super-admin séparé du rôle)
}
-- Affiliations multi-office
user_office_affiliations {
uid, user_uid, office_uid, is_primary, role_in_office (string),
status, idnot_sync
}
-- Clients
customers {
uid, contact_uid, office_uid, status, password, deleted_at
}
-- Tiers
folder_third_parties {
uid, folder_uid, added_by_uid, role_type, role_description,
first_name, last_name, email, phone, auth_method, email_verified
}
-- Rôles globaux
roles {
uid, name (admin, super-admin, notary, default)
}
-- Rôles par office
office_roles {
uid, name (Notaire, Collaborateur), office_uid
}
-- Règles
rules {
uid, name (ex: "POST rib", "GET folders")
}
-- Liaisons
_RolesHasRules { A: role.uid, B: rule.uid }
_OfficeRolesHasRules { A: office_role.uid, B: rule.uid }
```
---
## Matrice des Permissions
### Vue d'Ensemble
Le système de permissions de LeCoffre.io utilise une **matrice de permissions** stockée en base de données (`role_permissions_matrix`) qui détermine les accès pour chaque combinaison :
- **Ressource** (ex: `documents`, `folders`, `rib`)
- **Action** (ex: `read`, `create`, `update`, `delete`)
- **Rôle** (ex: `super-admin`, `admin`, `notary`, `collaborator`, `guest_notary`, `client`, `third_party`)
- **Scope** (ex: `GLOBAL`, `OFFICE`, `ASSIGNMENT`)
- **Scoped Entity** (optionnel, UID d'une entité spécifique comme un office)
### Architecture de la Matrice
#### Table `role_permissions_matrix`
**Structure** :
```sql
- uid (PK)
- resource (string) : nom de la ressource (ex: "documents", "folders")
- action (string) : action (ex: "read", "create", "update", "delete")
- role (string) : rôle utilisateur (ex: "super-admin", "notary")
- allowed (boolean) : true = autorisé, false = refusé
- scope (string) : "GLOBAL", "OFFICE", "ASSIGNMENT"
- scoped_entity (string | null) : UID d'une entité spécifique (ex: office_uid)
- pages (JSON) : pages frontend associées
- updated_by (string | null) : UID de l'utilisateur qui a modifié
- created_at, updated_at
```
#### Types de Scope
**GLOBAL** :
- Accès à toutes les ressources, tous offices confondus
- Utilisé pour `super-admin` et `admin` (dans certains cas)
**OFFICE** :
- Accès limité à l'office actif de l'utilisateur
- Utilisé pour `notary` et `collaborator`
- Vérifié via `activeOfficeInjector` middleware
**ASSIGNMENT** :
- Accès limité aux ressources assignées à l'utilisateur
- Utilisé pour `guest_notary` (dossiers partagés), `client`, `third_party`
- Vérifié dans les controllers via filtres sur `depositor_uid`, `third_party_depositor_uid`, `shared_to_office_uid`
### Flux de Vérification des Permissions
#### Middleware `ruleHandler`
**Ordre d'exécution** :
```text
Request → authHandler → activeOfficeInjector → ruleHandler → Controller
```
**Étapes dans `ruleHandler`** :
1. **Extraction du rôle utilisateur** :
- `resolveUserRole(user)` détermine le rôle effectif
- Priorité : `isSuperAdmin``role` global → `officeRole``userType`
2. **Résolution de la ressource et action** :
- `resolveResource(req, service)` : extrait la ressource depuis le path
- `resolveAction(req, resource)` : détermine l'action (read/create/update/delete)
3. **Consultation de la matrice** :
- `permissionsService.getDecision({ resource, action, role, scoped_entity })`
- Retourne : `"allow"`, `"deny"`, ou `"pass"`
4. **Décision finale** :
- **Super-admin** : bypass automatique (ligne 99-112)
- **"deny"** : refusé (avec exceptions pour certaines ressources)
- **"allow"** : autorisé
- **"pass"** : pas d'entrée dans la matrice → fallback sur règles JWT
#### Service `RolePermissionsMatrixService`
**Méthode `getDecision`** :
1. **Construction de la clé** :
- Format : `resource::action::role::scoped_entity`
- Exemple : `documents::read::notary::-` (sans scope)
- Exemple : `documents::read::notary::office-uid-123` (avec scope)
2. **Recherche dans le cache** :
- Cache en mémoire (Map) chargé depuis la BDD au démarrage
- Recherche exacte de la clé
3. **Retour** :
- `"allow"` si `allowed === true`
- `"deny"` si `allowed === false`
- `"pass"` si aucune entrée trouvée
### Permissions par Type d'Utilisateur
#### Super-Admin
- **Bypass automatique** dans `ruleHandler` (ligne 99-112)
- Accès à **toutes les ressources** avec scope `GLOBAL`
- Endpoints `/api/v1/super-admin/*` accessibles
- Peut modifier la matrice des permissions
#### Admin (Office)
- Scope généralement `OFFICE` (limité à son office actif)
- Peut gérer les utilisateurs de son office
- Accès aux ressources de son office
- Matrice : alignement des permissions `admin` sur `collaborator` pour les ressources notariales (migration `20260305120000_backfill_admin_permissions_from_collaborator` + script `ensure-role-permissions-matrix-folders.sql`).
#### Notary
- Scope `OFFICE` : accès aux ressources de son office actif
- Peut créer/modifier/supprimer dossiers, documents, RIB
- Accès complet aux fonctionnalités notariales
#### Collaborator
- Scope `OFFICE` : accès aux ressources de son office actif
- **Mêmes droits que Notaire** pour documents, files, folder_customer, folder_third_parties, folder_sharing, document_reminder, document_request (read, create, update, delete selon ressource). Pour **folders** : archive autorisé, **delete non** (les collaborateurs peuvent archiver mais pas supprimer un dossier).
- Matrice : script `ensure-role-permissions-matrix-folders` et migration `20260204120000_align_collaborator_notary_folders_members`. Après déploiement : `run-ensure-role-permissions-matrix-folders.sh` (test | pprod | prod).
#### Guest Notary (Notaire Invité)
- Scope `ASSIGNMENT` : accès uniquement aux dossiers partagés
- Peut lire/créer/modifier documents dans les dossiers partagés
- Accès limité aux ressources assignées via `shared_to_office_uid`
#### Client
- Scope `ASSIGNMENT` : accès uniquement à ses propres dossiers et documents
- Endpoints `/api/v1/customer/*`
- Peut lire ses documents, uploader des documents demandés
#### Third Party (Tiers)
- Scope `ASSIGNMENT` : accès uniquement au dossier auquel il est rattaché
- Endpoints `/api/v1/third-party/*`
- Peut lire ses documents, uploader des documents demandés
- **Consultation (Mark as Viewed)** : La route `PUT .../mark-as-viewed` est exclue du blocage de mise à jour des documents `SENT`. Cela permet au tiers de consulter un document (passant de SENT à DOWNLOADED) sans erreur 403 ni déconnexion.
### Points d'Attention
#### Super-Admin Bypass
Le super-admin **bypass automatiquement** la matrice des permissions. Cela signifie :
- ✅ Accès à toutes les routes
- ⚠️ Les controllers doivent quand même vérifier la logique métier
- ⚠️ Les filtres de scope (OFFICE, ASSIGNMENT) ne s'appliquent pas automatiquement
#### Fallback sur Règles JWT
Si la matrice retourne `"pass"` (pas d'entrée), le système fait un **fallback sur les règles JWT** :
- Vérifie si `user.rules` contient la règle requise (ex: `"POST documents"`)
- Si oui → autorisé
- Si non → refusé
#### Scoped Entity
Le paramètre `scoped_entity` permet de définir des permissions spécifiques pour une entité (ex: un office particulier) :
- Exemple : `documents::read::notary::office-uid-123`
- Si présent, prioritaire sur l'entrée sans scope
- Utilisé pour des permissions granulaires
### Modifications de la Matrice
#### Via Interface Super-Admin
- Endpoint : `PUT /api/v1/super-admin/role-permissions-matrix`
- Permet de modifier les permissions pour chaque combinaison rôle/ressource/action
- Met à jour le cache automatiquement
#### Via Migrations
- Les migrations peuvent créer/modifier des entrées dans `role_permissions_matrix`
- Exemple : `prisma/migrations/20251107_role_permissions_matrix/migration.sql`
### Pilotage dynamique (onglet Super Admin "Matrice des droits")
L'onglet `Super Admin > Matrice des droits` (`/super-admin/role-permissions`) permet désormais de piloter en temps réel la matrice.
**Interface** :
- Tableau double entrée (ressource × action × rôle) avec cases à cocher synchronisées avec la BDD.
- Colonne informative "Pages concernées" (liste des écrans impactés).
- Badges de portée (`Global`, `Étude active`, `Affectation`, `Personnel`) pour chaque rôle afin de rappeler le niveau de permission.
- Barre de recherche full-text (ressource, action, page, rôle, scope).
- Résumé des modifications en attente + bouton `Réinitialiser` (revenir aux valeurs chargées) + bouton `Enregistrer`.
- Feedbacks : loader centralisé, toasts de confirmation, surface d'erreur explicite.
**API & persistance** :
- `GET /api/v1/super-admin/role-permissions` → retourne la matrice structurée (ressource → action → [pages, rôles]).
- `PUT /api/v1/super-admin/role-permissions` → accepte une liste d'updates `{ resource, action, role, allowed, scope?, pages? }`.
- Table SQL `role_permissions_matrix` : clé unique `(resource, scoped_entity, action, role)`, colonnes `allowed`, `scope`, `pages`, `updated_by`, `updated_at`.
- Service backend : cache mémoire (chargé au démarrage, invalidé après update) + logging (`SafeLogger.debug`).
**Middleware** :
- `RulesHandler` interroge la matrice (décision `allow|deny|pass`) avant le fallback sur les règles JWT historiques.
- Résolution automatique du trio `{resource, action, role}` à partir du chemin, du verbe HTTP et du JWT (`guest_notary`, `client`, `third_party`, etc.).
- Journalisation standardisée des décisions (`resource`, `action`, `role`, `decision`, `userId`).
#### Exceptions et fallbacks dans RulesHandler
Le `RulesHandler` implémente des **fallbacks spécifiques** pour certaines ressources/actions lorsque la matrice retourne `pass` (pas de règle explicite) ou `deny` (refus explicite) :
**1. `folder_third_parties` + action `resend`** :
- Si décision = `pass` et aucun JWT rules : autoriser par défaut pour les rôles `notary`, `collaborator`, `admin`, `super-admin`.
- Permet le renvoi de code de vérification aux tiers sans nécessiter une règle explicite dans la matrice.
**2. `folder_sharing` + action `delete` (révocation de partage)** :
- **Si décision = `pass`** et aucun JWT rules : autoriser par défaut pour les rôles `notary`, `collaborator`, `admin`, `super-admin`.
- **Si décision = `deny`** mais que l'utilisateur a des permissions sur les dossiers (`POST folders`, `DELETE folders`, ou `PUT folders`) : autoriser pour les rôles `notary`, `collaborator`, `admin`, `super-admin`.
- **Sécurité** : Le contrôleur `FolderSharingController.revokeShare()` vérifie de toute façon que l'utilisateur est membre de l'office propriétaire du dossier (`folder.office_uid === activeOffice.uid`). Cette double vérification garantit que seuls les membres de l'office propriétaire peuvent révoquer un partage, même si la matrice de permissions ne contient pas de règle explicite.
**Raison des fallbacks** :
- Permettre aux membres de l'office propriétaire d'un dossier de gérer les partages même si la matrice de permissions n'a pas été configurée explicitement pour `folder_sharing` delete.
- Le contrôleur effectue toujours une vérification finale basée sur la propriété du dossier, garantissant la sécurité.
La matrice devient ainsi la source de vérité opérationnelle. Les sections précédentes restent la documentation fonctionnelle de référence ; l'interface Super Admin permet de l'appliquer sans redéploiement.
---
## JWT Multi-Office et Calcul Dynamique des Règles
### Principe
Lorsqu'un utilisateur a plusieurs affiliations (`user_office_affiliations`), le JWT doit refléter :
- L'**office actif** (affiliation avec `is_primary = true`)
- Les **règles du rôle** dans cet office actif
### Implémentation dans AuthService
Le processus de calcul des règles est plus complexe que la simple addition des règles globales et office. Voici le processus complet :
```typescript
// AuthService.getUserJwtPayload()
// 1. Déterminer l'office actif via determineActiveOffice()
// - Gère les affiliations PRIMARY
// - Gère les dossiers partagés (GUEST_FOLDERS) si pas d'abonnement actif
// - Retourne { officeId, activeAffiliation, isGuestFolders }
const { officeId, activeAffiliation, isGuestFolders } = await this.determineActiveOffice(user);
// 2. Normaliser le nom du rôle office
// - Utilise normalizeIdNotOfficeRoleName() pour standardiser les noms de rôles IdNot
// - Gère les variations de noms (accents, casse, underscores)
const rawAffiliationRoleName = activeAffiliation?.role_in_office ?? null;
const normalizedAffiliationRoleName = normalizeIdNotOfficeRoleName(rawAffiliationRoleName);
// 3. Calculer les règles dans l'ordre suivant :
const rules = new Set<string>();
// 3.1. Ajouter les règles du rôle global
user.role.rules.forEach((rule) => {
if (rule?.name) {
rules.add(rule.name);
}
});
// 3.2. Ajouter les règles super-admin si applicable
// - Vérifie is_super_admin OU rôle = "super_admin"
// - Ajoute toutes les règles super-admin via addSuperAdminRules()
await this.addSuperAdminRules(rules, isSuperAdmin);
// 3.3. Ajouter les règles office ou guest notary
if (isGuestFolders) {
// Cas "Dossiers invités" : ajoute les règles de notaire invité
this.addGuestNotaryRules(rules);
} else if (activeAffiliation && normalizedAffiliationRoleName && officeId) {
// Cas normal : charge le rôle office et ajoute ses règles
await this.addOfficeRoleRules(rules, officeId, normalizedAffiliationRoleName, user);
} else if (user.office_role) {
// Fallback : utilise le rôle office de l'utilisateur
user.office_role.rules.forEach((rule) => {
if (rule?.name) {
rules.add(rule.name);
}
});
}
// 4. Filtrer les règles pour les visiteurs (si pas d'abonnement actif)
if (!hasActiveSubscription && !isGuestFolders) {
uniqueRules = this.filterVisitorRules(uniqueRules);
}
// 5. JWT contient : userId, office_Id, role, officeRole, rules, isVisitor, isGuestFolders
```
**Points importants** :
- **GUEST_FOLDERS** : Si l'utilisateur n'a pas d'abonnement actif mais a des dossiers partagés, `officeId = "GUEST_FOLDERS"` et les règles de notaire invité sont appliquées.
- **Normalisation des rôles** : Les noms de rôles IdNot sont normalisés pour correspondre à la matrice de permissions.
- **Règles super-admin** : Ajoutées séparément après les règles globales, pas fusionnées.
- **Filtrage visiteurs** : Les règles sont filtrées si l'utilisateur est un visiteur (pas d'abonnement actif et pas GUEST_FOLDERS).
### Switch d'Office
Lors du switch d'office via `PUT /api/v1/notary/users/:userUid/offices/active` :
1. ✅ Modification de l'affiliation PRIMARY en BDD
2. ✅ Régénération du JWT avec :
- Nouvel `office_Id` (l'office cible)
- Nouvelles `rules` (rôle dans l'office cible)
3. ✅ Frontend met à jour le JWT en localStorage
4. ✅ Rechargement automatique des dossiers/permissions
### Vérification Frontend Réactive
Le frontend vérifie désormais les règles via `PermissionContext`/`useRolePermissions()` :
```typescript
import { useRolePermissions } from "@Front/Stores/RolePermissionsStore";
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
const { can, loading: permissionsLoading } = useRolePermissions();
const canCreateFolder = can(AppRuleNames.officeFolders, AppRuleActions.create);
// Exemple : gérer le clic sur "Créer un dossier"
const handleCreateFolderClick = useCallback(() => {
if (permissionsLoading) {
return;
}
if (canCreateFolder) {
router.push("/folders/create");
} else {
subscriptionModal.open();
}
}, [canCreateFolder, permissionsLoading, router, subscriptionModal]);
```
### Cohérence Matrice ↔ JWT et fallback frontend
#### Source de vérité et arbitrage
- La matrice `role_permissions_matrix` (pilotée dans l'onglet Super Admin) est la cible de gouvernance.
- Le middleware `RulesHandler` doit prioriser la matrice; en son absence pour un couple (ressource, action, rôle), la décision retombe sur les `rules` incluses dans le JWT.
#### Fallback côté frontend (non superadmin)
- Par défaut, seuls les superadmins chargent la matrice côté front.
- Pour les autres rôles, `RolePermissionsStore.can(resource, action)` s'appuie sur `jwt.rules` avec:
- mapping actions → méthodes HTTP: `create→POST`, `read→GET`, `update→PUT|PATCH`, `delete→DELETE`, `add→POST|PUT`, `remove→DELETE`, `share→POST`, `remind→POST`, `resend→POST`
- normalisation singulier/pluriel de ressource (ex. `folder_customer``folder_customers`)
- heuristiques:
- clients dossier: `add` accepte `POST/PUT folder_customers` ou `PUT folders`
- tiers dossier: `add` accepte `POST folder_third_parties` ou `PUT folders`
- partage dossier: `create` accepte `POST folder_sharings` ou `POST folders`
#### Recommandations d'alignement
- Garder cohérents: seed des `rules` et affectations (`roles`, `office_roles`), calcul `AuthService.getUserJwtPayload()`, et configuration de la matrice.
### Licence (hasActiveSubscription) et comportement d'accès
#### Calcul côté serveur
- `SubscriptionsService.isUserSubscribed(user.uid, officeId)` pilote `hasActiveSubscription`.
- En échec de vérification: journalisation `warn` et fallback `hasActiveSubscription=false`.
- Si `false``isVisitor=true`, `officeRole="Visiteur"`, `rules` réduites: lecture + parcours abonnement.
#### Affichages et contrôles UI
- L'UI masque/désactive les actions d'écriture si la règle correspondante manque dans `jwt.rules`.
- Carte « Gestionnaires du projet »: « Licence active » s'affiche si `subscription.status === "active"` et `subscription.office.uid === folder.office.uid`.
---
## Gestion des Fichiers
### Nommage des Fichiers (Préfixe Dépositaire)
**Objectif** : Identifier l'origine et le contexte de chaque fichier via un préfixe standardisé.
**Format** : `folderNumber.[Prefix.]FirstName.LastName.TypeDocument.aplc.ext`
*Exception RIB* : `RIB.[Prefix.]officeName.aplc.ext`
**Préfixes (Type de dépositaire)** :
- **N** : Notaire gestionnaire
- **C** : Client
- **T** : Tiers
- **NI** : Notaire invité (office partagé)
**Composants** :
1. **Numéro de dossier** (ou "RIB") : Toujours en premier.
2. **Préfixe** : `N`, `C`, `T` ou `NI`.
3. **Prénom** : Formaté et inséré dans le nom.
4. **Nom** : Formaté en majuscules, espaces remplacés par tirets (ex. `DUPONT-MOREAU`).
5. **Type de document** : Nom du type de document.
6. **Suffixe** : `.aplc` (ancrage), sans index de version.
**Implémentation** :
- **FileNamingService** : Centralise la génération avec `generateDisplayName`.
- **DepositaryPrefixHelper** : Gère l'ajout/retrait du préfixe.
- **FileNamingParsingHelper** : Parse le format pour extraction.
---
## Statuts des Documents et Flux
### Distinction Sémantique
- **DEPOSITED** : Document envoyé par un déposant (Client, Tiers, Notaire Invité) vers le Notaire en charge.
- *Note* : Un document envoyé par un Notaire Invité au Notaire en charge est DEPOSITED.
- **SENT** : Document envoyé par le Notaire en charge vers un destinataire (Client, Tiers, Notaire Invité).
- *Note* : Un document envoyé par le Notaire en charge au Notaire Invité est SENT.
- **DOWNLOADED** : Document SENT qui a été consulté/téléchargé par le destinataire.
- **ASKED** : Document demandé par le Notaire en charge.
- **VALIDATED/REFUSED** : Statuts de validation par le Notaire en charge.
### Règles de Visibilité et Actions
- **"Documents à envoyer" (Front)** : Affiche uniquement `ASKED`, `REFUSED`, `VALIDATED`.
- Exclut `DEPOSITED`, `SENT`, `DOWNLOADED`.
- **Blocage API Customer** :
- **Modification/Suppression** : Interdite si statut est `SENT` (403). Autorisé si `DOWNLOADED`.
- **Passage en SENT** : Interdit via PUT (réservé au `send`).
---
## Évolution planifiée - Documents unifiés
**Statut** : 📋 Planifié | **Version** : cible
### Objectif
Unifier le flux d'envoi de documents et les métadonnées d'émetteur pour tous les types de destinataires et de déposants.
### Décisions validées
#### 1. Tables (court terme)
- Conserver `documents` et `documents_notary` pour des raisons historiques/techniques.
- Unifier le flux d'envoi côté API.
- Fusion des tables prévue à terme.
#### 2. Destinataire (recipient)
Type union unique pour tous les envois :
```typescript
{
type: 'customer' | 'third_party' | 'office' | 'invited_notary';
uid: string;
}
```
Types de destinataires possibles :
- `customer` : client du dossier
- `third_party` : tiers du dossier
- `office` : notaire admin de l'office
- `invited_notary` : notaire invité du dossier
#### 3. Émetteur (emitter)
- **Champ** : `emitter` (équivalent anglais de « émetteur »).
- **Structure** : `{ uid, first_name, last_name, office_uid?, office_name? }`.
- **Règle** : celui qui envoie le document ; absent pour documents ASKED (personne n'a encore envoyé).
- **Sources** :
- Client dépose : `depositor.contact`
- Tiers dépose : `third_party_depositor`
- Notaire envoie (DocumentsNotary) : `depositor` (User)
- Notaire envoie (Documents shared_to_office) : notaire du `folder.office`
- RIB : `rib_anchor_proof_data.depositor`
#### 4. Documents du notaire invité
Les documents créés par le notaire invité sont traités comme ceux des tiers ou des clients : même modèle, pas de cas particulier. Le notaire invité est un membre du dossier comme les autres ; il ne joue pas le rôle de notaire gestionnaire dans ce dossier.
**Implémentation** : Concept de membre unifié (`FolderMember`) :
- **Base** : `BaseFolderMember` (uid, display_name, email) — champs communs hérités
- **Types dérivés** : `CustomerMember`, `ThirdPartyMember`, `InvitedNotaryMember` (extends BaseFolderMember + type)
- **Recipient** : `BaseRecipient` (uid), `TypedRecipient` (extends BaseRecipient + type)
- **API** : GET folder retourne `members` (tableau unifié clients + tiers + notaires invités)
- **Backend** : `buildFolderMembers()` dans `folderMemberHelpers.ts`
- **Frontend** : `folder.members` disponible ; `buildRecipientsFromMembers()` pour le flux d'envoi
#### 5. Espaces et onglets membres (dérivation depuis cas parent member)
Les espaces (client/tiers/notaires invités) et les onglets des membres des dossiers dérivent d'un cas parent « member ».
**Espaces (FolderInformationUserContext)** :
- Type de base : `BaseMemberContextType` = `"customer" | "third_party" | "invited_notary"`
- Constantes : `MEMBER_CONTEXT_THIRD_PARTY`, `MEMBER_CONTEXT_INVITED_NOTARY`
- `FolderInformationUserContext` = `"notary" | MEMBER_CONTEXT_THIRD_PARTY | MEMBER_CONTEXT_INVITED_NOTARY`
**Onglets membres (ClientView)** :
- Type de base : `BaseMemberTabValue` avec `tabType: BaseMemberTabType`
- `BaseMemberTabType` = `"customer" | "third_party" | "shared_notary"`
- Constantes : `TAB_TYPE_CUSTOMER`, `TAB_TYPE_THIRD_PARTY`, `TAB_TYPE_SHARED_NOTARY`
- Types dérivés : `CustomerTabValue`, `ThirdPartyTabValue`, `SharedNotaryTabValue` (extends BaseMemberTabValue)
**Fichiers** : `MemberTypes.ts`, `folderInformationUserContext.ts`, `ClientView/types.ts`
#### 6. Flux d'envoi unifié
Une seule fonction d'envoi API avec un destinataire de type union. Pas de distinction par type de destinataire : envoi au bon membre en tant que notaire gestionnaire.
**Implémentation** : L'API `POST /v1/notary/documents_notary/send` accepte désormais :
- **Format unifié** : `recipients: { type: 'customer' | 'third_party' | 'office' | 'invited_notary'; uid: string }[]`
- **Format legacy** : `recipientUid` ou `recipientUids` (résolution automatique du type)
Le service `DocumentBatchService` route vers Documents (third_party, office/invited_notary) ou DocumentsNotary (customer) selon le type.
#### 7. Orchestrateurs de sortie API (plain)
Les réponses API documents sont normalisées via des orchestrateurs de post-hydratation pour garantir une sortie `plain` cohérente entre endpoints liste, mono-entité, transformation et suppression.
**DocumentsNotary** :
- `#Common/utils/documentsNotaryPostHydrationHelper`
- `buildDocumentsNotaryPlainWithRelations` : enrichit la sortie (`emitter`) et réinjecte `files` si nécessaire
- `hydrateDocumentsNotaryToPlainWithRelations` : pipeline liste
- `hydrateSingleDocumentNotaryToPlainWithRelations` : pipeline mono-entité (get by uid, mark-as-viewed, suppression, transformations)
- `logDocumentsNotaryPlainFilesGuard` : garde-fou de cohérence `files`
**Documents (non-notary)** :
- `#Common/utils/documentsPostHydrationHelper`
- `buildDocumentsPlainWithRelations` : pipeline liste
- `buildSingleDocumentPlainWithRelations` : pipeline mono-entité (get by uid, création, mark-as-viewed, suppression)
- `logDocumentsPlainFilesGuard` : garde-fou de cohérence des fichiers téléchargeables
**Règle d'architecture** :
- Les controllers/helpers ne construisent plus la sortie JSON avec un `instanceToPlain` local suivi de merges manuels.
- Toute sortie document doit passer par l'orchestrateur correspondant (`DocumentsNotary` ou `Documents`).
### Références pour implémentation
- `DocumentsEnrichmentHelper` : enrichir les réponses avec `emitter` au lieu de limiter à `{ uid }`.
- `DocumentsNotary` : inclure `depositor` (avec `contact`) dans les requêtes et exposer `emitter`.
- RIB : exposer `emitter` depuis `rib_anchor_proof_data.depositor` (POST/GET office, GET office/:uid).
---
## Partage Inter-Études
**Statut** : ✅ 100% | **Version** : 2.0.0
### Distinction importante : Partage avec une personne spécifique
**Concept fondamental** : Le partage se fait avec **une personne spécifique** (notaire invité) qui reçoit un email et se connecte ensuite.
**Sémantique du partage** :
- **Partage = Invitation d'une personne spécifique** (IdNot + email de préférence pour les envois, `invited_notary_email`)
- **Documents = Demandés à l'office de cette personne** (`shared_to_office_uid`)
- **Accès aux dossiers invités = Croisement sur l'id IdNot** (`invited_notary_idnot`) ; **email en base** = envois uniquement
**Workflow** :
1. Le notaire principal partage un dossier avec un confrère (sélection dans l'annuaire + email de préférence pour les envois, stocké en `invited_notary_email`)
2. Un email d'invitation est envoyé au confrère à l'adresse `invited_notary_email` (après création, seul cet email en base est utilisé pour tous les envois)
3. Le confrère se connecte et accède aux dossiers partagés via "Dossiers invités" (GUEST_FOLDERS)
4. Les documents demandés sont créés avec `shared_to_office_uid` (l'office du confrère)
5. L'accès aux dossiers invités est déterminé par l'IdNot ; les envois d'email utilisent `invited_notary_email` en base
**Gestion de l'affichage et de la sélection des dossiers** :
- **`is_primary` pour GUEST_FOLDERS** : Si un utilisateur n'a pas d'office avec licence active mais a des dossiers partagés, "Dossiers invités" est ajouté avec `is_primary: true` pour permettre la sélection automatique
- **Stabilisation de la liste** : Les hooks frontend (`useInvitedCustomerFolders`) utilisent des refs pour stabiliser la liste des dossiers et éviter les re-rendus lors de la navigation
- **Badge "Invité"** : Affiché uniquement pour les dossiers partagés en contexte non-GUEST_FOLDERS (évite la redondance)
- **Mise à jour IdNot** : Les utilisateurs invités sont mis à jour via l'API IdNot même s'ils n'ont pas de `officeMembership.idNot` (recherche par rattachements)
**Contrainte du schéma** : `@@unique([folder_uid, shared_to_office_uid])`
- Un dossier ne peut être partagé qu'**une seule fois avec un office donné**
- Cette contrainte garantit l'isolation des documents par notaire invité
- Si plusieurs notaires du même office doivent être invités, cela nécessiterait des partages distincts (non supporté actuellement)
**Distinction avec un partage d'office complet** :
Le système **ne supporte pas** le partage avec un office entier où tous les membres auraient accès. Le partage est toujours avec une **personne spécifique** identifiée par son IdNot et son email de préférence pour les envois. L'office (`shared_to_office_uid`) sert uniquement à :
- Identifier l'office du notaire invité pour la création des documents
- Permettre l'affichage dans l'interface "Dossiers invités"
L'accès aux dossiers invités est déterminé par l'**IdNot** du notaire connecté (`invited_notary_idnot`). L'email en base (`invited_notary_email`) sert uniquement aux envois (invitations, documents).
### Architecture Backend
**Service** : `FolderSharingService` (~250 lignes)
**Fichier** : `lecoffre-back-main/src/services/notary/FolderSharingService/FolderSharingService.ts`
**Controller** : `FolderSharingController` (~280 lignes)
**Fichier** : `lecoffre-back-main/src/app/api/notary/FolderSharingController.ts`
**Endpoints** :
- `POST /api/v1/notary/folders/:uid/share` : Partager dossier avec confrère
- **Paramètres** : `invitedNotaryEmail` (requis, email de préférence pour les envois), `invitedNotaryIdnot` (optionnel), `invitedNotaryFirstName`, `invitedNotaryLastName` (optionnels). CRPCEN optionnel.
- **Note** : L'email stocké (`invited_notary_email`) est utilisé pour tous les envois après création. L'IdNot (`invited_notary_idnot`) sert au croisement login / dossiers invités.
- `GET /api/v1/notary/folders/shared` : Liste dossiers reçus
- `GET /api/v1/notary/folders/:uid/shares` : Liste partages d'un dossier
- `POST /api/v1/notary/shares/:uid/revoke` : Révoquer partage
### Base de Données
**Table** : `folder_sharing`
**Champs** :
- `folder_uid` : Dossier partagé
- `shared_from_office_uid` : Office qui partage
- `shared_to_office_uid` : Office qui reçoit (office du notaire invité)
- `invited_notary_email` : Email de préférence pour les envois (invitations, documents). Seul champ email pour le notaire invité ; par défaut pré-rempli depuis IdNot à l'invitation, modifiable avant enregistrement. **Après création, seul ce champ est utilisé pour tous les envois** (aucune relecture IdNot).
- `invited_notary_idnot` : Identifiant IdNot du notaire invité. Utilisé pour le **croisement login / dossiers invités** : l'accès aux dossiers partagés est déterminé par cet IdNot (quand l'utilisateur connecté a un IdNot).
- `invited_notary_first_name` : Prénom du notaire invité
- `invited_notary_last_name` : Nom du notaire invité
- `shared_by_user_uid` : Utilisateur qui a partagé
- `share_role` : Rôle accordé (lecture seule)
- `status` : ACTIVE, REVOKED
- `expires_at` : Date expiration optionnelle
- `can_view_other_members_documents` : Permission pour voir les documents des autres membres
**Email et IdNot du notaire invité** :
- **Un seul champ email en base** : `invited_notary_email` = email de préférence (et par défaut email IdNot à l'invitation). Il n'y a pas de champ séparé « email IdNot » ; l'email IdNot et l'email de préférence sont donc le même champ persisté.
- **Accès aux dossiers invités** : croisement sur `invited_notary_idnot` (voir `buildGuestNotaryShareWhere`). L'email en base n'est pas utilisé pour l'accès.
- **Notaires invités sans seat** : à l'invitation, le partage enregistre `invited_notary_idnot`. Lors de la première connexion via IdNot, un profil `User` est créé ou mis à jour avec `users.idNot`. Les notaires invités ont donc en base un identifiant IdNot (sur le partage, et sur le User après connexion).
**Contrainte unique** : `@@unique([folder_uid, shared_to_office_uid])`
- Empêche de partager plusieurs fois le même dossier avec le même office
- Garantit l'isolation des documents par notaire invité
### Frontend
**Pages** :
- `/folders/shared` : Dossiers reçus
- **Modal** : `ShareFolderModal` (partager avec confrère)
**API** : `FolderSharingApi` (4 méthodes)
**Fonctionnalités** :
- Onglets notaires invités créés dans `ClientView`
- Badge "Lecture seule" pour notaires invités
- Query inclut `folder_sharings` avec relations
- **Demande de documents** : Possibilité de demander des documents aux confrères invités (v2.0.1)
- **Relance de documents** : Possibilité de relancer les confrères pour les documents en attente (v2.0.1)
- **Invitation simplifiée** : Seul l'email est requis pour inviter un confrère. Le CRPCEN est automatiquement récupéré depuis IdNot lors de la connexion (v2.0.1)
### Email
**Destinataire notaire invité** : Tous les envois (invitation partage, relance, documents) utilisent **uniquement** l'email en base `invited_notary_email`. Aucune relecture de l'email côté IdNot après création du partage.
**Templates** :
- `FOLDER_SHARING_INVITATION` : Invitation partage dossier
- `DOCUMENT_REQUEST` : Demande de document (utilisé aussi pour les confrères)
- Documentation : `docs/MAILCHIMP_TEMPLATE_FOLDER_SHARING_INVITATION.md`
### Demande et Relance de Documents aux Confrères (v2.0.1)
**Fonctionnalités** :
- **Demander un document** : Bouton disponible sur l'onglet du confrère invité
- **Relancer un confrère** : Bouton disponible pour les documents en statut ASKED
- **Affichage des documents** : Table des documents avec filtrage par `shared_to_office_uid`
**Frontend** :
- `ClientView` : Gestion des onglets notaires invités avec boutons
- `AskDocuments` : Support du format `shared-office:uid` dans l'URL
- `DocumentTables` : Affichage des documents avec flag `isSharedNotary`
- `EmailReminder` : Support des relances pour les confrères
**Backend** :
- Utilise le champ `shared_to_office_uid` existant dans la table `documents`
- Les documents sont demandés à l'office du notaire invité, mais l'accès est contrôlé par l'email
- `EmailBuilder.sendDocumentEmails()` : Gère déjà les emails pour les confrères
- `DocumentsController.resendRequest()` : Fonctionne pour tous les types de documents
**Important** : Les documents avec `shared_to_office_uid` sont techniquement visibles par tous les membres de cet office, mais la contrainte unique `@@unique([folder_uid, shared_to_office_uid])` limite l'exposition car un dossier ne peut être partagé qu'une seule fois avec un office donné. L'accès individuel est contrôlé via `invited_notary_email` dans le contexte `GUEST_FOLDERS`.
**Documentation détaillée** : voir section « Partage Inter-Études » ci-dessus. Déploiement : exécuter les migrations Prisma puis `deploy/scripts/build-and-deploy.sh`. Analyse : vérifier en base `invited_notary_idnot` sur folder_sharing ; créer un partage depuis lUI ; connexion notaire invité (IdNot) → dossiers partagés accessibles.
---
### Accès et Téléchargement pour Notaire Invité
**Problème résolu (v2.0.2)** : Les notaires invités ne pouvaient pas voir/télécharger les documents envoyés par le notaire gestionnaire (table `documents_notary`).
**Architecture d'accès** :
1. **Vue Document** : Permission `canViewDocuments` étendue si `isGuestNotary` (rôle ou contexte GUEST_FOLDERS).
2. **API Customer** :
- **GET documents_notary** : Support du filtre `where: { AND: [...] }` pour extraire `folderUid`.
- **Download** : `FileNotaryAccessGuestNotaryHelper` vérifie désormais dans `documents_notary` (en plus de `documents`) et valide l'accès via `folder_sharing`.
3. **Fallback Téléchargement** : Si le document est dans la table `documents` (envoyé à l'invité), le front utilise l'API Customer (`/customer/files/download/:uid`) au lieu de Notary, car l'API Customer valide correctement l'accès via `folder_sharing`.
---
## Gestion Multi-Office
**Statut** : ✅ 100% | **Version** : 2.0.0
### Architecture Backend (Multi-Office)
**Service** : `UserOfficeAffiliationsService` (~250 lignes)
**Fichier** : `lecoffre-back-main/src/services/notary/UserOfficeAffiliationsService/UserOfficeAffiliationsService.ts`
**Middleware** : `ActiveOfficeInjector` (injection activeOfficeUid dans req.body)
**Fichier** : `lecoffre-back-main/src/app/middlewares/ActiveOfficeInjector.ts`
**Endpoints** :
- `GET /api/v1/notary/users/:uid/offices` : Liste offices utilisateur
- `POST /api/v1/notary/users/:uid/offices/active` : Changer office actif
- `POST /api/v1/notary/users/:uid/offices/sync` : Synchroniser depuis IdNot
### Base de Données (Multi-Office)
**Table** : `user_office_affiliations`
**Champs** :
- `user_uid` : Utilisateur
- `office_uid` : Office
- `role` : Rôle dans cet office
- `is_primary` : Office principal (booléen)
**Unique** : `(user_uid, office_uid)`
### Frontend (Multi-Office)
**Store** : `ActiveOfficeStore.ts` (MobX, 150 lignes)
**Composant** : `OfficeSelector` (dropdown header, masqué si 1 seul office)
**API** : `UserOffices` (4 méthodes)
### Fonctionnalités
- **Sync automatique** : Depuis IdNot API Annuaire
- **Changement office** : Sans déconnexion (reload page)
- **Filtrage automatique** : Par office actif
- **Méthode `setPrimary()`** : Atomique (transaction)
- **Compatible** : Tous les TODOs existants
### Middleware Injection
**Transparente** : Ajoute `activeOfficeUid` dans tous les endpoints notary
**Alias** : `officeUid` pour compatibilité code existant
### Switch d'Office : Régénération JWT et Permissions
**Flux attendu** :
1. `PUT /api/v1/notary/users/:userUid/offices/active` (auth requis, pas de `ruleHandler`)
2. `UserOfficeAffiliationsService.setActiveOffice(...)` vérifie l'affiliation `ACTIVE` et bascule `is_primary`
3. `AuthService.getUserJwtPayload(...)` régénère le JWT avec:
- nouvel `office_Id`
- `rules` recalculées (rôle d'office + règles globales)
- filtrage licence (`hasActiveSubscription`, `isVisitor`) pour le nouvel office
4. Front: remplace les tokens, vérifie le cookie, et relit les droits (journaux: `office_Id`, `rulesCount`)
**Points de contrôle** :
- Le backend doit renvoyer de nouveaux tokens après chaque bascule.
- Le front doit vérifier la mise à jour effective du cookie `leCoffreAccessToken` puis conditionner l'UI.
---
## Ancrage Blockchain
LeCoffre.io utilise l'ancrage blockchain Bitcoin Signet pour garantir l'intégrité et la traçabilité des documents.
### Évolution des Versions
| Version | Blockchain | Périmètre | Déclenchement | Filigrane | Fallback |
|---------|-----------|-----------|---------------|-----------|----------|
| **V1** | Tezos | Dossiers uniquement | Manuel | ❌ Non | ⚠️ Lecture seule |
| **V2** | Bitcoin Signet | Docs + Dossiers | Auto | ❌ Non | - |
| **V3** | Bitcoin Signet | Docs + Dossiers | Auto | ✅ Oui | ✅ Tezos masqué |
### Nouveautés V3
1. **Filigrane automatique** : "lecoffre.io" sur tous documents
2. **Double version** : Original + filigrané (conservés en BDD)
3. **ZIP enrichi** : Originaux + filigranés + certificats + preuves JSON
4. **Preuves structurées** : JSON standardisé pour vérification externe
5. **Ancrage dossier** : Hash du ZIP complet (pas seulement Merkle tree)
6. **Fallback Tezos** : Accès masqué aux anciens ancrages (lecture seule)
### Principes Fondamentaux
- **Ancrage immédiat** : L'ancrage est déclenché automatiquement lors de la validation d'un document
- **Hash filigrané** : Seule la version filigranée est ancrée sur blockchain
- **Double hash** : Hash original et hash filigrané sont stockés pour vérification
- **Preuve on-chain** : Le `tx_id` (transaction ID Bitcoin) est la seule preuve fiable d'ancrage
### Services Backend
- **WatermarkService** : Conversion documents → PDF (hors tableurs : Excel/ODS exclus), ajout filigrane "lecoffre.io"
- **DocumentAnchorsService** : Ancrage automatique des documents validés
- **OfficeFolderAnchorsService** : Ancrage des dossiers complets (ZIP)
- **BitcoinSignetService** : Ancrage hash sur Bitcoin Signet via API externe
- **ProofDataService** : Génération JSON de preuve d'ancrage
### Processus d'Ancrage
**Séquence de traitement (Upload)** :
1. Calcul hash original (fichier original)
2. Filigrane (sur fichier original non chiffré)
3. Calcul hash filigrané et ancrage blockchain (hash filigrané, génération txid)
4. Chiffrement du document filigrané
5. Upload IPFS du document chiffré
6. Stockage métadonnées fichier (hash original + hash filigrané)
7. Stockage certificat BDD (document_anchor avec hash filigrané + hash original + txid)
**Ancrage automatique** : Déclenché lors de la validation d'un document via `PUT /api/v1/notary/documents/:uid` (asynchrone, non bloquant).
### Certificats et ZIP
**Structure ZIP dossier complet** :
```text
dossier_12345678/
├── _LISEZ-MOI.txt
├── 01_originaux/
├── 02_filigranes/
├── 03_certificats/
└── 04_preuves/
```
**Certificats PDF** : Générés automatiquement pour les documents validés et ancrés, contenant les hashs complets, le lien de transaction, et toutes les métadonnées d'ancrage.
**Documentation complète** : Voir `docs/ANCRAGE_COMPLETE.md` pour les détails complets de l'architecture V3, des processus d'ancrage, des certificats, et du troubleshooting.
---
## Références
- **Déploiement** : [DEPLOYMENT.md](./DEPLOYMENT.md)
- **Monitoring** : [DEPLOYMENT.md](./DEPLOYMENT.md#monitoring)
- **Ancrage** : [ANCRAGE_COMPLETE.md](./ANCRAGE_COMPLETE.md)
- **Base de données** : [DATABASE_COMPLETE.md](./DATABASE_COMPLETE.md)
- **Consolidation opérationnelle** : [README.md](./README.md#consolidation-operationnelle-ex-operationsmd)
- **Correctifs et dépannage** : [FRONTEND.md](./FRONTEND.md#8-consolidation-evolutions-et-correctifs), [DEPLOYMENT.md](./DEPLOYMENT.md#maintenance-et-troubleshooting), [ANCRAGE_COMPLETE.md](./ANCRAGE_COMPLETE.md)
---
**Dernière mise à jour** : 2026-01-28