internal/ — l'inventaire

Ce qu'il y a dans la boîte

L'inventaire complet : chaque paquet métier et ce qu'il fait, de vrais extraits du code que vous lirez, et la base de sécurité en dessous. Tout ce qui figure sur cette page est mesuré sur le code que reçoit l'acheteur.

Inventaire

Tout ce qu'exige un SaaS B2B, déjà écrit et testé

Pas un squelette qui s'arrête à l'écran de connexion. Chaque ligne ci-dessous est du code qui tourne, couvert par des tests, dans le dépôt que vous achetez.

internal/auth

Une authentification faite correctement

Tous les parcours qu'exige un vrai produit, et les cas d'échec traités : énumération, rejeu, limitation de débit, invalidation des sessions.

  • Email et mot de passe hachés en argon2id (64 Mio, 3 itérations)
  • Vérification d'email, réinitialisation, et un lien magique qui sert aussi d'inscription sans mot de passe
  • Google, GitHub, Microsoft Entra ID, et n'importe quel fournisseur OIDC par découverte
  • Double facteur TOTP : enrôlement par QR code, dix codes de secours à usage unique, exigé sur tous les chemins de connexion
  • Réponses neutres partout — inscription, connexion et réinitialisation ne révèlent jamais l'existence d'un compte
  • Limitation de débit par IP et par compte ; un changement de mot de passe termine toutes les autres sessions
  • Jetons à usage unique stockés en SHA-256, avec une durée de vie propre à chaque usage
  • OAuth ne fusionne jamais un compte en silence : une collision d'email exige un rattachement explicite

internal/org

Équipes, rôles et isolation stricte

Le multi-tenant est une règle au niveau de la ligne, imposée par un middleware et revérifiée par les tests — pas une convention à se rappeler.

  • Organisations avec les rôles propriétaire, admin et membre, appliqués côté serveur
  • Invitations par email : jetons à usage unique, valables sept jours
  • Chaque table métier porte organization_id ; chaque requête filtre dessus
  • Un tiers reçoit un 404, jamais un 403 qui confirmerait l'existence de l'organisation
  • Le dernier propriétaire ne peut être rétrogradé, retiré ni partir sans transmettre
  • ORG_MODE=solo transforme le même code en produit B2C, avec une organisation personnelle par compte
  • Un module Projets est livré comme modèle à copier pour vos propres ressources
  • L'isolation entre organisations est couverte de bout en bout, pas affirmée en prose

internal/billing

Des paiements qui survivent à un webhook dupliqué

Deux fournisseurs derrière une même couture, et une idempotence que vous pourriez expliquer à un auditeur.

  • Abonnements et produits à l'unité via Stripe, derrière une couture fournisseur unique
  • Webhooks idempotents : remboursements et litiges gérés, les rejeux sont sans effet
  • L'abonnement appartient à l'organisation : Checkout, portail client, changement de plan, factures
  • Les paiements uniques partagent la même configuration de plans et atterrissent dans une table purchases liée à l'organisation
  • Les webhooks lisent le corps brut, vérifient la signature et répondent vite
  • L'identifiant d'événement et l'écriture métier sont validés dans la même transaction — un rejeu ne fait rien
  • Les objets sont re-lus depuis l'API du fournisseur par identifiant ; le contenu du payload n'est jamais cru
  • Les plans sont de la configuration : la page de prix et le serveur ne peuvent pas diverger

internal/jobs · internal/email

Emails et tâches de fond qui survivent à un redéploiement

La file est une table, pas une goroutine qui retient les emails de vos utilisateurs jusqu'au prochain redémarrage.

  • File de tâches durable en base : réservation, exécution, replanification, réessai
  • Mailer à trois implémentations : le log serveur en développement, n'importe quel relais SMTP, ou Resend
  • 16 modèles transactionnels, embarqués dans le binaire, en français et en anglais
  • Trois tentatives avec back-off exponentiel ; les échecs définitifs sont journalisés en dead letters
  • Les emails sont rendus depuis la langue enregistrée sur le compte — une tâche de fond n'a aucun en-tête à lire
  • SPF, DKIM et DMARC documentés, parce que la délivrabilité n'est pas optionnelle

internal/storage

Fichiers, sessions et remontée d'erreurs

