Skip to content
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8481f05
ent re-generation
JeffResc Sep 7, 2025
2e685d8
add oidc integration
JeffResc Sep 7, 2025
5b146be
document oidc integration
JeffResc Sep 7, 2025
a12062e
go fmt
JeffResc Sep 7, 2025
05a9db9
address backend linter findings
JeffResc Sep 7, 2025
aa22330
run prettier on index.vue
JeffResc Sep 7, 2025
55972f2
State cookie domain can mismatch when Hostname override is used (brea…
JeffResc Sep 7, 2025
69f4419
Delete state cookie with matching domain and MaxAge; add SameSite.
JeffResc Sep 7, 2025
cc53768
Fix endpoint path in comments and error to include /api/v1.
JeffResc Sep 7, 2025
f2d8269
Also use request context when verifying the ID token.
JeffResc Sep 7, 2025
4340be6
Do not return raw auth errors to clients (user-enumeration risk).
JeffResc Sep 7, 2025
7a41739
consistently set cookie the same way across function
JeffResc Sep 7, 2025
b9d23db
remove baseURL after declaration
JeffResc Sep 7, 2025
806ab62
only enable OIDC routes if OIDC is enabled
JeffResc Sep 7, 2025
6ed5538
swagger doc for failure
JeffResc Sep 7, 2025
41185b3
Only block when provider=local; move the check after parsing provider
JeffResc Sep 7, 2025
d2ca7fc
fix extended session comment
JeffResc Sep 7, 2025
65601fb
reduce pii logging
JeffResc Sep 7, 2025
6584c04
futher reduce pii logging
JeffResc Sep 7, 2025
3ab4aea
remove unused DiscoveryDocument
JeffResc Sep 7, 2025
4933796
remove unused offline_access from default oidc scopes
JeffResc Sep 7, 2025
f09c708
remove offline access from AuthCodeURL
JeffResc Sep 7, 2025
2cff50b
support host from X-Forwarded-Host
JeffResc Sep 7, 2025
2168155
set sane default claim names if unset
JeffResc Sep 7, 2025
fa6fe20
error strings should not be capitalized
JeffResc Sep 7, 2025
47c8062
Revert "run prettier on index.vue"
JeffResc Sep 7, 2025
5779c40
Add timeout to provider discovery
JeffResc Sep 7, 2025
75a0f30
Split scopes robustly
JeffResc Sep 7, 2025
3cad2f3
refactor hostname calculation
JeffResc Sep 7, 2025
6bfad86
address frontend prettier findings
JeffResc Sep 7, 2025
3a09d64
add property oidc on type APISummary
JeffResc Sep 7, 2025
9063acb
LoginOIDC: Normalize inputs, only create if not found
JeffResc Sep 7, 2025
521e40a
add oidc email verification
JeffResc Sep 7, 2025
77fdeb8
oidc handleCallback: clear state cookie before each return
JeffResc Sep 7, 2025
77fd590
add support for oidc nonce parameter
JeffResc Sep 7, 2025
d7e2c92
Harden first-login race: handle concurrent creates gracefully and fix…
JeffResc Sep 7, 2025
e7cf944
support email verified claim as bool or string
JeffResc Sep 7, 2025
3474145
fail fast on empty email
JeffResc Sep 7, 2025
640bcbf
PKCE verifier
JeffResc Sep 7, 2025
ba965d4
fix: add timing delay to attachment test to resolve CI race condition
JeffResc Sep 7, 2025
7dac37c
Revert "fix: add timing delay to attachment test to resolve CI race c…
JeffResc Sep 7, 2025
b8c0d46
oidc error state, use ref
JeffResc Sep 11, 2025
b9a09cc
rename oidc.force to oidc.authRedirect
JeffResc Sep 11, 2025
10f60f5
remove hardcoded oidc error timeout
JeffResc Sep 11, 2025
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
30 changes: 30 additions & 0 deletions backend/app/api/handlers/v1/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/hay-kot/httpkit/errchain"
"github.com/hay-kot/httpkit/server"
"github.com/rs/zerolog/log"
"github.com/sysadminsmedia/homebox/backend/app/api/providers"
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
"github.com/sysadminsmedia/homebox/backend/internal/core/services/reporting/eventbus"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
Expand Down Expand Up @@ -74,6 +75,7 @@ type V1Controller struct {
bus *eventbus.EventBus
url string
config *config.Config
oidcProvider *providers.OIDCProvider
}

type (
Expand All @@ -95,6 +97,14 @@ type (
Demo bool `json:"demo"`
AllowRegistration bool `json:"allowRegistration"`
LabelPrinting bool `json:"labelPrinting"`
OIDC OIDCStatus `json:"oidc"`
}

OIDCStatus struct {
Enabled bool `json:"enabled"`
ButtonText string `json:"buttonText,omitempty"`
Force bool `json:"force,omitempty"`
AllowLocal bool `json:"allowLocal"`
}
)

Expand All @@ -111,9 +121,23 @@ func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *event
opt(ctrl)
}

