T2 Authentification IoBase
/Token ou /refresh avec le même token = device en erreur permanent. Recovery = suppression manuelle du device par l'administrateur IoBase + recommencer toute la procédure d'enrôlement. Toute l'architecture ci-dessous existe pour rendre cette situation impossible.
| Étape | Appel | Ce qui en sort | Bloquant |
|---|---|---|---|
| 1 · Inscription | /register | un code d'appareil | — |
| 2 · Validation | aucun | — | humain, côté plateforme — sinon 425 |
| 3 · Premier jeton | /token |
jeton d'accès + jeton de renouvellement | — |
| 4 · Renouvellement | /refresh | deux nouveaux jetons | toutes les 12 h, automatique |
L'utiliser deux fois met l'équipement en erreur permanente côté plateforme, et la récupération passe par une intervention humaine. Tout le reste de ce chapitre — verrou, garde des six heures, cloisonnement — existe pour rendre cette situation impossible.
Source de vérité
La doc officielle : Centre d'aide io-base · Procédure d'authentification via le DAP (extraits ci-dessous).
Endpoints DAP
| Endpoint | Méthode | Body | Réponse OK | Réponse KO |
|---|---|---|---|---|
/register |
POST | {"client_id": "<device_name>"} |
200 + {device_code} |
403 name déjà pris, 400 "Missing mandatory parameter(s) client_id" si mauvaise clé |
/Token |
POST | {"device_code": "<...>", "client_id": "<device_name>"} |
200 + {access_token, refresh_token} |
425 en attente validation administrateur IoBase |
/refresh |
POST | {"refresh_token": "<...>", "client_id": "<device_name>"} |
200 + {access_token, refresh_token} (nouveaux) |
401 refresh_token déjà consommé |
Cycle de vie
Phase 1 — Enrôlement (1 seule fois)
build_dap_register émet un POST /register avec body {"client_id":"<device_name>"}.handle_dap_resp stocke global.dap_device_code et persiste l'état dans /mnt/data/luvitred/dap_state.json.build_dap_token (mutex protégé) émet un POST /Token avec body {"device_code","client_id"}.handle_dap_resp stocke global.iobase_token + iobase_refresh_token, reset le mutex, persiste flash.Phase 2 — Refresh automatique (en continu)
repeat=43200 (toutes les 12 h)b. au boot : inject
once=true + onceDelay=120 (refresh ~120 s après démarrage, le temps que wwan0 monte)c. 401 reçu sur un POST IoBase ou Health →
fn_trigger_refresh_on_401d. transition WAN
false→true (retour de connectivité après une coupure longue)
{"refresh_token","client_id"} · une seule requête à la fois grâce au mutex.dap_state.json ré-écrit avant tout usage du nouveau Bearer.Les 5 protections critiques
1 · Mutex single-use
Au début de build_dap_token et build_dap_refresh, on vérifie le flag global.dap_refreshing. Si déjà true, on log DAP_REFRESH_SKIP mutex et on retourne nil. Sinon on le set à true et on continue.
Dans handle_dap_resp, le flag est reset à false aussi bien sur succès que sur échec, garantissant qu'aucun deadlock n'est possible.
2 · Persistance flash
À chaque token update (register, token, refresh), handle_dap_resp propage msg.dap_persist contenant la nouvelle state. fn_persist_dap attache msg.filename = '/mnt/data/luvitred/dap_state.json', le node save dap state (file out, overwriteFile=true) écrit. Format :
{
"device_code": "DC...",
"access_token": "AT...",
"refresh_token": "RT...",
"saved_at": 1714056789
}
3 · Restore au boot
Au démarrage du flow (deploy ou reboot CG) :
inj_load_dap_state(once @startup, +3s)file_load_dap_statelit le JSON depuis flashfn_restore_dap_stateparse via regex (sandbox Lua n'a pas json.decode garanti) et set les globals
Résultat : un reboot CG ne nécessite pas de re-enrôlement administrateur IoBase. Les tokens restent valides jusqu'à expiration naturelle.
4 · Retry sur 401
handle_iobase_resp et handle_health_resp émettent msg avec statusCode=401 vers fn_trigger_refresh_on_401 qui filtre, puis émet vers build_dap_refresh. Le mutex protège des refreshes en rafale (par ex. 10 trames qui prennent 401 d'affilée → 1 seul refresh est tiré).
La trame qui a essuyé le 401 est perdue : il n'existe pas de file de rejeu sur cette passerelle. Le renouvellement protège les trames suivantes, pas celle-là.
5 · Refresh préventif au retour de WAN
handle_cg_metrics (kind=internet) maintient global.wan_connected. Sur transition false → true, il émet un msg vers build_dap_refresh. Avantage : après une coupure longue on repart avec un jeton valide sans attendre la prochaine échéance du cron, qui n'a lieu que toutes les 12 heures → pas de boucle de 401.
Validation par tests
| Scénario | Test | Résultat attendu | Mesuré |
|---|---|---|---|
| Refresh concurrent | Tirer 3 fois en parallèle l'inject DAP refresh |
Mock voit 1 POST /refresh, pas 3 |
delta = 1 |
| Persistance après token | Run register + Token, puis cat /mnt/data/luvitred/dap_state.json |
Fichier 159 bytes avec device_code + access + refresh + saved_at | OK 159 bytes |
| Restore au boot | Tirer load dap @startup (sans refaire register/Token) |
Globals repopulés depuis le fichier flash | DAP_RESTORE_OK age=12s |
| Retry 401 | Force un faux access_token, trigger un health POST | Mock 401 → trigger refresh auto → has_access redevient True (delta DAP=1) | DAP_REFRESH_OK delta=1 |
| WAN false→true | Logique en place (revue de code), pas testé en live (demande coupure WAN) | Trigger refresh préventif | code review only |
Deux environnements, cloisonnés
La passerelle peut s'authentifier auprès de deux environnements IoBase distincts. Rien n'est partagé entre eux — c'est la condition pour que le mécanisme à usage unique reste sûr :
| Environnement principal | Second environnement | |
|---|---|---|
| Identifiants | dap_host, dap_client_id, dap_api_key |
les mêmes, suffixés _b |
| Jetons | iobase_token, iobase_refresh_token |
iobase_token_b, iobase_refresh_token_b |
| Verrou | dap_refreshing | dap_refreshing_b |
| Fichier d'état | dap_state.json | dap_state_b.json |
| Déclencheur | injection DAP refresh | injection DAP refresh B |
Chaque déclencheur porte son environnement dans le message ; les fonctions le lisent et choisissent le jeu de clés correspondant. Un renouvellement bloqué sur un environnement ne bloque pas l'autre, et un ré-enrôlement en cours sur l'un n'entraîne pas l'autre sur la mauvaise route.
Le moteur de la passerelle n'émet qu'un seul message par sortie. Une première version faisait éclater un déclencheur unique en plusieurs messages : elle a interrompu le renouvellement des jetons pendant 48 heures, du 29 au 31 août 2026. La forme retenue — un déclencheur par environnement, chacun portant le sien — supprime le problème au lieu de le contourner.
Restauration au démarrage
Chaque environnement recharge son propre fichier d'état au démarrage. Sans cela, le second environnement perdrait son jeton de renouvellement à chaque redémarrage du flux — un jeton à usage unique, obtenu par un enrôlement validé par un administrateur : le perdre coûte un aller-retour de plusieurs jours avec la plateforme.
Le journal le montre au démarrage, un message par environnement :
DAP_RESTORE_OK env='a' has_refresh=true
DAP_REFRESH_SKIP_FRESH env='a' saved_age_s=2047
DAP_RESTORE_OK env='b' has_refresh=false
La garde DAP_REFRESH_SKIP_FRESH est essentielle : tout redéploiement redémarre les
flux et rejoue cette restauration. Sans elle, chaque déploiement consommerait un jeton. Elle
n'agit que si l'état a moins de six heures ; au-delà, un renouvellement part —
c'est normal, mais c'est à savoir avant de déployer.
Variables utilisées
Voir la référence des variables globales pour la liste complète, qui
indique aussi lesquelles se saisissent au cockpit. Les quatre valeurs de configuration
(dap_host, dap_client_id, dap_api_key et leurs variantes
_b) s'y modifient sans redéploiement du flux ; les jetons, eux, sont
produits par l'enrôlement et ne se saisissent pas.
Le fichier d'état conserve une copie de l'hôte et des identifiants. Si une valeur est saisie au cockpit, c'est elle qui s'applique, y compris après un redémarrage ; le fichier d'état est un cache de survie, pas la source de vérité de la configuration. Vider le champ rend la main au fichier. Les jetons ne sont jamais figés par une saisie — les geler empêcherait leur restauration.
Premier enrôlement
- Renseigner
dap_host,dap_client_idetdap_api_keydans le cockpit (onglet Globals, rôle admin) - Déclencher l'injection DAP register depuis le canvas
- Vérifier
DAP_REGISTER_OK device_code=…dans le journal - Un administrateur de la plateforme valide l'équipement (« Services et
équipements » → « Valider l'équipement »). Sans cette étape, l'appel suivant répond
425 Pending. C'est une dépendance externe : elle se demande tôt. - Une fois validé, déclencher l'injection DAP get token
- Vérifier
DAP_TOKEN_OKet l'écriture du fichier d'état
Le second environnement suit exactement la même procédure, avec ses propres identifiants et sa propre validation côté plateforme.
Le renouvellement est automatique : une tâche périodique s'exécute toutes les 12 heures, les jetons sont écrits sur la mémoire de la passerelle et rechargés à chaque redémarrage. Si un envoi essuie un 401, le renouvellement se déclenche immédiatement — la trame concernée est perdue, les suivantes passent.