Contrôle d'accès — Feature Flags
Cascade de features App/Network/Customer, activation conditionnelle et intégration dans les guards.
Contrôle d'accès — Feature Flags
Ce chapitre couvre la cascade de features et leur utilisation concrète. Lisez Modèle de droits et Guards & Hooks en premier.
Modèle de cascade
Une feature est le résultat de trois activations indépendantes. Si une seule est false, la feature est indisponible pour l'utilisateur :
Chargement du diagramme…
Conséquence pratique : un Partner peut activer une feature globalement au niveau App, mais un Customer spécifique peut la désactiver pour ses utilisateurs. Ce mécanisme permet une granularité fine sans code custom.
Features standard de la plateforme
| Feature | Description |
|---|---|
invitations | Flux d'invitations utilisateur (envoi email, acceptation, provisioning) |
antivirus | Scan antivirus des fichiers uploadés (ClamAV via le module antivirus) |
hds | Fonctionnalités Healthcare Data Security (le module HDS) |
ai-translation | Traduction automatique IA (consomme des crédits) |
export | Export + ancrage blockchain Stellar (voir Module Stellar pour les limites de valeur probatoire selon le réseau configuré) |
audit | Consultation des logs d'audit |
billing | Module de facturation et gestion des crédits |
Utilisation côté frontend
Vérification simple
typescriptimport { useEffect, useState } from "react"; import { useAuth } from "@/lib/auth-context"; function InvitationsSection() { const { user } = useAuth(); const [enabled, setEnabled] = useState<boolean | null>(null); useEffect(() => { if (!user?.customer?.wid) return; fetch(`/api/proxy/config/app-features/check/${user.customer.wid}/invitations`) .then((r) => r.json()) .then((data) => setEnabled(data.enabled)); }, [user?.customer?.wid]); if (enabled === false) { return null; // Masquer complètement la section } return <InvitationsList />; }
Ce n'est PAS un champ synchrone de
useAppRights()//me— la vérification passe par un appel réseau àGET /api/config/app-features/check/{customerWid}/{featureCode}(voir Guards & Hooks).
Combinaison feature + droit
tsx// Résolvez la feature en amont (cf. exemple ci-dessus), puis combinez avec le droit : {enabled && ( <Protected right="invitations.create"> <InviteUserButton /> </Protected> )}
Ordre recommandé : vérifier la feature en premier. Si la feature est désactivée, inutile de tester les droits — l'API retournera
403 Feature désactivéede toute façon.
Activation via l'API
Les endpoints réels vivent sous /api/config/app-features — pas de route dédiée par customerWid/appWid + nom de feature :
httpGET /api/config/app-features/check/{customerWid}/{featureCode} Authorization: Bearer <wakaToken>
Vérifie si une feature précise est activée en cascade (App ∧ Network ∧ Customer) pour ce Customer — c'est l'endpoint à appeler côté frontend/backend pour une vérification ponctuelle (voir Guards & Hooks).
httpGET /api/config/app-features/enabled/{customerWid} Authorization: Bearer <wakaToken>
Liste toutes les features actives pour ce Customer.
httpGET /api/config/app-features/by-app/{appWid} Authorization: Bearer <wakaToken>
Liste les features définies pour une App (niveau Partner).
L'activation/désactivation elle-même (création, modification, suppression d'une entrée AppFeature/NetworkAppFeature/CustomerAppFeature) passe par un CRUD générique par identifiant interne : GET/POST/PATCH/DELETE /api/config/app-features/{id}. Consultez la Référence API ou la spec OpenAPI pour le corps exact de ces requêtes — il n'existe pas de route qui prend directement {customerWid}/{appWid} + un nom de feature en paramètre pour un PATCH d'activation.
Guard backend NestJS — RequireFeature
Si votre Wakapp expose des endpoints qui nécessitent une feature active :
typescript// decorators/require-feature.decorator.ts import { SetMetadata } from "@nestjs/common"; export const REQUIRED_FEATURE_KEY = "requiredFeature"; export const RequireFeature = (feature: string) => SetMetadata(REQUIRED_FEATURE_KEY, feature);
typescript// guards/feature.guard.ts import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common"; import { Reflector } from "@nestjs/core"; @Injectable() export class FeatureGuard implements CanActivate { constructor(private reflector: Reflector) {} async canActivate(context: ExecutionContext): Promise<boolean> { const requiredFeature = this.reflector.getAllAndOverride<string>( REQUIRED_FEATURE_KEY, [context.getHandler(), context.getClass()] ); if (!requiredFeature) return true; const request = context.switchToHttp().getRequest(); const enrichedPayload = request.enrichedPayload; // mis à jour par AuthMiddleware const customerWid = enrichedPayload?.cid; // Pas de champ "features" dans le token/enrichedPayload : on interroge // l'API dédiée à chaque requête (envisagez un cache court côté Wakapp). const res = await fetch( `${process.env.WAKASTART_API_URL}/api/config/app-features/check/${customerWid}/${requiredFeature}`, { headers: { "x-api-key": process.env.WAKASTART_API_KEY! } }, ); const { enabled } = (await res.json()) as { enabled: boolean }; if (!enabled) { throw new ForbiddenException( `La fonctionnalité ${requiredFeature} n'est pas activée pour ce customer` ); } return true; } }
Usage :
typescript@Controller("invitations") @UseGuards(FeatureGuard, AppRightGuard) export class InvitationsController { @Post() @RequireFeature("invitations") @RequireAppRight("invitations.create") async createInvitation(@Body() dto: CreateInvitationDto) { // ... } }
Bonnes pratiques
- Vérifier la feature avant d'afficher l'UI : ne pas attendre le
403de l'API pour masquer un bouton. - Pas de feature par défaut : si
GET app-features/check/...renvoieenabled: false(ou si la feature n'apparaît pas dansapp-features/enabled/{customerWid}), considérez-la désactivée. - Logging : loggez les tentatives d'accès à des features désactivées pour identifier les bugs d'UI.
Pièges classiques
- Feature absente ≠ erreur : si
app-features/enabled/{customerWid}renvoie un tableau vide, ce n'est pas un bug — le customer n'a simplement aucune feature activée. - Ne pas confondre feature et AppRight : une feature active ne garantit pas le droit — il faut les deux.
- Pas de cache automatique : contrairement à
adminLevel/appRights(dans/me, mis en cache côté client), l'état d'une feature n'est PAS dans/me— chaque vérification appelleapp-features/check/.... Mettez en place votre propre cache court côté Wakapp si le volume d'appels le justifie.
Aller plus loin
- Modèle de droits : adminLevel, appRights
- Invitations : exemple complet de feature + droit combinés