La surface d'exploitation qu'un produit finit par exiger, traitée sans SDK et sans enfermement.

  • Le stockage passe par une seule interface : disque local en développement, service compatible S3 en production
  • Le client S3 fait ~250 lignes lisibles de SigV4 sur net/http, pas un SDK
  • Les fichiers sont servis par l'application : les buckets restent privés et l'appartenance est vérifiée à chaque téléchargement
  • Les envois sont plafonnés et leur type est déterminé côté serveur sur une liste blanche (le SVG est refusé, volontairement)
  • Chaque utilisateur voit les appareils connectés à son compte et les révoque, un par un ou tous à la fois
  • Chaque log de niveau Error atteint un reporter interchangeable ; SENTRY_DSN active celui fourni

internal/admin

Un back-office, et les chiffres qui vont avec

Modération et métriques pour la personne qui tient la boutique, protégés côté serveur par une seule variable d'environnement.

  • Console super-admin, réservée côté serveur via SUPER_ADMIN_EMAIL
  • MRR, ARPU, churn à 30 jours, inscriptions et répartition des plans, calculés depuis votre propre base
  • Les montants sont rapportés par devise plutôt qu'additionnés en une fiction
  • Modération : bannir, supprimer, se faire passer pour un utilisateur — l'usurpation est tracée dans le journal d'audit
  • Liste des organisations en lecture seule et journal d'audit consultable
  • Aucun aller-retour vers le fournisseur : la page s'affiche instantanément et fonctionne même si Stripe est en panne

web/

Une application Vue 3 que vous auriez écrite

TypeScript, Composition API, Pinia, Tailwind — avec les parties ingrates déjà traitées : CSRF, erreurs, toasts, mode sombre, i18n.

  • Vue 3 et TypeScript, Vite, Pinia, Vue Router, Tailwind CSS v4
  • Un client fetch typé qui porte le jeton CSRF et remonte les erreurs de l'API
  • Kit d'interface : modale, toasts, tableau paginé, avatars, alertes de formulaire
  • Mode sombre appliqué avant le premier rendu et conservé au rechargement
  • Français et anglais sur chaque vue, avec la langue enregistrée sur le compte
  • SEO intégré : pages publiques pré-rendues en HTML statique au build — canonical, hreflang, URLs par langue et sitemap — lisibles par les moteurs et les assistants IA, sans JavaScript (cette page même est servie ainsi)
  • Écrans applicatifs : tableau de bord avec checklist d'onboarding, projets, membres, facturation, réglages du compte

Makefile

Deux commandes font le travail répétitif

Le kit génère la chaîne que vous recopieriez à la main, et documente ses arbitrages au lieu de laisser des TODO.

  • make setup pose quelques questions et écrit .env, clé CSRF comprise
  • make scaffold génère une ressource complète liée à l'organisation : migration, requêtes, handler, routes, store, vue, tests
  • make seed remplit une base de développement avec des utilisateurs, une organisation et des projets
  • sqlc transforme du vrai SQL en Go typé — pas d'ORM, pas de query builder, rien à désapprendre
  • Un blog markdown rendu par Go hors de la SPA, avec SEO par page, sitemap et robots.txt
  • DECISIONS.md consigne chaque arbitrage ambigu ; le code ne contient aucun TODO

Architecture

Lisez la partie qu'on vous cache d'habitude

Deux extraits, copiés du dépôt. C'est la même règle vue des deux bouts : le tenant est décidé à un seul endroit, et chaque requête le respecte.

Dépôt
cmd/server/main.gointernal/  auth/        sign-in, tokens, OAuth, TOTP  org/         organizations, members, invitations  billing/     provider seam → stripe (webhooks, refunds)  jobs/        durable queue and workers  email/       mailer + embedded templates  storage/     local disk · S3-compatible  safehttp/    hardened outbound client  admin/       metrics, moderation, audit log  http/        chi router · handlers/ · middleware/  db/          sqlc queries, goose migrationsmigrations/    portable SQL, both enginesweb/           Vue 3 app, embedded in the binary
Vingt-quatre packages Go, un par sujet. Aucune couche à traverser avant d'atteindre le code qui répond à la requête.
internal/db/queries/projects.sqlsql
-- name: ListOrgProjects :manySELECT * FROM projectsWHERE organization_id = $1ORDER BY created_at DESC; -- name: GetProject :oneSELECT * FROM projectsWHERE id = $1 AND organization_id = $2;
Les requêtes sont des fichiers SQL. sqlc les compile en Go typé, et chaque requête métier filtre par organisation.
internal/http/middleware/org.gogo
// Non-members get a 404, indistinguishable from an org// that does not exist: ids never leak across tenants.m, err := o.Q.GetOrgMember(r.Context(), sqlcgen.GetOrgMemberParams{	OrganizationID: orgID, UserID: u.ID,})if errors.Is(err, sql.ErrNoRows) {	writeError(w, http.StatusNotFound, "not found")	return}
L'autre bout de la règle : l'appartenance est résolue dans un middleware, et un tiers ne peut pas distinguer une organisation qu'il n'a pas le droit de voir d'une organisation qui n'existe pas.

Sécurité

Une checklist auditable, pas un badge

La sécurité, c'est surtout une liste de choses qui doivent être vraies. Voici la liste, et chaque ligne a un test derrière elle.

  • Mots de passe

    argon2id à 64 Mio et 3 itérations, avec un sel par mot de passe. Aucun pepper à perdre, aucun hachage hérité à migrer.

  • Sessions

    Côté serveur, stockées en base. Les cookies sont HttpOnly, SameSite=Lax et Secure en production.

  • CSRF

    Chaque route qui modifie l'état est protégée, et le client du frontend porte le jeton pour vous.

  • Limitation de débit

    Par IP et par compte sur la connexion, l'inscription, la réinitialisation et le formulaire de contact.

  • Pas d'énumération

    Inscription, connexion et réinitialisation répondent la même chose, que l'adresse soit connue ou non.

  • Isolation des tenants

    Filtrage ligne à ligne sur chaque requête, un 404 pour les tiers, et des tests de bout en bout qui tentent de franchir la limite.

  • RBAC côté serveur

    Les rôles sont imposés par middleware. Ce que le frontend masque est cosmétique, et les tests le disent.

  • Requêtes sortantes

    Un client durci valide l'IP au moment de la connexion, revérifie chaque redirection et retire les identifiants en cours de route.

  • En-têtes

    CSP stricte, HSTS, X-Content-Type-Options, X-Frame-Options et politique de référent, sur chaque réponse.

  • Jetons

    Stockés en SHA-256, à usage unique, comparés en temps constant, avec une durée de vie par usage.

  • Webhooks

    Signature vérifiée, rejeu neutralisé par clé primaire, et contenu jamais cru au-delà de l'identifiant de l'objet.

  • RGPD

    Export des données et suppression de compte sont des fonctionnalités, et les actions d'administration sont tracées dans un journal d'audit.

232 tests Go tournent en CI sur les deux moteurs de base, plus 14 parcours Playwright qui pilotent le vrai binaire de production : inscription et vérification, invitations, refus RBAC, isolation entre organisations, signature et double livraison de webhook.

Déployer

Un serveur, trois commandes, HTTPS compris

Aucune plateforme de build à louer, aucun pipeline à apprendre. Le frontend est embarqué dans le binaire et les migrations s'appliquent au démarrage.

  1. 01

    Configurer

    make setup demande ce dont vous avez besoin et écrit .env en générant la clé CSRF. Quarante et une variables sont documentées ; les valeurs par défaut suffisent en développement.

  2. 02

    Compiler

    make build produit un binaire unique avec l'application Vue à l'intérieur. Ou confiez le même dépôt à Docker : l'image de production est un build multi-étapes qui finit sur une base distroless.

  3. 03

    Servir

    Traefik s'occupe de Let's Encrypt et de la redirection HTTP vers HTTPS. L'application ne fait confiance à X-Forwarded-For que depuis le réseau du proxy, et la base n'est jamais exposée.

La production, depuis zéro
$ docker network create traefik$ ACME_EMAIL=you@example.com \    docker compose -f deploy/traefik/docker-compose.yml up -d$ cp .env.example .env && make setup$ docker compose -f docker-compose.prod.yml up -d --build

Mettre à jour, c'est git pull puis le même up -d --build. Sauvegardez deux volumes : la base et les fichiers envoyés — les blobs vivent hors du dump.

En développement, c'est encore plus court : make dev tourne sur SQLite sans Docker, en créant et migrant le fichier de base pour vous.

Arrêtez de reconstruire les mêmes fondations.

Comptes, équipes, facturation, emails, back-office, sécurité : les semaines qui passent avant la première ligne de votre produit. Elles sont déjà écrites, testées et documentées.

Acheter la licence — €199