Module 7 — Gouvernance & Sécurité

RBAC & Contrôle d'Accès

Définissez précisément les droits de chaque membre de l'équipe, protégez chaque route API, et suivez chaque action critique via le journal d'audit immuable.

Modèle RBAC Hiérarchique

DIXX applique une sécurité stricte par défaut via un contrôle d'accès basé sur les rôles (RBAC — Role-Based Access Control). Les droits sont propagés automatiquement sur l'interface UI et sur toutes les routes API sans configuration supplémentaire.

RôleNiveauPermissions Clés
Owner
  • Propriétaire du projet
  • Toutes permissions Admin
  • Suppression du projet
  • Gestion BYOS
Admin
  • Gestion des membres
  • Gestion des connexions
  • Delete/Drop tables
  • Configuration backups
Éditeur
  • Exécution de requêtes SQL
  • Création de pipelines
  • Migration Sandbox
  • Génération de dashboards
Lecteur
  • Lecture des dashboards
  • Consultation des logs
  • Export de rapports
  • Aucune mutation possible

Implémentation Technique

L'implémentation RBAC repose sur deux vérifications en cascade : d'abord la propriété du projet (owner_id), puis le rôle de membre actif dans team_members.

typescript
// src/lib/services/access-control.ts
// Hiérarchie des rôles avec poids numérique
const roleWeight: Record = {
  viewer: 1,
  editor: 2,
  admin:  3,
  owner:  4,
};

// Vérification : Le rôle satisfait-il le niveau requis ?
export function roleSatisfies(
  role: ProjectRole | null, 
  required: RequiredRole
): boolean {
  if (!role) return false;
  return roleWeight[role] >= roleWeight[required];
}

// Résolution du rôle effectif (Owner > Team Member)
export async function getProjectRole(
  supabase, projectId, userId
): Promise {
  // 1. Vérifier si l'utilisateur est le propriétaire
  const { data: project } = await supabase
    .from("projects").select("owner_id")
    .eq("id", projectId).maybeSingle();

  if (project?.owner_id === userId) return "owner";

  // 2. Sinon, chercher dans team_members (actif uniquement)
  const { data: member } = await supabase
    .from("team_members")
    .select("role, status")
    .eq("project_id", projectId)
    .eq("member_user_id", userId)
    .eq("status", "active")  // ← Seulement les membres actifs
    .maybeSingle();

  return member?.role || null; // "admin" | "editor" | "viewer"
}

Protection des Routes API

Chaque route API critique vérifie le rôle RBAC avant d'exécuter l'action. Un Lecteur qui tente d'exécuter une commande DELETE reçoit une réponse 403 Forbidden.

typescript
// Exemple : Protection d'une route de suppression
// src/app/api/connections/[id]/route.ts
export async function DELETE(req: Request, { params }) {
  const supabase = await createClient();
  const userId = await getAuthenticatedUserId(supabase);
  
  // Vérification RBAC : Admin ou Owner requis pour supprimer
  const canDelete = await userCanAccessProject(
    supabase, projectId, userId, "admin" // ← niveau minimum requis
  );

  if (!canDelete) {
    return NextResponse.json(
      { error: "Insufficient permissions. Admin role required." },
      { status: 403 }
    );
  }
  // ... exécution de la suppression
}

Journal d'Audit (Audit Trail)

Toute action effectuée par un membre — lecture via Smart Terminal, modification de pipeline, migration validée — est enregistrée dans le Journal d'Audit de façon immuable.

Format Standardisé
Le format strict du journal : Date — Heure — Utilisateur (Rôle) — Action — Entité ciblée.
10 Avr 2026 — 14:32 — jean@corp.io (Éditeur) — SELECT exécuté — production_db
10 Avr 2026 — 09:15 — marie@corp.io (Admin) — Pipeline Backup WORM activé — sales_history
09 Avr 2026 — 23:01 — api_key:k_xxx (System) — Migration validée — old_invoices DROP
09 Avr 2026 — 17:44 — paul@corp.io (Lecteur) — Dashboard consulté — Q1 2026 Sales
typescript
// src/lib/audit.ts — Service d'audit centralisé
export async function recordAuditEvent({
  supabase,
  projectId,
  userId,
  action,       // ex: "query.execute", "backup.create", "member.invite"
  entityType,   // ex: "database_connection", "automation", "dashboard"
  entityId,
  metadata,
}: AuditEventParams) {
  await supabase.from("audit_events").insert({
    project_id: projectId,
    user_id: userId,
    action,
    entity_type: entityType,
    entity_id: entityId,
    metadata,
    created_at: new Date().toISOString(),
  });
}

Row Level Security (RLS) Supabase

En plus du RBAC applicatif, Supabase RLS assure une isolation des données au niveau base de données. Chaque ligne est protégée par des policies qui vérifient l'identité de l'utilisateur authentifié.

sql
-- Migration: 20250412121100_dixx_rls_database_connections_team_rbac.sql
-- Politique RLS sur database_connections

-- Lecture : propriétaire du projet OU membre actif
CREATE POLICY "connections_select_team"
ON public.database_connections FOR SELECT
USING (
  project_id IN (
    SELECT p.id FROM projects p WHERE p.owner_id = auth.uid()
    UNION
    SELECT tm.project_id FROM team_members tm
    WHERE tm.member_user_id = auth.uid() AND tm.status = 'active'
  )
);

-- Écriture : propriétaire du projet OU admin de l'équipe
CREATE POLICY "connections_insert_owner_or_admin"
ON public.database_connections FOR INSERT
WITH CHECK (
  project_id IN (
    SELECT p.id FROM projects p WHERE p.owner_id = auth.uid()
    UNION
    SELECT tm.project_id FROM team_members tm
    WHERE tm.member_user_id = auth.uid() 
    AND tm.role IN ('admin') AND tm.status = 'active'
  )
);

Chiffrement AES-256

Tous les credentials de connexion (host, user, password, API keys) sont chiffrés avec AES-256 dès la saisie via src/lib/crypto.ts. Ils ne transitent jamais en clair dans les logs ou les réponses API.

typescript
// src/lib/crypto.ts (AES-256 encryption)
import crypto from "crypto";

const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!; // 32 bytes hex

export function encrypt(plainText: string): string {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(
    "aes-256-cbc",
    Buffer.from(ENCRYPTION_KEY, "hex"),
    iv
  );
  const encrypted = Buffer.concat([
    cipher.update(Buffer.from(plainText, "utf8")),
    cipher.final(),
  ]);
  return iv.toString("hex") + ":" + encrypted.toString("hex");
}

export function decrypt(encryptedText: string): string {
  const [ivHex, encryptedHex] = encryptedText.split(":");
  const iv = Buffer.from(ivHex, "hex");
  const decipher = crypto.createDecipheriv(
    "aes-256-cbc",
    Buffer.from(ENCRYPTION_KEY, "hex"),
    iv
  );
  return Buffer.concat([
    decipher.update(Buffer.from(encryptedHex, "hex")),
    decipher.final(),
  ]).toString("utf8");
}

Conformité & Traçabilité

L'architecture RBAC + Audit Trail de DIXX permet de répondre aux audits de conformité les plus exigeants. Chaque action est horodatée, attribuée et immuable.

RGPD
Traçabilité complète des accès aux données personnelles. Droit à l'oubli via politique de rétention.
SOC 2
Journalisation de toutes les actions administratives et accès aux systèmes critiques.
ISO 27001
Contrôle d'accès basé sur le moindre privilège avec hiérarchie RBAC documentée.