**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
1790 lines
78 KiB
Markdown
1790 lines
78 KiB
Markdown
# 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 ; **l’email en base n’est pas mis à jour** s’il existe déjà (les notaires conservent l’email 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 super‑admin)
|
||
|
||
- Par défaut, seuls les super‑admins 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 l’UI ; 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
|