02 Auth DAP 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.
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 | {"name": "<device_name>"} |
200 + {device_code} |
403 name déjà pris |
/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 {"name":"<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 en échec est mise dans la queue offline (queueenabled=true) et sera replay automatiquement avec le nouveau token.
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 WAN longue (> 50 min, le cron ratera) on est sûr d'avoir un access_token valide avant que la queue ne se déverse → 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 |
Globals utilisés
'host:port' (préfixe http:// ajouté par les builds).name envoyé à /register et client_id envoyé à /Token + /refresh. À choisir lors du déploiement (par convention cloudgate-<site>-XX).handle_dap_resp sur register OK. Persisté.true pendant un refresh ou Token en cours. Reset par handle_dap_resp.Procédure de premier enrôlement
- Set
global.dap_hostpar inject startup ou console Lua → URL du DAP cible - Tirer l'inject
DAP register(manuel) - Vérifier dans le canvas debug :
DAP_REGISTER_OK device_code=DC... - L'administrateur IoBase doit alors valider le device dans le portail → « Services et équipements » → « Valider l'équipement ». Sans cette étape, l'appel suivant retournera HTTP 425 Pending.
- Une fois validé, tirer l'inject
DAP get token - Vérifier
DAP_TOKEN_OK token_len=...dans le debug - À partir de là, le cron
DAP refresh(50 min) prend le relais automatiquement