Skip to content

Commit d603113

Browse files
authored
feat: gate OIDC routes behind explicit configuration (#21)
1 parent adeb4fa commit d603113

File tree

5 files changed

+111
-10
lines changed

5 files changed

+111
-10
lines changed

cmd/server/main.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66
"os"
77
"time"
8+
"strings"
89

910
"github.com/damacus/iron-buckets/internal/handlers"
1011
customMiddleware "github.com/damacus/iron-buckets/internal/middleware"
@@ -41,7 +42,8 @@ func newServer(minioEndpoint string) *echo.Echo {
4142
// Services
4243
authService := services.NewAuthService()
4344
minioFactory := &services.RealMinioFactory{}
44-
authHandler := handlers.NewAuthHandler(authService, minioFactory, minioEndpoint)
45+
oidcEnabled := oidcEnabledFromEnv()
46+
authHandler := handlers.NewAuthHandler(authService, minioFactory, minioEndpoint, oidcEnabled)
4547
usersHandler := handlers.NewUsersHandler(minioFactory)
4648
groupsHandler := handlers.NewGroupsHandler(minioFactory)
4749
bucketsHandler := handlers.NewBucketsHandler(minioFactory)
@@ -69,16 +71,40 @@ func newServer(minioEndpoint string) *echo.Echo {
6971
e.Renderer = renderer.New()
7072

7173
// Public Routes (auth middleware will skip these)
74+
registerPublicRoutes(e, authHandler, oidcEnabled)
75+
76+
// Protected Routes
77+
registerProtectedRoutes(e, drivesHandler, dashboardHandler, usersHandler, groupsHandler, bucketsHandler, settingsHandler)
78+
79+
return e
80+
}
81+
82+
func oidcEnabledFromEnv() bool {
83+
return strings.EqualFold(os.Getenv("OIDC_ENABLED"), "true")
84+
}
85+
86+
func registerPublicRoutes(e *echo.Echo, authHandler *handlers.AuthHandler, oidcEnabled bool) {
7287
e.GET("/health", func(c echo.Context) error {
7388
return c.String(http.StatusOK, "OK")
7489
})
7590
e.GET("/login", authHandler.LoginPage)
7691
e.POST("/login", authHandler.Login)
77-
e.GET("/login/oauth", authHandler.LoginOIDC)
78-
e.GET("/oauth/callback", authHandler.CallbackOIDC)
92+
if oidcEnabled {
93+
e.GET("/login/oauth", authHandler.LoginOIDC)
94+
e.GET("/oauth/callback", authHandler.CallbackOIDC)
95+
}
7996
e.GET("/logout", authHandler.Logout)
97+
}
8098

81-
// Protected Routes
99+
func registerProtectedRoutes(
100+
e *echo.Echo,
101+
drivesHandler *handlers.DrivesHandler,
102+
dashboardHandler *handlers.DashboardHandler,
103+
usersHandler *handlers.UsersHandler,
104+
groupsHandler *handlers.GroupsHandler,
105+
bucketsHandler *handlers.BucketsHandler,
106+
settingsHandler *handlers.SettingsHandler,
107+
) {
82108
e.GET("/", func(c echo.Context) error {
83109
return c.Render(http.StatusOK, "dashboard", map[string]interface{}{
84110
"ActiveNav": "dashboard",
@@ -153,6 +179,4 @@ func newServer(minioEndpoint string) *echo.Echo {
153179
e.GET("/settings", settingsHandler.ShowSettings)
154180
e.POST("/settings/restart", settingsHandler.RestartService)
155181
e.GET("/settings/logs", settingsHandler.GetLogs)
156-
157-
return e
158182
}

cmd/server/oidc_routes_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
8+
"github.com/damacus/iron-buckets/internal/handlers"
9+
"github.com/damacus/iron-buckets/internal/services"
10+
"github.com/labstack/echo/v4"
11+
)
12+
13+
func TestOIDCRoutesDisabledByDefault(t *testing.T) {
14+
e := echo.New()
15+
authHandler := handlers.NewAuthHandler(services.NewAuthService(), &services.RealMinioFactory{}, "localhost:9000", false)
16+
registerPublicRoutes(e, authHandler, false)
17+
18+
req := httptest.NewRequest(http.MethodGet, "/login/oauth", nil)
19+
rec := httptest.NewRecorder()
20+
e.ServeHTTP(rec, req)
21+
22+
if rec.Code != http.StatusNotFound {
23+
t.Fatalf("expected 404 when OIDC is disabled, got %d", rec.Code)
24+
}
25+
}
26+
27+
func TestOIDCRoutesEnabledWithEnvFlag(t *testing.T) {
28+
e := echo.New()
29+
authHandler := handlers.NewAuthHandler(services.NewAuthService(), &services.RealMinioFactory{}, "localhost:9000", true)
30+
registerPublicRoutes(e, authHandler, true)
31+
32+
req := httptest.NewRequest(http.MethodGet, "/login/oauth", nil)
33+
rec := httptest.NewRecorder()
34+
e.ServeHTTP(rec, req)
35+
36+
if rec.Code != http.StatusOK {
37+
t.Fatalf("expected 200 when OIDC is enabled, got %d", rec.Code)
38+
}
39+
}
40+
41+
func TestOIDCEnabledFromEnv(t *testing.T) {
42+
t.Setenv("OIDC_ENABLED", "TRUE")
43+
if !oidcEnabledFromEnv() {
44+
t.Fatal("expected OIDC_ENABLED=TRUE to be treated as enabled")
45+
}
46+
47+
t.Setenv("OIDC_ENABLED", "false")
48+
if oidcEnabledFromEnv() {
49+
t.Fatal("expected OIDC_ENABLED=false to be treated as disabled")
50+
}
51+
}

cmd/server/user_journey_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func TestUserJourney(t *testing.T) {
107107
// Login uses ListBuckets to verify credentials
108108
mockClient.On("ListBuckets", mock.Anything).Return([]minio.BucketInfo{}, nil)
109109

110-
authHandler := handlers.NewAuthHandler(authService, mockFactory, minioEndpoint)
110+
authHandler := handlers.NewAuthHandler(authService, mockFactory, minioEndpoint, false)
111111

112112
// Setup Routes
113113
e.POST("/login", authHandler.Login)

internal/handlers/auth_handler.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ type AuthHandler struct {
1313
authService *services.AuthService
1414
minioFactory services.MinioClientFactory
1515
minioEndpoint string
16+
oidcEnabled bool
1617
}
1718

18-
func NewAuthHandler(authService *services.AuthService, minioFactory services.MinioClientFactory, minioEndpoint string) *AuthHandler {
19+
func NewAuthHandler(authService *services.AuthService, minioFactory services.MinioClientFactory, minioEndpoint string, oidcEnabled bool) *AuthHandler {
1920
return &AuthHandler{
2021
authService: authService,
2122
minioFactory: minioFactory,
2223
minioEndpoint: minioEndpoint,
24+
oidcEnabled: oidcEnabled,
2325
}
2426
}
2527

@@ -103,13 +105,21 @@ func (h *AuthHandler) Logout(c echo.Context) error {
103105

104106
// LoginOIDC initiates the OIDC flow
105107
func (h *AuthHandler) LoginOIDC(c echo.Context) error {
108+
if !h.oidcEnabled {
109+
return echo.NewHTTPError(http.StatusServiceUnavailable, "OIDC login is not enabled")
110+
}
111+
106112
// TODO: Generate state, store in cookie, redirect to OIDC provider
107113
// For now, just return a message
108114
return c.HTML(http.StatusOK, "<h1>OIDC Redirect...</h1><p>(Not fully configured yet)</p>")
109115
}
110116

111117
// CallbackOIDC handles the OIDC callback
112118
func (h *AuthHandler) CallbackOIDC(c echo.Context) error {
119+
if !h.oidcEnabled {
120+
return echo.NewHTTPError(http.StatusServiceUnavailable, "OIDC login is not enabled")
121+
}
122+
113123
// TODO: Exchange code for token, assume role with MinIO, set cookie
114124
return echo.NewHTTPError(http.StatusNotImplemented, "OIDC Callback not implemented")
115125
}

internal/handlers/auth_handler_test.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func TestLoginSetsSecureCookieOverTLS(t *testing.T) {
133133
c := e.NewContext(req, rec)
134134

135135
authService := services.NewAuthService()
136-
handler := NewAuthHandler(authService, &authTestFactory{client: &authTestMinioClient{}}, "play.min.io:9000")
136+
handler := NewAuthHandler(authService, &authTestFactory{client: &authTestMinioClient{}}, "play.min.io:9000", false)
137137

138138
err := handler.Login(c)
139139
require.NoError(t, err)
@@ -161,7 +161,7 @@ func TestLogoutClearsCookieWithMatchingSecurityAttributes(t *testing.T) {
161161
rec := httptest.NewRecorder()
162162
c := e.NewContext(req, rec)
163163

164-
handler := NewAuthHandler(services.NewAuthService(), &authTestFactory{client: &authTestMinioClient{}}, "play.min.io:9000")
164+
handler := NewAuthHandler(services.NewAuthService(), &authTestFactory{client: &authTestMinioClient{}}, "play.min.io:9000", false)
165165

166166
err := handler.Logout(c)
167167
require.NoError(t, err)
@@ -183,3 +183,19 @@ func TestLogoutClearsCookieWithMatchingSecurityAttributes(t *testing.T) {
183183
assert.True(t, sessionCookie.Secure)
184184
assert.Equal(t, "/", sessionCookie.Path)
185185
}
186+
187+
func TestLoginOIDCReturnsDisabledErrorWhenFeatureFlagOff(t *testing.T) {
188+
e := echo.New()
189+
req := httptest.NewRequest(http.MethodGet, "/login/oauth", nil)
190+
rec := httptest.NewRecorder()
191+
c := e.NewContext(req, rec)
192+
193+
handler := NewAuthHandler(services.NewAuthService(), &authTestFactory{client: &authTestMinioClient{}}, "play.min.io:9000", false)
194+
195+
err := handler.LoginOIDC(c)
196+
require.Error(t, err)
197+
198+
httpErr, ok := err.(*echo.HTTPError)
199+
require.True(t, ok)
200+
assert.Equal(t, http.StatusServiceUnavailable, httpErr.Code)
201+
}

0 commit comments

Comments
 (0)