Technique.NET 10 · YARP · Keycloak · PostgreSQL

Documentation technique

Architecture mise en place et détail des programmes, application par application.

Vue d'ensemble

Écosystème .NET multi-applications derrière une passerelle unique (reverse proxy). Authentification centralisée Keycloak (OIDC), persistance PostgreSQL, stockage documentaire objet (S3/MinIO), e-facturation via une PDP. Chaque application est servie sur son propre sous-domaine et ne s'écoute que sur localhost ; seule la passerelle est exposée sur le réseau.

graph TD subgraph EXPO["Exposé réseau"] GW["Iiqarion.Gateway (YARP)
:80 dev / :443 prod"] end subgraph LOCAL["localhost uniquement"] SITE["Site vitrine :5161"] POR["Portail Client :5199"] FAC["IIQARION Factures :5087"] POS["IIQARION POS :5210"] KC["Keycloak :8091"] ID["Identité :5100"] CAT["Catalogue :5130"] TI["Tiers :5140"] end subgraph DATA["Données"] PG[("PostgreSQL")] OBJ[("Objet S3/MinIO")] end GW -->|www| SITE GW -->|portail| POR GW -->|factures| FAC GW -->|pos| POS GW -->|auth| KC POR --> ID & CAT & TI FAC --> ID & CAT & TI POS --> CAT & TI POR --> PG & OBJ FAC --> PG & OBJ POS --> PG ID --> PG KC --> PG
CoucheTechnologie
Runtime.NET 10 (POS & briques historiques : .NET 8) · C#
WebASP.NET Core — Razor Pages + Controllers (Factures), Blazor (Portail, Site), Minimal API (POS, Identité, Catalogue, Tiers, Gateway)
Reverse proxyYARP 2.3 (+ LettuceEncrypt pour HTTPS auto en prod)
AuthKeycloak 26 (realm eportail, OIDC) + cookie de session applicatif
DonnéesPostgreSQL (EF Core 10 / Npgsql) — Factur-X : QuestPDF ; e-mail : MailKit
Stockage documentaireObjet compatible S3 (MinIO en dev, OVH/S3 en prod) via Theia.Stockage

Passerelle & exposition

Projet Iiqarion.Gateway (YARP). Seul service exposé sur le réseau ; il route par nom d'hôte vers les applications, qui restent en localhost. Les apps conservent leurs chemins absolus (/api, /js) car chacune est servie sur sa propre origine (sous-domaine) — aucune réécriture de code.

Sous-domaineCible (localhost)Application
www.iiq.test:5161Site vitrine
portail.iiq.test:5199Portail Client
factures.iiq.test:5087IIQARION Factures
pos.iiq.test:5210IIQARION POS
auth.iiq.test:8091Keycloak (IdP)
Dev / LAN : HTTP :80, domaine iiq.test résolu via le fichier hosts des postes (finaliser-lan.ps1). Prod : Tls:Enabled=true → HTTPS :443 automatique (Let's Encrypt / LettuceEncrypt), vrai DNS, la passerelle tourne en Service Windows. Voir DEPLOIEMENT-PROD.md.
Durcissement prod Keycloak : ne jamais exposer publiquement la console /admin ni le realm master ; changer le compte bootstrap ; activer la détection brute-force.

SSO Keycloak (OIDC)

Realm unique eportail. Chaque application front est un client OIDC ; l'autorisation fine (droits par module/dossier) vit dans la base Identité, pas dans Keycloak.

ClientApplicationredirect_uri
portailPortail…portail.iiq.test/signin-oidc
theiaFactures…factures.iiq.test/signin-oidc
theiaposPOS…pos.iiq.test/signin-oidc

Points d'implémentation clés (valables derrière la passerelle, en HTTP comme en HTTPS) :

  • ForwardedHeaders (X-Forwarded-Proto/Host) dans chaque app → le redirect_uri est reconstruit sur l'URL publique (sous-domaine).
  • response_mode=query (au lieu de form_post) → le code revient par une redirection GET, compatible avec des cookies SameSite=Lax.
  • Cookies de corrélation/nonce en Lax + SecurePolicy=SameAsRequest (indispensable en HTTP).
  • Provisioning JIT : à la 1ʳᵉ connexion, l'utilisateur Keycloak (sub) est rapproché/créé et le principal est réécrit avec l'identité interne + habilitations (service Identité).
sequenceDiagram participant N as Navigateur participant GW as Passerelle participant APP as App (Portail/Factures/POS) participant KC as Keycloak N->>GW: GET portail.iiq.test GW->>APP: proxy (localhost) APP-->>N: 302 vers auth.iiq.test (challenge OIDC) N->>KC: login (via passerelle) KC-->>N: 302 redirect_uri ?code=… (GET) N->>APP: /signin-oidc?code (via passerelle) APP->>KC: échange code ↔ token (back-channel) APP->>APP: provisioning JIT + cookie de session

Multi-tenant (multi-société)

  • Control plane : le Portail possède l'annuaire (Theia.Tenancy.ControlPlane) — liste des shards mutualisés et des tenants (dossiers) ; THEIA/POS le lisent.
  • Routing par CodeDossier (Theia.Tenancy) : chaque dossier est résolu vers sa base — pool (mutualisé, réparti least-loaded) ou silo (base dédiée épinglée).
  • Middlewares THEIA TenantRoutingMiddleware / TenantMiddleware : choisissent la base avant tout accès données ; cookie theia_dossier.
  • Cloisonnement applicatif : filtre global EF par SocieteId ; l'accès à une société est validé contre les habilitations de l'utilisateur (fail-closed).
  • Bascule cross-shard (corrigé) : le DbContext d'une requête est lié au shard du cookie courant. Pour ouvrir un dossier hébergé sur un autre shard, on résout la connexion cible et on ouvre un contexte dédié le temps de retrouver la société (mêmes cas pour les widgets KPI serveur→serveur).
  • Provisioning JIT conscient du shard : les sociétés manquantes sont créées dans la base du shard assigné au dossier (allocateur least-loaded), et la liste des sociétés d'un utilisateur est agrégée cross-shard.
  • Administration des shards : répartition & charge, ajout semi-automatique d'un shard (création base + provisioning) depuis THEIA et depuis la console Portail (clé plateforme partagée). Cible prod : ~2500 membres → 3 shards.
Deux clusters PostgreSQL (dev). :5432 (la plupart des bases) et :5433 (Catalogue & POS) — le :5433 doit tourner sinon Catalogue/POS échouent au démarrage.

Référentiels partagés & stockage

📚 Catalogue (:5130)

Référentiel articles / stock central (module maître). Consommé par THEIA et POS (Contracts + Client HTTP). Sync descendante.

📇 Tiers (:5140)

Référentiel clients partagé THEIA ⇄ POS (write-through / synchro). Base theia_tiers.

🗄️ Stockage objet

Theia.Stockage : tous les documents (achats/ventes/BL/PJ/GED) sortis de la base et du disque → stockage objet S3 (MinIO :9000 en dev). URLs signées.

🔎 RNE / INPI

Base RNE locale (lecture) pour enrichir les fournisseurs (SIREN, dirigeants).

PDP / e-facturation

  • Émission : ServicePdpEffinum (OAuth + dépôt Factur-X sur Serensia/Effinum, API AFNOR).
  • Réception : ServicePdpAchats (flux entrants, cycle de vie, extraction des lignes depuis le XML).
  • Synchronisation : ServicePollingPdp (poller intégré, repli) et worker dédié Theia.PdpWorker (pull).
  • Ingress public : Iiqarion.WebhookIngress — service autonome (.NET + PostgreSQL + LettuceEncrypt) qui reçoit les webhooks PDP et qu'un worker draine.

Choix techniques récents MAJ 27/09

🧱 Migrateur additif (MigrateurLeger)

  • Au démarrage, par base physique : EnsureCreated puis, en PostgreSQL, ALTER TABLE ADD COLUMN des colonnes manquantes et CREATE TABLE des tables absentes (jamais de recréation → données préservées).
  • Robustesse dates : défaut SQL valide pour DateOnly/DateTime/TimeOnly (fini le DEFAULT '' refusé par PostgreSQL) ; sur une table neuve, colonnes NOT NULL sans DEFAULT.

📄 Génération & lecture de documents

  • QuestPDF 2026.7 : PDF/A-3 Factur-X (fond de page + filigrane via page.Background().Layers), tickets & Z POS.
  • PDFtoImage (PDFium/SkiaSharp) : rastérisation PDF→PNG pour l'aperçu fit-width.
  • ClosedXML : lecture des imports Excel (.xlsx) — reprise SAGE/EFAC/CEGID (CSV UTF-8/Latin1 + XLSX).

🔐 Keycloak sur PostgreSQL (prod)

  • Bascule du stockage Keycloak vers PostgreSQL via docker-compose (dev + prod), KC_HOSTNAME piloté par environnement, HTTPS strict en prod.

🗄️ Stockage objet & reprise

  • Tous les documents (achats/ventes/BL/PJ/GED) en objet S3/MinIO (URLs signées) via Theia.Stockage.
  • Outil d'export/réinjection MinIO (mc mirror) pour migrer d'une instance objet à l'autre.
E-reporting (nouveau module). Le brouillard de caisse ajoute ses tables (Caisse, JourneeCaisse, LigneRecette, VentilationPaiement/Tva, MouvementEspeces, ExportComptable, ParametrageComptable, ZReportManuel) dans la base theia, avec dépôt e-reporting sur la PDP.

Ports & déploiement

ServicePort (dev)Exposition
Passerelle (YARP)80 (443 prod)Exposé
Keycloak8091localhost (via auth.iiq.test)
Portail Client5199localhost
IIQARION Factures5087localhost
IIQARION POS5210localhost
Site vitrine5161localhost
Identité5100localhost (serveur→serveur)
Catalogue5130localhost
Tiers5140localhost
MinIO (objet)9000localhost
smtp4dev (mails dev)2525 / 5050localhost

Bases PostgreSQL

Cluster :5432 — portail (control plane + GED) · theia / theia_shard2 (facturation, shards) · theia_tiers · identite · rne (lecture) · iiqarion_site · webhook_ingress · base Keycloak.
Cluster :5433 — catalogue · pos (doit être démarré, sinon Catalogue/POS échouent au boot).

Lancement (dev)

  • eportail-iam\local\start-eportail.ps1 — Keycloak (localhost, hostname auth.iiq.test).
  • start-portail-stack.ps1 — Identité, Catalogue, Tiers, Factures, Portail.
  • POS, Site, Passerelle — dotnet run (localhost) + passerelle sur :80.
  • Iiqarion.Gateway\finaliser-lan.ps1 (admin) — hosts + pare-feu (port 80 uniquement).
Prod. Passerelle en Service Windows, HTTPS :443 auto (LettuceEncrypt), DNS réel pour les sous-domaines, PostgreSQL managé, Keycloak start (pas start-dev) avec console admin non exposée.
IIQARION — Documentation technique · 27/09/2026.