Skip to content

[GH-P2] JWT d'app + cache des tokens d'installation, résolveur par projet, flag DEVFORGE_GITHUB_APP - #233

Merged
bobdivx merged 1 commit into
mainfrom
feat/gh-app-p2-tokens
Oct 14, 2026
Merged

bobdivx merged 1 commit into
mainfrom
feat/gh-app-p2-tokens

Conversation

@bobdivx

@bobdivx bobdivx commented Oct 13, 2026

Copy link
Copy Markdown
Owner

[GH-P2] JWT d'app & tokens d'installation en cache, résolveur par projet

Plan : devforge-plan-github-app.md, phase P2. S'appuie sur P0 (#229) et P1 (#231).

Checklist

  • P2-01 crates/github/src/app_auth.rs : AppCredentials { app_id, private_key } et app_jwt() (RS256, iat = now−60, exp = now+540, iss = app_id). Le helper JWT de P1 a été retiré de github_app_routes.rs ; il y a maintenant load_credentials() (clé descellée en mémoire uniquement). SecretString (Debug/Display = ***).
  • P2-02 InstallationTokenCache dans AppState.gh_tokens.
    • Renvoie le token en cache s'il expire dans plus de 5 min, sinon fait POST /app/installations/{id}/access_tokens.
    • Single-flight par installation (un mutex par id).
    • scoped() : repository_ids + permissions restreints, jamais mis en cache (tokens des runners).
  • P2-03 apps/server/src/github_auth.rs : enum GithubAuth { Pat, Installation { id, repo_id } } et for_project().
    • Installation si projects.github_installation_id est non nul et le mode App est actif, sinon PAT (inchangé).
    • Feature flag : DEVFORGE_GITHUB_APP=1 ou présence d'une ligne github_app.
    • Branché sur : draft_routes::remote_url, correction::remote, les runs d'agents liés à un projet (routes.rs, 2 sites), les routines (rich_chat.rs). Pour ces runs, les outils agent (publish_to_github, create_github_fix, sync_workdir_to_github, read_github_file, github_list_prs, github_workflow_runs) passent par with_token et reçoivent donc le token d'installation.
    • Branché aussi sur les déploiements prod (routes.rs) et dev (dev_env.rs).
  • P2-04 Clone/push par runner :
    • Les déploiements d'un projet lié reçoivent un token d'installation restreint au dépôt (contents:read, metadata:read, TTL GitHub ≤ 1 h).
    • Nouveau champ DeployRequest.github_token_ephemeral. Dans ce mode, git s'authentifie par en-tête (GIT_CONFIG_* → http.https://github.com/.extraheader) et le remote est réécrit sans token (remote set-url). Rien n'est persisté dans .git/config, rien ne passe dans l'URL ni dans les logs.
    • PAT : chemin historique strictement identique.
    • redact_tokens() masque x-access-token:… et ghs_/ghp_/github_pat_… dans un texte de log.
  • [~] P2-05 401 au moment d'obtenir le token : invalidation + 1 seul nouvel essai (nouveau JWT). 403 suspended ⇒ github_installations.suspended_at, puis repli sur le PAT. Différé : l'invalidation + nouvel essai sur un 401 d'un appel API utilisant le token (il faudrait instrumenter HttpGitHubClient). Le risque est faible : un token mis en cache est toujours renouvelé avant les 5 dernières minutes.

Tests

  • app_auth_tests.rs (wiremock, clé RSA générée à la volée par openssl, jamais committée) :
    • JWT décodé avec la clé publique : iss, exp − iat ≤ 600.
    • 2 appels concurrents ⇒ 1 seul POST, puis service par le cache.
    • Token à 4 min de l'expiration ⇒ renouvelé ; à 6 min ⇒ conservé.
    • 401 ⇒ exactement 2 POST puis erreur.
    • 403 suspended ⇒ Suspended.
    • Corps restreint repository_ids/permissions ; scoped non mis en cache.
    • Masquage : x-access-token:ghs_xxx ⇒ x-access-token:***.
  • github_auth :
    • Résolveur : installation ⇒ Installation, sans installation ⇒ Pat, flag absent ⇒ Pat même si le projet est lié.
    • Lecture base (projet lié/non lié, ligne github_app) et parsing du flag.
  • deploy : la commande git en mode en-tête ne contient ni le token ni x-access-token: et nettoie le remote ; l'URL PAT est inchangée.
  • Local : cargo test --workspace ⇒ 617 passés, 0 échec.

Validation (manuelle, après merge + P1 validée en prod)

PAT supprimé des réglages + projet lié à l'installation (github_installation_id/github_repo_id ; le lien vient en P6/P7, ou à la main en SQL pour le test) ⇒ le déploiement clone et build sur un runner self-hosted. Pas encore faite.

Risques

  • Le mode en-tête requiert git ≥ 2.31 (GIT_CONFIG_COUNT) sur l'hôte de build ; le mode PAT n'est pas concerné. Sous Windows, le chemin historique est conservé.
  • Le token restreint transite dans la commande shell envoyée à l'exécuteur (variable d'environnement, pas l'argv de git). Il n'est ni journalisé ni persisté et vit au plus 1 h.
  • Si le token d'installation n'est pas disponible, on se replie sur le PAT : la migration se fait sans coupure, mais un échec App peut passer inaperçu. Un warning est journalisé, sans secret.
  • DeployRequest dérive Debug et contient github_token. Ce n'est pas journalisé aujourd'hui, mais c'est à masquer au durcissement (P7).

@bobdivx
bobdivx force-pushed the feat/gh-app-p2-tokens branch from b421679 to 1f7eeba Compare October 14, 2026 00:58
@bobdivx
bobdivx merged commit 07ffe4f into main Oct 14, 2026
4 checks passed
@bobdivx
bobdivx deleted the feat/gh-app-p2-tokens branch October 14, 2026 06:32
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