Add logout endpoint (#107)

Add logout endpoint that clears the auth cookie + optional "logout-redirect" config option, to which, when set, the user will be redirected.
This commit is contained in:
Thom Seddon
2020-06-03 14:00:47 +01:00
committed by GitHub
parent 655eddeaf9
commit 8b3a950162
7 changed files with 96 additions and 1 deletions

View File

@ -144,6 +144,19 @@ func MakeCookie(r *http.Request, email string) *http.Cookie {
}
}
// ClearCookie clears the auth cookie
func ClearCookie(r *http.Request) *http.Cookie {
return &http.Cookie{
Name: config.CookieName,
Value: "",
Path: "/",
Domain: cookieDomain(r),
HttpOnly: true,
Secure: !config.InsecureCookie,
Expires: time.Now().Local().Add(time.Hour * -1),
}
}
// MakeCSRFCookie makes a csrf cookie (used during login only)
func MakeCSRFCookie(r *http.Request, nonce string) *http.Cookie {
return &http.Cookie{

View File

@ -34,6 +34,7 @@ type Config struct {
DefaultProvider string `long:"default-provider" env:"DEFAULT_PROVIDER" default:"google" choice:"google" choice:"oidc" description:"Default provider"`
Domains CommaSeparatedList `long:"domain" env:"DOMAIN" env-delim:"," description:"Only allow given email domains, can be set multiple times"`
LifetimeString int `long:"lifetime" env:"LIFETIME" default:"43200" description:"Lifetime in seconds"`
LogoutRedirect string `long:"logout-redirect" env:"LOGOUT_REDIRECT" description:"URL to redirect to following logout"`
Path string `long:"url-path" env:"URL_PATH" default:"/_oauth" description:"Callback URL Path"`
SecretString string `long:"secret" env:"SECRET" description:"Secret used for signing (required)" json:"-"`
Whitelist CommaSeparatedList `long:"whitelist" env:"WHITELIST" env-delim:"," description:"Only allow given email addresses, can be set multiple times"`

View File

@ -33,6 +33,7 @@ func TestConfigDefaults(t *testing.T) {
assert.Equal("google", c.DefaultProvider)
assert.Len(c.Domains, 0)
assert.Equal(time.Second*time.Duration(43200), c.Lifetime)
assert.Equal("", c.LogoutRedirect)
assert.Equal("/_oauth", c.Path)
assert.Len(c.Whitelist, 0)

View File

@ -41,6 +41,9 @@ func (s *Server) buildRoutes() {
// Add callback handler
s.router.Handle(config.Path, s.AuthCallbackHandler())
// Add logout handler
s.router.Handle(config.Path+"/logout", s.LogoutHandler())
// Add a default handler
if config.DefaultAction == "allow" {
s.router.NewRoute().Handler(s.AllowHandler("default"))
@ -180,6 +183,23 @@ func (s *Server) AuthCallbackHandler() http.HandlerFunc {
}
}
// LogoutHandler logs a user out
func (s *Server) LogoutHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Clear cookie
http.SetCookie(w, ClearCookie(r))
logger := s.logger(r, "Logout", "default", "Handling logout")
logger.Info("Logged out user")
if config.LogoutRedirect != "" {
http.Redirect(w, r, config.LogoutRedirect, http.StatusTemporaryRedirect)
} else {
http.Error(w, "You have been logged out", 401)
}
}
}
func (s *Server) authRedirect(logger *logrus.Entry, w http.ResponseWriter, r *http.Request, p provider.Provider) {
// Error indicates no cookie, generate nonce
err, nonce := Nonce()

View File

@ -170,6 +170,49 @@ func TestServerAuthCallback(t *testing.T) {
assert.Equal("", fwd.Path, "valid request should be redirected to return url")
}
func TestServerLogout(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
config = newDefaultConfig()
req := newDefaultHttpRequest("/_oauth/logout")
res, _ := doHttpRequest(req, nil)
require.Equal(401, res.StatusCode, "should return a 401")
// Check for cookie
var cookie *http.Cookie
for _, c := range res.Cookies() {
if c.Name == config.CookieName {
cookie = c
}
}
require.NotNil(cookie)
require.Less(cookie.Expires.Local().Unix(), time.Now().Local().Unix()-50, "cookie should have expired")
// Test with redirect
config.LogoutRedirect = "http://redirect/path"
req = newDefaultHttpRequest("/_oauth/logout")
res, _ = doHttpRequest(req, nil)
require.Equal(307, res.StatusCode, "should return a 307")
// Check for cookie
cookie = nil
for _, c := range res.Cookies() {
if c.Name == config.CookieName {
cookie = c
}
}
require.NotNil(cookie)
require.Less(cookie.Expires.Local().Unix(), time.Now().Local().Unix()-50, "cookie should have expired")
fwd, _ := res.Location()
require.NotNil(fwd)
assert.Equal("http", fwd.Scheme, "valid request should be redirected to return url")
assert.Equal("redirect", fwd.Host, "valid request should be redirected to return url")
assert.Equal("/path", fwd.Path, "valid request should be redirected to return url")
}
func TestServerDefaultAction(t *testing.T) {
assert := assert.New(t)
config = newDefaultConfig()