From b349716d2ddbb3f20003307f7159036b59e3e351 Mon Sep 17 00:00:00 2001 From: Janis Hutz Date: Sat, 19 Sep 2026 16:30:35 +0200 Subject: [PATCH] feat: improve error handling, some docs, stubs --- README.md | 8 ++++++++ handlers.go | 45 +++++++++++++++++++++++++++++++++---------- main.go | 38 ++++++++++++++++++++++++++---------- public/autherror.tmpl | 0 public/oidcerror.tmpl | 1 + stubs.go | 22 +++++++++++++++++++++ 6 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 public/autherror.tmpl create mode 100644 stubs.go diff --git a/README.md b/README.md index 9d1e443..14b3a1f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # oidclogin OpenID Connect Login SDK written in Go for Gin. + +The following environment variables are expected to be set: +```env +OIDC_CLIENT_ID= +OIDC_CLIENT_SECRET= +OIDC_ISSUE= +``` +If these env vars are not present, then depending on configuration, stubs will be used for the endpoints or the program will crash diff --git a/handlers.go b/handlers.go index 03ac67a..27f90ee 100644 --- a/handlers.go +++ b/handlers.go @@ -15,7 +15,11 @@ func LoginHandler(c *gin.Context) { 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) @@ -27,23 +31,26 @@ 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").(string) + codeVerifier := session.Get("jhid_oauth_code_verifier") - if c.Query("state") != state { - log.Println("State invalid") + if c.Query("state") != state || codeVerifier == nil { + log.Println("State invalid or verifier was not stored") // TODO: Proper pages c.HTML(500, "oidcerror.tmpl", gin.H{ - "error": "", + "error": "ERR_INVALID_STATE", }) - c.AbortWithStatus(500) + c.Abort() return } // Token exchange - tok, err := config.Exchange(c, c.Query("code"), oauth2.VerifierOption(codeVerifier)) + tok, err := config.Exchange(c, c.Query("code"), oauth2.VerifierOption(codeVerifier.(string))) if err != nil { log.Println("Token exchange failed with error", err) - c.AbortWithStatus(500) + c.HTML(500, "oidcerror.tmpl", gin.H{ + "error": "ERR_AUTH", + }) + c.Abort() return } @@ -51,13 +58,19 @@ func CallbackHandler(c *gin.Context) { rawIdToken, ok := tok.Extra("id_token").(string) if !ok { log.Println("Failed to get ID token", err) - c.AbortWithStatus(500) + 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.AbortWithStatus(500) + c.HTML(500, "oidcerror.tmpl", gin.H{ + "error": "ERR_AUTH", + }) + c.Abort() return } @@ -85,12 +98,21 @@ func CallbackHandler(c *gin.Context) { 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.Save() + + if redir != nil { + c.Redirect(307, redir.(string)) + } else { + c.Redirect(307, defaultRedirect) + } } func EnsureLogin(redirectFail bool) func(c *gin.Context) { @@ -104,7 +126,10 @@ func EnsureLogin(redirectFail bool) func(c *gin.Context) { c.Abort() return } else { - c.AbortWithStatus(401) + c.HTML(401, "autherror.tmpl", gin.H{ + "error": "ERR_AUTH", + }) + c.Abort() return } } diff --git a/main.go b/main.go index c92fa28..693b347 100644 --- a/main.go +++ b/main.go @@ -21,27 +21,45 @@ func createRandomString(n int) (string, error) { } var ( - config oauth2.Config - userFunc func(userid string, name string, email string) - verifier oidc.IDTokenVerifier + config oauth2.Config + userFunc func(userid string, name string, email string) + verifier oidc.IDTokenVerifier + defaultRedirect string ) -func Configure(r *gin.Engine, app_url string, scopes []string) { - r.GET("/auth/v2/login", LoginHandler) - r.GET("/auth/v2/verify", CallbackHandler) - - provider, err := oidc.NewProvider(context.Background(), os.Getenv("OIDC_ISSUER")) +func Configure(r *gin.Engine, app_url string, default_redirect string, stubs_on_unconfigured bool) { + issuer := os.Getenv("OIDC_ISSUER") clientID := os.Getenv("OIDC_CLIENT_ID") + clientSecret := os.Getenv("OIDC_CLIENT_SECRET") + + if issuer == "" || clientID == "" || clientSecret == "" { + if stubs_on_unconfigured { + log.Println("[JHID] WARNING: OIDC not set up due to missing environment variables. Falling back to stubs") + startStubs(r) + return + } else { + log.Fatal("[JHID] One or more requried environment variables are missing. See docs for more information") + } + } + + provider, err := oidc.NewProvider(context.Background(), issuer) verifier = *provider.Verifier(&oidc.Config{ClientID: clientID}) + defaultRedirect = default_redirect + if err != nil { - log.Fatal("Provider resolution failed with error", err) + log.Fatal("[JHID] Provider resolution failed with error", err) } config = oauth2.Config{ ClientID: clientID, - ClientSecret: os.Getenv("OIDC_CLIENT_SECRET"), + ClientSecret: clientSecret, Endpoint: provider.Endpoint(), RedirectURL: app_url + "/auth/v2/verify", Scopes: []string{oidc.ScopeOpenID, "email", "profile"}, } + + r.GET("/auth/v2/login", LoginHandler) + r.GET("/auth/v2/verify", CallbackHandler) + + log.Println("[JHID] Configured successfully") } diff --git a/public/autherror.tmpl b/public/autherror.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/public/oidcerror.tmpl b/public/oidcerror.tmpl index 72b4b30..c6e3dde 100644 --- a/public/oidcerror.tmpl +++ b/public/oidcerror.tmpl @@ -4,5 +4,6 @@

Login Failed!

+

{{ .error }}

diff --git a/stubs.go b/stubs.go new file mode 100644 index 0000000..f26973a --- /dev/null +++ b/stubs.go @@ -0,0 +1,22 @@ +package oidclogin + +import "github.com/gin-gonic/gin" + +func startStubs(r *gin.Engine) { + r.GET("/auth/v2/login", func(ctx *gin.Context) { + redir := ctx.Query("returnTo") + if redir != "" { + ctx.Redirect(307, redir) + } else { + ctx.Redirect(307, defaultRedirect) + } + }) + r.GET("/auth/v2/verify", func(ctx *gin.Context) { + redir := ctx.Query("returnTo") + if redir != "" { + ctx.Redirect(307, redir) + } else { + ctx.Redirect(307, defaultRedirect) + } + }) +}