git.iko.soy/typeable/oauth2-proxy
on master help sign in
Improvements to Session State code (#536)
f7b28cbJoel Speed
CHANGELOG.md+1
@@ -51,6 +51,7 @@
51
51
52## Changes since v5.1.1
52## Changes since v5.1.1
53
53
54+- [#536](https://github.com/oauth2-proxy/oauth2-proxy/pull/536) Improvements to Session State code (@JoelSpeed)
54- [#573](https://github.com/oauth2-proxy/oauth2-proxy/pull/573) Properly parse redis urls for cluster and sentinel connections (@amnay-mo)
55- [#573](https://github.com/oauth2-proxy/oauth2-proxy/pull/573) Properly parse redis urls for cluster and sentinel connections (@amnay-mo)
55- [#574](https://github.com/oauth2-proxy/oauth2-proxy/pull/574) render error page on 502 proxy status (@amnay-mo)
56- [#574](https://github.com/oauth2-proxy/oauth2-proxy/pull/574) render error page on 502 proxy status (@amnay-mo)
56- [#559](https://github.com/oauth2-proxy/oauth2-proxy/pull/559) Rename cookie-domain config to cookie-domains (@JoelSpeed)
57- [#559](https://github.com/oauth2-proxy/oauth2-proxy/pull/559) Rename cookie-domain config to cookie-domains (@JoelSpeed)
oauthproxy_test.go+23−15
@@ -484,8 +484,7 @@ func TestBasicAuthPassword(t *testing.T) {
484 })
484 })
485
485
486 rw := httptest.NewRecorder()
486 rw := httptest.NewRecorder()
487− req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
487+ req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
488− strings.NewReader(""))
489 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
488 req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
490 proxy.ServeHTTP(rw, req)
489 proxy.ServeHTTP(rw, req)
491 if rw.Code >= 400 {
490 if rw.Code >= 400 {
@@ -541,11 +540,12 @@ func TestBasicAuthWithEmail(t *testing.T) {
541 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
540 expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
542 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
541 expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
543
542
543+ created := time.Now()
544 session := &sessions.SessionState{
544 session := &sessions.SessionState{
545 User: userName,
545 User: userName,
546 Email: emailAddress,
546 Email: emailAddress,
547 AccessToken: "oauth_token",
547 AccessToken: "oauth_token",
548− CreatedAt: time.Now(),
548+ CreatedAt: &created,
549 }
549 }
550 {
550 {
551 rw := httptest.NewRecorder()
551 rw := httptest.NewRecorder()
@@ -582,11 +582,12 @@ func TestPassUserHeadersWithEmail(t *testing.T) {
582 const emailAddress = "john.doe@example.com"
582 const emailAddress = "john.doe@example.com"
583 const userName = "9fcab5c9b889a557"
583 const userName = "9fcab5c9b889a557"
584
584
585+ created := time.Now()
585 session := &sessions.SessionState{
586 session := &sessions.SessionState{
586 User: userName,
587 User: userName,
587 Email: emailAddress,
588 Email: emailAddress,
588 AccessToken: "oauth_token",
589 AccessToken: "oauth_token",
589− CreatedAt: time.Now(),
590+ CreatedAt: &created,
590 }
591 }
591 {
592 {
592 rw := httptest.NewRecorder()
593 rw := httptest.NewRecorder()
@@ -959,7 +960,8 @@ func (p *ProcessCookieTest) LoadCookiedSession() (*sessions.SessionState, error)
959func TestLoadCookiedSession(t *testing.T) {
960func TestLoadCookiedSession(t *testing.T) {
960 pcTest := NewProcessCookieTestWithDefaults()
961 pcTest := NewProcessCookieTestWithDefaults()
961
962
963+ created := time.Now()
962− startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: time.Now()}
964+ startSession := &sessions.SessionState{Email: "john.doe@example.com", AccessToken: "my_access_token", CreatedAt: &created}
963 pcTest.SaveSession(startSession)
965 pcTest.SaveSession(startSession)
964
966
965 session, err := pcTest.LoadCookiedSession()
967 session, err := pcTest.LoadCookiedSession()
@@ -985,7 +987,7 @@ func TestProcessCookieRefreshNotSet(t *testing.T) {
985 })
987 })
986 reference := time.Now().Add(time.Duration(-2) * time.Hour)
988 reference := time.Now().Add(time.Duration(-2) * time.Hour)
987
989
988− startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
990+ startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
989 pcTest.SaveSession(startSession)
991 pcTest.SaveSession(startSession)
990
992
991 session, err := pcTest.LoadCookiedSession()
993 session, err := pcTest.LoadCookiedSession()
@@ -1001,7 +1003,7 @@ func TestProcessCookieFailIfCookieExpired(t *testing.T) {
1001 opts.Cookie.Expire = time.Duration(24) * time.Hour
1003 opts.Cookie.Expire = time.Duration(24) * time.Hour
1002 })
1004 })
1003 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1005 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1004− startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
1006+ startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1005 pcTest.SaveSession(startSession)
1007 pcTest.SaveSession(startSession)
1006
1008
1007 session, err := pcTest.LoadCookiedSession()
1009 session, err := pcTest.LoadCookiedSession()
@@ -1016,7 +1018,7 @@ func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
1016 opts.Cookie.Expire = time.Duration(24) * time.Hour
1018 opts.Cookie.Expire = time.Duration(24) * time.Hour
1017 })
1019 })
1018 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1020 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1019− startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
1021+ startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1020 pcTest.SaveSession(startSession)
1022 pcTest.SaveSession(startSession)
1021
1023
1022 pcTest.proxy.CookieRefresh = time.Hour
1024 pcTest.proxy.CookieRefresh = time.Hour
@@ -1062,8 +1064,9 @@ func NewAuthOnlyEndpointTest(modifiers ...OptionsModifier) *ProcessCookieTest {
1062
1064
1063func TestAuthOnlyEndpointAccepted(t *testing.T) {
1065func TestAuthOnlyEndpointAccepted(t *testing.T) {
1064 test := NewAuthOnlyEndpointTest()
1066 test := NewAuthOnlyEndpointTest()
1067+ created := time.Now()
1065 startSession := &sessions.SessionState{
1068 startSession := &sessions.SessionState{
1066− Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
1069+ Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1067 test.SaveSession(startSession)
1070 test.SaveSession(startSession)
1068
1071
1069 test.proxy.ServeHTTP(test.rw, test.req)
1072 test.proxy.ServeHTTP(test.rw, test.req)
@@ -1087,7 +1090,7 @@ func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1087 })
1090 })
1088 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1091 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1089 startSession := &sessions.SessionState{
1092 startSession := &sessions.SessionState{
1090− Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
1093+ Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &reference}
1091 test.SaveSession(startSession)
1094 test.SaveSession(startSession)
1092
1095
1093 test.proxy.ServeHTTP(test.rw, test.req)
1096 test.proxy.ServeHTTP(test.rw, test.req)
@@ -1098,8 +1101,9 @@ func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1098
1101
1099func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1102func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
1100 test := NewAuthOnlyEndpointTest()
1103 test := NewAuthOnlyEndpointTest()
1104+ created := time.Now()
1101 startSession := &sessions.SessionState{
1105 startSession := &sessions.SessionState{
1102− Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: time.Now()}
1106+ Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: &created}
1103 test.SaveSession(startSession)
1107 test.SaveSession(startSession)
1104 test.validateUser = false
1108 test.validateUser = false
1105
1109
@@ -1129,8 +1133,9 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1129 pcTest.req, _ = http.NewRequest("GET",
1133 pcTest.req, _ = http.NewRequest("GET",
1130 pcTest.opts.ProxyPrefix+"/auth", nil)
1134 pcTest.opts.ProxyPrefix+"/auth", nil)
1131
1135
1136+ created := time.Now()
1132 startSession := &sessions.SessionState{
1137 startSession := &sessions.SessionState{
1133− User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
1138+ User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1134 pcTest.SaveSession(startSession)
1139 pcTest.SaveSession(startSession)
1135
1140
1136 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1141 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
@@ -1160,8 +1165,9 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1160 pcTest.req, _ = http.NewRequest("GET",
1165 pcTest.req, _ = http.NewRequest("GET",
1161 pcTest.opts.ProxyPrefix+"/auth", nil)
1166 pcTest.opts.ProxyPrefix+"/auth", nil)
1162
1167
1168+ created := time.Now()
1163 startSession := &sessions.SessionState{
1169 startSession := &sessions.SessionState{
1164− User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
1170+ User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1165 pcTest.SaveSession(startSession)
1171 pcTest.SaveSession(startSession)
1166
1172
1167 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1173 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
@@ -1193,8 +1199,9 @@ func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1193 pcTest.req, _ = http.NewRequest("GET",
1199 pcTest.req, _ = http.NewRequest("GET",
1194 pcTest.opts.ProxyPrefix+"/auth", nil)
1200 pcTest.opts.ProxyPrefix+"/auth", nil)
1195
1201
1202+ created := time.Now()
1196 startSession := &sessions.SessionState{
1203 startSession := &sessions.SessionState{
1197− User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: time.Now()}
1204+ User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
1198 pcTest.SaveSession(startSession)
1205 pcTest.SaveSession(startSession)
1199
1206
1200 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
1207 pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
@@ -1569,10 +1576,11 @@ func TestGetJwtSession(t *testing.T) {
1569 }
1576 }
1570
1577
1571 // Bearer
1578 // Bearer
1579+ expires := time.Unix(1912151821, 0)
1572 session, _ := test.proxy.GetJwtSession(test.req)
1580 session, _ := test.proxy.GetJwtSession(test.req)
1573 assert.Equal(t, session.User, "john@example.com")
1581 assert.Equal(t, session.User, "john@example.com")
1574 assert.Equal(t, session.Email, "john@example.com")
1582 assert.Equal(t, session.Email, "john@example.com")
1575− assert.Equal(t, session.ExpiresOn, time.Unix(1912151821, 0))
1583+ assert.Equal(t, session.ExpiresOn, &expires)
1576 assert.Equal(t, session.IDToken, goodJwt)
1584 assert.Equal(t, session.IDToken, goodJwt)
1577
1585
1578 test.proxy.ServeHTTP(test.rw, test.req)
1586 test.proxy.ServeHTTP(test.rw, test.req)
pkg/apis/sessions/session_state.go+34−98
@@ -2,7 +2,6 @@ package sessions
2
2
3import (
3import (
4 "encoding/json"
4 "encoding/json"
5− "errors"
6 "fmt"
5 "fmt"
7 "time"
6 "time"
8
7
@@ -11,26 +10,19 @@ import (
11
10
12// SessionState is used to store information about the currently authenticated user session
11// SessionState is used to store information about the currently authenticated user session
13type SessionState struct {
12type SessionState struct {
14− AccessToken string `json:",omitempty"`
13+ AccessToken string `json:",omitempty"`
15− IDToken string `json:",omitempty"`
14+ IDToken string `json:",omitempty"`
16− CreatedAt time.Time `json:"-"`
15+ CreatedAt *time.Time `json:",omitempty"`
17− ExpiresOn time.Time `json:"-"`
16+ ExpiresOn *time.Time `json:",omitempty"`
18− RefreshToken string `json:",omitempty"`
17+ RefreshToken string `json:",omitempty"`
19− Email string `json:",omitempty"`
18+ Email string `json:",omitempty"`
20− User string `json:",omitempty"`
19+ User string `json:",omitempty"`
21− PreferredUsername string `json:",omitempty"`
20+ PreferredUsername string `json:",omitempty"`
22−}
23−
24−// SessionStateJSON is used to encode SessionState into JSON without exposing time.Time zero value
25−type SessionStateJSON struct {
26− *SessionState
27− CreatedAt *time.Time `json:",omitempty"`
28− ExpiresOn *time.Time `json:",omitempty"`
29}
21}
30
22
31// IsExpired checks whether the session has expired
23// IsExpired checks whether the session has expired
32func (s *SessionState) IsExpired() bool {
24func (s *SessionState) IsExpired() bool {
33− if !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(time.Now()) {
25+ if s.ExpiresOn != nil && !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(time.Now()) {
34 return true
26 return true
35 }
27 }
36 return false
28 return false
@@ -38,8 +30,8 @@ func (s *SessionState) IsExpired() bool {
38
30
39// Age returns the age of a session
31// Age returns the age of a session
40func (s *SessionState) Age() time.Duration {
32func (s *SessionState) Age() time.Duration {
41− if !s.CreatedAt.IsZero() {
33+ if s.CreatedAt != nil && !s.CreatedAt.IsZero() {
42− return time.Now().Truncate(time.Second).Sub(s.CreatedAt)
34+ return time.Now().Truncate(time.Second).Sub(*s.CreatedAt)
43 }
35 }
44 return 0
36 return 0
45}
37}
@@ -75,80 +67,36 @@ func (s *SessionState) EncodeSessionState(c *encryption.Cipher) (string, error)
75 ss.PreferredUsername = s.PreferredUsername
67 ss.PreferredUsername = s.PreferredUsername
76 } else {
68 } else {
77 ss = *s
69 ss = *s
78− var err error
70+ for _, s := range []*string{
79− if ss.Email != "" {
80− ss.Email, err = c.Encrypt(ss.Email)
71+ &ss.Email,
81− if err != nil {
82− return "", err
83− }
84− }
85− if ss.User != "" {
86− ss.User, err = c.Encrypt(ss.User)
72+ &ss.User,
87− if err != nil {
88− return "", err
89− }
90− }
91− if ss.PreferredUsername != "" {
92− ss.PreferredUsername, err = c.Encrypt(ss.PreferredUsername)
73+ &ss.PreferredUsername,
93− if err != nil {
94− return "", err
95− }
96− }
97− if ss.AccessToken != "" {
74+ &ss.AccessToken,
98− ss.AccessToken, err = c.Encrypt(ss.AccessToken)
99− if err != nil {
100− return "", err
101− }
102− }
103− if ss.IDToken != "" {
75+ &ss.IDToken,
104− ss.IDToken, err = c.Encrypt(ss.IDToken)
76+ &ss.RefreshToken,
105− if err != nil {
106− return "", err
107− }
108− }
77+ } {
109− if ss.RefreshToken != "" {
78+ err := c.EncryptInto(s)
110− ss.RefreshToken, err = c.Encrypt(ss.RefreshToken)
111 if err != nil {
79 if err != nil {
112 return "", err
80 return "", err
113 }
81 }
114 }
82 }
115 }
83 }
116− // Embed SessionState and ExpiresOn pointer into SessionStateJSON
84+
117− ssj := &SessionStateJSON{SessionState: &ss}
118− if !ss.CreatedAt.IsZero() {
119− ssj.CreatedAt = &ss.CreatedAt
120− }
121− if !ss.ExpiresOn.IsZero() {
122− ssj.ExpiresOn = &ss.ExpiresOn
123− }
124− b, err := json.Marshal(ssj)
85+ b, err := json.Marshal(ss)
125 return string(b), err
86 return string(b), err
126}
87}
127
88
128// DecodeSessionState decodes the session cookie string into a SessionState
89// DecodeSessionState decodes the session cookie string into a SessionState
129func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) {
90func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) {
130− var ssj SessionStateJSON
131− var ss *SessionState
91+ var ss SessionState
132− err := json.Unmarshal([]byte(v), &ssj)
92+ err := json.Unmarshal([]byte(v), &ss)
133 if err != nil {
93 if err != nil {
134 return nil, fmt.Errorf("error unmarshalling session: %w", err)
94 return nil, fmt.Errorf("error unmarshalling session: %w", err)
135 }
95 }
136− if ssj.SessionState == nil {
137− return nil, errors.New("expected session state to not be nil")
138− }
139−
140− // Extract SessionState and CreatedAt,ExpiresOn value from SessionStateJSON
141− ss = ssj.SessionState
142− if ssj.CreatedAt != nil {
143− ss.CreatedAt = *ssj.CreatedAt
144− }
145− if ssj.ExpiresOn != nil {
146− ss.ExpiresOn = *ssj.ExpiresOn
147− }
148
96
149 if c == nil {
97 if c == nil {
150 // Load only Email and User when cipher is unavailable
98 // Load only Email and User when cipher is unavailable
151− ss = &SessionState{
99+ ss = SessionState{
152 Email: ss.Email,
100 Email: ss.Email,
153 User: ss.User,
101 User: ss.User,
154 PreferredUsername: ss.PreferredUsername,
102 PreferredUsername: ss.PreferredUsername,
@@ -168,30 +116,18 @@ func DecodeSessionState(v string, c *encryption.Cipher) (*SessionState, error) {
168 ss.User = decryptedUser
116 ss.User = decryptedUser
169 }
117 }
170 }
118 }
171− if ss.PreferredUsername != "" {
119+
172− ss.PreferredUsername, err = c.Decrypt(ss.PreferredUsername)
120+ for _, s := range []*string{
173− if err != nil {
121+ &ss.PreferredUsername,
174− return nil, err
175− }
176− }
177− if ss.AccessToken != "" {
178− ss.AccessToken, err = c.Decrypt(ss.AccessToken)
122+ &ss.AccessToken,
179− if err != nil {
180− return nil, err
181− }
182− }
183− if ss.IDToken != "" {
123+ &ss.IDToken,
184− ss.IDToken, err = c.Decrypt(ss.IDToken)
124+ &ss.RefreshToken,
185− if err != nil {
186− return nil, err
187− }
188− }
125+ } {
189− if ss.RefreshToken != "" {
126+ err := c.DecryptInto(s)
190− ss.RefreshToken, err = c.Decrypt(ss.RefreshToken)
191 if err != nil {
127 if err != nil {
192 return nil, err
128 return nil, err
193 }
129 }
194 }
130 }
195 }
131 }
196− return ss, nil
132+ return &ss, nil
197}
133}
pkg/apis/sessions/session_state_test.go+23−16
@@ -13,6 +13,10 @@ import (
13const secret = "0123456789abcdefghijklmnopqrstuv"
13const secret = "0123456789abcdefghijklmnopqrstuv"
14const altSecret = "0000000000abcdefghijklmnopqrstuv"
14const altSecret = "0000000000abcdefghijklmnopqrstuv"
15
15
16+func timePtr(t time.Time) *time.Time {
17+ return &t
18+}
19+
16func TestSessionStateSerialization(t *testing.T) {
20func TestSessionStateSerialization(t *testing.T) {
17 c, err := encryption.NewCipher([]byte(secret))
21 c, err := encryption.NewCipher([]byte(secret))
18 assert.Equal(t, nil, err)
22 assert.Equal(t, nil, err)
@@ -23,8 +27,8 @@ func TestSessionStateSerialization(t *testing.T) {
23 PreferredUsername: "user",
27 PreferredUsername: "user",
24 AccessToken: "token1234",
28 AccessToken: "token1234",
25 IDToken: "rawtoken1234",
29 IDToken: "rawtoken1234",
26− CreatedAt: time.Now(),
30+ CreatedAt: timePtr(time.Now()),
27− ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
31+ ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Hour)),
28 RefreshToken: "refresh4321",
32 RefreshToken: "refresh4321",
29 }
33 }
30 encoded, err := s.EncodeSessionState(c)
34 encoded, err := s.EncodeSessionState(c)
@@ -66,8 +70,8 @@ func TestSessionStateSerializationWithUser(t *testing.T) {
66 PreferredUsername: "ju",
70 PreferredUsername: "ju",
67 Email: "user@domain.com",
71 Email: "user@domain.com",
68 AccessToken: "token1234",
72 AccessToken: "token1234",
69− CreatedAt: time.Now(),
73+ CreatedAt: timePtr(time.Now()),
70− ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
74+ ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Hour)),
71 RefreshToken: "refresh4321",
75 RefreshToken: "refresh4321",
72 }
76 }
73 encoded, err := s.EncodeSessionState(c)
77 encoded, err := s.EncodeSessionState(c)
@@ -102,8 +106,8 @@ func TestSessionStateSerializationNoCipher(t *testing.T) {
102 Email: "user@domain.com",
106 Email: "user@domain.com",
103 PreferredUsername: "user",
107 PreferredUsername: "user",
104 AccessToken: "token1234",
108 AccessToken: "token1234",
105− CreatedAt: time.Now(),
109+ CreatedAt: timePtr(time.Now()),
106− ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
110+ ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Hour)),
107 RefreshToken: "refresh4321",
111 RefreshToken: "refresh4321",
108 }
112 }
109 encoded, err := s.EncodeSessionState(nil)
113 encoded, err := s.EncodeSessionState(nil)
@@ -125,8 +129,8 @@ func TestSessionStateSerializationNoCipherWithUser(t *testing.T) {
125 Email: "user@domain.com",
129 Email: "user@domain.com",
126 PreferredUsername: "user",
130 PreferredUsername: "user",
127 AccessToken: "token1234",
131 AccessToken: "token1234",
128− CreatedAt: time.Now(),
132+ CreatedAt: timePtr(time.Now()),
129− ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
133+ ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Hour)),
130 RefreshToken: "refresh4321",
134 RefreshToken: "refresh4321",
131 }
135 }
132 encoded, err := s.EncodeSessionState(nil)
136 encoded, err := s.EncodeSessionState(nil)
@@ -143,10 +147,10 @@ func TestSessionStateSerializationNoCipherWithUser(t *testing.T) {
143}
147}
144
148
145func TestExpired(t *testing.T) {
149func TestExpired(t *testing.T) {
146− s := &sessions.SessionState{ExpiresOn: time.Now().Add(time.Duration(-1) * time.Minute)}
150+ s := &sessions.SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(-1) * time.Minute))}
147 assert.Equal(t, true, s.IsExpired())
151 assert.Equal(t, true, s.IsExpired())
148
152
149− s = &sessions.SessionState{ExpiresOn: time.Now().Add(time.Duration(1) * time.Minute)}
153+ s = &sessions.SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Minute))}
150 assert.Equal(t, false, s.IsExpired())
154 assert.Equal(t, false, s.IsExpired())
151
155
152 s = &sessions.SessionState{}
156 s = &sessions.SessionState{}
@@ -182,8 +186,8 @@ func TestEncodeSessionState(t *testing.T) {
182 User: "just-user",
186 User: "just-user",
183 AccessToken: "token1234",
187 AccessToken: "token1234",
184 IDToken: "rawtoken1234",
188 IDToken: "rawtoken1234",
185− CreatedAt: c,
189+ CreatedAt: &c,
186− ExpiresOn: e,
190+ ExpiresOn: &e,
187 RefreshToken: "refresh4321",
191 RefreshToken: "refresh4321",
188 },
192 },
189 Encoded: `{"Email":"user@domain.com","User":"just-user"}`,
193 Encoded: `{"Email":"user@domain.com","User":"just-user"}`,
@@ -249,8 +253,8 @@ func TestDecodeSessionState(t *testing.T) {
249 User: "just-user",
253 User: "just-user",
250 AccessToken: "token1234",
254 AccessToken: "token1234",
251 IDToken: "rawtoken1234",
255 IDToken: "rawtoken1234",
252− CreatedAt: created,
256+ CreatedAt: &created,
253− ExpiresOn: e,
257+ ExpiresOn: &e,
254 RefreshToken: "refresh4321",
258 RefreshToken: "refresh4321",
255 },
259 },
256 Encoded: fmt.Sprintf(`{"Email":"FsKKYrTWZWrxSOAqA/fTNAUZS5QWCqOBjuAbBlbVOw==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw==","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
260 Encoded: fmt.Sprintf(`{"Email":"FsKKYrTWZWrxSOAqA/fTNAUZS5QWCqOBjuAbBlbVOw==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw==","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
@@ -291,7 +295,10 @@ func TestDecodeSessionState(t *testing.T) {
291 assert.Equal(t, tc.AccessToken, ss.AccessToken)
295 assert.Equal(t, tc.AccessToken, ss.AccessToken)
292 assert.Equal(t, tc.RefreshToken, ss.RefreshToken)
296 assert.Equal(t, tc.RefreshToken, ss.RefreshToken)
293 assert.Equal(t, tc.IDToken, ss.IDToken)
297 assert.Equal(t, tc.IDToken, ss.IDToken)
298+ if tc.ExpiresOn != nil {
299+ assert.NotEqual(t, nil, ss.ExpiresOn)
294− assert.Equal(t, tc.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
300+ assert.Equal(t, tc.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
301+ }
295 }
302 }
296 }
303 }
297}
304}
@@ -303,6 +310,6 @@ func TestSessionStateAge(t *testing.T) {
303 assert.Equal(t, time.Duration(0), ss.Age())
310 assert.Equal(t, time.Duration(0), ss.Age())
304
311
305 // Set CreatedAt to 1 hour ago
312 // Set CreatedAt to 1 hour ago
306− ss.CreatedAt = time.Now().Add(-1 * time.Hour)
313+ ss.CreatedAt = timePtr(time.Now().Add(-1 * time.Hour))
307 assert.Equal(t, time.Hour, ss.Age().Round(time.Minute))
314 assert.Equal(t, time.Hour, ss.Age().Round(time.Minute))
308}
315}
pkg/encryption/cipher.go+27
@@ -156,3 +156,30 @@ func (c *Cipher) Decrypt(s string) (string, error) {
156
156
157 return string(encrypted), nil
157 return string(encrypted), nil
158}
158}
159+
160+// EncryptInto encrypts the value and stores it back in the string pointer
161+func (c *Cipher) EncryptInto(s *string) error {
162+ return into(c.Encrypt, s)
163+}
164+
165+// DecryptInto decrypts the value and stores it back in the string pointer
166+func (c *Cipher) DecryptInto(s *string) error {
167+ return into(c.Decrypt, s)
168+}
169+
170+// codecFunc is a function that takes a string and encodes/decodes it
171+type codecFunc func(string) (string, error)
172+
173+func into(f codecFunc, s *string) error {
174+ // Do not encrypt/decrypt nil or empty strings
175+ if s == nil || *s == "" {
176+ return nil
177+ }
178+
179+ d, err := f(*s)
180+ if err != nil {
181+ return err
182+ }
183+ *s = d
184+ return nil
185+}
pkg/encryption/cipher_test.go+22
@@ -133,3 +133,25 @@ func TestEncodeAndDecodeAccessTokenB64(t *testing.T) {
133 assert.NotEqual(t, token, encoded)
133 assert.NotEqual(t, token, encoded)
134 assert.Equal(t, token, decoded)
134 assert.Equal(t, token, decoded)
135}
135}
136+
137+func TestEncodeIntoAndDecodeIntoAccessToken(t *testing.T) {
138+ const secret = "0123456789abcdefghijklmnopqrstuv"
139+ c, err := NewCipher([]byte(secret))
140+ assert.Equal(t, nil, err)
141+
142+ token := "my access token"
143+ originalToken := token
144+
145+ assert.Equal(t, nil, c.EncryptInto(&token))
146+ assert.NotEqual(t, originalToken, token)
147+
148+ assert.Equal(t, nil, c.DecryptInto(&token))
149+ assert.Equal(t, originalToken, token)
150+
151+ // Check no errors with empty or nil strings
152+ empty := ""
153+ assert.Equal(t, nil, c.EncryptInto(&empty))
154+ assert.Equal(t, nil, c.DecryptInto(&empty))
155+ assert.Equal(t, nil, c.EncryptInto(nil))
156+ assert.Equal(t, nil, c.DecryptInto(nil))
157+}
pkg/sessions/cookie/session_store.go+4−3
@@ -34,14 +34,15 @@ type SessionStore struct {
34// Save takes a sessions.SessionState and stores the information from it
34// Save takes a sessions.SessionState and stores the information from it
35// within Cookies set on the HTTP response writer
35// within Cookies set on the HTTP response writer
36func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
36func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
37− if ss.CreatedAt.IsZero() {
37+ if ss.CreatedAt == nil || ss.CreatedAt.IsZero() {
38− ss.CreatedAt = time.Now()
38+ now := time.Now()
39+ ss.CreatedAt = &now
39 }
40 }
40 value, err := cookieForSession(ss, s.CookieCipher)
41 value, err := cookieForSession(ss, s.CookieCipher)
41 if err != nil {
42 if err != nil {
42 return err
43 return err
43 }
44 }
44− s.setSessionCookie(rw, req, value, ss.CreatedAt)
45+ s.setSessionCookie(rw, req, value, *ss.CreatedAt)
45 return nil
46 return nil
46}
47}
47
48
pkg/sessions/redis/redis_store.go+4−3
@@ -133,8 +133,9 @@ func parseRedisURLs(urls []string) ([]string, error) {
133// Save takes a sessions.SessionState and stores the information from it
133// Save takes a sessions.SessionState and stores the information from it
134// to redies, and adds a new ticket cookie on the HTTP response writer
134// to redies, and adds a new ticket cookie on the HTTP response writer
135func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
135func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
136− if s.CreatedAt.IsZero() {
136+ if s.CreatedAt == nil || s.CreatedAt.IsZero() {
137− s.CreatedAt = time.Now()
137+ now := time.Now()
138+ s.CreatedAt = &now
138 }
139 }
139
140
140 // Old sessions that we are refreshing would have a request cookie
141 // Old sessions that we are refreshing would have a request cookie
@@ -154,7 +155,7 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se
154 req,
155 req,
155 ticketString,
156 ticketString,
156 store.CookieOptions.Expire,
157 store.CookieOptions.Expire,
157− s.CreatedAt,
158+ *s.CreatedAt,
158 )
159 )
159
160
160 http.SetCookie(rw, ticketCookie)
161 http.SetCookie(rw, ticketCookie)
pkg/sessions/session_store_test.go+11−7
@@ -15,6 +15,7 @@ import (
15 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
15 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
16 cookiesapi "github.com/oauth2-proxy/oauth2-proxy/pkg/cookies"
16 cookiesapi "github.com/oauth2-proxy/oauth2-proxy/pkg/cookies"
17 "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
17 "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
18+ "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
18 "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
19 "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
19 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
20 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
20 "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/redis"
21 "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/redis"
@@ -23,6 +24,8 @@ import (
23)
24)
24
25
25func TestSessionStore(t *testing.T) {
26func TestSessionStore(t *testing.T) {
27+ logger.SetOutput(GinkgoWriter)
28+
26 RegisterFailHandler(Fail)
29 RegisterFailHandler(Fail)
27 RunSpecs(t, "SessionStore")
30 RunSpecs(t, "SessionStore")
28}
31}
@@ -253,16 +256,16 @@ var _ = Describe("NewSessionStore", func() {
253
256
254 // Can't compare time.Time using Equal() so remove ExpiresOn from sessions
257 // Can't compare time.Time using Equal() so remove ExpiresOn from sessions
255 l := *loadedSession
258 l := *loadedSession
256− l.CreatedAt = time.Time{}
259+ l.CreatedAt = nil
257− l.ExpiresOn = time.Time{}
260+ l.ExpiresOn = nil
258 s := *session
261 s := *session
259− s.CreatedAt = time.Time{}
262+ s.CreatedAt = nil
260− s.ExpiresOn = time.Time{}
263+ s.ExpiresOn = nil
261 Expect(l).To(Equal(s))
264 Expect(l).To(Equal(s))
262
265
263 // Compare time.Time separately
266 // Compare time.Time separately
264− Expect(loadedSession.CreatedAt.Equal(session.CreatedAt)).To(BeTrue())
267+ Expect(loadedSession.CreatedAt.Equal(*session.CreatedAt)).To(BeTrue())
265− Expect(loadedSession.ExpiresOn.Equal(session.ExpiresOn)).To(BeTrue())
268+ Expect(loadedSession.ExpiresOn.Equal(*session.ExpiresOn)).To(BeTrue())
266 }
269 }
267 })
270 })
268 }
271 }
@@ -392,10 +395,11 @@ var _ = Describe("NewSessionStore", func() {
392 SameSite: "",
395 SameSite: "",
393 }
396 }
394
397
398+ expires := time.Now().Add(1 * time.Hour)
395 session = &sessionsapi.SessionState{
399 session = &sessionsapi.SessionState{
396 AccessToken: "AccessToken",
400 AccessToken: "AccessToken",
397 IDToken: "IDToken",
401 IDToken: "IDToken",
398− ExpiresOn: time.Now().Add(1 * time.Hour),
402+ ExpiresOn: &expires,
399 RefreshToken: "RefreshToken",
403 RefreshToken: "RefreshToken",
400 Email: "john.doe@example.com",
404 Email: "john.doe@example.com",
401 User: "john.doe",
405 User: "john.doe",
providers/azure.go+4−2
@@ -126,11 +126,13 @@ func (p *AzureProvider) Redeem(ctx context.Context, redirectURL, code string) (s
126 return
126 return
127 }
127 }
128
128
129+ created := time.Now()
130+ expires := time.Unix(jsonResponse.ExpiresOn, 0)
129 s = &sessions.SessionState{
131 s = &sessions.SessionState{
130 AccessToken: jsonResponse.AccessToken,
132 AccessToken: jsonResponse.AccessToken,
131 IDToken: jsonResponse.IDToken,
133 IDToken: jsonResponse.IDToken,
132− CreatedAt: time.Now(),
134+ CreatedAt: &created,
133− ExpiresOn: time.Unix(jsonResponse.ExpiresOn, 0),
135+ ExpiresOn: &expires,
134 RefreshToken: jsonResponse.RefreshToken,
136 RefreshToken: jsonResponse.RefreshToken,
135 }
137 }
136 return
138 return
providers/gitlab.go+4−3
@@ -67,7 +67,7 @@ func (p *GitLabProvider) Redeem(ctx context.Context, redirectURL, code string) (
67// RefreshSessionIfNeeded checks if the session has expired and uses the
67// RefreshSessionIfNeeded checks if the session has expired and uses the
68// RefreshToken to fetch a new ID token if required
68// RefreshToken to fetch a new ID token if required
69func (p *GitLabProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
69func (p *GitLabProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
70− if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
70+ if s == nil || (s.ExpiresOn != nil && s.ExpiresOn.After(time.Now())) || s.RefreshToken == "" {
71 return false, nil
71 return false, nil
72 }
72 }
73
73
@@ -209,12 +209,13 @@ func (p *GitLabProvider) createSessionState(ctx context.Context, token *oauth2.T
209 return nil, fmt.Errorf("could not verify id_token: %v", err)
209 return nil, fmt.Errorf("could not verify id_token: %v", err)
210 }
210 }
211
211
212+ created := time.Now()
212 return &sessions.SessionState{
213 return &sessions.SessionState{
213 AccessToken: token.AccessToken,
214 AccessToken: token.AccessToken,
214 IDToken: rawIDToken,
215 IDToken: rawIDToken,
215 RefreshToken: token.RefreshToken,
216 RefreshToken: token.RefreshToken,
216− CreatedAt: time.Now(),
217+ CreatedAt: &created,
217− ExpiresOn: idToken.Expiry,
218+ ExpiresOn: &idToken.Expiry,
218 }, nil
219 }, nil
219}
220}
220
221
providers/google.go+8−4
@@ -153,11 +153,14 @@ func (p *GoogleProvider) Redeem(ctx context.Context, redirectURL, code string) (
153 if err != nil {
153 if err != nil {
154 return
154 return
155 }
155 }
156+
157+ created := time.Now()
158+ expires := time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second)
156 s = &sessions.SessionState{
159 s = &sessions.SessionState{
157 AccessToken: jsonResponse.AccessToken,
160 AccessToken: jsonResponse.AccessToken,
158 IDToken: jsonResponse.IDToken,
161 IDToken: jsonResponse.IDToken,
159− CreatedAt: time.Now(),
162+ CreatedAt: &created,
160− ExpiresOn: time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second),
163+ ExpiresOn: &expires,
161 RefreshToken: jsonResponse.RefreshToken,
164 RefreshToken: jsonResponse.RefreshToken,
162 Email: c.Email,
165 Email: c.Email,
163 User: c.Subject,
166 User: c.Subject,
@@ -245,7 +248,7 @@ func (p *GoogleProvider) ValidateGroup(email string) bool {
245// RefreshSessionIfNeeded checks if the session has expired and uses the
248// RefreshSessionIfNeeded checks if the session has expired and uses the
246// RefreshToken to fetch a new ID token if required
249// RefreshToken to fetch a new ID token if required
247func (p *GoogleProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
250func (p *GoogleProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
248− if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
251+ if s == nil || (s.ExpiresOn != nil && s.ExpiresOn.After(time.Now())) || s.RefreshToken == "" {
249 return false, nil
252 return false, nil
250 }
253 }
251
254
@@ -260,9 +263,10 @@ func (p *GoogleProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions
260 }
263 }
261
264
262 origExpiration := s.ExpiresOn
265 origExpiration := s.ExpiresOn
266+ expires := time.Now().Add(duration).Truncate(time.Second)
263 s.AccessToken = newToken
267 s.AccessToken = newToken
264 s.IDToken = newIDToken
268 s.IDToken = newIDToken
265− s.ExpiresOn = time.Now().Add(duration).Truncate(time.Second)
269+ s.ExpiresOn = &expires
266 logger.Printf("refreshed access token %s (expired on %s)", s, origExpiration)
270 logger.Printf("refreshed access token %s (expired on %s)", s, origExpiration)
267 return true, nil
271 return true, nil
268}
272}
providers/logingov.go+5−2
@@ -250,12 +250,15 @@ func (p *LoginGovProvider) Redeem(ctx context.Context, redirectURL, code string)
250 return
250 return
251 }
251 }
252
252
253+ created := time.Now()
254+ expires := time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second)
255+
253 // Store the data that we found in the session state
256 // Store the data that we found in the session state
254 s = &sessions.SessionState{
257 s = &sessions.SessionState{
255 AccessToken: jsonResponse.AccessToken,
258 AccessToken: jsonResponse.AccessToken,
256 IDToken: jsonResponse.IDToken,
259 IDToken: jsonResponse.IDToken,
257− CreatedAt: time.Now(),
260+ CreatedAt: &created,
258− ExpiresOn: time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second),
261+ ExpiresOn: &expires,
259 Email: email,
262 Email: email,
260 }
263 }
261 return
264 return
providers/oidc.go+5−4
@@ -72,7 +72,7 @@ func (p *OIDCProvider) Redeem(ctx context.Context, redirectURL, code string) (s
72// RefreshSessionIfNeeded checks if the session has expired and uses the
72// RefreshSessionIfNeeded checks if the session has expired and uses the
73// RefreshToken to fetch a new Access Token (and optional ID token) if required
73// RefreshToken to fetch a new Access Token (and optional ID token) if required
74func (p *OIDCProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
74func (p *OIDCProvider) RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error) {
75− if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
75+ if s == nil || (s.ExpiresOn != nil && s.ExpiresOn.After(time.Now())) || s.RefreshToken == "" {
76 return false, nil
76 return false, nil
77 }
77 }
78
78
@@ -163,10 +163,11 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
163 }
163 }
164 }
164 }
165
165
166+ created := time.Now()
166 newSession.AccessToken = token.AccessToken
167 newSession.AccessToken = token.AccessToken
167 newSession.RefreshToken = token.RefreshToken
168 newSession.RefreshToken = token.RefreshToken
168− newSession.CreatedAt = time.Now()
169+ newSession.CreatedAt = &created
169− newSession.ExpiresOn = token.Expiry
170+ newSession.ExpiresOn = &token.Expiry
170 return newSession, nil
171 return newSession, nil
171}
172}
172
173
@@ -179,7 +180,7 @@ func (p *OIDCProvider) CreateSessionStateFromBearerToken(ctx context.Context, ra
179 newSession.AccessToken = rawIDToken
180 newSession.AccessToken = rawIDToken
180 newSession.IDToken = rawIDToken
181 newSession.IDToken = rawIDToken
181 newSession.RefreshToken = ""
182 newSession.RefreshToken = ""
182− newSession.ExpiresOn = idToken.Expiry
183+ newSession.ExpiresOn = &idToken.Expiry
183
184
184 return newSession, nil
185 return newSession, nil
185}
186}
providers/oidc_test.go+4−4
@@ -204,8 +204,8 @@ func TestOIDCProviderRefreshSessionIfNeededWithoutIdToken(t *testing.T) {
204 existingSession := &sessions.SessionState{
204 existingSession := &sessions.SessionState{
205 AccessToken: "changeit",
205 AccessToken: "changeit",
206 IDToken: idToken,
206 IDToken: idToken,
207− CreatedAt: time.Time{},
207+ CreatedAt: nil,
208− ExpiresOn: time.Time{},
208+ ExpiresOn: nil,
209 RefreshToken: refreshToken,
209 RefreshToken: refreshToken,
210 Email: "janedoe@example.com",
210 Email: "janedoe@example.com",
211 User: "11223344",
211 User: "11223344",
@@ -238,8 +238,8 @@ func TestOIDCProviderRefreshSessionIfNeededWithIdToken(t *testing.T) {
238 existingSession := &sessions.SessionState{
238 existingSession := &sessions.SessionState{
239 AccessToken: "changeit",
239 AccessToken: "changeit",
240 IDToken: "changeit",
240 IDToken: "changeit",
241− CreatedAt: time.Time{},
241+ CreatedAt: nil,
242− ExpiresOn: time.Time{},
242+ ExpiresOn: nil,
243 RefreshToken: refreshToken,
243 RefreshToken: refreshToken,
244 Email: "changeit",
244 Email: "changeit",
245 User: "changeit",
245 User: "changeit",
providers/provider_default.go+3−2
@@ -81,7 +81,8 @@ func (p *ProviderData) Redeem(ctx context.Context, redirectURL, code string) (s
81 return
81 return
82 }
82 }
83 if a := v.Get("access_token"); a != "" {
83 if a := v.Get("access_token"); a != "" {
84+ created := time.Now()
84− s = &sessions.SessionState{AccessToken: a, CreatedAt: time.Now()}
85+ s = &sessions.SessionState{AccessToken: a, CreatedAt: &created}
85 } else {
86 } else {
86 err = fmt.Errorf("no access token found %s", body)
87 err = fmt.Errorf("no access token found %s", body)
87 }
88 }
@@ -168,7 +169,7 @@ func (p *ProviderData) CreateSessionStateFromBearerToken(ctx context.Context, ra
168 newSession.AccessToken = rawIDToken
169 newSession.AccessToken = rawIDToken
169 newSession.IDToken = rawIDToken
170 newSession.IDToken = rawIDToken
170 newSession.RefreshToken = ""
171 newSession.RefreshToken = ""
171− newSession.ExpiresOn = idToken.Expiry
172+ newSession.ExpiresOn = &idToken.Expiry
172
173
173 return newSession, nil
174 return newSession, nil
174}
175}
providers/provider_default_test.go+3−1
@@ -11,8 +11,10 @@ import (
11
11
12func TestRefresh(t *testing.T) {
12func TestRefresh(t *testing.T) {
13 p := &ProviderData{}
13 p := &ProviderData{}
14+
15+ expires := time.Now().Add(time.Duration(-11) * time.Minute)
14 refreshed, err := p.RefreshSessionIfNeeded(context.Background(), &sessions.SessionState{
16 refreshed, err := p.RefreshSessionIfNeeded(context.Background(), &sessions.SessionState{
15− ExpiresOn: time.Now().Add(time.Duration(-11) * time.Minute),
17+ ExpiresOn: &expires,
16 })
18 })
17 assert.Equal(t, false, refreshed)
19 assert.Equal(t, false, refreshed)
18 assert.Equal(t, nil, err)
20 assert.Equal(t, nil, err)