feat: application n8n Pilot complète (Expo managed workflow)

- Stack : Expo Router, Axios, Zustand, React Native Paper (thème sombre), date-fns
- Sécurité : secrets dans Android Keystore via expo-secure-store, TLS obligatoire,
  headers X-N8N-API-KEY + X-App-Token injectés par intercepteur Axios
- API : client.ts centralisé + workflows.ts + executions.ts (TypeScript strict)
- Store : Zustand appStore avec chargement depuis secure store au démarrage
- Hooks : usePolling (générique), useWorkflows, useExecutions
- Composants : StatusBadge, WorkflowCard, ExecutionCard, SkeletonLoader
- Screens : Dashboard, Workflows, Executions, Logs (détail exécution), Settings
- Navigation Expo Router : 4 tabs + stack Logs + écran Setup initial
- Docs : INSTALL.md, UPDATE.md, BACKUP.md, HAPROXY.md, SECURITY.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 17:31:55 +02:00
parent ea1705d3b0
commit 92e67d0769
41 changed files with 4891 additions and 58 deletions
+89
View File
@@ -0,0 +1,89 @@
# Sauvegarde de n8n Pilot
## Ce qu'il faut sauvegarder
### 1. Credentials de signature EAS (`credentials.json`)
**Où** : généré par EAS lors du premier build dans `~/.eas/` ou téléchargeable depuis le dashboard Expo.
**Pourquoi** : sans le keystore de signature, il est **impossible** de mettre à jour l'APK sur un appareil
qui a déjà la version signée avec ce keystore. Android refuse les mises à jour avec un keystore différent.
**Comment sauvegarder** :
```bash
# Télécharger les credentials depuis EAS
eas credentials
# Choisir : Android > Keystore > Download
# Sauvegarder le fichier .jks et les mots de passe associés
```
> **Criticalité : MAXIMALE** — perte du keystore = obligation de désinstaller l'app sur tous les appareils
> avant de pouvoir installer une nouvelle version.
---
### 2. Fichier `.env` de développement
**Où** : racine du projet (jamais dans git)
**Pourquoi** : contient l'URL de l'instance, la clé API et le token HAProxy pour l'environnement de dev.
Sans ce fichier, les développeurs doivent reconfigurer manuellement l'environnement.
**Comment sauvegarder** : gestionnaire de mots de passe (Bitwarden, 1Password, Vault) avec les champs :
- `N8N_BASE_URL`
- `N8N_API_KEY`
- `N8N_APP_TOKEN`
---
### 3. Configuration EAS (`eas.json`)
**Où** : racine du projet, **committé dans git**
**Pourquoi** : définit les profils de build (development, preview, production).
**Statut** : déjà sauvegardé via git — aucune action supplémentaire requise.
---
### 4. Données utilisateur sur l'appareil
Les credentials (URL, clé API, token) sont chiffrés dans l'**Android Keystore** de l'appareil.
Ils ne sont **pas** sauvegardés dans les sauvegardes Android standard (par conception de sécurité).
**Conséquence** : après réinitialisation usine ou changement d'appareil, l'utilisateur doit
reconfigurer l'app depuis l'écran Paramètres.
---
## Fréquence recommandée
| Élément | Fréquence | Stockage |
|---------|-----------|----------|
| Keystore EAS | À chaque rotation + après génération | Coffre chiffré hors-ligne |
| `.env` dev | À chaque rotation des tokens | Gestionnaire de mots de passe |
| Code source | En continu via git push | Gitea self-hosted |
---
## Procédure de restauration
### Restaurer le keystore
```bash
# Uploader le keystore sauvegardé vers EAS
eas credentials
# Choisir : Android > Keystore > Upload existing keystore
```
### Restaurer l'environnement de développement
```bash
git clone https://homegit.gyozamancave.fr/billisdead/n8n-mobile.git
cd n8n-mobile
cp .env.example .env
# Remplir .env depuis le gestionnaire de mots de passe
npm install --legacy-peer-deps
```
+195
View File
@@ -0,0 +1,195 @@
# Configuration HAProxy pour n8n Pilot
## Architecture
```
Android App → HTTPS → HAProxy (443) → HTTP → n8n (5678)
Validation X-App-Token
Rate limiting
TLS termination
```
HAProxy agit comme reverse proxy devant n8n. Il :
1. Termine TLS (certificat Let's Encrypt ou autosigné)
2. Valide le header `X-App-Token` (token secret partagé)
3. Applique du rate limiting pour protéger l'API
4. Forward les requêtes vers n8n en HTTP interne
---
## Configuration HAProxy complète
```haproxy
#---------------------------------------------------------------------
# global : paramètres du processus HAProxy
#---------------------------------------------------------------------
global
log /dev/log local0
log /dev/log local1 notice
maxconn 2000
daemon
#---------------------------------------------------------------------
# defaults : valeurs par défaut pour tous les frontends/backends
#---------------------------------------------------------------------
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5s
timeout client 30s
timeout server 30s
# Timeout plus long pour les webhooks n8n qui peuvent prendre du temps
timeout tunnel 3600s
#---------------------------------------------------------------------
# frontend : point d'entrée HTTPS sur le port 443
#---------------------------------------------------------------------
frontend n8n_pilot_frontend
bind *:443 ssl crt /etc/ssl/n8n/fullchain.pem alpn h2,http/1.1
# Sécurité TLS : désactiver les versions obsolètes
ssl-min-ver TLSv1.2
# Headers de sécurité injectés sur toutes les réponses
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
http-response set-header X-Content-Type-Options "nosniff"
http-response set-header X-Frame-Options "DENY"
# ACL : vérification du X-App-Token
# La valeur doit correspondre exactement au token configuré dans l'app
acl valid_app_token req.hdr(X-App-Token) -m str "VOTRE_TOKEN_ICI"
# Refuser les requêtes sans token valide avec 403
http-request deny deny_status 403 if !valid_app_token
# Rate limiting : max 60 requêtes par minute par IP
# Protège contre les scripts de scan et les abus
stick-table type ip size 100k expire 60s store http_req_rate(60s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 60 }
# Redirection HTTP → HTTPS (optionnel si port 80 ouvert)
# redirect scheme https if !{ ssl_fc }
default_backend n8n_backend
#---------------------------------------------------------------------
# backend : instance n8n locale
#---------------------------------------------------------------------
backend n8n_backend
balance roundrobin
# Supprimer le X-App-Token avant de forwarder à n8n (ne doit pas atteindre n8n)
http-request del-header X-App-Token
# Health check : n8n répond sur /healthz
option httpchk GET /healthz
http-check expect status 200
server n8n_local 127.0.0.1:5678 check inter 10s fall 3 rise 2
```
---
## Rotation du X-App-Token
Le token doit être tourné régulièrement (recommandé : tous les 90 jours minimum).
### Procédure de rotation sans downtime
1. **Générer un nouveau token** :
```bash
openssl rand -hex 32
# Exemple : a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1
```
2. **Phase de transition — accepter les deux tokens** :
```haproxy
# Accepter l'ancien ET le nouveau pendant la transition
acl valid_app_token req.hdr(X-App-Token) -m str "ANCIEN_TOKEN"
acl valid_app_token_new req.hdr(X-App-Token) -m str "NOUVEAU_TOKEN"
http-request deny deny_status 403 if !valid_app_token !valid_app_token_new
```
3. **Mettre à jour l'app** sur tous les appareils (Paramètres > Token HAProxy > Sauvegarder)
4. **Retirer l'ancien token** de HAProxy une fois tous les appareils mis à jour :
```haproxy
acl valid_app_token req.hdr(X-App-Token) -m str "NOUVEAU_TOKEN"
http-request deny deny_status 403 if !valid_app_token
```
5. **Recharger HAProxy** sans coupure :
```bash
haproxy -f /etc/haproxy/haproxy.cfg -c # Valider la config
systemctl reload haproxy # Rechargement gracieux
```
---
## Troubleshooting
### Erreur 403 Forbidden
**Cause** : `X-App-Token` absent ou incorrect.
**Diagnostic** :
```bash
# Tester manuellement avec curl
curl -v -H "X-App-Token: VOTRE_TOKEN" https://n8n.votre-domaine.com/api/v1/workflows
```
**Solutions** :
- Vérifier que le token dans l'app (Paramètres) correspond exactement à la config HAProxy
- Contrôler les espaces ou caractères invisibles dans le token
- Vérifier les logs HAProxy : `journalctl -u haproxy -f`
---
### Erreur 429 Too Many Requests
**Cause** : rate limit dépassé (> 60 req/min).
**Solutions** :
- Augmenter l'intervalle de polling dans l'app (Paramètres > Intervalle)
- Ajuster la limite dans HAProxy si l'usage légitime est plus élevé
- Vérifier qu'aucun processus tiers ne spam l'endpoint
---
### Timeout / connexion refusée
**Cause** : n8n inaccessible sur le port 5678, ou HAProxy mal configuré.
**Diagnostic** :
```bash
# Vérifier que n8n tourne
curl http://127.0.0.1:5678/healthz
# Vérifier le statut HAProxy
systemctl status haproxy
haproxy -f /etc/haproxy/haproxy.cfg -c
# Logs HAProxy en temps réel
journalctl -u haproxy -f
```
---
### Certificat TLS expiré
```bash
# Renouveler avec Certbot
certbot renew --quiet
# Recharger HAProxy pour prendre en compte le nouveau certificat
systemctl reload haproxy
```
Automatiser le renouvellement :
```bash
# Ajouter dans crontab (root)
0 3 * * 1 certbot renew --quiet && systemctl reload haproxy
```
+136
View File
@@ -0,0 +1,136 @@
# Installation de n8n Pilot
## Prérequis
| Outil | Version minimale | Rôle |
|-------|-----------------|------|
| Node.js | 20 LTS | Runtime JS |
| npm | 10+ | Gestionnaire de paquets |
| EAS CLI | 3.0+ | Build cloud Expo |
| Android | 8.0 (API 26) | Cible minimum |
| Compte Expo | gratuit | Build EAS |
```bash
# Vérifier les prérequis
node --version # >= 20
npm --version # >= 10
eas --version # >= 3.0 (après install)
```
---
## 1. Cloner le dépôt
```bash
git clone https://homegit.gyozamancave.fr/billisdead/n8n-mobile.git
cd n8n-mobile
```
---
## 2. Installer les dépendances
```bash
npm install --legacy-peer-deps
```
---
## 3. Configurer l'environnement de développement
Copiez le fichier exemple et remplissez vos valeurs **localement uniquement** :
```bash
cp .env.example .env
# Éditez .env avec votre URL, clé API et token HAProxy
```
> ⚠️ Le fichier `.env` est dans `.gitignore` — ne jamais le committer.
> En production, les secrets transitent **uniquement** par `expo-secure-store`.
---
## 4. Lancer en développement
```bash
# Démarrer le serveur Metro
npm start
# Sur appareil Android (USB ou WiFi) ou émulateur
npm run android
```
Pour le développement, l'app vous demandera de saisir l'URL et la clé API
directement dans l'écran Paramètres — elles seront stockées dans le Keystore Android.
---
## 5. Installer EAS CLI
```bash
npm install -g eas-cli
eas login # Connectez-vous avec votre compte Expo
```
---
## 6. Configurer le projet EAS
```bash
# Initialise l'ID de projet Expo (première fois uniquement)
eas init
# Vérifie la configuration
eas build:configure
```
---
## 7. Builder l'APK (profil preview)
```bash
# Build Android APK — exécuté dans le cloud Expo
eas build --platform android --profile preview
```
Le build prend environ 1015 minutes. EAS envoie un email avec le lien de téléchargement.
Pour un build local (nécessite Android Studio + JDK 17) :
```bash
eas build --platform android --profile preview --local
```
---
## 8. Sideloader l'APK sur Android
### Via ADB
```bash
# Connecter l'appareil en USB avec le débogage USB activé
adb devices
# Installer l'APK
adb install chemin/vers/n8n-pilot.apk
```
### Manuellement
1. Transférez l'APK sur l'appareil (câble, Google Drive, etc.)
2. Ouvrez l'APK depuis le gestionnaire de fichiers
3. Autorisez l'installation depuis des sources inconnues si demandé
4. Suivez l'assistant d'installation
---
## 9. Premier lancement
Au premier démarrage, l'écran de configuration apparaît automatiquement :
1. Saisissez l'URL HTTPS de votre instance n8n
2. Collez votre clé API n8n (Settings > API Keys dans n8n)
3. Saisissez le token HAProxy si applicable
4. Appuyez sur **Sauvegarder**
Les credentials sont chiffrés dans l'Android Keystore — ils ne quittent jamais l'appareil.
+147
View File
@@ -0,0 +1,147 @@
# Sécurité — n8n Pilot
## Modèle de menaces
### Acteurs et vecteurs
| Menace | Impact | Probabilité | Mitigation |
|--------|--------|-------------|-----------|
| Vol de l'appareil Android | Accès aux credentials n8n | Moyen | Android Keystore chiffré, biométrie optionnelle |
| Interception réseau (MITM) | Exposition de la clé API | Faible (TLS) | TLS obligatoire, refus HTTP |
| Fuite du token HAProxy | Accès non autorisé à n8n | Moyen | Rotation régulière, token hors bundle |
| Compromission du build APK | Backdoor dans l'app | Faible | Keystore signé, builds EAS reproductibles |
| Brute force API | Épuisement quotas n8n | Faible | Rate limiting HAProxy (429) |
| Logs applicatifs exposés | Fuite de secrets | Faible | Jamais de log de secrets, même en dev |
---
## Principes appliqués
### Least Privilege (moindre privilège)
La clé API n8n doit être créée avec le **scope minimal** nécessaire :
```
Recommandé :
✓ workflows:list — lister les workflows
✓ workflows:read — lire les détails
✓ workflows:update — activer/désactiver (toggle)
✓ executions:list — voir l'historique
✓ executions:read — lire les logs
✓ executions:delete — supprimer des exécutions
✓ workflows:run — déclencher manuellement
Non recommandé :
✗ credentials:* — inutile pour cette app
✗ users:* — inutile pour cette app
✗ admin:* — jamais
```
**Comment créer la clé** dans n8n :
1. Settings > n8n API
2. Create API Key
3. Nommer la clé "n8n-pilot-mobile"
4. Définir les scopes minimaux ci-dessus
5. Copier la clé — elle n'est affichée qu'une seule fois
---
### Defense in Depth (défense en profondeur)
L'accès à l'API n8n passe par **deux barrières indépendantes** :
```
Requête → [TLS] → [X-App-Token HAProxy] → [X-N8N-API-KEY n8n] → Données
```
Si un token est compromis :
- Compromission du `X-App-Token` seul → HAProxy bloque, n8n jamais atteint
- Compromission de la clé API seul → sans le `X-App-Token`, HAProxy bloque à 403
- Les deux compromis → rotation immédiate des deux (procédures dans HAPROXY.md et BACKUP.md)
---
### Zero Trust (réseau local inclus)
n8n ne doit **pas** être accessible directement sur le réseau, même en local.
Toutes les requêtes passent par HAProxy qui valide le token.
```
# Bloquer l'accès direct au port n8n (iptables)
iptables -A INPUT -p tcp --dport 5678 -s 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp --dport 5678 -j DROP
```
---
## Gestion des secrets — cycle de vie
### Stockage
```
┌─────────────────────────────────────────────────────┐
│ Appareil Android │
│ ┌─────────────────────────────────────────────┐ │
│ │ Android Keystore (expo-secure-store) │ │
│ │ n8n_base_url → https://n8n.example.com │ │
│ │ n8n_api_key → clé API (chiffrée) │ │
│ │ n8n_app_token → token HAProxy (chiffré) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ État React (Zustand) — en mémoire uniquement : │
│ config.baseUrl → copie non sensible │
│ config.isConfigured → booléen │
│ JAMAIS : apiKey, appToken en mémoire JS │
└─────────────────────────────────────────────────────┘
```
### Transit
- Les secrets sont lus depuis le Keystore **à chaque requête** HTTP dans `client.ts`
- Ils sont injectés dans les headers HTTP et **ne sont pas loggués**
- Ils ne sont **jamais** :
- Sérialisés dans AsyncStorage
- Inclus dans des logs (même en dev)
- Embarqués dans le bundle JS (`app.json`, `package.json`, code source)
- Envoyés à des services tiers (analytics, crash reporters)
### Rotation
| Secret | Fréquence recommandée | Procédure |
|--------|-----------------------|-----------|
| Clé API n8n | Tous les 180 jours, ou en cas de compromission | Créer nouvelle clé dans n8n, mettre à jour dans l'app |
| X-App-Token | Tous les 90 jours | Voir docs/HAPROXY.md |
| Keystore EAS | Jamais (sauf compromission) | Voir docs/BACKUP.md |
---
## Checklist avant mise en production
### Application
- [ ] `app.json` : `userInterfaceStyle` = `"dark"` (pas d'info sensible en clair)
- [ ] `.env` non commité dans git (`git status` ne montre pas `.env`)
- [ ] Aucun `console.log` de valeurs sensibles dans le code
- [ ] `__DEV__` gate sur tous les logs dans `errorHandler.ts`
- [ ] Expo SDK à jour — vérifier les CVE : `npx expo-doctor`
- [ ] Dépendances à jour : `npm audit`
### Infrastructure
- [ ] HAProxy : TLS 1.2 minimum, TLS 1.0/1.1 désactivés
- [ ] Certificat TLS valide et non expiré
- [ ] n8n inaccessible directement sur le port 5678 depuis l'extérieur
- [ ] Rate limiting HAProxy activé (429 configuré)
- [ ] Logs HAProxy ne contiennent pas les valeurs des headers secrets
### n8n
- [ ] Clé API avec scope minimal (voir § Least Privilege ci-dessus)
- [ ] n8n mis à jour vers la dernière version stable
- [ ] Sauvegardes n8n actives (workflows, credentials n8n)
### Appareil
- [ ] Verrouillage d'écran activé (PIN, pattern ou biométrie)
- [ ] Biométrie configurée dans l'app si disponible
- [ ] Appareil Android 8+ (API 26+) pour garantir la robustesse du Keystore
+78
View File
@@ -0,0 +1,78 @@
# Mise à jour de n8n Pilot
## Processus de mise à jour
### 1. Récupérer les changements
```bash
git pull origin main
npm install --legacy-peer-deps
```
### 2. Bumper la version
Éditez `app.json` et incrémentez `version` :
```json
{
"expo": {
"version": "1.1.0",
"android": {
"versionCode": 2
}
}
}
```
> **Règle** : `versionCode` doit être strictement supérieur à la version précédente.
> `version` suit [semver](https://semver.org/) : MAJEUR.MINEUR.CORRECTIF
### 3. Rebuilder l'APK
```bash
eas build --platform android --profile preview
```
### 4. Distribuer la mise à jour
#### Option A — Sideload manuel
Répétez la procédure de sideload (voir INSTALL.md § 8) avec le nouvel APK.
L'Android Package Manager gère la mise à jour en place si le `versionCode` est supérieur.
#### Option B — OTA (Over The Air) via Expo Updates
Pour les mises à jour mineures (JS uniquement, pas de changements natifs) :
```bash
eas update --branch production --message "Fix: correction du polling"
```
L'app télécharge la mise à jour au prochain démarrage si elle est connectée.
> **Important** : les changements dans `app.json`, l'ajout de plugins natifs
> ou la modification des permissions nécessitent un rebuild APK complet.
---
## Vérifications avant release
- [ ] Tests sur appareil physique (pas seulement émulateur)
- [ ] Vérifier que la connexion à l8n fonctionne avec les nouvelles données
- [ ] Valider le polling sur WiFi et données mobiles
- [ ] Vérifier que les secrets restent dans le Keystore après mise à jour
- [ ] Contrôler les logs Android (`adb logcat`) pour des erreurs inattendues
---
## Rollback
En cas de régression critique :
```bash
# Revenir au commit précédent
git revert HEAD
# Rebuilder et redéployer la version précédente
eas build --platform android --profile preview
```