diff --git a/home/auth.go b/home/auth.go index 21b74adc..52b62e70 100644 --- a/home/auth.go +++ b/home/auth.go @@ -224,19 +224,10 @@ func getSession(u *User) []byte { return hash[:] } -func handleLogin(w http.ResponseWriter, r *http.Request) { - req := loginJSON{} - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - httpError(w, http.StatusBadRequest, "json decode: %s", err) - return - } - +func httpCookie(req loginJSON) string { u := config.auth.UserFind(req.Name, req.Password) if len(u.Name) == 0 { - time.Sleep(1 * time.Second) - httpError(w, http.StatusBadRequest, "invalid login or password") - return + return "" } sess := getSession(&u) @@ -250,8 +241,25 @@ func handleLogin(w http.ResponseWriter, r *http.Request) { expireSess := uint32(now.Unix()) + expireTime*60*60 config.auth.storeSession(sess, expireSess) - s := fmt.Sprintf("session=%s; Path=/; HttpOnly; Expires=%s", hex.EncodeToString(sess), expstr) - w.Header().Set("Set-Cookie", s) + return fmt.Sprintf("session=%s; Path=/; HttpOnly; Expires=%s", hex.EncodeToString(sess), expstr) +} + +func handleLogin(w http.ResponseWriter, r *http.Request) { + req := loginJSON{} + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + httpError(w, http.StatusBadRequest, "json decode: %s", err) + return + } + + cookie := httpCookie(req) + if len(cookie) == 0 { + time.Sleep(1 * time.Second) + httpError(w, http.StatusBadRequest, "invalid login or password") + return + } + + w.Header().Set("Set-Cookie", cookie) w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate") w.Header().Set("Pragma", "no-cache") @@ -324,7 +332,6 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re if err == nil { r := config.auth.CheckSession(cookie.Value) if r == 0 { - ok = true } else if r < 0 { log.Debug("Auth: invalid cookie value: %s", cookie) diff --git a/home/auth_test.go b/home/auth_test.go index abb6dfc4..2ae532fd 100644 --- a/home/auth_test.go +++ b/home/auth_test.go @@ -2,6 +2,8 @@ package home import ( "encoding/hex" + "net/http" + "net/url" "os" "path/filepath" "testing" @@ -10,19 +12,25 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAuth(t *testing.T) { - config.ourWorkingDir = "." - fn := filepath.Join(config.getDataDir(), "sessions.db") +func prepareTestDir() string { + const dir = "./agh-test" + _ = os.RemoveAll(dir) + _ = os.MkdirAll(dir, 0755) + return dir +} - _ = os.RemoveAll(config.getDataDir()) - defer func() { _ = os.RemoveAll(config.getDataDir()) }() +func TestAuth(t *testing.T) { + dir := prepareTestDir() + defer func() { _ = os.RemoveAll(dir) }() + fn := filepath.Join(dir, "sessions.db") users := []User{ User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, } + a := InitAuth(fn, nil) - os.MkdirAll(config.getDataDir(), 0755) - a := InitAuth(fn, users) + user := User{Name: "name"} + a.UserAdd(&user, "password") assert.True(t, a.CheckSession("notfound") == -1) a.RemoveSession("notfound") @@ -59,3 +67,103 @@ func TestAuth(t *testing.T) { a.Close() os.Remove(fn) } + +// implements http.ResponseWriter +type testResponseWriter struct { + hdr http.Header + statusCode int +} + +func (w *testResponseWriter) Header() http.Header { + return w.hdr +} +func (w *testResponseWriter) Write([]byte) (int, error) { + return 0, nil +} +func (w *testResponseWriter) WriteHeader(statusCode int) { + w.statusCode = statusCode +} + +func TestAuthHTTP(t *testing.T) { + dir := prepareTestDir() + defer func() { _ = os.RemoveAll(dir) }() + fn := filepath.Join(dir, "sessions.db") + + users := []User{ + User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, + } + config.auth = InitAuth(fn, users) + + handlerCalled := false + handler := func(w http.ResponseWriter, r *http.Request) { + handlerCalled = true + } + handler2 := optionalAuth(handler) + w := testResponseWriter{} + w.hdr = make(http.Header) + r := http.Request{} + r.Header = make(http.Header) + r.Method = "GET" + + // get / - we're redirected to login page + r.URL = &url.URL{Path: "/"} + handlerCalled = false + handler2(&w, &r) + assert.True(t, w.statusCode == http.StatusFound) + assert.True(t, w.hdr.Get("Location") != "") + assert.True(t, !handlerCalled) + + // go to login page + loginURL := w.hdr.Get("Location") + r.URL = &url.URL{Path: loginURL} + handlerCalled = false + handler2(&w, &r) + assert.True(t, handlerCalled) + + // perform login + cookie := httpCookie(loginJSON{Name: "name", Password: "password"}) + assert.True(t, cookie != "") + + // get / + handler2 = optionalAuth(handler) + w.hdr = make(http.Header) + r.Header.Set("Cookie", cookie) + r.URL = &url.URL{Path: "/"} + handlerCalled = false + handler2(&w, &r) + assert.True(t, handlerCalled) + r.Header.Del("Cookie") + + // get / with basic auth + handler2 = optionalAuth(handler) + w.hdr = make(http.Header) + r.URL = &url.URL{Path: "/"} + r.SetBasicAuth("name", "password") + handlerCalled = false + handler2(&w, &r) + assert.True(t, handlerCalled) + r.Header.Del("Authorization") + + // get login page with a valid cookie - we're redirected to / + handler2 = optionalAuth(handler) + w.hdr = make(http.Header) + r.Header.Set("Cookie", cookie) + r.URL = &url.URL{Path: loginURL} + handlerCalled = false + handler2(&w, &r) + assert.True(t, w.hdr.Get("Location") != "") + assert.True(t, !handlerCalled) + r.Header.Del("Cookie") + + // get login page with an invalid cookie + handler2 = optionalAuth(handler) + w.hdr = make(http.Header) + r.Header.Set("Cookie", "bad") + r.URL = &url.URL{Path: loginURL} + handlerCalled = false + handler2(&w, &r) + assert.True(t, handlerCalled) + r.Header.Del("Cookie") + + config.auth.Close() +}