WakaStart
Modules

API Invitations — Vérification & Acceptation

Endpoints verify et accept : implémentation de la page d'acceptation, effets serveur et pièges.

Version v1.04 min de lecture

API Invitations — Vérification & Acceptation

Ce chapitre couvre les endpoints publics verify et accept. Lisez Invitations — Création d'abord pour le contexte.


Endpoint — Vérifier un token (PUBLIC)

À appeler depuis votre page d'acceptation au chargement, avant d'afficher le formulaire.

http
GET /api/invitations/verify/{token}

Auth : Aucune — endpoint public. Throttle : 10 req/min/IP.

Réponse valide (200) :

json
{ "valid": true, "invitation": { "id": "uuid", "invitedEmail": "alice@acme.com", "invitedFirstName": "Alice", "invitedLastName": "Martin", "expiresAt": "2026-05-29T10:30:00.000Z", "customer": { "name": "Acme Corp" }, "profile": { "name": "Participant", "app": { "name": "MonApp" } }, "inviter": { "firstName": "Bob", "lastName": "Smith", "email": "bob@..." } } }

Réponse invalide (200 mais valid: false) :

json
{ "valid": false, "errorMessage": "Cette invitation a expiré" }

errorMessage possibles :

  • "Invitation non trouvée"
  • "Cette invitation a déjà été acceptée"
  • "Cette invitation a expiré"
  • "Cette invitation a été annulée"

Si le token est PENDING mais que expiresAt < now(), l'API marque automatiquement l'invitation EXPIRED avant de répondre.


Pattern React — Page d'acceptation

typescript
// app/accept-invitation/[token]/page.tsx async function AcceptInvitationPage({ params }: { params: { token: string } }) { const res = await fetch(`/api/proxy/invitations/verify/${params.token}`); const { valid, invitation, errorMessage } = await res.json(); if (!valid) { return <InvitationErrorPage message={errorMessage} />; } return <AcceptInvitationForm invitation={invitation} token={params.token} />; }

Composant d'erreur recommandé :

typescript
function InvitationErrorPage({ message }: { message: string }) { const messages: Record<string, { title: string; action: string }> = { "Cette invitation a expiré": { title: "Ce lien a expiré", action: "Demandez un nouveau lien à l'administrateur.", }, "Cette invitation a déjà été acceptée": { title: "Compte déjà créé", action: "Connectez-vous directement avec votre email.", }, "Cette invitation a été annulée": { title: "Invitation annulée", action: "Contactez l'administrateur pour obtenir une nouvelle invitation.", }, "Invitation non trouvée": { title: "Lien invalide", action: "Ce lien d'invitation n'existe pas. Vérifiez l'URL.", }, }; const info = messages[message] ?? { title: "Erreur", action: "Contactez le support." }; return ( <div className="flex flex-col items-center gap-4 p-8"> <h1 className="text-2xl font-bold">{info.title}</h1> <p className="text-muted-foreground">{info.action}</p> </div> ); }

Endpoint — Accepter une invitation (PUBLIC)

http
POST /api/invitations/accept/{token} Content-Type: application/json { "password": "MotDePasse2026!" }

Auth : Aucune — endpoint public. Throttle : 5 req/min/IP.

Réponse 200 :

json
{ "id": "uuid", "wid": "abc123", "email": "alice@acme.com", "firstName": "Alice", "lastName": "Martin", "customerId": "uuid", "profileId": "uuid", "adminLevel": "USER", "isActive": true, "keycloakId": "uuid-keycloak" }

Effets côté serveur :

  1. Création du compte Keycloak dans le realm du Network
  2. Création de l'utilisateur en base (adminLevel: USER, userLevel: USER — forcés)
  3. Création des UserTeam pour chaque teamAssignment
  4. Marquage de l'invitation ACCEPTED
  5. Lien UserApp (user × Customer × App) créé
  6. Envoi du mail Keycloak de configuration du mot de passe

Note : le champ password dans le body est documenté mais actuellement ignoré côté service — Keycloak envoie un email de reset password séparément.


Exemple curl — flux complet

bash
# 1. Votre backend crée l'invitation curl -X POST https://api.wakastart.app/api/invitations \ -H "x-api-key: sk_live_yourkey" \ -H "Content-Type: application/json" \ -d '{ "customerId": "11111111-1111-1111-1111-111111111111", "invitedEmail": "alice@acme.com", "profileId": "22222222-2222-2222-2222-222222222222", "language": "fr" }' # 2. L'invité clique le lien, votre page vérifie le token curl https://api.wakastart.app/api/invitations/verify/a3f8c2d1e4b7... # → 200 { valid: true, invitation: { invitedEmail, ... } } # 3. L'invité soumet le formulaire curl -X POST https://api.wakastart.app/api/invitations/accept/a3f8c2d1e4b7... \ -H "Content-Type: application/json" \ -d '{ "password": "MotDePasseSolide2026!" }' # → 200 { user créé }

Bonnes pratiques

  • Toujours appeler verify au mount avant d'afficher le formulaire — évite à l'invité de saisir un mot de passe pour rien.
  • Gérer les 4 états d'erreur avec des messages explicites adaptés.

Pièges classiques

  • Appeler accept deux fois : après acceptation, le token est invalidé. Le deuxième appel retourne 400 Cette invitation a déjà été acceptée.
  • Oublier le verify initial : l'invité remplit le formulaire, l'API répond 400 expiré, mauvaise UX.
  • userLevel forcé à USER : les invitations ne peuvent pas promouvoir un utilisateur en admin. Les droits admin s'accordent manuellement après l'acceptation.
  • Throttle sur accept : 5 req/min/IP. Un bot qui tente de brute-forcer des tokens sera bloqué après 5 essais.

Aller plus loin