Skip to content

Commit 870b208

Browse files
authored
Merge pull request #59 from luizfonseca/feat/jwt-and-cookie-expiration
feat: allow users to configure cookie/jwt expiration
2 parents 3530aaa + 7010409 commit 870b208

4 files changed

Lines changed: 62 additions & 19 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ apiSecretKey: optional_secret_key_if_not_on_the_internal_network
8585
authPath: /_auth
8686
# optional jwt secret key, if not set, the plugin will generate a random key
8787
jwtSecretKey: optional_secret_key
88+
# optional jwt expiration in hours, defaults to 24 hours
89+
jwtExpirationInHours: 24
90+
8891
# The log level, defaults to info
8992
# Available values: debug, info, warn, error
9093
logLevel: info

internal/pkg/jwt/jwt.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package jwt
22

33
import (
44
"fmt"
5+
"time"
56

67
"github.com/golang-jwt/jwt/v4"
78
)
@@ -12,11 +13,13 @@ type PayloadUser struct {
1213
Teams []string `json:"teams"`
1314
}
1415

15-
func GenerateJwtTokenString(id string, login string, teamIds []string, key string) (string, error) {
16+
func GenerateJwtTokenString(id string, login string, teamIds []string, key string, exp time.Time) (string, error) {
1617
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
1718
"id": id,
1819
"login": login,
1920
"teams": teamIds,
21+
// buffer of time to expire token is 60 seconds from the set time
22+
"exp": jwt.NewNumericDate(exp.Add(time.Second * 60)),
2023
})
2124
return token.SignedString([]byte(key))
2225
}
@@ -34,6 +37,11 @@ func ParseTokenString(tokenString, key string) (*PayloadUser, error) {
3437
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
3538
var teamFromClaims []interface{}
3639

40+
// Check for expiration time
41+
// if claims.Valid() != nil {
42+
// return nil, fmt.Errorf("token is expired")
43+
// }
44+
3745
switch claims["teams"].(type) {
3846
case []interface{}:
3947
teamFromClaims = claims["teams"].([]interface{})

internal/pkg/jwt/jwt_test.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package jwt
22

33
import (
44
"testing"
5+
"time"
56

67
"github.com/stretchr/testify/assert"
78
)
@@ -16,7 +17,16 @@ const (
1617

1718
func TestGenerateJwtTokenString(t *testing.T) {
1819
// execution
19-
tokenString, err := GenerateJwtTokenString(id, login, testTeams, key)
20+
tokenString, err := GenerateJwtTokenString(id, login, testTeams, key, time.Now())
21+
22+
// assertion
23+
assert.NoError(t, err)
24+
assert.NotEmpty(t, tokenString)
25+
}
26+
27+
func TestGenerateJwtTokenString_Expiration(t *testing.T) {
28+
// execution
29+
tokenString, err := GenerateJwtTokenString(id, login, testTeams, key, time.Now().Add(time.Hour*24*7))
2030

2131
// assertion
2232
assert.NoError(t, err)
@@ -25,7 +35,7 @@ func TestGenerateJwtTokenString(t *testing.T) {
2535

2636
func TestParseTokenString(t *testing.T) {
2737
// setup
28-
tokenString, _ := GenerateJwtTokenString(id, login, testTeams, key)
38+
tokenString, _ := GenerateJwtTokenString(id, login, testTeams, key, time.Now())
2939

3040
// execution
3141
payload, err := ParseTokenString(tokenString, key)
@@ -37,9 +47,21 @@ func TestParseTokenString(t *testing.T) {
3747
assert.Equal(t, testTeams, payload.Teams)
3848
}
3949

50+
func TestParseTokenString_Expired(t *testing.T) {
51+
// setup
52+
tokenString, _ := GenerateJwtTokenString(id, login, testTeams, key, time.Now().Add(-time.Hour*24*7))
53+
54+
// execution
55+
payload, err := ParseTokenString(tokenString, key)
56+
57+
// assertion
58+
assert.Error(t, err)
59+
assert.Nil(t, payload)
60+
}
61+
4062
func TestParseTokenString_EmptyTeams(t *testing.T) {
4163
// setup
42-
tokenString, _ := GenerateJwtTokenString(id, login, []string{}, key)
64+
tokenString, _ := GenerateJwtTokenString(id, login, []string{}, key, time.Now())
4365

4466
// execution
4567
payload, err := ParseTokenString(tokenString, key)
@@ -53,7 +75,7 @@ func TestParseTokenString_EmptyTeams(t *testing.T) {
5375

5476
func TestParseTokenString_NoTeams(t *testing.T) {
5577
// setup
56-
tokenString, _ := GenerateJwtTokenString(id, login, nil, key)
78+
tokenString, _ := GenerateJwtTokenString(id, login, nil, key, time.Now())
5779

5880
// execution
5981
payload, err := ParseTokenString(tokenString, key)
@@ -67,7 +89,7 @@ func TestParseTokenString_NoTeams(t *testing.T) {
6789

6890
func TestParseTokenString_With2FAEnabled(t *testing.T) {
6991
// setup
70-
tokenString, _ := GenerateJwtTokenString(id, login, nil, key)
92+
tokenString, _ := GenerateJwtTokenString(id, login, nil, key, time.Now())
7193

7294
// execution
7395
payload, err := ParseTokenString(tokenString, key)
@@ -93,7 +115,7 @@ func TestParseTokenString_InvalidToken(t *testing.T) {
93115

94116
func TestParseTokenString_InvalidKey(t *testing.T) {
95117
// setup
96-
tokenString, _ := GenerateJwtTokenString(id, login, testTeams, key)
118+
tokenString, _ := GenerateJwtTokenString(id, login, testTeams, key, time.Now())
97119
invalidKey := "invalidkey"
98120

99121
// execution

middleware_plugin.go

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,18 @@ import (
2222

2323
const (
2424
DefaultConfigAuthPath = "/_auth"
25+
OneDayInHours = 24
2526
)
2627

2728
// Config the middleware configuration.
2829
type Config struct {
29-
ApiBaseUrl string `json:"api_base_url,omitempty"`
30-
ApiSecretKey string `json:"api_secret_key,omitempty"`
31-
AuthPath string `json:"auth_path,omitempty"`
32-
JwtSecretKey string `json:"jwt_secret_key,omitempty"`
33-
LogLevel string `json:"log_level,omitempty"`
34-
Whitelist ConfigWhitelist `json:"whitelist,omitempty"`
30+
ApiBaseUrl string `json:"api_base_url,omitempty"`
31+
ApiSecretKey string `json:"api_secret_key,omitempty"`
32+
AuthPath string `json:"auth_path,omitempty"`
33+
JwtSecretKey string `json:"jwt_secret_key,omitempty"`
34+
JwtExpirationInHours int64 `json:"jwt_expiration_in_hours,omitempty"`
35+
LogLevel string `json:"log_level,omitempty"`
36+
Whitelist ConfigWhitelist `json:"whitelist,omitempty"`
3537
}
3638

3739
// ConfigWhitelist the middleware configuration whitelist.
@@ -47,13 +49,14 @@ type ConfigWhitelist struct {
4749
Teams []string `json:"teams,omitempty"`
4850
}
4951

50-
// CreateConfig creates the default middleware configuration.
52+
// CreateConfig creates the default middleware configuration. Required by Traefik.
5153
func CreateConfig() *Config {
5254
return &Config{
53-
ApiBaseUrl: "",
54-
ApiSecretKey: "",
55-
AuthPath: DefaultConfigAuthPath,
56-
JwtSecretKey: getRandomString32(),
55+
ApiBaseUrl: "",
56+
ApiSecretKey: "",
57+
AuthPath: DefaultConfigAuthPath,
58+
JwtSecretKey: getRandomString32(),
59+
JwtExpirationInHours: OneDayInHours,
5760
Whitelist: ConfigWhitelist{
5861
Ids: []string{},
5962
Logins: []string{},
@@ -73,6 +76,7 @@ type TraefikGithubOauthMiddleware struct {
7376
apiSecretKey string
7477
authPath string
7578
jwtSecretKey string
79+
jwtExpirationInHours int64
7680
whitelistIdSet *strset.Set
7781
whitelistLoginSet *strset.Set
7882
whitelistTeamSet *strset.Set
@@ -83,7 +87,7 @@ type TraefikGithubOauthMiddleware struct {
8387

8488
var _ http.Handler = (*TraefikGithubOauthMiddleware)(nil)
8589

86-
// New creates a new TraefikGithubOauthMiddleware.
90+
// New creates a new TraefikGithubOauthMiddleware. Required by Traefik.
8791
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
8892
logger := log.New(os.Stdout, "service=traefik-github-oauth-middleware level=debug msg=", 0)
8993
// endregion Setup logger
@@ -104,6 +108,7 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
104108
apiSecretKey: config.ApiSecretKey,
105109
authPath: authPath,
106110
jwtSecretKey: config.JwtSecretKey,
111+
jwtExpirationInHours: config.JwtExpirationInHours,
107112
whitelistIdSet: strset.New(config.Whitelist.Ids...),
108113
whitelistLoginSet: strset.New(config.Whitelist.Logins...),
109114
whitelistTeamSet: strset.New(config.Whitelist.Teams...),
@@ -132,6 +137,7 @@ func (middleware *TraefikGithubOauthMiddleware) handleRequest(rw http.ResponseWr
132137
if err != nil {
133138
if req.Method == http.MethodGet {
134139
middleware.redirectToOAuthPage(rw, req)
140+
return
135141
}
136142
middleware.logger.Printf("Failed to get user from cookie: %s", err.Error())
137143
http.Error(rw, "", http.StatusUnauthorized)
@@ -171,12 +177,15 @@ func (p TraefikGithubOauthMiddleware) handleAuthRequest(rw http.ResponseWriter,
171177
return
172178
}
173179

180+
exp := time.Now().Add(time.Duration(p.jwtExpirationInHours) * time.Hour)
181+
174182
// Generate JWTs
175183
tokenString, err := jwt.GenerateJwtTokenString(
176184
result.GitHubUserID,
177185
result.GitHubUserLogin,
178186
result.GithubTeamIDs,
179187
p.jwtSecretKey,
188+
exp,
180189
)
181190
if err != nil {
182191
p.logger.Printf("Failed to generate JWT: %s", err.Error())
@@ -187,6 +196,7 @@ func (p TraefikGithubOauthMiddleware) handleAuthRequest(rw http.ResponseWriter,
187196
Name: constant.COOKIE_NAME_JWT,
188197
Value: tokenString,
189198
HttpOnly: true,
199+
Expires: exp,
190200
})
191201
http.Redirect(rw, req, result.RedirectURI, http.StatusFound)
192202
}

0 commit comments

Comments
 (0)