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 + │
└─────────────────────────────────────┘| Champ | Validation | Notes |
|---|---|---|
| MSISDN | Regex E.164 +229\d{8} | Obligatoire, formaté auto |
| Rate limit | Max 3 OTP par MSISDN / 60s | Redis 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 + │
└─────────────────────────────────────┘| Champ | Validation | Notes |
|---|---|---|
| OTP | 6 chiffres uniquement | Entrée numérique, masquée |
| TTL | 300 secondes (5 min) | Redis clé rgz:otp:{phone} |
| Tentatives | Max 3 par OTP | Après 3 : "OTP invalide, demandez un nouveau" |
| Chrono | Regressif 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 + │
└─────────────────────────────────────┘| Forfait | Durée | Prix | Données | Notes |
|---|---|---|---|---|
| Journalier | 24h | 500 F | Illimitées | Reset 00:00 UTC |
| Hebdomadaire | 7j | 2 000 F | Illimitées | Reset chaque dimanche |
| Mensuel | 30j | 5 000 F | Illimitées | Reset 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 + │
└─────────────────────────────────────┘| Fournisseur | Code Appel | Notes | Timeout |
|---|---|---|---|
| MTN MoMo | *143# → Saisissez montant | Provider officiel RGZ | 10 min |
| Moov Flooz | #136# → Menu Payer | Accepte E-money | 10 min |
| Wave Benin | App Wave / SMS | Bank benin | 10 min |
| Celtiis Cash | Code *133# | Minority provider | 10 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ément | Source | Mise à jour |
|---|---|---|
| Subscriber ref | DB (UUID → "RGZ-MSISDN") | À la création |
| Forfait actif | Redis rgz:session:{subscriber_id} | Real-time |
| MAC de l'appareil | RADIUS User-Name (MAC) | À chaque session |
| NAS-ID (point accès) | Détecté par portail | Automatique |
| Expiration | Calculée (session_start + durée forfait) | Dashboard réel-time |
| Data usage | RADIUS Acct-Output-Octets | Polling 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)
// 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-secretvalidé, 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 MSISDNDerniè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)