Skip to content

Fix/issue 82 oauth callback flow - #351

Open
Adeyemiadigun wants to merge 2 commits into
AyinkxLab:mainfrom
Adeyemiadigun:fix/issue-82-oauth-callback-flow
Open

Adeyemiadigun wants to merge 2 commits into
AyinkxLab:mainfrom
Adeyemiadigun:fix/issue-82-oauth-callback-flow

Conversation

@Adeyemiadigun

Copy link
Copy Markdown

Summary

Adds integration coverage for the full /github/callback OAuth flow with thetoken exchange and /user verification mocked at the requests layer, and
hardens the callback's token-error path so a malformed upstream response can no
longer 500 or echo a raw body back to the user.

Linked issue

Closes #82

Changes

  • tests/test_github_routes.py — new TestOAuthCallbackFlow class (8 tests)
    covering success, state mismatch, missing code, and GitHub error responses
    (?error=, a 4xx token body, a non-JSON body, a non-dict body, and a rejected
    user lookup). Added shared doubles/helpers: FakeHTMLResponse,
    _flashes, _forbid_token_exchange, _token_exchange,
    _assert_no_error_leakage, and a sessions= hook on _make_fake_session
    so tests can assert the Authorization header used for /user.
  • app/github/routes.py — normalize a non-dict token body (null/list from
    a gateway) to {} so it degrades to the friendly redirect instead of raising
    AttributeError and returning a 500; and stop flashing the raw upstream body. Only GitHub's own error / error_description are surfaced now — the
    raw body is truncated to 200 chars and logged server-side.

Validation

  • pytest passes — partially. The8 new tests pass and tests/test_github_routes.py is 41/41
    green. The full suite is 1865 passed / 37 failed, but all 37 failures are in
    tests/test_migrations.py and reproduce identically on a clean main
    worktree, so they are pre-existing and unrelated (see Notes).
  • ruff check . passes
  • black --check . passes
  • Added/updated tests for the change
  • Docs updated if user-visible — no user-facing docs change needed; behavior change is limited to error messages on an already-failing path.

Notes for reviewers

  • The app/github/routes.py change is a behavior fix, not a test. It is split
    into its own commit (6d82c21) so it can be reviewed or split into a separate
    PR. It is required for the "no error leakage" criterion to actually hold:
    with only the tests applied, 2 of the 8 fail — the raw gateway HTML was rendered
    to the user, and the null body 500'd. The other 6 are regression guards over
    behavior that already worked.
  • "No error leakage" is not absolute, by design. The callback still flashes
    GitHub's own error_description (e.g. "The code passed is incorrect or
    expired."
    ) since that is a user-facing OAuth string. The tests assert the
    machine-readable code is not shown and that no client secret, access token, or
    internal marker (Traceback, GitHubError, RequestException) reaches the page.
  • Known gap, deliberately out of scope: an absent state still passes the
    check, because routes.py:109 compares None != None. A callback carrying a
    code with no state param and no session state will exchange and persist a
    connection. The guard only rejects a mismatched state, not a missing one, so
    the CSRF protection the parameter exists for is weaker than intended. Worth its
    own issue.
  • Pre-existing test failure (not caused by this PR): tests/test_migrations.py
    fails 37/37 with Multiple head revisions are present. Cause is three
    migrations sharing down_revision = 'd8e9f0a1b2c3' — 91a2b3c4d5e6
    (add_secure_conversation_share_links), 91a2b3c4d5e7 (add_team_prompt_scope),
    and 91a2b3c4d5e8 (add_file_analysis_history) — which forks the graph into
    three heads. Unrelated to this change; needs a merge migration.
  • Environment note for reproducing: the repo's .venv was empty/broken, so it was
    rebuilt with Python 3.12 (matching the project's target-version = "py312") and
    requirements-dev.txt. .venv/ is gitignored, so it is not part of the diff.

A token endpoint that answers with a non-dict body (`null` or a list,
e.g. from a gateway) raised AttributeError and returned a 500. Normalize it
so a malformed body degrades to the friendly failure path.

Also stop flashing the raw upstream body: only GitHub's own error and
error_description are surfaced to the user, while the raw body is truncated
and logged server-side.
Drive GET /github/callback end to end with the token exchange and the
/user verification mocked at the requests layer.

Success asserts the authorization code is exchanged with the app credentials
and that the stored access token is ciphertext which round-trips through
decrypt_secret, with the plaintext never reaching the browser.

Failure paths (state mismatch, missing code, GitHub error responses, a
rejected user lookup) each assert a friendly redirect back to the dashboard,
a readable flash, and that no client secret, access token, or raw upstream
error body leaks into the page.
@drips-wave

drips-wave Bot commented Oct 1, 2026

Copy link
Copy Markdown

@Adeyemiadigun Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

Add integration tests for the full OAuth callback flow

1 participant