-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Implementation Plan
Context
Roadmapper currently uses a single shared GITHUB_TOKEN (PAT) for all GitHub API calls — fetching issues, verifying .roadmapper files, and checking repo existence during registration. This shares a single 5,000 req/hr rate limit pool across all users. As adoption grows, this becomes a bottleneck.
GitHub App integration gives each installation its own 5,000 req/hr rate limit pool, scaling linearly with users. It also provides a more professional onboarding experience — users install the app on GitHub rather than manually creating and committing .roadmapper files.
Goal: Add GitHub App as an alternative auth/verification path alongside the existing .roadmapper key approach. Both paths coexist cleanly, and the old approach can be removed later without touching the GitHub App code.
Constraints:
- Public repos only for now (permissions:
issues:read,metadata:read) - Auth + rate limits only — no webhook-driven cache invalidation
- Graceful fallback: if GitHub App env vars aren't set, everything works exactly as today
- Clean separation: GitHub App logic in its own modules
Environment Variables
| Variable | Description |
|---|---|
GITHUB_APP_ID |
App ID from GitHub App settings |
GITHUB_APP_PRIVATE_KEY |
PEM private key (base64-encoded for Vercel) |
GITHUB_APP_WEBHOOK_SECRET |
Webhook secret for signature verification |
New Dependencies
jsonwebtoken— sign JWTs for GitHub App authentication
New Redis Keys
gh-app:installation:{owner}/{repo} → String (installation ID)
gh-app:token:{installationId} → String (installation access token, ~55min TTL)
New Files
1. lib/github-app.js — GitHub App authentication
Core GitHub App logic, isolated from the rest of the system:
isGitHubAppConfigured()— returnstrueifGITHUB_APP_IDandGITHUB_APP_PRIVATE_KEYare setgenerateAppJwt()— signs a JWT with the private key (10-minute expiry per GitHub spec)getInstallationToken(installationId)— checks Redis for cached token, if missing/expired exchanges JWT for installation access token viaPOST /app/installations/{id}/access_tokens, caches with ~55min TTLgetInstallationId(owner, repo)— checks Redis for cached installation ID, if missing fetches viaGET /repos/{owner}/{repo}/installation, caches resultgetTokenForRepo(owner, repo)— combines the above: get installation ID → get installation token. Returnsnullif app not installed on this repo
All functions return null on failure (not installed, not configured, etc.) — callers fall through to the next auth method.
2. lib/github-token.js — Token resolution abstraction
Single function that replaces the 4 scattered GITHUB_TOKEN checks:
const resolveGitHubToken = async (owner, repo) => {
// 1. Try GitHub App installation token (per-repo, own rate limit pool)
if (isGitHubAppConfigured()) {
const token = await getTokenForRepo(owner, repo);
if (token) return { token, source: 'app' };
}
// 2. Fall back to shared PAT
if (process.env.GITHUB_TOKEN) {
return { token: process.env.GITHUB_TOKEN, source: 'pat' };
}
// 3. Unauthenticated (60 req/hr)
return { token: null, source: 'none' };
};3. api/github/webhook.js — Installation lifecycle webhook
Minimal webhook endpoint for installation and installation_repositories events:
- Verifies webhook signature using
GITHUB_APP_WEBHOOK_SECRET - On
installation.created/installation_repositories.added: storesgh-app:installation:{owner}/{repo}for each granted repo - On
installation.deleted/installation_repositories.removed: deletes the Redis keys - Returns 200 OK for all other events
4. api/github/setup.js — Post-installation redirect
After a user installs the app on GitHub, GitHub redirects to this URL:
- Receives
installation_idandsetup_actionquery params from GitHub - Redirects the user back to the Roadmapper landing page with a success message
Modified Files
1. roadmap.js — Use token resolution in fetchIssues
Add optional githubToken parameter to fetchIssues(owner, repo, cacheTtlSeconds, githubToken). Replace the 2 inline GITHUB_TOKEN checks with if (githubToken) headers['Authorization'] = .... The caller (middleware) resolves the token and passes it in.
2. lib/verify.js — Dual-path verification
Add verifyRepoViaApp(owner, repo) that checks for a GitHub App installation. Modify verifyRepo() to try GitHub App first, then fall back to .roadmapper. Also update fetchRoadmapperFile to use resolveGitHubToken instead of inline GITHUB_TOKEN check.
3. lib/middleware.js — Token resolution in middleware
After verification succeeds, resolve the GitHub token via resolveGitHubToken(owner, repo) and attach to req.githubToken.
4. api/roadmap.js — Pass resolved token to fetchIssues
Change fetchIssues(owner, repo, req.cacheTtl) to fetchIssues(owner, repo, req.cacheTtl, req.githubToken).
5. api/register.js — Use token resolution for repo existence check
Replace inline GITHUB_TOKEN check with resolveGitHubToken.
6. api/index.js — Landing page updates
Add an "Install GitHub App" button alongside the existing registration form, giving users two onboarding paths.
7. server.js + vercel.json — Add webhook and setup routes
Add POST /api/github/webhook and GET /api/github/setup routes in both files.
Test Strategy
New test files:
tests/lib/github-app.test.js— JWT generation, installation token caching, token refresh, graceful failurestests/lib/github-token.test.js— Token resolution priority: app token > PAT > nulltests/api-github-webhook.test.js— Webhook signature verification, installation events, Redis keystests/api-github-setup.test.js— Redirect behavior
Updated test files:
tests/lib/verify.test.js— Dual-path verificationtests/lib/middleware.test.js—req.githubTokenattachmenttests/roadmap.test.js—fetchIssueswithgithubTokenparameter
GitHub App Registration Checklist (manual, one-time)
- Create GitHub App at
github.com/settings/apps/new - Permissions:
Issues: Read,Metadata: Read - Subscribe to events:
Installation,Installation repositories - Webhook URL:
https://roadmapper.rocketstack.co/api/github/webhook - Setup URL:
https://roadmapper.rocketstack.co/api/github/setup - Generate and download private key
- Add env vars to Vercel:
GITHUB_APP_ID,GITHUB_APP_PRIVATE_KEY(base64),GITHUB_APP_WEBHOOK_SECRET