GitHub App one-click install flow#18
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Implements a one-click GitHub App installation flow and webhook ingestion so self-hosters can connect Odin to GitHub without the previous manual webhook setup.
Changes:
- Adds
backend/app/api/github_app.pywith install redirect, OAuth callback, installations listing, webhook handler, and a simple landing page. - Registers the new GitHub App router under
/api/githuband extends settings with GitHub App env vars. - Adds end-to-end tests covering redirects, callback persistence, installations listing, webhook signature verification, and landing page rendering.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
backend/app/api/github_app.py |
New router implementing install/callback/installations/webhook + landing page for GitHub App setup. |
backend/app/main.py |
Includes the new GitHub App router under /api/github. |
backend/app/config.py |
Adds GitHub App-related settings/env vars to Settings. |
backend/tests/test_github_app.py |
Adds tests for the GitHub App install flow, callback, installations list, webhook signature checks, and landing page. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ) -> dict[str, str]: | ||
| """Receive and dispatch GitHub App webhook events. | ||
|
|
||
| Handles: installation, pull_request, push. |
| raise HTTPException(status_code=401, detail="Invalid webhook signature") | ||
|
|
||
| event_type = request.headers.get("X-GitHub-Event", "") | ||
| payload: dict[str, Any] = json.loads(payload_bytes) |
Comment on lines
+8
to
+10
| from unittest.mock import AsyncMock, patch | ||
|
|
||
| import pytest |
| import pytest | ||
| from fastapi.testclient import TestClient | ||
|
|
||
| from app.api.github_app import _installations, _make_jwt |
| @github_app_router.get("/app/install") | ||
| async def github_app_install() -> RedirectResponse: | ||
| """Redirect the user to the GitHub App installation page.""" | ||
| _require_app_configured() |
| """Process installation created/deleted events.""" | ||
| action = payload.get("action", "") | ||
| installation = payload.get("installation", {}) | ||
| installation_id: int = installation.get("id", 0) |
Comment on lines
+341
to
+345
| payload_bytes = await request.body() | ||
| sig_header = request.headers.get("X-Hub-Signature-256") | ||
|
|
||
| if not _verify_app_webhook_signature(payload_bytes, sig_header): | ||
| logger.warning("app webhook signature verification failed") |
Comment on lines
+106
to
+110
| @github_app_router.get("/", response_class=HTMLResponse, include_in_schema=False) | ||
| async def landing_page() -> HTMLResponse: | ||
| """Self-hoster landing page with a one-click GitHub App install button.""" | ||
| app_configured = bool(settings.github_app_id and settings.github_app_client_id) | ||
| if app_configured: |
| # GitHub App integration (one-click install flow) | ||
| github_app_id: str = "" | ||
| github_app_client_id: str = "" | ||
| github_app_client_secret: str = "" |
RahulModugula
force-pushed
the
github-app-install
branch
from
April 11, 2026 13:32
603a088 to
8cdfad7
Compare
| <p>Installation ID: <code>{installation_id}</code></p> | ||
| </body> | ||
| </html>""" | ||
| return HTMLResponse(content=html, status_code=200) |
Replaces the 15-step manual webhook setup with a single redirect. Visiting /api/github/app/install sends the user to the GitHub App authorization page; the callback stores the installation and shows a confirmation page. Includes JWT generation (PyJWT with cryptography fallback), installation storage, app webhook handler, and a self-hoster landing page. All endpoints return 503 with setup instructions when ODIN_GITHUB_APP_ID is unset. 14 new tests.
RahulModugula
force-pushed
the
github-app-install
branch
from
April 11, 2026 13:34
8cdfad7 to
860e3f5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/api/github/app/installand are sent directly to GitHub's App authorization page/api/github/app/callbackreceives theinstallation_id, fetches account details via JWT-authenticated GitHub API, and stores the record in memory (keyed byinstallation_id)GET /api/github/app/installations(JSON list),POST /api/github/app/webhook(handlesinstallationandpull_requestevents, dispatches reviews into the existing pipeline)GET /api/github/with an "Install Odin on GitHub" button503with setup instructions whenODIN_GITHUB_APP_IDis not configuredcryptography.hazmatprimitives — no new hard dependencies addedNew env vars
ODIN_GITHUB_APP_IDODIN_GITHUB_APP_CLIENT_IDODIN_GITHUB_APP_CLIENT_SECRETODIN_GITHUB_APP_PRIVATE_KEY\n)ODIN_GITHUB_APP_WEBHOOK_SECRETTest plan
test_install_redirect_returns_302_when_configured— redirect fires when app is configuredtest_install_redirect_returns_503_when_not_configured— 503 with helpful message when unconfiguredtest_callback_stores_installation_and_returns_200— callback persists record and returns HTMLtest_callback_stores_record_in_memory— installation dict contains correct account logintest_installations_list_returns_json— list endpoint returns correct JSON shapetest_app_webhook_rejects_bad_signature— HMAC verification blocks unsigned requeststest_app_webhook_accepts_valid_installation_event— signed installation events are acceptedtest_landing_page_shows_install_button_when_configured— landing page renders install button