-
-
Notifications
You must be signed in to change notification settings - Fork 251
Add support for SSO / OpenID Connect (OIDC) #996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8481f05
2e685d8
5b146be
a12062e
05a9db9
aa22330
55972f2
69f4419
cc53768
f2d8269
4340be6
7a41739
b9d23db
806ab62
6ed5538
41185b3
d2ca7fc
65601fb
6584c04
3ab4aea
4933796
f09c708
2cff50b
2168155
fa6fe20
47c8062
5779c40
75a0f30
3cad2f3
6bfad86
3a09d64
9063acb
521e40a
77fdeb8
77fd590
d7e2c92
e7cf944
3474145
640bcbf
ba965d4
7dac37c
b8c0d46
b9a09cc
10f60f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ package v1 | |
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "net/http" | ||
| "strconv" | ||
| "strings" | ||
|
|
@@ -106,6 +107,11 @@ func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFu | |
| provider = "local" | ||
| } | ||
|
|
||
| // Block local only when disabled | ||
| if provider == "local" && !ctrl.config.Options.AllowLocalLogin { | ||
| return validate.NewRequestError(fmt.Errorf("local login is not enabled"), http.StatusForbidden) | ||
| } | ||
|
|
||
| // Get the provider | ||
| p, ok := providers[provider] | ||
| if !ok { | ||
|
|
@@ -114,8 +120,8 @@ func (ctrl *V1Controller) HandleAuthLogin(ps ...AuthProvider) errchain.HandlerFu | |
|
|
||
| newToken, err := p.Authenticate(w, r) | ||
| if err != nil { | ||
| log.Err(err).Msg("failed to authenticate") | ||
| return server.JSON(w, http.StatusInternalServerError, err.Error()) | ||
| log.Warn().Err(err).Msg("authentication failed") | ||
| return validate.NewUnauthorizedError() | ||
| } | ||
|
|
||
| ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true) | ||
|
|
@@ -247,3 +253,65 @@ func (ctrl *V1Controller) unsetCookies(w http.ResponseWriter, domain string) { | |
| Path: "/", | ||
| }) | ||
| } | ||
|
|
||
| // HandleOIDCLogin godoc | ||
| // | ||
| // @Summary OIDC Login Initiation | ||
| // @Tags Authentication | ||
| // @Produce json | ||
| // @Success 302 | ||
| // @Router /v1/users/login/oidc [GET] | ||
| func (ctrl *V1Controller) HandleOIDCLogin() errchain.HandlerFunc { | ||
| return func(w http.ResponseWriter, r *http.Request) error { | ||
| // Forbidden if OIDC is not enabled | ||
| if !ctrl.config.OIDC.Enabled { | ||
| return validate.NewRequestError(fmt.Errorf("OIDC is not enabled"), http.StatusForbidden) | ||
| } | ||
|
|
||
| // Check if OIDC provider is available | ||
| if ctrl.oidcProvider == nil { | ||
| log.Error().Msg("OIDC provider not initialized") | ||
| return validate.NewRequestError(errors.New("OIDC provider not available"), http.StatusInternalServerError) | ||
| } | ||
|
|
||
| // Initiate OIDC flow | ||
| _, err := ctrl.oidcProvider.InitiateOIDCFlow(w, r) | ||
| return err | ||
| } | ||
| } | ||
|
|
||
| // HandleOIDCCallback godoc | ||
| // | ||
| // @Summary OIDC Callback Handler | ||
| // @Tags Authentication | ||
| // @Param code query string true "Authorization code" | ||
| // @Param state query string true "State parameter" | ||
| // @Success 302 | ||
| // @Router /v1/users/login/oidc/callback [GET] | ||
| func (ctrl *V1Controller) HandleOIDCCallback() errchain.HandlerFunc { | ||
| return func(w http.ResponseWriter, r *http.Request) error { | ||
| // Forbidden if OIDC is not enabled | ||
| if !ctrl.config.OIDC.Enabled { | ||
| return validate.NewRequestError(fmt.Errorf("OIDC is not enabled"), http.StatusForbidden) | ||
| } | ||
|
|
||
| // Check if OIDC provider is available | ||
| if ctrl.oidcProvider == nil { | ||
| log.Error().Msg("OIDC provider not initialized") | ||
| return validate.NewRequestError(errors.New("OIDC provider not available"), http.StatusInternalServerError) | ||
| } | ||
|
|
||
| // Handle callback | ||
| newToken, err := ctrl.oidcProvider.HandleCallback(w, r) | ||
| if err != nil { | ||
| log.Err(err).Msg("OIDC callback failed") | ||
| http.Redirect(w, r, "/?oidc_error=oidc_auth_failed", http.StatusFound) | ||
| return nil | ||
| } | ||
|
|
||
| // Set cookies and redirect to home | ||
| ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true) | ||
| http.Redirect(w, r, "/home", http.StatusFound) | ||
| return nil | ||
|
Comment on lines
+312
to
+315
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Set cookies with proxy/hostname-aware domain to avoid lost sessions behind reverse proxies. noPort(r.Host) can differ from the effective host when Options.Hostname or X-Forwarded-Host are used, causing the browser not to send cookies back. Derive the cookie domain like the OIDC provider does. Apply: - // Set cookies and redirect to home
- ctrl.setCookies(w, noPort(r.Host), newToken.Raw, newToken.ExpiresAt, true)
+ // Set cookies and redirect to home (respect Options.Hostname / proxies)
+ domain := noPort(r.Host)
+ if h := strings.TrimSpace(ctrl.config.Options.Hostname); h != "" {
+ domain = strings.Split(h, ":")[0]
+ } else if ctrl.config.Options.TrustProxy {
+ if xf := r.Header.Get("X-Forwarded-Host"); xf != "" {
+ // take first host if multiple
+ xfh := strings.TrimSpace(strings.Split(xf, ",")[0])
+ domain = strings.Split(xfh, ":")[0]
+ }
+ }
+ ctrl.setCookies(w, domain, newToken.Raw, newToken.ExpiresAt, true) |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Startup initialization is fine; ensure handlers guard nil provider.
If provider init fails, ctrl.oidcProvider remains nil. Verify login/callback handlers short-circuit with a 503-style error instead of panicking.
Run:
🏁 Script executed:
Length of output: 1555
Guard against nil OIDC provider in handlers
In HandleOIDCLogin (v1_ctrl_auth.go:264) and HandleOIDCCallback (v1_ctrl_auth.go:291), add a check for
ctrl.oidcProvider != niland return a 503 Service Unavailable if it’s nil to prevent panics when initialization fails.🤖 Prompt for AI Agents