ctrl.initOIDCProvider()

return ctrl
}

func (ctrl *V1Controller) initOIDCProvider() {
if ctrl.config.OIDC.Enabled {
oidcProvider, err := providers.NewOIDCProvider(ctrl.svc.User, &ctrl.config.OIDC, &ctrl.config.Options, ctrl.cookieSecure)
if err != nil {
log.Err(err).Msg("failed to initialize OIDC provider at startup")
} else {
ctrl.oidcProvider = oidcProvider
log.Info().Msg("OIDC provider initialized successfully at startup")
}
}
}
Comment on lines +124 to +139
Copy link
Contributor

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:

#!/bin/bash
# Check handlers gate on nil provider
rg -n -C2 'HandleOIDC(Login|Callback)' backend/app/api/handlers/v1 | cat

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 != nil and return a 503 Service Unavailable if it’s nil to prevent panics when initialization fails.

🤖 Prompt for AI Agents
In backend/app/api/handlers/v1/controller.go around lines 124 to 139 and update
the handlers in v1_ctrl_auth.go at lines ~264 and ~291: both HandleOIDCLogin and
HandleOIDCCallback must check if ctrl.oidcProvider == nil at the start and
immediately return a 503 Service Unavailable response when it is nil to avoid
nil-pointer panics; add the nil-check, log or trace the condition as needed, and
write a 503 response (consistent JSON/error format used by other handlers)
instead of proceeding when the provider is not initialized.


// HandleBase godoc
//
// @Summary Application Info
Expand All @@ -132,6 +156,12 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.Hand
Demo: ctrl.isDemo,
AllowRegistration: ctrl.allowRegistration,
LabelPrinting: ctrl.config.LabelMaker.PrintCommand != nil,
OIDC: OIDCStatus{
Enabled: ctrl.config.OIDC.Enabled,
ButtonText: ctrl.config.OIDC.ButtonText,
Force: ctrl.config.OIDC.Force,
AllowLocal: ctrl.config.Options.AllowLocalLogin,
},
})
}
}
Expand Down
72 changes: 70 additions & 2 deletions backend/app/api/handlers/v1/v1_ctrl_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package v1

import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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)

}
}
6 changes: 6 additions & 0 deletions backend/app/api/handlers/v1/v1_ctrl_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ import (
// @Produce json
// @Param payload body services.UserRegistration true "User Data"
// @Success 204
// @Failure 403 {string} string "Local login is not enabled"
// @Router /v1/users/register [Post]
func (ctrl *V1Controller) HandleUserRegistration() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
// Forbidden if local login is not enabled
if !ctrl.config.Options.AllowLocalLogin {
return validate.NewRequestError(fmt.Errorf("local login is not enabled"), http.StatusForbidden)
}

regData := services.UserRegistration{}

if err := server.Decode(r, &regData); err != nil {
Expand Down
Loading
Loading