Skip to content

Commit 0a42b05

Browse files
authored
Merge pull request #45 from luizfonseca/fix/team-check
fix: check for the correct variable holding GitHub team information
2 parents 64c1d31 + 92ccd69 commit 0a42b05

File tree

7 files changed

+56
-63
lines changed

7 files changed

+56
-63
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16-
dist/*
16+
dist/*
17+
.envrc

README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ providing a more secure way for users to access protected routes.
5050
--label 'traefik.http.middlewares.whoami-github-oauth.plugin.github-oauth.apiBaseUrl=http://traefik-github-oauth-server' \
5151
--label 'traefik.http.middlewares.whoami-github-oauth.plugin.github-oauth.whitelist.logins[0]=luizfonseca' \
5252
--label 'traefik.http.middlewares.whoami-github-oauth.plugin.github-oauth.whitelist.teams[0]=827726' \
53-
--label 'traefik.http.middlewares.whoami-github-oauth.plugin.github-oauth.whitelist.twoFactorAuthRequired=true' \
5453
--label 'traefik.http.routers.whoami.rule=Host(`whoami.example.com`)' \
5554
--label 'traefik.http.routers.whoami.middlewares=whoami-github-oauth' \
5655
traefik/whoami
@@ -89,11 +88,6 @@ logLevel: info
8988

9089
# whitelist
9190
whitelist:
92-
# When set to `true`, the middleware will check if the given user has 2FA
93-
# configured, otherwise they will be denied access
94-
# Default is `false`
95-
twoFactorAuthRequired: 'true'
96-
9791
# The list of GitHub user ids that are whitelisted to access the resources
9892
ids:
9993
- 996

internal/app/traefik-github-oauth-server/app.go

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77
"os"
88
"os/signal"
9+
"strings"
910
"time"
1011

1112
"github.com/go-chi/chi/v5"
@@ -35,22 +36,6 @@ func NewApp(
3536
authRequestManager *AuthRequestManager,
3637
logger *zerolog.Logger,
3738
) *App {
38-
if config.DebugMode {
39-
config.LogLevel = "DEBUG"
40-
}
41-
42-
switch config.LogLevel {
43-
case "DEBUG", "debug":
44-
logger.Level(zerolog.DebugLevel)
45-
case "INFO", "info":
46-
logger.Level(zerolog.InfoLevel)
47-
case "WARNING", "warning", "WARN", "warn":
48-
logger.Level(zerolog.WarnLevel)
49-
case "ERROR", "error":
50-
logger.Level(zerolog.ErrorLevel)
51-
default:
52-
logger.Level(zerolog.InfoLevel)
53-
}
5439

5540
server.Addr = config.ServerAddress
5641
server.Handler = router
@@ -72,8 +57,31 @@ func NewApp(
7257
return app
7358
}
7459

60+
func stringToLogLevel(config *Config) zerolog.Level {
61+
if config.DebugMode {
62+
config.LogLevel = "DEBUG"
63+
}
64+
65+
switch strings.ToLower(config.LogLevel) {
66+
case "debug":
67+
return zerolog.DebugLevel
68+
case "info":
69+
return zerolog.InfoLevel
70+
case "warn":
71+
return zerolog.WarnLevel
72+
case "error":
73+
return zerolog.ErrorLevel
74+
case "fatal":
75+
return zerolog.FatalLevel
76+
default:
77+
return zerolog.InfoLevel
78+
}
79+
}
80+
7581
func NewDefaultApp() *App {
76-
logger := zerolog.New(os.Stdout).With().Str("service", "traefik-github-oauth").Timestamp().Logger()
82+
config := NewConfigFromEnv()
83+
84+
logger := zerolog.New(os.Stdout).Level(stringToLogLevel(config)).With().Str("service", "traefik-github-oauth").Timestamp().Logger()
7785

7886
router := chi.NewRouter()
7987

@@ -100,7 +108,6 @@ func NewDefaultApp() *App {
100108
// Recoverer middleware recovers from panics and writes a 500 if there was one.
101109
router.Use(middleware.Recoverer)
102110

103-
config := NewConfigFromEnv()
104111
return NewApp(
105112
config,
106113
&http.Server{

internal/app/traefik-github-oauth-server/router/oauth.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func OauthRedirectHandler(app *server.App) http.HandlerFunc {
111111
authRequest.GitHubUserLogin = githubData.User.GetLogin()
112112
authRequest.GithubUserTwoFactorAuth = githubData.User.GetTwoFactorAuthentication()
113113

114-
if authRequest.GithubTeamIDs != nil {
114+
if githubData.Teams != nil {
115115
var teamIDs []string
116116
for _, team := range githubData.Teams {
117117
teamIDs = append(teamIDs, cast.ToString(team.GetID()))
@@ -120,6 +120,8 @@ func OauthRedirectHandler(app *server.App) http.HandlerFunc {
120120
authRequest.GithubTeamIDs = teamIDs
121121
}
122122

123+
app.Logger.Debug().Interface("currentAuthRequest", authRequest).Msg("user auth request")
124+
123125
authURL, err := url.Parse(authRequest.AuthURL)
124126
if err != nil {
125127
app.Logger.Warn().
@@ -189,6 +191,7 @@ func oAuthCodeToUser(ctx context.Context, oAuthConfig *oauth2.Config, code strin
189191
ctxExchange, cancelExchange := context.WithCancel(ctx)
190192
defer cancelExchange()
191193
token, err := oAuthConfig.Exchange(ctxExchange, code)
194+
192195
if err != nil {
193196
return nil, err
194197
}
@@ -201,7 +204,9 @@ func oAuthCodeToUser(ctx context.Context, oAuthConfig *oauth2.Config, code strin
201204
// Get user information, login and ID
202205
ctxGetUser, cancelGetUser := context.WithCancel(ctx)
203206
defer cancelGetUser()
207+
204208
user, _, err := gitHubApiClient.Users.Get(ctxGetUser, "")
209+
205210
if err != nil {
206211
return nil, err
207212
}
@@ -210,6 +215,7 @@ func oAuthCodeToUser(ctx context.Context, oAuthConfig *oauth2.Config, code strin
210215
// This won't cancel the main request
211216
ctxTeams, cancelListTeams := context.WithCancel(ctx)
212217
teams, _, err := gitHubApiClient.Teams.ListUserTeams(ctxTeams, &github.ListOptions{PerPage: 100})
218+
213219
defer cancelListTeams()
214220
if err != nil {
215221
// If the user is not a member of any teams, the API will return a 404

internal/pkg/jwt/jwt.go

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@ import (
77
)
88

99
type PayloadUser struct {
10-
Id string `json:"id"`
11-
Login string `json:"login"`
12-
Teams []string `json:"teams"`
13-
TwoFactorEnabled bool `json:"two_factor_enabled"`
10+
Id string `json:"id"`
11+
Login string `json:"login"`
12+
Teams []string `json:"teams"`
1413
}
1514

16-
func GenerateJwtTokenString(id string, login string, teamIds []string, key string, two_factor_enabled bool) (string, error) {
15+
func GenerateJwtTokenString(id string, login string, teamIds []string, key string) (string, error) {
1716
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
18-
"id": id,
19-
"login": login,
20-
"teams": teamIds,
21-
"two_factor_enabled": two_factor_enabled,
17+
"id": id,
18+
"login": login,
19+
"teams": teamIds,
2220
})
2321
return token.SignedString([]byte(key))
2422
}
@@ -51,18 +49,10 @@ func ParseTokenString(tokenString, key string) (*PayloadUser, error) {
5149
}
5250
}
5351

54-
twoFactorEnabled := false
55-
if claims["two_factor_enabled"] != nil {
56-
if factorEnabled, ok := claims["two_factor_enabled"].(bool); ok {
57-
twoFactorEnabled = factorEnabled
58-
}
59-
}
60-
6152
return &PayloadUser{
62-
Id: claims["id"].(string),
63-
Login: claims["login"].(string),
64-
Teams: teams,
65-
TwoFactorEnabled: twoFactorEnabled,
53+
Id: claims["id"].(string),
54+
Login: claims["login"].(string),
55+
Teams: teams,
6656
}, nil
6757
} else {
6858
return nil, fmt.Errorf("invalid token")

internal/pkg/jwt/jwt_test.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const (
1616

1717
func TestGenerateJwtTokenString(t *testing.T) {
1818
// execution
19-
tokenString, err := GenerateJwtTokenString(id, login, testTeams, key, false)
19+
tokenString, err := GenerateJwtTokenString(id, login, testTeams, key)
2020

2121
// assertion
2222
assert.NoError(t, err)
@@ -25,7 +25,7 @@ func TestGenerateJwtTokenString(t *testing.T) {
2525

2626
func TestParseTokenString(t *testing.T) {
2727
// setup
28-
tokenString, _ := GenerateJwtTokenString(id, login, testTeams, key, false)
28+
tokenString, _ := GenerateJwtTokenString(id, login, testTeams, key)
2929

3030
// execution
3131
payload, err := ParseTokenString(tokenString, key)
@@ -35,12 +35,11 @@ func TestParseTokenString(t *testing.T) {
3535
assert.Equal(t, id, payload.Id)
3636
assert.Equal(t, login, payload.Login)
3737
assert.Equal(t, testTeams, payload.Teams)
38-
assert.False(t, payload.TwoFactorEnabled)
3938
}
4039

4140
func TestParseTokenString_EmptyTeams(t *testing.T) {
4241
// setup
43-
tokenString, _ := GenerateJwtTokenString(id, login, []string{}, key, false)
42+
tokenString, _ := GenerateJwtTokenString(id, login, []string{}, key)
4443

4544
// execution
4645
payload, err := ParseTokenString(tokenString, key)
@@ -50,12 +49,11 @@ func TestParseTokenString_EmptyTeams(t *testing.T) {
5049
assert.Equal(t, id, payload.Id)
5150
assert.Equal(t, login, payload.Login)
5251
assert.Equal(t, payload.Teams, []string{})
53-
assert.False(t, payload.TwoFactorEnabled)
5452
}
5553

5654
func TestParseTokenString_NoTeams(t *testing.T) {
5755
// setup
58-
tokenString, _ := GenerateJwtTokenString(id, login, nil, key, false)
56+
tokenString, _ := GenerateJwtTokenString(id, login, nil, key)
5957

6058
// execution
6159
payload, err := ParseTokenString(tokenString, key)
@@ -65,12 +63,11 @@ func TestParseTokenString_NoTeams(t *testing.T) {
6563
assert.Equal(t, id, payload.Id)
6664
assert.Equal(t, login, payload.Login)
6765
assert.Equal(t, payload.Teams, []string{})
68-
assert.False(t, payload.TwoFactorEnabled)
6966
}
7067

7168
func TestParseTokenString_With2FAEnabled(t *testing.T) {
7269
// setup
73-
tokenString, _ := GenerateJwtTokenString(id, login, nil, key, true)
70+
tokenString, _ := GenerateJwtTokenString(id, login, nil, key)
7471

7572
// execution
7673
payload, err := ParseTokenString(tokenString, key)
@@ -80,7 +77,6 @@ func TestParseTokenString_With2FAEnabled(t *testing.T) {
8077
assert.Equal(t, id, payload.Id)
8178
assert.Equal(t, login, payload.Login)
8279
assert.Equal(t, payload.Teams, []string{})
83-
assert.True(t, payload.TwoFactorEnabled)
8480
}
8581

8682
func TestParseTokenString_InvalidToken(t *testing.T) {
@@ -97,7 +93,7 @@ func TestParseTokenString_InvalidToken(t *testing.T) {
9793

9894
func TestParseTokenString_InvalidKey(t *testing.T) {
9995
// setup
100-
tokenString, _ := GenerateJwtTokenString(id, login, testTeams, key, false)
96+
tokenString, _ := GenerateJwtTokenString(id, login, testTeams, key)
10197
invalidKey := "invalidkey"
10298

10399
// execution

middleware_plugin.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,11 @@ func (middleware *TraefikGithubOauthMiddleware) handleRequest(rw http.ResponseWr
139139
}
140140

141141
// Early check for 2FA -- if user is not whitelisted and 2FA is required, return 401
142-
if middleware.whitelistRequires2FA && !user.TwoFactorEnabled {
143-
setNoCacheHeaders(rw)
144-
http.Error(rw, "", http.StatusUnauthorized)
145-
return
146-
}
142+
// if middleware.whitelistRequires2FA && !user.TwoFactorEnabled {
143+
// setNoCacheHeaders(rw)
144+
// http.Error(rw, "", http.StatusUnauthorized)
145+
// return
146+
// }
147147

148148
// If cookie is present, check if user is whitelisted
149149
// If nothing can be found, returns 404 as we don't want to leak information
@@ -152,7 +152,7 @@ func (middleware *TraefikGithubOauthMiddleware) handleRequest(rw http.ResponseWr
152152
if !middleware.whitelistIdSet.Has(user.Id) &&
153153
!middleware.whitelistLoginSet.Has(user.Login) && !middleware.whitelistTeamSet.HasAny(user.Teams...) {
154154
setNoCacheHeaders(rw)
155-
http.Error(rw, "", http.StatusNotFound)
155+
http.Error(rw, "", http.StatusUnauthorized)
156156
return
157157
}
158158

@@ -177,7 +177,6 @@ func (p TraefikGithubOauthMiddleware) handleAuthRequest(rw http.ResponseWriter,
177177
result.GitHubUserLogin,
178178
result.GithubTeamIDs,
179179
p.jwtSecretKey,
180-
p.whitelistRequires2FA,
181180
)
182181
if err != nil {
183182
p.logger.Printf("Failed to generate JWT: %s", err.Error())

0 commit comments

Comments
 (0)