autobrr/internal/http/auth_test.go
martylukyy 1a23b69bcf
feat(web): move from react-router to @tanstack/router (#1338)
* fix(auth): invalid cookie handling and wrongful basic auth invalidation

* fix(auth): fix test to reflect new HTTP status code

* fix(auth/web): do not throw on error

* fix(http): replace http codes in middleware to prevent basic auth invalidation
fix typo in comment

* fix test

* fix(web): api client handle 403

* refactor(http): auth_test use testify.assert

* refactor(http): set session opts after valid login

* refactor(http): send more client headers

* fix(http): test

* refactor(web): move router to tanstack/router

* refactor(web): use route loaders and suspense

* refactor(web): useSuspense for settings

* refactor(web): invalidate cookie in middleware

* fix: loclfile

* fix: load filter/id

* fix(web): login, onboard, types, imports

* fix(web): filter load

* fix(web): build errors

* fix(web): ts-expect-error

* fix(tests): filter_test.go

* fix(filters): tests

* refactor: remove duplicate spinner components
refactor: ReleaseTable.tsx loading animation
refactor: remove dedicated `pendingComponent` for `settingsRoute`

* fix: refactor missed SectionLoader to RingResizeSpinner

* fix: substitute divides with borders to account for unloaded elements

* fix(api): action status URL param

* revert: action status URL param
add comment

* fix(routing): notfound handling and split files

* fix(filters): notfound get params

* fix(queries): colon

* fix(queries): comments ts-ignore

* fix(queries): extract queryKeys

* fix(queries): remove err

* fix(routes): move zob schema inline

* fix(auth): middleware and redirect to login

* fix(auth): failing test

* fix(logs): invalidate correct key

* fix(logs): invalidate correct key

* fix(logs): invalidate correct key

* fix: JSX element stealing focus from searchbar

* reimplement empty release table state text

* fix(context): use deep-copy

* fix(releases): empty state and filter input warnings

* fix(releases): empty states

* fix(auth): onboarding

* fix(cache): invalidate queries

---------

Co-authored-by: ze0s <43699394+zze0s@users.noreply.github.com>
2024-02-12 13:07:00 +01:00

407 lines
9.3 KiB
Go

// Copyright (c) 2021 - 2024, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
//go:build integration
package http
import (
"bytes"
"context"
"encoding/json"
"log"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"testing"
"github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/go-chi/chi/v5"
"github.com/gorilla/sessions"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
)
type authServiceMock struct {
users map[string]*domain.User
}
func (a authServiceMock) GetUserCount(ctx context.Context) (int, error) {
return len(a.users), nil
}
func (a authServiceMock) Login(ctx context.Context, username, password string) (*domain.User, error) {
u, ok := a.users[username]
if !ok {
return nil, errors.New("invalid login")
}
if u.Password != password {
return nil, errors.New("bad credentials")
}
return u, nil
}
func (a authServiceMock) CreateUser(ctx context.Context, req domain.CreateUserRequest) error {
if req.Username != "" {
a.users[req.Username] = &domain.User{
ID: len(a.users) + 1,
Username: req.Username,
Password: req.Password,
}
}
return nil
}
func (a authServiceMock) UpdateUser(ctx context.Context, req domain.UpdateUserRequest) error {
u, ok := a.users[req.UsernameCurrent]
if !ok {
return errors.New("user not found")
}
if req.UsernameNew != "" {
u.Username = req.UsernameNew
}
if req.PasswordNew != "" {
u.Password = req.PasswordNew
}
return nil
}
func setupServer() chi.Router {
r := chi.NewRouter()
//r.Use(middleware.Logger)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
return r
}
func runTestServer(s chi.Router) *httptest.Server {
return httptest.NewServer(s)
}
func setupAuthHandler() {
}
func TestAuthHandlerLogin(t *testing.T) {
logger := zerolog.Nop()
encoder := encoder{}
cookieStore := sessions.NewCookieStore([]byte("test"))
service := authServiceMock{
users: map[string]*domain.User{
"test": {
ID: 0,
Username: "test",
Password: "pass",
},
},
}
server := Server{
log: logger,
cookieStore: cookieStore,
}
handler := newAuthHandler(encoder, logger, server, &domain.Config{}, cookieStore, service)
s := setupServer()
s.Route("/auth", handler.Routes)
testServer := runTestServer(s)
defer testServer.Close()
// generate request, here we'll use login as example
reqBody, err := json.Marshal(map[string]string{
"username": "test",
"password": "pass",
})
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
jarOptions := &cookiejar.Options{PublicSuffixList: nil}
jar, err := cookiejar.New(jarOptions)
if err != nil {
log.Fatalf("error creating cookiejar: %v", err)
}
client := http.DefaultClient
client.Jar = jar
// make request
resp, err := client.Post(testServer.URL+"/auth/login", "application/json", bytes.NewBuffer(reqBody))
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
defer resp.Body.Close()
// check for response, here we'll just check for 204 NoContent
assert.Equalf(t, http.StatusNoContent, resp.StatusCode, "login handler: unexpected http status")
if v := resp.Header.Get("Set-Cookie"); v == "" {
t.Errorf("handler returned no cookie")
}
}
func TestAuthHandlerValidateOK(t *testing.T) {
logger := zerolog.Nop()
encoder := encoder{}
cookieStore := sessions.NewCookieStore([]byte("test"))
service := authServiceMock{
users: map[string]*domain.User{
"test": {
ID: 0,
Username: "test",
Password: "pass",
},
},
}
server := Server{
log: logger,
cookieStore: cookieStore,
}
handler := newAuthHandler(encoder, logger, server, &domain.Config{}, cookieStore, service)
s := setupServer()
s.Route("/auth", handler.Routes)
testServer := runTestServer(s)
defer testServer.Close()
// generate request, here we'll use login as example
reqBody, err := json.Marshal(map[string]string{
"username": "test",
"password": "pass",
})
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
jarOptions := &cookiejar.Options{PublicSuffixList: nil}
jar, err := cookiejar.New(jarOptions)
if err != nil {
log.Fatalf("error creating cookiejar: %v", err)
}
client := http.DefaultClient
client.Jar = jar
// make request
resp, err := client.Post(testServer.URL+"/auth/login", "application/json", bytes.NewBuffer(reqBody))
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
defer resp.Body.Close()
// check for response, here we'll just check for 204 NoContent
assert.Equalf(t, http.StatusNoContent, resp.StatusCode, "login handler: bad response")
if v := resp.Header.Get("Set-Cookie"); v == "" {
assert.Equalf(t, "", v, "login handler: expected Set-Cookie header")
}
// validate token
resp, err = client.Get(testServer.URL + "/auth/validate")
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
defer resp.Body.Close()
assert.Equalf(t, http.StatusNoContent, resp.StatusCode, "validate handler: unexpected http status")
}
func TestAuthHandlerValidateBad(t *testing.T) {
logger := zerolog.Nop()
encoder := encoder{}
cookieStore := sessions.NewCookieStore([]byte("test"))
service := authServiceMock{
users: map[string]*domain.User{
"test": {
ID: 0,
Username: "test",
Password: "pass",
},
},
}
server := Server{
log: logger,
cookieStore: cookieStore,
}
handler := newAuthHandler(encoder, logger, server, &domain.Config{}, cookieStore, service)
s := setupServer()
s.Route("/auth", handler.Routes)
testServer := runTestServer(s)
defer testServer.Close()
jarOptions := &cookiejar.Options{PublicSuffixList: nil}
jar, err := cookiejar.New(jarOptions)
if err != nil {
log.Fatalf("error creating cookiejar: %v", err)
}
client := http.DefaultClient
client.Jar = jar
// validate token
resp, err := client.Get(testServer.URL + "/auth/validate")
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
defer resp.Body.Close()
assert.Equalf(t, http.StatusForbidden, resp.StatusCode, "validate handler: unexpected http status")
}
func TestAuthHandlerLoginBad(t *testing.T) {
logger := zerolog.Nop()
encoder := encoder{}
cookieStore := sessions.NewCookieStore([]byte("test"))
service := authServiceMock{
users: map[string]*domain.User{
"test": {
ID: 0,
Username: "test",
Password: "pass",
},
},
}
server := Server{
log: logger,
}
handler := newAuthHandler(encoder, logger, server, &domain.Config{}, cookieStore, service)
s := setupServer()
s.Route("/auth", handler.Routes)
testServer := runTestServer(s)
defer testServer.Close()
// generate request, here we'll use login as example
reqBody, err := json.Marshal(map[string]string{
"username": "test",
"password": "notmypass",
})
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
// make request
resp, err := http.Post(testServer.URL+"/auth/login", "application/json", bytes.NewBuffer(reqBody))
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
defer resp.Body.Close()
// check for response, here we'll just check for 403 Forbidden
assert.Equalf(t, http.StatusForbidden, resp.StatusCode, "login handler: unexpected http status")
}
func TestAuthHandlerLogout(t *testing.T) {
logger := zerolog.Nop()
encoder := encoder{}
cookieStore := sessions.NewCookieStore([]byte("test"))
service := authServiceMock{
users: map[string]*domain.User{
"test": {
ID: 0,
Username: "test",
Password: "pass",
},
},
}
server := Server{
log: logger,
cookieStore: cookieStore,
}
handler := newAuthHandler(encoder, logger, server, &domain.Config{}, cookieStore, service)
s := setupServer()
s.Route("/auth", handler.Routes)
testServer := runTestServer(s)
defer testServer.Close()
// generate request, here we'll use login as example
reqBody, err := json.Marshal(map[string]string{
"username": "test",
"password": "pass",
})
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
jarOptions := &cookiejar.Options{PublicSuffixList: nil}
jar, err := cookiejar.New(jarOptions)
if err != nil {
log.Fatalf("error creating cookiejar: %v", err)
}
client := http.DefaultClient
client.Jar = jar
// make request
resp, err := client.Post(testServer.URL+"/auth/login", "application/json", bytes.NewBuffer(reqBody))
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
defer resp.Body.Close()
// check for response, here we'll just check for 204 NoContent
if status := resp.StatusCode; status != http.StatusNoContent {
t.Errorf("login: handler returned wrong status code: got %v want %v", status, http.StatusNoContent)
}
assert.Equalf(t, http.StatusNoContent, resp.StatusCode, "login handler: unexpected http status")
if v := resp.Header.Get("Set-Cookie"); v == "" {
t.Errorf("handler returned no cookie")
}
// validate token
resp, err = client.Get(testServer.URL + "/auth/validate")
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
defer resp.Body.Close()
assert.Equalf(t, http.StatusNoContent, resp.StatusCode, "validate handler: unexpected http status")
// logout
resp, err = client.Post(testServer.URL+"/auth/logout", "application/json", nil)
if err != nil {
log.Fatalf("Error occurred: %v", err)
}
defer resp.Body.Close()
assert.Equalf(t, http.StatusNoContent, resp.StatusCode, "logout handler: unexpected http status")
//if v := resp.Header.Get("Set-Cookie"); v != "" {
// t.Errorf("logout handler returned cookie")
//}
}