package server import ( "html/template" "net/http" "net/http/httptest" "net/url" "path/filepath" "strings" "testing" "time" "github.com/gin-gonic/gin" "warpbox/lib/boxstore" "warpbox/lib/config" "warpbox/lib/metastore" ) func TestAccountLoginSuccess(t *testing.T) { app, _ := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) response := postAccountLogin(router, "admin", "secret") if response.Code != http.StatusSeeOther { t.Fatalf("expected login redirect, got %d", response.Code) } if location := response.Header().Get("Location"); location != "/account" { t.Fatalf("expected redirect to /account, got %q", location) } if cookie := findResponseCookie(response, accountSessionCookie); cookie == nil || cookie.Value == "" { t.Fatal("expected account session cookie") } } func TestAccountLoginFailure(t *testing.T) { app, _ := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) response := postAccountLogin(router, "admin", "wrong") if response.Code != http.StatusOK { t.Fatalf("expected failed login to render form, got %d", response.Code) } if cookie := findResponseCookie(response, accountSessionCookie); cookie != nil { t.Fatal("did not expect account session cookie") } if !strings.Contains(response.Body.String(), "not accepted") { t.Fatal("expected login failure message") } } func TestAccountDisabledUserLoginFailure(t *testing.T) { app, user := setupAccountTestApp(t) user.Disabled = true if err := app.store.UpdateUser(user); err != nil { t.Fatalf("UpdateUser returned error: %v", err) } router := setupAccountTestRouter(t, app) response := postAccountLogin(router, "admin", "secret") if response.Code != http.StatusOK { t.Fatalf("expected disabled login to render form, got %d", response.Code) } if cookie := findResponseCookie(response, accountSessionCookie); cookie != nil { t.Fatal("did not expect account session cookie") } if !strings.Contains(response.Body.String(), "not accepted") { t.Fatal("expected login failure message") } } func TestAccountLogoutRequiresCSRF(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } request := httptest.NewRequest(http.MethodPost, "/account/logout", nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusForbidden { t.Fatalf("expected missing CSRF token to be forbidden, got %d", response.Code) } } func TestAccountDashboardRequiresAuth(t *testing.T) { app, _ := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) request := httptest.NewRequest(http.MethodGet, "/account", nil) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected dashboard redirect, got %d", response.Code) } if location := response.Header().Get("Location"); location != "/account/login" { t.Fatalf("expected redirect to /account/login, got %q", location) } } func TestAccountDashboardLoadsForBootstrapAdmin(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } request := httptest.NewRequest(http.MethodGet, "/account", nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("expected dashboard to load, got %d", response.Code) } body := response.Body.String() for _, text := range []string{"Dashboard", "Recent Boxes", "Users"} { if !strings.Contains(body, text) { t.Fatalf("expected dashboard body to contain %q", text) } } } func TestAccountDashboardHidesAdminOnlyLinksForRegularUser(t *testing.T) { app, _ := setupAccountTestApp(t) user, err := app.store.CreateUserWithPassword("maya", "maya@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session, err := app.store.CreateSession(user.ID, time.Hour) if err != nil { t.Fatalf("CreateSession returned error: %v", err) } request := httptest.NewRequest(http.MethodGet, "/account", nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("expected dashboard to load, got %d", response.Code) } body := response.Body.String() for _, text := range []string{">Users<", ">Settings<"} { if strings.Contains(body, text) { t.Fatalf("expected dashboard body to hide %q", text) } } } func TestAdminEntryRedirectsToAccount(t *testing.T) { app, _ := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) cases := map[string]string{ "/admin/login": "/account/login", "/admin": "/account", } for path, wantLocation := range cases { request := httptest.NewRequest(http.MethodGet, path, nil) response := httptest.NewRecorder() router.ServeHTTP(response, request) if response.Code != http.StatusSeeOther { t.Fatalf("expected %s redirect, got %d", path, response.Code) } if location := response.Header().Get("Location"); location != wantLocation { t.Fatalf("expected %s to redirect to %s, got %q", path, wantLocation, location) } } } func setupAccountTestApp(t *testing.T) (*App, metastore.User) { t.Helper() gin.SetMode(gin.TestMode) restoreUploadRoot := boxstore.UploadRoot() t.Cleanup(func() { boxstore.SetUploadRoot(restoreUploadRoot) }) boxstore.SetUploadRoot(t.TempDir()) store, err := metastore.Open(t.TempDir()) if err != nil { t.Fatalf("Open returned error: %v", err) } t.Cleanup(func() { _ = store.Close() }) cfg, err := config.Load() if err != nil { t.Fatalf("Load returned error: %v", err) } cfg.AdminUsername = "admin" cfg.AdminPassword = "secret" cfg.AdminEmail = "admin@example.test" cfg.AdminEnabled = config.AdminEnabledAuto cfg.SessionTTLSeconds = 3600 bootstrap, err := metastore.BootstrapAdmin(cfg, store) if err != nil { t.Fatalf("BootstrapAdmin returned error: %v", err) } if bootstrap.AdminUser == nil { t.Fatal("expected bootstrap admin user") } app := &App{ config: cfg, store: store, adminLoginEnabled: bootstrap.AdminLoginEnabled, } return app, *bootstrap.AdminUser } func setupAccountTestRouter(t *testing.T, app *App) *gin.Engine { t.Helper() router := gin.New() templates, err := template.ParseGlob(filepath.Join("..", "..", "templates", "*.html")) if err != nil { t.Fatalf("ParseGlob returned error: %v", err) } router.SetHTMLTemplate(templates) app.registerAccountRoutes(router) app.registerAdminRoutes(router) return router } func postAccountLogin(router *gin.Engine, username string, password string) *httptest.ResponseRecorder { form := url.Values{} form.Set("username", username) form.Set("password", password) request := httptest.NewRequest(http.MethodPost, "/account/login", strings.NewReader(form.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") response := httptest.NewRecorder() router.ServeHTTP(response, request) return response } func findResponseCookie(response *httptest.ResponseRecorder, name string) *http.Cookie { for _, cookie := range response.Result().Cookies() { if cookie.Name == name { return cookie } } return nil }