fix: preserve post-login redirect destination through OIDC flow

The next query parameter was silently dropped when users chose OIDC
login, always redirecting to / after authentication. Forward the
validated next value to /auth/oidc, store it in the session during
redirect, and use it for the final redirect in the callback, matching
the behaviour of password-based login.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-03-07 23:58:56 +07:00
commit 7f4c87937c
2 changed files with 15 additions and 2 deletions

View file

@ -46,6 +46,7 @@ const (
SESSION_KEY_OIDC_STATE = "oidc-state"
SESSION_KEY_OIDC_PKCE = "oidc-pkce"
SESSION_KEY_OIDC_NONCE = "oidc-nonce"
SESSION_KEY_OIDC_NEXT = "oidc-next"
)
type OIDCProvider struct {
@ -95,6 +96,12 @@ func NewOIDCProvider(cfg *happydns.Options, authService happydns.AuthenticationU
func (p *OIDCProvider) RedirectOIDC(c *gin.Context) {
session := sessions.Default(c)
// Capture and validate the post-login redirect destination.
// Only accept same-origin relative paths to prevent open redirect.
if next := c.Query("next"); next != "" && strings.HasPrefix(next, "/") && !strings.HasPrefix(next, "//") {
session.Set(SESSION_KEY_OIDC_NEXT, next)
}
state := make([]byte, 32)
_, err := rand.Read(state)
if err != nil {
@ -139,10 +146,12 @@ func (p *OIDCProvider) CompleteOIDC(c *gin.Context) {
pkceVerifier, _ := session.Get(SESSION_KEY_OIDC_PKCE).(string)
expectedNonce, _ := session.Get(SESSION_KEY_OIDC_NONCE).(string)
nextPath, _ := session.Get(SESSION_KEY_OIDC_NEXT).(string)
session.Delete(SESSION_KEY_OIDC_STATE)
session.Delete(SESSION_KEY_OIDC_PKCE)
session.Delete(SESSION_KEY_OIDC_NONCE)
session.Delete(SESSION_KEY_OIDC_NEXT)
err := session.Save()
if err != nil {
log.Println("Unable to CompleteOIDC, session.Save fails:", err)
@ -212,5 +221,9 @@ func (p *OIDCProvider) CompleteOIDC(c *gin.Context) {
middleware.SessionLoginOK(c, &profile)
c.Redirect(http.StatusFound, p.config.GetBaseURL()+"/")
redirectTo := p.config.GetBaseURL() + "/"
if nextPath != "" {
redirectTo = p.config.GetBaseURL() + nextPath
}
c.Redirect(http.StatusFound, redirectTo)
}

View file

@ -175,7 +175,7 @@
</Button>
{#if $appConfig.oidc_configured}
{#await getOidcProvider() then oidc}
<Button href="/auth/oidc" color="secondary">
<Button href={"/auth/oidc" + (page.url.searchParams.get("next") ? "?next=" + page.url.searchParams.get("next") : "")} color="secondary">
{#if oidc.provider == "google.com"}
<i class="bi bi-google"></i>
{:else if oidc.provider == "gitlab.com" || oidc.provider == "framagit.org"}