mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 00:39:13 +00:00
refactor(http): auth handlers (#1311)
* fix(auth): implement invalid cookie handling * that escalated quickly * refactor(http): auth handlers * add tests for auth handler * refactor methods * chore(tests): add header and build tag * add build tag integration * chore(tests): run in ci --------- Co-authored-by: ze0s <ze0s@riseup.net>
This commit is contained in:
parent
df2612602b
commit
6a94ecacca
18 changed files with 537 additions and 80 deletions
402
internal/http/auth_test.go
Normal file
402
internal/http/auth_test.go
Normal file
|
@ -0,0 +1,402 @@
|
|||
// Copyright (c) 2021 - 2023, 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"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if status := resp.StatusCode; status != http.StatusNoContent {
|
||||
t.Errorf("validate: handler returned wrong status code: got %v want %v", status, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if status := resp.StatusCode; status != http.StatusUnauthorized {
|
||||
t.Errorf("validate: handler returned wrong status code: got %v want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// check for response, here we'll just check for 204 NoContent
|
||||
if status := resp.StatusCode; status != http.StatusUnauthorized {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if status := resp.StatusCode; status != http.StatusNoContent {
|
||||
t.Errorf("validate: handler returned wrong status code: got %v want %v", status, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// logout
|
||||
resp, err = client.Post(testServer.URL+"/auth/logout", "application/json", nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred: %v", err)
|
||||
}
|
||||
|
||||
if status := resp.StatusCode; status != http.StatusNoContent {
|
||||
t.Errorf("validate: handler returned wrong status code: got %v want %v", status, http.StatusNoContent)
|
||||
}
|
||||
|
||||
//if v := resp.Header.Get("Set-Cookie"); v != "" {
|
||||
// t.Errorf("logout handler returned cookie")
|
||||
//}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue