You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
6.0 KiB
155 lines
6.0 KiB
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"webfilebrowser/internal/auth"
|
|
)
|
|
|
|
func TestUnauthenticatedRequestsNeverReachProtectedRoutes(t *testing.T) {
|
|
_, handler := newTestApplication(t)
|
|
|
|
for _, target := range []string{"/", "/files/media", "/download/media/report.pdf", "/stream/media/video.mp4", "/admin/users"} {
|
|
t.Run(target, func(t *testing.T) {
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, target, nil))
|
|
|
|
if response.Code != http.StatusSeeOther {
|
|
t.Errorf("GET %s status = %d, want %d", target, response.Code, http.StatusSeeOther)
|
|
}
|
|
if location := response.Header().Get("Location"); location != loginPath {
|
|
t.Errorf("GET %s Location = %q, want %q", target, location, loginPath)
|
|
}
|
|
if strings.Contains(response.Body.String(), "File browser is being prepared") {
|
|
t.Errorf("GET %s returned protected content", target)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoginForcesPasswordChangeBeforeAccessingOtherRoutes(t *testing.T) {
|
|
_, handler := newTestApplication(t)
|
|
|
|
loginResponse := postForm(handler, loginPath, url.Values{
|
|
"email": {auth.SeedAdminEmail},
|
|
"password": {auth.SeedAdminPassword},
|
|
}, nil)
|
|
if loginResponse.Code != http.StatusSeeOther {
|
|
t.Fatalf("POST %s status = %d, want %d", loginPath, loginResponse.Code, http.StatusSeeOther)
|
|
}
|
|
if location := loginResponse.Header().Get("Location"); location != changePasswordPath {
|
|
t.Fatalf("POST %s Location = %q, want %q", loginPath, location, changePasswordPath)
|
|
}
|
|
cookie := responseCookie(t, loginResponse, sessionCookieName)
|
|
if !cookie.HttpOnly || !cookie.Secure || cookie.SameSite != http.SameSiteLaxMode {
|
|
t.Errorf("session cookie security attributes = HttpOnly:%t Secure:%t SameSite:%v, want true:true:Lax", cookie.HttpOnly, cookie.Secure, cookie.SameSite)
|
|
}
|
|
|
|
for _, target := range []string{"/", "/files/media", "/download/media/report.pdf", "/stream/media/video.mp4", "/admin/users"} {
|
|
response := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodGet, target, nil)
|
|
request.AddCookie(cookie)
|
|
handler.ServeHTTP(response, request)
|
|
|
|
if response.Code != http.StatusSeeOther || response.Header().Get("Location") != changePasswordPath {
|
|
t.Errorf("GET %s during forced change = (%d, %q), want (%d, %q)", target, response.Code, response.Header().Get("Location"), http.StatusSeeOther, changePasswordPath)
|
|
}
|
|
}
|
|
|
|
samePasswordResponse := postForm(handler, changePasswordPath, url.Values{
|
|
"current_password": {auth.SeedAdminPassword},
|
|
"new_password": {auth.SeedAdminPassword},
|
|
}, cookie)
|
|
if samePasswordResponse.Code != http.StatusBadRequest {
|
|
t.Errorf("POST %s with unchanged password status = %d, want %d", changePasswordPath, samePasswordResponse.Code, http.StatusBadRequest)
|
|
}
|
|
|
|
changedPasswordResponse := postForm(handler, changePasswordPath, url.Values{
|
|
"current_password": {auth.SeedAdminPassword},
|
|
"new_password": {"A-new-password-123!"},
|
|
}, cookie)
|
|
if changedPasswordResponse.Code != http.StatusSeeOther || changedPasswordResponse.Header().Get("Location") != "/" {
|
|
t.Errorf("POST %s status/location = (%d, %q), want (%d, %q)", changePasswordPath, changedPasswordResponse.Code, changedPasswordResponse.Header().Get("Location"), http.StatusSeeOther, "/")
|
|
}
|
|
|
|
rootResponse := httptest.NewRecorder()
|
|
rootRequest := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
rootRequest.AddCookie(cookie)
|
|
handler.ServeHTTP(rootResponse, rootRequest)
|
|
if rootResponse.Code != http.StatusOK {
|
|
t.Errorf("GET / after changing password status = %d, want %d", rootResponse.Code, http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestLogoutDeletesTheServerSession(t *testing.T) {
|
|
_, handler := newTestApplication(t)
|
|
cookie := loginSeedAdmin(t, handler)
|
|
|
|
logoutResponse := postForm(handler, logoutPath, nil, cookie)
|
|
if logoutResponse.Code != http.StatusSeeOther || logoutResponse.Header().Get("Location") != loginPath {
|
|
t.Fatalf("POST %s status/location = (%d, %q), want (%d, %q)", logoutPath, logoutResponse.Code, logoutResponse.Header().Get("Location"), http.StatusSeeOther, loginPath)
|
|
}
|
|
|
|
protectedResponse := httptest.NewRecorder()
|
|
protectedRequest := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
protectedRequest.AddCookie(cookie)
|
|
handler.ServeHTTP(protectedResponse, protectedRequest)
|
|
if protectedResponse.Code != http.StatusSeeOther || protectedResponse.Header().Get("Location") != loginPath {
|
|
t.Errorf("GET / after logout status/location = (%d, %q), want (%d, %q)", protectedResponse.Code, protectedResponse.Header().Get("Location"), http.StatusSeeOther, loginPath)
|
|
}
|
|
}
|
|
|
|
func newTestApplication(t *testing.T) (*auth.Store, http.Handler) {
|
|
t.Helper()
|
|
store, err := auth.Open(filepath.Join(t.TempDir(), "webfilebrowser.db"), auth.Options{})
|
|
if err != nil {
|
|
t.Fatalf("Open() error = %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = store.Close() })
|
|
return store, New(store)
|
|
}
|
|
|
|
func loginSeedAdmin(t *testing.T, handler http.Handler) *http.Cookie {
|
|
t.Helper()
|
|
response := postForm(handler, loginPath, url.Values{
|
|
"email": {auth.SeedAdminEmail},
|
|
"password": {auth.SeedAdminPassword},
|
|
}, nil)
|
|
if response.Code != http.StatusSeeOther {
|
|
t.Fatalf("POST %s status = %d, want %d", loginPath, response.Code, http.StatusSeeOther)
|
|
}
|
|
return responseCookie(t, response, sessionCookieName)
|
|
}
|
|
|
|
func postForm(handler http.Handler, target string, values url.Values, cookie *http.Cookie) *httptest.ResponseRecorder {
|
|
var body io.Reader
|
|
if values != nil {
|
|
body = bytes.NewBufferString(values.Encode())
|
|
}
|
|
request := httptest.NewRequest(http.MethodPost, target, body)
|
|
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
if cookie != nil {
|
|
request.AddCookie(cookie)
|
|
}
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
return response
|
|
}
|
|
|
|
func responseCookie(t *testing.T, response *httptest.ResponseRecorder, name string) *http.Cookie {
|
|
t.Helper()
|
|
for _, cookie := range response.Result().Cookies() {
|
|
if cookie.Name == name {
|
|
return cookie
|
|
}
|
|
}
|
|
t.Fatalf("response did not set cookie %q", name)
|
|
return nil
|
|
}
|
|
|