WakaStart
Sécurité

Contrôle d'accès — Feature Flags

Cascade de features App/Network/Customer, activation conditionnelle et intégration dans les guards.

Version v1.04 min de lecture

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

FeatureDescription
invitationsFlux d'invitations utilisateur (envoi email, acceptation, provisioning)
antivirusScan antivirus des fichiers uploadés (ClamAV via le module antivirus)
hdsFonctionnalités Healthcare Data Security (le module HDS)
ai-translationTraduction automatique IA (consomme des crédits)
exportExport + ancrage blockchain Stellar (voir Module Stellar pour les limites de valeur probatoire selon le réseau configuré)
auditConsultation des logs d'audit
billingModule de facturation et gestion des crédits

Utilisation côté frontend

Vérification simple

typescript
import { 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ée de 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 :

http
GET /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).

http
GET /api/config/app-features/enabled/{customerWid} Authorization: Bearer <wakaToken>

Liste toutes les features actives pour ce Customer.

http
GET /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 403 de l'API pour masquer un bouton.
  • Pas de feature par défaut : si GET app-features/check/... renvoie enabled: false (ou si la feature n'apparaît pas dans app-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 appelle app-features/check/.... Mettez en place votre propre cache court côté Wakapp si le volume d'appels le justifie.

Aller plus loin