Skip to content

[GH-P1] Manifest flow : création de l'app GitHub en 1 clic + retour d'installation - #231

Merged
bobdivx merged 1 commit into
mainfrom
feat/gh-app-p1-manifest
Oct 13, 2026
Merged

bobdivx merged 1 commit into
mainfrom
feat/gh-app-p1-manifest

Conversation

@bobdivx

@bobdivx bobdivx commented Oct 13, 2026

Copy link
Copy Markdown
Owner

[GH-P1] Manifest flow : création de l'app GitHub en 1 clic

Plan : devforge-plan-github-app.md, phase P1. S'appuie sur P0 (#229, déjà mergée).

Checklist d'acceptation

  • P1-01 POST /api/v1/github-app/manifest (admin) : state de 32 octets aléatoires (TTL 10 min, github_manifest_states), renvoie { post_url, manifest, state }. post_url pointe vers /settings/apps/new ou /organizations/{org}/settings/apps/new (org validée). Le manifest est exactement celui du plan. Permissions = liste blanche contents:write, metadata:read, pull_requests:write, checks:write, statuses:write, deployments:write. workflows:write seulement en opt-in (workflows: true, désactivé par défaut). Jamais administration, secrets ni members.
  • P1-02 GET …/manifest/callback?code&state : state à usage unique (supprimé même s'il est refusé), non expiré, même utilisateur ⇒ sinon 400. Ensuite POST /app-manifests/{code}/conversions sans auth. pem, webhook_secret et client_secret sont scellés (secret_box) puis upsert dans github_app. Redirection vers /apps/{slug}/installations/new?state=… avec un nouveau state lié à l'utilisateur.
  • P1-03 GET …/installed?installation_id&setup_action&state : on ne fait pas confiance à l'installation_id de la query. Il est vérifié via GET /app/installations/{id} avec le JWT RS256 de l'app, puis upsert dans github_installations. Les dépôts sont synchronisés dans github_installation_repos (token d'installation éphémère, puis GET /installation/repositories, paginé). Redirection vers /app/settings?tab=github&installed=1. Le state est optionnel, car setup_on_update rappelle sans lui ; s'il est présent, il est vérifié et consommé.
  • P1-04 GET /api/v1/github-app : statut (app, slug, propriétaire, URL d'install/gestion, installations, permissions requises vs accordées, missing_permissions), sans aucun secret. DELETE /api/v1/github-app : oubli local (app + installations en cascade) et renvoi de manage_url pour supprimer l'app côté GitHub.
  • P1-05 GithubAppSettingsPanel.tsx, intégré à la section GitHub des réglages (SettingsPage.tsx, rendu par settings.astro), admin seulement. Le bouton « Créer l'app GitHub DevForge » appelle /manifest puis soumet un <form method="post"> caché avec le champ manifest. Le panneau propose aussi le champ Organisation, la liste des installations, « Installer sur un autre compte » et une alerte si des permissions manquent. Les erreurs de retour (?error=) et ?installed=1 sont affichées.
  • P1-06 api.ts : types GithubAppStatus, GithubInstallation, GithubAppManifest et fonctions githubAppStatus, githubAppManifest, githubAppForget.

Tests

  • Rust (github_app_routes_tests.rs) :
    • manifest : URLs webhook/redirect/setup/callback, permissions exactement égales à la liste blanche, opt-in workflows, nom ≤ 34 caractères, URL perso/org, org invalide refusée ;
    • state réutilisé, autre utilisateur, expiré ou inconnu ⇒ erreur et réponse 400 ;
    • conversion mockée (wiremock) ⇒ secrets scellés en base (private_key_enc sans BEGIN RSA), statut sans aucun secret ;
    • conversion 404 / code invalide ⇒ erreur ;
    • installation mockée : un id inventé ⇒ refusé, rien en base ; un id valide ⇒ installation + dépôts stockés, idempotent, missing_permissions correct ;
    • JWT RS256 décodé avec la clé publique : iss, exp − iat ≤ 600, Debug = ***.
  • Vitest (GithubAppSettingsPanel.test.tsx) : création ⇒ soumission du manifest ; liste des installations et alerte de permissions ; formulaire caché POST avec le champ manifest JSON.
  • Local : cargo test --workspace ⇒ 604 passés, 0 échec. Web : 140 tests Vitest OK, contrôle des imports JSX et des marques interdites OK. Le build web n'a pas pu tourner localement (binaire natif tailwind absent) ; il est couvert par le job CI web (Docker).

Validation (manuelle, après merge + déploiement)

Depuis l'UI de prod : Réglages → GitHub → « Créer l'app GitHub DevForge », sur le compte bobdivx → installer sur 1 dépôt → retour dans DevForge avec l'installation listée, en moins d'1 min et sans copier de secret. Pas encore faite : la PR n'est pas mergée.

Décisions / risques

  • Les routes sont montées sans feature flag. Elles sont réservées à l'admin et ne modifient pas le comportement PAT/webhook (la bascule vers les tokens d'installation arrive en P2, derrière DEVFORGE_GITHUB_APP).
  • Les retours GitHub (callback/installed) sont des navigations sans en-tête Authorization. L'authentification passe par le cookie df_session (SameSite=Lax, envoyé sur une navigation de premier niveau). Nouvelle fonction auth_routes::user_from_bearer_or_cookie.
  • Un app_jwt minimal (RS256) est ajouté dès P1, car P1-03 doit vérifier l'installation. Il sera déplacé dans crates/github/src/app_auth.rs en P2. Le token d'installation utilisé pour synchroniser les dépôts n'est pas mis en cache (cache prévu en P2).
  • Nouvelle colonne github_app.owner_type (ALTER dont l'échec est toléré), qui sert au lien de gestion user vs org.
  • DEVFORGE_GITHUB_API_URL / DEVFORGE_GITHUB_WEB_URL (facultatives) permettent de cibler un mock ou GHES. Par défaut : api.github.com / github.com.
  • L'URL publique d'instance (instance_settings.instance_url) doit être en https et joignable par GitHub pour le webhook (route webhook app-level en P3).

@bobdivx
bobdivx force-pushed the feat/gh-app-p1-manifest branch from 3005175 to 526fab4 Compare October 13, 2026 21:26
@bobdivx
bobdivx merged commit 30c5f19 into main Oct 13, 2026
4 checks passed
@bobdivx
bobdivx deleted the feat/gh-app-p1-manifest branch October 13, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant