Skip to content

Start GitHub's OAuth flow server-side, and let the wizard report its own failures - #636

Merged
compscidr merged 3 commits into
mainfrom
feat/631-632-login-and-wizard-errors
Sep 25, 2026
Merged

compscidr merged 3 commits into
mainfrom
feat/631-632-login-and-wizard-errors

Conversation

@compscidr

@compscidr compscidr commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #631. Fixes #632.

#631 — GitHub's OAuth flow starts server-side now

Every theme assembled the authorize URL in an inline script:

"https://github.com/login/oauth/authorize?client_id=" + id + "&redirect_uri=" + window.location

window.location went in raw. A next containing & ended the redirect_uri value early and everything after it reached GitHub as further authorize parameters. Reproduced as a test against the old behaviour:

authorize URL has 3 parameters, want exactly client_id and redirect_uri:
  map[b:[c] client_id:[abc123] redirect_uri:[https://example.com/login?next=/search?q=a]]

/login/github now does it in Go and the themes point a plain anchor at it. No client_id in the page, no inline script, and the escaping is tested once instead of duplicated across four repos.

The issue's proposed fix was wrong

#631 suggested stripping the query from redirect_uri — "next already survives separately". It does not. next round-trips through redirect_uri: GitHub returns the visitor to that exact URL with &code= appended, and Login reads next from that query. Stripping it would have quietly sent everyone to / after signing in. So this escapes rather than strips, at both layers — the redirect, and the next inside it. I've corrected the issue.

Two deliberate choices

SafeNext guards the new entry point. next reaches redirect_uri, so an open redirect here would be handed to GitHub. Covered by TestGithubLogin_RejectsOffsiteNext.

The origin comes from the request, not the site_url setting. GitHub matches redirect_uri against the callback registered for the app, so it has to be the host the visitor is actually on — which is what window.location gave. Using site_url would have been a behaviour change with real breakage risk if it does not match what is registered.

#632 — the wizard can report its own failures

Server. Both database handlers returned nothing when ParseForm failed, which gin sends as a bare 200 with an empty body. For /test_db that is worse than silence: the wizard's jQuery call sets no dataType, so an empty 200 takes the success path and the Test Database button turns green on a request the server never read. /wizard_db rendered a blank page despite already having a fail() helper for exactly this.

Client. The error handler read data.responseJSON.error. responseJSON is undefined whenever the body was not JSON — a proxy error, an HTML error page, a dropped connection — so reading .error threw: the button went red and the reason never appeared, which is the one thing someone stuck on that step needs. It falls back through statusText now, and renders with .text() rather than .html().

⚠️ Merge order — the opposite way round from #624

This goblog PR must merge and release before the theme PRs. The themes link to /login/github, which does not exist until this ships; on an older goblog that link 404s and GitHub login is dead.

(#624 was the reverse — themes first — because there the themes had to tolerate a goblog change. Here they depend on a new route.)

Note for the deploy: goblog-site-theme is cloned at main by iac, so merging its PR before goblog is released would break goblog.live's GitHub login on the next playbook run, even one for an unrelated reason.

Tests

All verified to fail without the fix:

  • TestGithubAuthorizeURL_EscapesAmpersandInNext — the GitHub OAuth redirect_uri is built from window.location without encoding #631 bug, asserting the authorize URL has exactly two parameters
  • TestGithubAuthorizeURL_KeepsNextThroughTheRoundTrip — redirect_uri keeps ?next=, since that is how it survives
  • TestGithubLogin_RejectsOffsiteNext — //evil.example and friends never reach redirect_uri
  • TestRequestOrigin — scheme detection including X-Forwarded-Proto chains
  • TestTestDB_MalformedFormIsNotReportedAsSuccess — a form the server could not read is not a passing database test
  • TestUpdateDB_MalformedFormRendersTheWizardAgain — no more blank page

Verified against a local instance

  • the anchor renders href="/login/github?next=%2Fadmin%2Fsettings"
  • /login/github 302s with both layers escaped
  • an offsite next is dropped
  • X-Forwarded-Proto: https is honoured
  • replaying GitHub's return leg resolves next back to /search?q=a&b=c — the exact case that was broken

🤖 Generated with Claude Code

compscidr and others added 2 commits September 25, 2026 11:05
report its own failures (#632)

#631 — every theme assembled GitHub's authorize URL in an inline
script, concatenating window.location straight into redirect_uri. A
next containing & ended the redirect_uri value early and the rest
reached GitHub as further authorize parameters.

/login/github now does it in Go, and the themes point a plain anchor
at it: no client_id in the page, no inline script, and the escaping is
tested rather than duplicated four times.

The issue proposed stripping the query from redirect_uri, which would
have been a regression: next round-trips *through* redirect_uri, since
GitHub returns the visitor to that exact URL with &code= appended and
Login reads next from the query. So the fix escapes rather than
strips, and both the redirect and the next inside it are escaped.
SafeNext guards the new entry point too — next reaches redirect_uri,
so an open redirect here would be handed to GitHub.

The origin comes from the request rather than the site_url setting, on
purpose: GitHub matches redirect_uri against the callback registered
for the app, so it has to be the host the visitor is on — which is
what window.location used to give.

#632 — both wizard database handlers returned nothing when ParseForm
failed, which gin sends as a bare 200 with an empty body. For
/test_db that is worse than silence: the wizard's jQuery call has no
dataType, so an empty 200 takes the success path and the Test Database
button turns green on a request the server never read. /wizard_db
rendered a blank page despite having a fail() helper for exactly this.

On the client, the error handler read data.responseJSON.error, which
is undefined whenever the body was not JSON — a proxy error, an HTML
error page, a dropped connection. Reading .error threw, so the button
went red and the reason never appeared. It now falls back through
statusText, and renders with .text() rather than .html().

Verified against a local instance: the anchor carries an escaped next,
/login/github escapes both layers, an offsite next is dropped,
X-Forwarded-Proto is honoured, and replaying GitHub's return leg
resolves next back to /search?q=a&b=c — the case that was broken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.26087% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
blog/blog.go 78.26% 3 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The new server-side GitHub OAuth authorize URL construction still omits an OAuth state parameter, leaving the login flow vulnerable to login CSRF/session-swapping unless state is generated and validated end-to-end.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Low severity

Open (2)
What changed in this PR

This PR hardens the GitHub OAuth entry-point by moving authorize URL construction from theme inline JavaScript into server-side Go code (fixing redirect URI escaping issues), and improves the install wizard’s ability to reliably report database form/connection failures to the user.

Changes:

  • Add a new /login/github route that redirects to GitHub with a correctly-escaped redirect_uri (including a safely-carried next).
  • Fix wizard DB handlers to return actionable responses on ParseForm errors, and improve the wizard UI error fallback handling.
  • Add targeted tests covering OAuth URL escaping/origin detection and wizard malformed-form behavior.
File Description
goblog.go Adds /login/github route; fixes wizard DB handlers to respond on malformed forms.
blog/​blog.go Implements RequestOrigin, GithubAuthorizeURL, and GithubLogin redirect logic.
themes/​default/​templates/​login.html Replaces inline JS authorize URL construction with a plain link to /login/github.
themes/​default/​templates/​wizard_db.html Makes the wizard client robust to non-JSON failures and avoids HTML injection by using .text().
blog/​github_login_test.go Adds tests for redirect escaping, next round-trip behavior, origin detection, and offsite-next rejection.
wizard_errors_test.go Adds regression tests ensuring malformed forms don’t appear as successful wizard DB actions.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread blog/blog.go
Comment thread themes/default/templates/login.html Outdated
Review caught it on the login button. Fixed everywhere it appears as
user-facing text, leaving identifiers (github_url, fa-github,
/login/github) alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@compscidr
compscidr merged commit cc811d0 into main Sep 25, 2026
1 check passed
@compscidr
compscidr deleted the feat/631-632-login-and-wizard-errors branch September 25, 2026 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants