Implement cookie auto-refresh
The intention is to refresh the cookie whenever the user accesses an authenticated service with less than `cookie-refresh` time to go before the cookie expires.
This commit is contained in:
		
							parent
							
								
									5cbdb74518
								
							
						
					
					
						commit
						8e2d83600c
					
				|  | @ -15,11 +15,11 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func validateCookie(cookie *http.Cookie, seed string) (string, bool) { | func validateCookie(cookie *http.Cookie, seed string) (string, time.Time, bool) { | ||||||
| 	// value, timestamp, sig
 | 	// value, timestamp, sig
 | ||||||
| 	parts := strings.Split(cookie.Value, "|") | 	parts := strings.Split(cookie.Value, "|") | ||||||
| 	if len(parts) != 3 { | 	if len(parts) != 3 { | ||||||
| 		return "", false | 		return "", time.Unix(0, 0), false | ||||||
| 	} | 	} | ||||||
| 	sig := cookieSignature(seed, cookie.Name, parts[0], parts[1]) | 	sig := cookieSignature(seed, cookie.Name, parts[0], parts[1]) | ||||||
| 	if checkHmac(parts[2], sig) { | 	if checkHmac(parts[2], sig) { | ||||||
|  | @ -28,11 +28,11 @@ func validateCookie(cookie *http.Cookie, seed string) (string, bool) { | ||||||
| 			// it's a valid cookie. now get the contents
 | 			// it's a valid cookie. now get the contents
 | ||||||
| 			rawValue, err := base64.URLEncoding.DecodeString(parts[0]) | 			rawValue, err := base64.URLEncoding.DecodeString(parts[0]) | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| 				return string(rawValue), true | 				return string(rawValue), time.Unix(int64(ts), 0), true | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return "", false | 	return "", time.Unix(0, 0), false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func signedCookieValue(seed string, key string, value string) string { | func signedCookieValue(seed string, key string, value string) string { | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								main.go
								
								
								
								
							
							
						
						
									
										1
									
								
								main.go
								
								
								
								
							|  | @ -45,6 +45,7 @@ func main() { | ||||||
| 	flagSet.String("cookie-secret", "", "the seed string for secure cookies") | 	flagSet.String("cookie-secret", "", "the seed string for secure cookies") | ||||||
| 	flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*") | 	flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*") | ||||||
| 	flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie") | 	flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie") | ||||||
|  | 	flagSet.Duration("cookie-refresh", time.Duration(24)*time.Hour, "refresh the cookie when this much time remains before expiration") | ||||||
| 	flagSet.Bool("cookie-https-only", true, "set secure (HTTPS) cookies (deprecated. use --cookie-secure setting)") | 	flagSet.Bool("cookie-https-only", true, "set secure (HTTPS) cookies (deprecated. use --cookie-secure setting)") | ||||||
| 	flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag") | 	flagSet.Bool("cookie-secure", true, "set secure (HTTPS) cookie flag") | ||||||
| 	flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") | 	flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") | ||||||
|  |  | ||||||
|  | @ -33,6 +33,7 @@ type OauthProxy struct { | ||||||
| 	CookieSecure   bool | 	CookieSecure   bool | ||||||
| 	CookieHttpOnly bool | 	CookieHttpOnly bool | ||||||
| 	CookieExpire   time.Duration | 	CookieExpire   time.Duration | ||||||
|  | 	CookieRefresh  time.Duration | ||||||
| 	Validator      func(string) bool | 	Validator      func(string) bool | ||||||
| 
 | 
 | ||||||
| 	redirectUrl         *url.URL // the url to receive requests at
 | 	redirectUrl         *url.URL // the url to receive requests at
 | ||||||
|  | @ -136,6 +137,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy { | ||||||
| 		CookieSecure:   opts.CookieSecure, | 		CookieSecure:   opts.CookieSecure, | ||||||
| 		CookieHttpOnly: opts.CookieHttpOnly, | 		CookieHttpOnly: opts.CookieHttpOnly, | ||||||
| 		CookieExpire:   opts.CookieExpire, | 		CookieExpire:   opts.CookieExpire, | ||||||
|  | 		CookieRefresh:  opts.CookieRefresh, | ||||||
| 		Validator:      validator, | 		Validator:      validator, | ||||||
| 
 | 
 | ||||||
| 		clientID:           opts.ClientID, | 		clientID:           opts.ClientID, | ||||||
|  | @ -259,10 +261,11 @@ func (p *OauthProxy) SetCookie(rw http.ResponseWriter, req *http.Request, val st | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (email, user, access_token string, ok bool) { | func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (email, user, access_token string, ok bool) { | ||||||
|  | 	var value string | ||||||
|  | 	var timestamp time.Time | ||||||
| 	cookie, err := req.Cookie(p.CookieKey) | 	cookie, err := req.Cookie(p.CookieKey) | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| 		var value string | 		value, timestamp, ok = validateCookie(cookie, p.CookieSeed) | ||||||
| 		value, ok = validateCookie(cookie, p.CookieSeed) |  | ||||||
| 		if ok { | 		if ok { | ||||||
| 			email, user, access_token, err = parseCookieValue( | 			email, user, access_token, err = parseCookieValue( | ||||||
| 				value, p.AesCipher) | 				value, p.AesCipher) | ||||||
|  | @ -270,6 +273,11 @@ func (p *OauthProxy) ProcessCookie(rw http.ResponseWriter, req *http.Request) (e | ||||||
| 	} | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Printf(err.Error()) | 		log.Printf(err.Error()) | ||||||
|  | 	} else if p.CookieRefresh != time.Duration(0) { | ||||||
|  | 		refresh_threshold := time.Now().Add(p.CookieRefresh) | ||||||
|  | 		if refresh_threshold.Unix() > timestamp.Unix() { | ||||||
|  | 			p.SetCookie(rw, req, value) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -353,8 +353,31 @@ func TestProcessCookie(t *testing.T) { | ||||||
| 	assert.Equal(t, "michael.bland", user) | 	assert.Equal(t, "michael.bland", user) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestProcessCookieError(t *testing.T) { | func TestProcessCookieNoCookieError(t *testing.T) { | ||||||
| 	pc_test := NewProcessCookieTest() | 	pc_test := NewProcessCookieTest() | ||||||
| 	_, _, _, ok := pc_test.ProcessCookie() | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
| 	assert.Equal(t, false, ok) | 	assert.Equal(t, false, ok) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookieRefreshNotSet(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 	cookie := pc_test.MakeCookie("michael.bland@gsa.gov") | ||||||
|  | 	cookie.Expires = time.Now().Add(time.Duration(23) * time.Hour) | ||||||
|  | 	pc_test.req.AddCookie(cookie) | ||||||
|  | 
 | ||||||
|  | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, true, ok) | ||||||
|  | 	assert.Equal(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestProcessCookieRefresh(t *testing.T) { | ||||||
|  | 	pc_test := NewProcessCookieTest() | ||||||
|  | 	cookie := pc_test.MakeCookie("michael.bland@gsa.gov") | ||||||
|  | 	cookie.Expires = time.Now().Add(time.Duration(23) * time.Hour) | ||||||
|  | 	pc_test.req.AddCookie(cookie) | ||||||
|  | 
 | ||||||
|  | 	pc_test.proxy.CookieRefresh = time.Duration(24) * time.Hour | ||||||
|  | 	_, _, _, ok := pc_test.ProcessCookie() | ||||||
|  | 	assert.Equal(t, true, ok) | ||||||
|  | 	assert.NotEqual(t, []string(nil), pc_test.rw.HeaderMap["Set-Cookie"]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ type Options struct { | ||||||
| 	CookieSecret    string        `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"` | 	CookieSecret    string        `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"` | ||||||
| 	CookieDomain    string        `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"` | 	CookieDomain    string        `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"` | ||||||
| 	CookieExpire    time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"` | 	CookieExpire    time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"` | ||||||
|  | 	CookieRefresh   time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"GOOGLE_AUTH_PROXY_COOKIE_REFRESH"` | ||||||
| 	CookieHttpsOnly bool          `flag:"cookie-https-only" cfg:"cookie_https_only"` // deprecated use cookie-secure
 | 	CookieHttpsOnly bool          `flag:"cookie-https-only" cfg:"cookie_https_only"` // deprecated use cookie-secure
 | ||||||
| 	CookieSecure    bool          `flag:"cookie-secure" cfg:"cookie_secure"` | 	CookieSecure    bool          `flag:"cookie-secure" cfg:"cookie_secure"` | ||||||
| 	CookieHttpOnly  bool          `flag:"cookie-httponly" cfg:"cookie_httponly"` | 	CookieHttpOnly  bool          `flag:"cookie-httponly" cfg:"cookie_httponly"` | ||||||
|  | @ -61,6 +62,7 @@ func NewOptions() *Options { | ||||||
| 		CookieSecure:        true, | 		CookieSecure:        true, | ||||||
| 		CookieHttpOnly:      true, | 		CookieHttpOnly:      true, | ||||||
| 		CookieExpire:        time.Duration(168) * time.Hour, | 		CookieExpire:        time.Duration(168) * time.Hour, | ||||||
|  | 		CookieRefresh:       time.Duration(0), | ||||||
| 		PassBasicAuth:       true, | 		PassBasicAuth:       true, | ||||||
| 		PassAccessToken:     false, | 		PassAccessToken:     false, | ||||||
| 		PassHostHeader:      true, | 		PassHostHeader:      true, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue