Skip to content

Interface Portail Captif

Portail d'authentification WiFi captif pour abonnés ACCESS — SPA HTML/JS/CSS <50KB, chargé par redirection DNS sinkhole.

URL de production : https://access-rgz.duckdns.org

Flux d'Authentification

Écrans Détaillés

Écran 1 : Accueil (Saisie MSISDN)

┌─────────────────────────────────────┐
│     [Logo ACCESS - Jaune/Bleu]      │
│                                     │
│  BIENVENUE SUR ACCESS WiFi          │
│  Connexion rapide & gratuite        │
│                                     │
│  Numéro de téléphone (E.164)        │
│  ┌─────────────────────────────────┐│
│  │ +229 [__________]               ││
│  │ Env: +22997979964               ││
│  └─────────────────────────────────┘│
│                                     │
│  [Recevoir OTP sur SMS]  (CTA jaune)│
│                                     │
│  ℹ️ Code 6 chiffres envoyé par SMS  │
│     Valide 5 minutes                │
│                                     │
│ Powered by RGZ · Toujours +         │
└─────────────────────────────────────┘
ChampValidationNotes
MSISDNRegex E.164 +229\d{8}Obligatoire, formaté auto
Rate limitMax 3 OTP par MSISDN / 60sRedis clé rgz:rate:{phone}
Erreur"Numéro invalide ou trop de tentatives"Rouge #da3747

API : POST /api/v1/auth/request-otp

TIP

Validation en temps réel — Le portail valide le format MSISDN avant envoi, affiche erreur rouge immédiate.


Écran 2 : Saisie OTP (Code 6 chiffres)

┌─────────────────────────────────────┐
│     [Logo ACCESS]                   │
│                                     │
│  SAISISSEZ VOTRE CODE OTP           │
│  Reçu par SMS à +229...             │
│                                     │
│  Saisie code 6 chiffres             │
│  ┌─────────────────────────────────┐│
│  │ [_][_][_][_][_][_]              ││
│  │ Entrée auto-focus, numériques    ││
│  └─────────────────────────────────┘│
│                                     │
│  [Valider le code]  (CTA jaune)     │
│  Pas reçu ? [Renvoyer]              │
│                                     │
│  ⏱️ Expire dans : 04:35              │
│  (Chrono régressif 5 min → 0)       │
│                                     │
│ Powered by RGZ · Toujours +         │
└─────────────────────────────────────┘
ChampValidationNotes
OTP6 chiffres uniquementEntrée numérique, masquée
TTL300 secondes (5 min)Redis clé rgz:otp:{phone}
TentativesMax 3 par OTPAprès 3 : "OTP invalide, demandez un nouveau"
ChronoRegressif live (JS)Si expire → bouton "Renvoyer OTP" actif
Erreur"Code invalide" ou "Code expiré"Texte rouge, retry possible

API : POST /api/v1/auth/verify-otp

WARNING

Sécurité — OTP lié à subscriber_id (UUID interne), pas seulement au téléphone. Stocké Redis avec salt, comparaison hmac.compare_digest().


Écran 3 : Sélection Forfaits

┌─────────────────────────────────────┐
│     [Logo ACCESS]                   │
│                                     │
│  CHOISISSEZ VOTRE FORFAIT           │
│  Accès illimité · Pas d'engagement  │
│                                     │
│  ┌──────────┐  ┌──────────┐         │
│  │ JOURNALIER│ │  HEBDO   │         │
│  │   500 F   │ │ 2 000 F  │         │
│  │ 24 heures │ │ 7 jours  │         │
│  │ [Choisir] │ │ [Choisir]│         │
│  └──────────┘  └──────────┘         │
│                                     │
│  ┌──────────────┐                   │
│  │    MENSUEL   │                   │
│  │  5 000 F     │                   │
│  │ 30 jours     │                   │
│  │ [Choisir]    │                   │
│  └──────────────┘                   │
│                                     │
│ ✓ 2 appareils connectés simult.     │
│ ✓ Support client 24/7               │
│                                     │
│ Powered by RGZ · Toujours +         │
└─────────────────────────────────────┘
ForfaitDuréePrixDonnéesNotes
Journalier24h500 FIllimitéesReset 00:00 UTC
Hebdomadaire7j2 000 FIllimitéesReset chaque dimanche
Mensuel30j5 000 FIllimitéesReset chaque mois

API : GET /api/v1/forfaits (récupère grille tarifaire dynamique)

INFO

Tarification — Modifiables via endpoint admin PUT /api/v1/forfaits/{id}. Les prix affichés = tarif_momo (MTN MoMo par défaut). Convertis dynamiquement pour Moov/Wave/Celtiis si sélectionnés.


Écran 4 : Paiement KKiaPay

┌─────────────────────────────────────┐
│     [Logo ACCESS]                   │
│                                     │
│  PAIEMENT SÉCURISÉ (KKiaPay)        │
│  Forfait MENSUEL · 5 000 F          │
│                                     │
│  Choisissez votre méthode :         │
│                                     │
│  ┌─────────────────────────────────┐│
│  │ 📱 MTN MoMo        (par défaut) ││
│  │ Envoyez 5000F au *143# (dialer) ││
│  └─────────────────────────────────┘│
│  ┌─────────────────────────────────┐│
│  │ 📱 Moov Flooz                   ││
│  │ Envoyez #136# → Payer           ││
│  └─────────────────────────────────┘│
│  ┌─────────────────────────────────┐│
│  │ 💳 Wave Benin                   ││
│  │ Paiement bancaire mobile        ││
│  └─────────────────────────────────┘│
│  ┌─────────────────────────────────┐│
│  │ 💰 Celtiis Cash                 ││
│  │ Portefeuille numérique          ││
│  └─────────────────────────────────┘│
│                                     │
│ 🔒 Paiement chiffré SSL/TLS         │
│ En cours de redirection KKiaPay...  │
│                                     │
│ Powered by RGZ · Toujours +         │
└─────────────────────────────────────┘
FournisseurCode AppelNotesTimeout
MTN MoMo*143# → Saisissez montantProvider officiel RGZ10 min
Moov Flooz#136# → Menu PayerAccepte E-money10 min
Wave BeninApp Wave / SMSBank benin10 min
Celtiis CashCode *133#Minority provider10 min

API : POST /api/v1/payments/kkiapay-request → Webhook POST /api/v1/payments/kkiapay-webhook

WARNING

Idempotency — Chaque transaction KKiaPay stockée en DB avec kkiapay_transaction_id UNIQUE. Webhook avec replay protection (Redis rgz:webhook:kkiapay:{txn_id} = processed). En cas de double webhook → déjà traitée, silencieux 200 OK.


Écran 5 : Succès (Session Active)

┌─────────────────────────────────────┐
│     [Logo ACCESS - Vert ✓]          │
│                                     │
│  CONNEXION RÉUSSIE ! ✓              │
│  Vous êtes connecté à ACCESS        │
│                                     │
│  Informations de session :          │
│  ┌─────────────────────────────────┐│
│  │ 📱 Abonné : RGZ-0197979964      ││
│  │ 🎫 Forfait : MENSUEL (5000F)    ││
│  │ 🖥️  Appareil (MAC) : C8:2A:...  ││
│  │ 📡 Point d'accès : access_foo_s1││
│  │ ⏱️  Valide jusqu'au : 25/03/2026 ││
│  │ 📊 Data : 0 MB / Illimitée       ││
│  └─────────────────────────────────┘│
│                                     │
│  [Fermer / Naviguer librement]      │
│                                     │
│  📞 Support : +229 97 98 XX XX      │
│  💬 Chat : livechat.access.local    │
│                                     │
│ Powered by RGZ · Toujours +         │
└─────────────────────────────────────┘
ÉlémentSourceMise à jour
Subscriber refDB (UUID → "RGZ-MSISDN")À la création
Forfait actifRedis rgz:session:{subscriber_id}Real-time
MAC de l'appareilRADIUS User-Name (MAC)À chaque session
NAS-ID (point accès)Détecté par portailAutomatique
ExpirationCalculée (session_start + durée forfait)Dashboard réel-time
Data usageRADIUS Acct-Output-OctetsPolling 30s

API : GET /api/v1/sessions/me (affiche session active) — appelée toutes les 30s

TIP

Dashboard Session — Refresh automatique à chaque changement. Affiche aussi bouton "Déconnecter" pour logout forcé (DELETE session → RADIUS CoA).


Composants Partagés (React)

javascript
// portal/js/portal.js
class PortalApp {
  screens = ['Accueil', 'OTP', 'Forfaits', 'Paiement', 'Succès']

  async requestOTP(msisdn) // Appel POST auth/request-otp
  async verifyOTP(phone, code) // Appel POST auth/verify-otp
  async loadForfaits() // Appel GET forfaits
  async selectForfait(forfait_id) // Prépare paiement
  async initKKiaPay(montant, provider) // Redirection externe
  async pollSessionStatus() // GET sessions/me toutes 30s
}

Considérations Sécurité

  • MSISDN validation : Regex E.164 + contre-appel SMS obligatoire (LL#06)
  • OTP : HMAC avec salt, TTL strict 300s, max 3 tentatives (LL#06)
  • KKiaPay webhook : Header x-kkiapay-secret validé, idempotency Redis (LL#03)
  • Session : JWT RS256 stocké Redis, SameSite=Strict cookie (LL#11)
  • CORS : Whitelist explicite (jamais wildcard), SameSite=Strict (LL#11)
  • Rate limiting : 3 OTP/min, 5 logins/min par IP (nginx limit_req_zone) (LL#14)

Considérations Réseau

Redirect DNS sinkhole (Unbound):
  *.access.local → IP portail (ngrok/localhost dev, 10.y.0.1 prod)

Proxy nginx (portail):
  / → static HTML (<50KB SPA)
  /api/* → proxify vers rgz-api:8000/api/v1/

User-Agent detection (JS):
  Si mobile + iOS/Android → affiche clavier mobile
  Si desktop → focus input MSISDN

Dernière mise à jour : 2026-02-24 Stack : HTML5 + Vanilla JS + CSS3 (Poppins font) Hébergement : nginx:alpine (docker/portal/) HTTPS : Let's Encrypt via Traefik (auto-renew)

PROJET MOSAÏQUE — 81 outils, 22 conteneurs, 500+ revendeurs WiFi Zone