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ôle | Niveau | Permissions Clés |
|---|---|---|
| Owner |
| |
| Admin |
| |
| Éditeur |
| |
| Lecteur |
|
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.
// 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.
// 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.
Date — Heure — Utilisateur (Rôle) — Action — Entité ciblée.// 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é.
-- 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.
// 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.