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.
149 lines
6.1 KiB
149 lines
6.1 KiB
package web
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"webfilebrowser/internal/auth"
|
|
)
|
|
|
|
func TestNonAdminUsersAreForbiddenFromEveryAdminRoute(t *testing.T) {
|
|
store, handler := newTestApplication(t)
|
|
created, err := store.CreateUser("reader@example.com", "Temporary-1!", auth.RoleUser)
|
|
if err != nil {
|
|
t.Fatalf("CreateUser() error = %v", err)
|
|
}
|
|
userCookie := completeUserPasswordChange(t, handler, created.Email, "Temporary-1!", "Reader-password-2!")
|
|
|
|
for _, target := range []struct {
|
|
method string
|
|
path string
|
|
}{
|
|
{method: http.MethodGet, path: "/admin"},
|
|
{method: http.MethodGet, path: adminUsersPath},
|
|
{method: http.MethodPost, path: adminCreateUserPath},
|
|
{method: http.MethodPost, path: adminResetUserPath},
|
|
{method: http.MethodPost, path: adminDisableUserPath},
|
|
{method: http.MethodPost, path: adminDeleteUserPath},
|
|
} {
|
|
t.Run(target.method+" "+target.path, func(t *testing.T) {
|
|
response := postOrGetWithCookie(handler, target.method, target.path, userCookie)
|
|
if response.Code != http.StatusForbidden {
|
|
t.Errorf("%s %s status = %d, want %d", target.method, target.path, response.Code, http.StatusForbidden)
|
|
}
|
|
if response.Header().Get("Location") != "" {
|
|
t.Errorf("%s %s redirected to %q, want no redirect", target.method, target.path, response.Header().Get("Location"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAdminCanManageUsersAndTemporaryPasswordsRequireChange(t *testing.T) {
|
|
store, handler := newTestApplication(t)
|
|
adminCookie := completeSeedPasswordChange(t, handler)
|
|
|
|
usersResponse := getWithCookie(handler, adminUsersPath, adminCookie)
|
|
if usersResponse.Code != http.StatusOK || !strings.Contains(usersResponse.Body.String(), "Create user") {
|
|
t.Fatalf("GET %s = (%d, %q), want admin management page", adminUsersPath, usersResponse.Code, usersResponse.Body.String())
|
|
}
|
|
|
|
createResponse := postForm(handler, adminCreateUserPath, url.Values{
|
|
"email": {"new-reader@example.com"},
|
|
"temporary_password": {"Temporary-1!"},
|
|
"role": {string(auth.RoleUser)},
|
|
}, adminCookie)
|
|
if createResponse.Code != http.StatusSeeOther || createResponse.Header().Get("Location") != adminUsersPath {
|
|
t.Fatalf("POST %s = (%d, %q), want redirect to %s", adminCreateUserPath, createResponse.Code, createResponse.Header().Get("Location"), adminUsersPath)
|
|
}
|
|
|
|
created := managedUser(t, store, "new-reader@example.com")
|
|
loginResponse := postForm(handler, loginPath, url.Values{
|
|
"email": {created.Email},
|
|
"password": {"Temporary-1!"},
|
|
}, nil)
|
|
if loginResponse.Code != http.StatusSeeOther || loginResponse.Header().Get("Location") != changePasswordPath {
|
|
t.Fatalf("new user login = (%d, %q), want redirect to %s", loginResponse.Code, loginResponse.Header().Get("Location"), changePasswordPath)
|
|
}
|
|
|
|
resetResponse := postForm(handler, adminResetUserPath, url.Values{
|
|
"id": {strconv.FormatInt(created.ID, 10)},
|
|
"temporary_password": {"Replacement-2!"},
|
|
}, adminCookie)
|
|
if resetResponse.Code != http.StatusSeeOther || resetResponse.Header().Get("Location") != adminUsersPath {
|
|
t.Fatalf("POST %s = (%d, %q), want redirect to %s", adminResetUserPath, resetResponse.Code, resetResponse.Header().Get("Location"), adminUsersPath)
|
|
}
|
|
if _, err := store.Authenticate(created.Email, "Replacement-2!"); err != nil {
|
|
t.Fatalf("Authenticate(reset user) error = %v", err)
|
|
}
|
|
|
|
disableResponse := postForm(handler, adminDisableUserPath, url.Values{"id": {strconv.FormatInt(created.ID, 10)}}, adminCookie)
|
|
if disableResponse.Code != http.StatusSeeOther || disableResponse.Header().Get("Location") != adminUsersPath {
|
|
t.Fatalf("POST %s = (%d, %q), want redirect to %s", adminDisableUserPath, disableResponse.Code, disableResponse.Header().Get("Location"), adminUsersPath)
|
|
}
|
|
if _, err := store.Authenticate(created.Email, "Replacement-2!"); !errors.Is(err, auth.ErrInvalidCredentials) {
|
|
t.Errorf("Authenticate(disabled user) error = %v, want invalid credentials", err)
|
|
}
|
|
|
|
deleteResponse := postForm(handler, adminDeleteUserPath, url.Values{"id": {strconv.FormatInt(created.ID, 10)}}, adminCookie)
|
|
if deleteResponse.Code != http.StatusSeeOther || deleteResponse.Header().Get("Location") != adminUsersPath {
|
|
t.Fatalf("POST %s = (%d, %q), want redirect to %s", adminDeleteUserPath, deleteResponse.Code, deleteResponse.Header().Get("Location"), adminUsersPath)
|
|
}
|
|
if got := managedUserOrNil(t, store, created.Email); got != nil {
|
|
t.Errorf("deleted user remains in management list: %#v", got)
|
|
}
|
|
}
|
|
|
|
func completeUserPasswordChange(t *testing.T, handler http.Handler, email, temporaryPassword, newPassword string) *http.Cookie {
|
|
t.Helper()
|
|
loginResponse := postForm(handler, loginPath, url.Values{
|
|
"email": {email},
|
|
"password": {temporaryPassword},
|
|
}, nil)
|
|
if loginResponse.Code != http.StatusSeeOther || loginResponse.Header().Get("Location") != changePasswordPath {
|
|
t.Fatalf("temporary user login = (%d, %q), want redirect to %s", loginResponse.Code, loginResponse.Header().Get("Location"), changePasswordPath)
|
|
}
|
|
cookie := responseCookie(t, loginResponse, sessionCookieName)
|
|
changeResponse := postForm(handler, changePasswordPath, url.Values{
|
|
"current_password": {temporaryPassword},
|
|
"new_password": {newPassword},
|
|
}, cookie)
|
|
if changeResponse.Code != http.StatusSeeOther {
|
|
t.Fatalf("POST %s = %d, want %d", changePasswordPath, changeResponse.Code, http.StatusSeeOther)
|
|
}
|
|
return cookie
|
|
}
|
|
|
|
func postOrGetWithCookie(handler http.Handler, method, target string, cookie *http.Cookie) *httptest.ResponseRecorder {
|
|
if method == http.MethodGet {
|
|
return getWithCookie(handler, target, cookie)
|
|
}
|
|
return postForm(handler, target, url.Values{}, cookie)
|
|
}
|
|
|
|
func managedUser(t *testing.T, store *auth.Store, email string) auth.User {
|
|
t.Helper()
|
|
user := managedUserOrNil(t, store, email)
|
|
if user == nil {
|
|
t.Fatalf("ListUsers() did not return %q", email)
|
|
}
|
|
return *user
|
|
}
|
|
|
|
func managedUserOrNil(t *testing.T, store *auth.Store, email string) *auth.User {
|
|
t.Helper()
|
|
users, err := store.ListUsers()
|
|
if err != nil {
|
|
t.Fatalf("ListUsers() error = %v", err)
|
|
}
|
|
for index := range users {
|
|
if users[index].Email == email {
|
|
return &users[index]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|