Files
2026-10-01 20:52:19 +02:00

168 lines
3.8 KiB
Go

package oidclogin
import (
"crypto/rand"
"log"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"golang.org/x/oauth2"
)
func loginHandler(c *gin.Context) {
// Set up user session
state := rand.Text()
nonce := rand.Text()
codeVerifier := oauth2.GenerateVerifier()
session := sessions.Default(c)
if c.Query("returnTo") != "" {
session.Set("redirect", c.Query("returnTo"))
}
session.Set("jhid_oauth_state", state)
session.Set("jhid_oauth_nonce", nonce)
session.Set("jhid_oauth_code_verifier", codeVerifier)
session.Save()
c.Redirect(301, config.AuthCodeURL(state, oidc.Nonce(nonce), oauth2.S256ChallengeOption(codeVerifier)))
}
func callbackHandler(c *gin.Context) {
session := sessions.Default(c)
state := session.Get("jhid_oauth_state")
nonce := session.Get("jhid_oauth_nonce")
codeVerifier := session.Get("jhid_oauth_code_verifier")
if c.Query("state") != state || codeVerifier == nil {
log.Println("State invalid or verifier was not stored")
c.HTML(500, "oidcerror.tmpl", gin.H{
"error": "ERR_INVALID_STATE",
})
c.Abort()
return
}
// Token exchange
tok, err := config.Exchange(c, c.Query("code"), oauth2.VerifierOption(codeVerifier.(string)))
if err != nil {
log.Println("Token exchange failed with error", err)
c.HTML(500, "oidcerror.tmpl", gin.H{
"error": "ERR_AUTH",
})
c.Abort()
return
}
// Extract ID token
rawIdToken, ok := tok.Extra("id_token").(string)
if !ok {
log.Println("Failed to get ID token", err)
c.HTML(500, "oidcerror.tmpl", gin.H{
"error": "ERR_AUTH",
})
c.Abort()
return
}
idToken, err := verifier.Verify(c, rawIdToken)
if err != nil {
log.Println("Token verification failed", err)
c.HTML(500, "oidcerror.tmpl", gin.H{
"error": "ERR_AUTH",
})
c.Abort()
return
}
// Get claims
var claims struct {
Uid string `json:"sub"`
Email string `json:"email"`
Name string `json:"name"`
Nonce string `json:"nonce"`
}
if err := idToken.Claims(&claims); err != nil {
log.Println("Token claims generation failed", err)
c.HTML(500, "oidcerror.tmpl", gin.H{
"error": "ERR_AUTH",
})
c.Abort()
return
}
// Verify NONCE
if nonce != claims.Nonce {
log.Println("Token verification failed (nonce missing)")
c.HTML(500, "oidcerror.tmpl", gin.H{
"error": "ERR_AUTH",
})
c.Abort()
return
}
if userFunc != nil {
userFunc(claims.Uid, claims.Name, claims.Email)
}
// Clear session data of oauth related state
session.Delete("jhid_oauth_nonce")
session.Delete("jhid_oauth_state")
session.Delete("jhid_oauth_code_verifier")
redir := session.Get("redirect")
session.Delete("redirect")
session.Set("jhid_auth", true)
session.Set("jhid_uid", claims.Uid)
session.Options(sessions.Options{
Path: "/",
SameSite: sameSiteMode,
HttpOnly: true,
Secure: prod,
MaxAge: 172800, // Expires in 2 days
})
session.Save()
if redir != nil {
c.Redirect(307, redir.(string))
} else {
c.Redirect(307, defaultRedirect)
}
}
func logoutHandler(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
session.Save()
redir := ""
if c.Query("returnTo") != "" {
redir = c.Query("returnTo")
}
if redir != "" {
c.Redirect(307, redir)
} else {
c.Redirect(307, defaultRedirect)
}
}
// Ensure that a user is currently logged in.
// If redirectFail is set, then if not, the user is redirected.
func EnsureLogin(redirectFail bool) func(c *gin.Context) {
return (func(c *gin.Context) {
session := sessions.Default(c)
if session.Get("jhid_auth") == true {
// Set the UID on the context
c.Set("uid", session.Get("jhid_uid").(string))
c.Next()
} else {
if redirectFail {
c.Redirect(307, "/auth/v2/login")
c.Abort()
return
} else {
c.HTML(401, "autherror.tmpl", gin.H{})
c.Abort()
return
}
}
})
}