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

78 KiB
Raw Permalink Blame History

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

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
  2. Architecture Logicielle (Monorepo)
  3. Configuration Initiale des Serveurs
  4. Environnements
  5. Services Externes
  6. Types d'Utilisateurs
  7. Architecture Rôles & Règles
  8. Matrice des Permissions
  9. JWT Multi-Office
  10. Évolution planifiée - Documents unifiés
  11. Partage Inter-Études
  12. Gestion Multi-Office
  13. 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

┌─────────────────────────────────────────────────────────────┐
│                    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 :

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

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 :

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 :

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 et section "Configuration Initiale des Serveurs" ci-dessus.

Utilisation

Exécution du script :

cd d:\code\lecoffreio
bash deploy/scripts/backup/setup-all-servers.sh

Configuration :

Le script contient les variables suivantes (modifiables si nécessaire) :

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 :

Client Local → Proxy (4nk.myftp.biz) → Serveur Backend (192.168.1.10x)

Commande SSH :

ssh -J ncantu@4nk.myftp.biz ncantu@192.168.1.101

Vérifications post-configuration

Vérification manuelle :

# 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) :

# 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 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

┌─────────────────────────────────────────────────────────────┐
│                        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)

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-planspas de ruleHandler

Clients

Request → customerAuthHandler → Customer endpoints only
          ↓
          JWT verify (ICustomerJwtPayload)
          req.body.customer

Tiers

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 :

// ✅ 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

-- 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 :

- 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 :

Request → authHandler → activeOfficeInjector → ruleHandler → Controller

Étapes dans ruleHandler :

  1. Extraction du rôle utilisateur :

    • resolveUserRole(user) détermine le rôle effectif
    • Priorité : isSuperAdminrole global → officeRoleuserType
  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 :

// 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() :

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_customerfolder_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 falseisVisitor=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 :

{
  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 :

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


Dernière mise à jour : 2026-01-28