mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-12 04:04:26 +00:00
Add option to exclude URLs from the authentication.
This commit is contained in:
+6
-3
@@ -12,11 +12,14 @@ testData:
|
|||||||
clientSecret: secret
|
clientSecret: secret
|
||||||
callbackURL: /oauth2/callback
|
callbackURL: /oauth2/callback
|
||||||
logoutURL: /oauth2/logout
|
logoutURL: /oauth2/logout
|
||||||
scopes:
|
scopes: # If not provided, default scopes will be used (openid, email, profile)
|
||||||
- openid
|
- openid
|
||||||
- email
|
- email
|
||||||
- profile
|
- profile
|
||||||
sessionEncryptionKey: potato-secret
|
sessionEncryptionKey: potato-secret
|
||||||
forceHTTPS: false
|
forceHTTPS: false
|
||||||
logLevel: debug
|
logLevel: debug # debug, info, warn, error
|
||||||
rateLimit: 100
|
rateLimit: 100 # Simple rate limiter to prevent brute force attacks
|
||||||
|
excludedURLs: # Determines the list of URLs which are NOT a subject to authentication
|
||||||
|
- /login
|
||||||
|
- /my-public-data
|
||||||
|
|||||||
@@ -90,12 +90,15 @@ http:
|
|||||||
clientSecret: secret
|
clientSecret: secret
|
||||||
callbackURL: /oauth2/callback
|
callbackURL: /oauth2/callback
|
||||||
logoutURL: /oauth2/logout
|
logoutURL: /oauth2/logout
|
||||||
scopes:
|
scopes: # If not provided, default scopes will be used (openid, email, profile)
|
||||||
- openid
|
- openid
|
||||||
- email
|
- email
|
||||||
- profile
|
- profile
|
||||||
sessionEncryptionKey: potato-secret
|
sessionEncryptionKey: potato-secret
|
||||||
forceHTTPS: false
|
forceHTTPS: false
|
||||||
logLevel: info
|
logLevel: debug # debug, info, warn, error
|
||||||
rateLimit: 100 # 100 requests per minute
|
rateLimit: 100 # Simple rate limiter to prevent brute force attacks
|
||||||
|
excludedURLs: # Determines the list of URLs which are NOT a subject to authentication
|
||||||
|
- /login # covers /login, /login/me, /login/reminder etc.
|
||||||
|
- /my-public-data
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type TraefikOidc struct {
|
|||||||
redirectURL string
|
redirectURL string
|
||||||
tokenVerifier TokenVerifier
|
tokenVerifier TokenVerifier
|
||||||
jwtVerifier JWTVerifier
|
jwtVerifier JWTVerifier
|
||||||
|
excludedURLs map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderMetadata struct {
|
type ProviderMetadata struct {
|
||||||
@@ -178,7 +179,14 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
|
|||||||
tokenCache: NewTokenCache(),
|
tokenCache: NewTokenCache(),
|
||||||
httpClient: httpClient,
|
httpClient: httpClient,
|
||||||
logger: NewLogger(config.LogLevel),
|
logger: NewLogger(config.LogLevel),
|
||||||
redirectURL: "",
|
excludedURLs: func() map[string]struct{} {
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
for _, url := range config.ExcludedURLs {
|
||||||
|
m[url] = struct{}{}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}(),
|
||||||
|
redirectURL: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
t.tokenVerifier = t
|
t.tokenVerifier = t
|
||||||
@@ -211,6 +219,11 @@ func discoverProviderMetadata(providerURL string, httpClient http.Client) (*Prov
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if t.determineExcludedURL(req.URL.Path) {
|
||||||
|
t.next.ServeHTTP(rw, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
t.scheme = t.determineScheme(req)
|
t.scheme = t.determineScheme(req)
|
||||||
host := t.determineHost(req)
|
host := t.determineHost(req)
|
||||||
|
|
||||||
@@ -266,6 +279,15 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
t.initiateAuthentication(rw, req, session, t.redirectURL)
|
t.initiateAuthentication(rw, req, session, t.redirectURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TraefikOidc) determineExcludedURL(currentRequest string) bool {
|
||||||
|
for excludedURL := range t.excludedURLs {
|
||||||
|
if strings.HasPrefix(currentRequest, excludedURL) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TraefikOidc) determineScheme(req *http.Request) string {
|
func (t *TraefikOidc) determineScheme(req *http.Request) string {
|
||||||
if t.forceHTTPS {
|
if t.forceHTTPS {
|
||||||
return "https"
|
return "https"
|
||||||
|
|||||||
@@ -749,3 +749,65 @@ func (suite *TraefikOidcTestSuite) TestDiscoverProviderMetadata_InvalidURL() {
|
|||||||
suite.Error(err)
|
suite.Error(err)
|
||||||
suite.Contains(err.Error(), "failed to fetch provider metadata")
|
suite.Contains(err.Error(), "failed to fetch provider metadata")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *TraefikOidcTestSuite) TestServeHTTP_ExcludedURLs() {
|
||||||
|
suite.oidc.excludedURLs = map[string]struct{}{
|
||||||
|
"/public": {},
|
||||||
|
"/api/health": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
expectedStatus int
|
||||||
|
expectedBody string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Excluded URL - public",
|
||||||
|
url: "http://example.com/public",
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: "Public content",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Excluded URL - api health",
|
||||||
|
url: "http://example.com/api/health",
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: "API is healthy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-excluded URL",
|
||||||
|
url: "http://example.com/private",
|
||||||
|
expectedStatus: http.StatusFound, // Expect a redirect to auth
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
req := httptest.NewRequest("GET", tc.url, nil)
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
|
||||||
|
if !strings.HasPrefix(req.URL.Path, "/public") && !strings.HasPrefix(req.URL.Path, "/api/health") {
|
||||||
|
// For non-excluded URLs, set up session mock
|
||||||
|
session := sessions.NewSession(suite.mockStore, cookieName)
|
||||||
|
suite.mockStore.On("Get", req, cookieName).Return(session, nil).Once()
|
||||||
|
suite.mockStore.On("Save", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||||
|
}
|
||||||
|
|
||||||
|
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(tc.expectedBody))
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.oidc.next = nextHandler
|
||||||
|
|
||||||
|
suite.oidc.ServeHTTP(rw, req)
|
||||||
|
|
||||||
|
suite.Equal(tc.expectedStatus, rw.Code)
|
||||||
|
if tc.expectedStatus == http.StatusOK {
|
||||||
|
suite.Equal(tc.expectedBody, rw.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.mockStore.AssertExpectations(suite.T())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type Config struct {
|
|||||||
SessionEncryptionKey string `json:"sessionEncryptionKey"`
|
SessionEncryptionKey string `json:"sessionEncryptionKey"`
|
||||||
ForceHTTPS bool `json:"forceHTTPS"`
|
ForceHTTPS bool `json:"forceHTTPS"`
|
||||||
RateLimit int `json:"rateLimit"`
|
RateLimit int `json:"rateLimit"`
|
||||||
|
ExcludedURLs []string `json:"excludedURLs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateConfig() *Config {
|
func CreateConfig() *Config {
|
||||||
|
|||||||
Reference in New Issue
Block a user