Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions blog/blog.go
Original file line number Diff line number Diff line change
Expand Up @@ -1379,14 +1379,18 @@ func (b *Blog) Login(c *gin.Context) {
}

clientID := os.Getenv("client_id")
next := SafeNext(c.Query("next"))
b.Render(c, http.StatusOK, "login.html", gin.H{
"logged_in": b.auth.IsLoggedIn(c),
"is_admin": b.auth.IsAdmin(c),
// The only page whose markup uses .btn-social, so the only one that
// loads bootstrap-social (#630).
"login_page": true,
"client_id": clientID,
"next": SafeNext(c.Query("next")),
"login_page": true,
"client_id": clientID,
// next is still handed over for the email login, which redirects to it
// in the browser once its AJAX call succeeds.
"next": next,
"github_login_url": GithubLoginURL(next),
"version": b.Version,
"title": "Login",
"email_login_enabled": b.auth.EmailLoginEnabled(),
Expand Down Expand Up @@ -1472,6 +1476,21 @@ func RequestOrigin(c *gin.Context) string {
return scheme + "://" + c.Request.Host
}

// GithubLoginURL is where the login page's GitHub button points: /login/github,
// carrying where the visitor was heading so GithubLogin can put it in the
// session.
//
// The themes each assembled this with a nested template conditional, which is
// the sort of thing moving the authorize URL into Go was meant to stop. A
// template prints this value instead, so the URL's shape stays the server's
// business and adding to it later touches no theme.
func GithubLoginURL(next string) string {
if next == "" || next == "/" {
return "/login/github"
}
return "/login/github?next=" + url.QueryEscape(next)
}

// GithubAuthorizeURL builds the URL that starts GitHub's OAuth flow.
//
// redirect_uri carries no query of its own. GitHub matches it against the
Expand Down
33 changes: 33 additions & 0 deletions blog/github_login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,36 @@ func TestOAuthSessionKeys(t *testing.T) {
t.Errorf("session keys are not distinct and non-empty: %q %q", auth.OAuthStateKey, auth.OAuthNextKey)
}
}

// TestGithubLoginURL: the themes each assembled this with a nested template
// conditional. It is one value now, so the escaping and the empty-next case
// are decided here rather than four times over in markup.
func TestGithubLoginURL(t *testing.T) {
for _, tc := range []struct{ next, want string }{
{"", "/login/github"},
{"/", "/login/github"},
{"/admin/settings", "/login/github?next=%2Fadmin%2Fsettings"},
// An unescaped & would start a second parameter rather than staying
// part of next.
{"/search?q=a&b=c", "/login/github?next=%2Fsearch%3Fq%3Da%26b%3Dc"},
} {
if got := blog.GithubLoginURL(tc.next); got != tc.want {
t.Errorf("GithubLoginURL(%q) = %q, want %q", tc.next, got, tc.want)
}
}
}

// TestGithubLoginURL_SurvivesARoundTrip: what the button points at must parse
// back to the next it was built from, since GithubLogin reads it as a query
// parameter.
func TestGithubLoginURL_SurvivesARoundTrip(t *testing.T) {
for _, next := range []string{"/admin/settings", "/search?q=a&b=c", "/posts/2026/01/01/a b"} {
u, err := url.Parse(blog.GithubLoginURL(next))
if err != nil {
t.Fatalf("next %q: does not parse: %v", next, err)
}
if got := u.Query().Get("next"); got != next {
t.Errorf("next %q came back as %q", next, got)
}
}
}
2 changes: 1 addition & 1 deletion themes/default/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ <h4>Site Login</h4>
</div>

<div class="container">
<a id="github-button" class="btn btn-social btn-github" href="/login/github{{ if .next }}{{ if ne .next "/" }}?next={{ .next | urlquery }}{{ end }}{{ end }}">
<a id="github-button" class="btn btn-social btn-github" href="{{ .github_login_url }}">
<i class="fab fa-github fa-1x"></i> Sign in with GitHub
</a>

Expand Down
Loading