- Implement tests for ImportSuccessModal to verify rendering and functionality. - Update AuthContext to store authentication token in localStorage and manage token state. - Modify useImport hook to capture and expose commit results, preventing unnecessary refetches. - Enhance useCertificates hook to support optional refetch intervals. - Update Dashboard to conditionally poll certificates based on pending status. - Integrate ImportSuccessModal into ImportCaddy for user feedback on import completion. - Adjust Login component to utilize returned token for authentication. - Refactor CrowdSecConfig tests for improved readability and reliability. - Add debug_db.py script for inspecting the SQLite database. - Update integration and test scripts for better configuration and error handling. - Introduce Trivy scan script for vulnerability assessment of Docker images.
842 lines
23 KiB
Go
842 lines
23 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/config"
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/Wikid82/charon/backend/internal/services"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func setupAuthHandler(t *testing.T) (*AuthHandler, *gorm.DB) {
|
|
dbName := "file:" + t.Name() + "?mode=memory&cache=shared"
|
|
db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
db.AutoMigrate(&models.User{}, &models.Setting{})
|
|
|
|
cfg := config.Config{JWTSecret: "test-secret"}
|
|
authService := services.NewAuthService(db, cfg)
|
|
return NewAuthHandler(authService), db
|
|
}
|
|
|
|
func TestAuthHandler_Login(t *testing.T) {
|
|
handler, db := setupAuthHandler(t)
|
|
|
|
// Create user
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "test@example.com",
|
|
Name: "Test User",
|
|
}
|
|
user.SetPassword("password123")
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.POST("/login", handler.Login)
|
|
|
|
// Success
|
|
body := map[string]string{
|
|
"email": "test@example.com",
|
|
"password": "password123",
|
|
}
|
|
jsonBody, _ := json.Marshal(body)
|
|
req := httptest.NewRequest("POST", "/login", bytes.NewBuffer(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(t, w.Body.String(), "token")
|
|
}
|
|
|
|
func TestSetSecureCookie_HTTPS_Strict(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
os.Setenv("CHARON_ENV", "production")
|
|
defer os.Unsetenv("CHARON_ENV")
|
|
recorder := httptest.NewRecorder()
|
|
ctx, _ := gin.CreateTestContext(recorder)
|
|
req := httptest.NewRequest("POST", "https://example.com/login", http.NoBody)
|
|
ctx.Request = req
|
|
|
|
setSecureCookie(ctx, "auth_token", "abc", 60)
|
|
cookies := recorder.Result().Cookies()
|
|
require.Len(t, cookies, 1)
|
|
c := cookies[0]
|
|
assert.True(t, c.Secure)
|
|
assert.Equal(t, http.SameSiteStrictMode, c.SameSite)
|
|
}
|
|
|
|
func TestSetSecureCookie_HTTP_Lax(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
recorder := httptest.NewRecorder()
|
|
ctx, _ := gin.CreateTestContext(recorder)
|
|
req := httptest.NewRequest("POST", "http://192.0.2.10/login", http.NoBody)
|
|
req.Header.Set("X-Forwarded-Proto", "http")
|
|
ctx.Request = req
|
|
|
|
setSecureCookie(ctx, "auth_token", "abc", 60)
|
|
cookies := recorder.Result().Cookies()
|
|
require.Len(t, cookies, 1)
|
|
c := cookies[0]
|
|
assert.False(t, c.Secure)
|
|
assert.Equal(t, http.SameSiteLaxMode, c.SameSite)
|
|
}
|
|
|
|
func TestAuthHandler_Login_Errors(t *testing.T) {
|
|
handler, _ := setupAuthHandler(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.POST("/login", handler.Login)
|
|
|
|
// 1. Invalid JSON
|
|
req := httptest.NewRequest("POST", "/login", bytes.NewBufferString("invalid"))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
|
|
// 2. Invalid Credentials
|
|
body := map[string]string{
|
|
"email": "nonexistent@example.com",
|
|
"password": "wrong",
|
|
}
|
|
jsonBody, _ := json.Marshal(body)
|
|
req = httptest.NewRequest("POST", "/login", bytes.NewBuffer(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_Register(t *testing.T) {
|
|
handler, _ := setupAuthHandler(t)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.POST("/register", handler.Register)
|
|
|
|
body := map[string]string{
|
|
"email": "new@example.com",
|
|
"password": "password123",
|
|
"name": "New User",
|
|
}
|
|
jsonBody, _ := json.Marshal(body)
|
|
req := httptest.NewRequest("POST", "/register", bytes.NewBuffer(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusCreated, w.Code)
|
|
assert.Contains(t, w.Body.String(), "new@example.com")
|
|
}
|
|
|
|
func TestAuthHandler_Register_Duplicate(t *testing.T) {
|
|
handler, db := setupAuthHandler(t)
|
|
db.Create(&models.User{UUID: uuid.NewString(), Email: "dup@example.com", Name: "Dup"})
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.POST("/register", handler.Register)
|
|
|
|
body := map[string]string{
|
|
"email": "dup@example.com",
|
|
"password": "password123",
|
|
"name": "Dup User",
|
|
}
|
|
jsonBody, _ := json.Marshal(body)
|
|
req := httptest.NewRequest("POST", "/register", bytes.NewBuffer(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_Logout(t *testing.T) {
|
|
handler, _ := setupAuthHandler(t)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.POST("/logout", handler.Logout)
|
|
|
|
req := httptest.NewRequest("POST", "/logout", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(t, w.Body.String(), "Logged out")
|
|
// Check cookie
|
|
cookie := w.Result().Cookies()[0]
|
|
assert.Equal(t, "auth_token", cookie.Name)
|
|
assert.Equal(t, -1, cookie.MaxAge)
|
|
}
|
|
|
|
func TestAuthHandler_Me(t *testing.T) {
|
|
handler, db := setupAuthHandler(t)
|
|
|
|
// Create user that matches the middleware ID
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "me@example.com",
|
|
Name: "Me User",
|
|
Role: "admin",
|
|
}
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
// Simulate middleware
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Set("role", user.Role)
|
|
c.Next()
|
|
})
|
|
r.GET("/me", handler.Me)
|
|
|
|
req := httptest.NewRequest("GET", "/me", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
assert.Equal(t, float64(user.ID), resp["user_id"])
|
|
assert.Equal(t, "admin", resp["role"])
|
|
assert.Equal(t, "Me User", resp["name"])
|
|
assert.Equal(t, "me@example.com", resp["email"])
|
|
}
|
|
|
|
func TestAuthHandler_Me_NotFound(t *testing.T) {
|
|
handler, _ := setupAuthHandler(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", uint(999)) // Non-existent ID
|
|
c.Next()
|
|
})
|
|
r.GET("/me", handler.Me)
|
|
|
|
req := httptest.NewRequest("GET", "/me", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_ChangePassword(t *testing.T) {
|
|
handler, db := setupAuthHandler(t)
|
|
|
|
// Create user
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "change@example.com",
|
|
Name: "Change User",
|
|
}
|
|
user.SetPassword("oldpassword")
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
// Simulate middleware
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Next()
|
|
})
|
|
r.POST("/change-password", handler.ChangePassword)
|
|
|
|
body := map[string]string{
|
|
"old_password": "oldpassword",
|
|
"new_password": "newpassword123",
|
|
}
|
|
jsonBody, _ := json.Marshal(body)
|
|
req := httptest.NewRequest("POST", "/change-password", bytes.NewBuffer(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(t, w.Body.String(), "Password updated successfully")
|
|
|
|
// Verify password changed
|
|
var updatedUser models.User
|
|
db.First(&updatedUser, user.ID)
|
|
assert.True(t, updatedUser.CheckPassword("newpassword123"))
|
|
}
|
|
|
|
func TestAuthHandler_ChangePassword_WrongOld(t *testing.T) {
|
|
handler, db := setupAuthHandler(t)
|
|
user := &models.User{UUID: uuid.NewString(), Email: "wrong@example.com"}
|
|
user.SetPassword("correct")
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Next()
|
|
})
|
|
r.POST("/change-password", handler.ChangePassword)
|
|
|
|
body := map[string]string{
|
|
"old_password": "wrong",
|
|
"new_password": "newpassword",
|
|
}
|
|
jsonBody, _ := json.Marshal(body)
|
|
req := httptest.NewRequest("POST", "/change-password", bytes.NewBuffer(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_ChangePassword_Errors(t *testing.T) {
|
|
handler, _ := setupAuthHandler(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.POST("/change-password", handler.ChangePassword)
|
|
|
|
// 1. BindJSON error (checked before auth)
|
|
req, _ := http.NewRequest("POST", "/change-password", bytes.NewBufferString("invalid json"))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
|
|
// 2. Unauthorized (valid JSON but no user in context)
|
|
body := map[string]string{
|
|
"old_password": "oldpassword",
|
|
"new_password": "newpassword123",
|
|
}
|
|
jsonBody, _ := json.Marshal(body)
|
|
req, _ = http.NewRequest("POST", "/change-password", bytes.NewBuffer(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
}
|
|
|
|
// setupAuthHandlerWithDB creates an AuthHandler with DB access for forward auth tests
|
|
func setupAuthHandlerWithDB(t *testing.T) (*AuthHandler, *gorm.DB) {
|
|
dbName := "file:" + t.Name() + "?mode=memory&cache=shared"
|
|
db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
db.AutoMigrate(&models.User{}, &models.Setting{}, &models.ProxyHost{})
|
|
|
|
cfg := config.Config{JWTSecret: "test-secret"}
|
|
authService := services.NewAuthService(db, cfg)
|
|
return NewAuthHandlerWithDB(authService, db), db
|
|
}
|
|
|
|
func TestNewAuthHandlerWithDB(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
assert.NotNil(t, handler)
|
|
assert.NotNil(t, handler.db)
|
|
assert.NotNil(t, db)
|
|
}
|
|
|
|
func TestAuthHandler_Verify_NoCookie(t *testing.T) {
|
|
handler, _ := setupAuthHandlerWithDB(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/verify", handler.Verify)
|
|
|
|
req := httptest.NewRequest("GET", "/verify", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
assert.Equal(t, "/login", w.Header().Get("X-Auth-Redirect"))
|
|
}
|
|
|
|
func TestAuthHandler_Verify_InvalidToken(t *testing.T) {
|
|
handler, _ := setupAuthHandlerWithDB(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/verify", handler.Verify)
|
|
|
|
req := httptest.NewRequest("GET", "/verify", http.NoBody)
|
|
req.AddCookie(&http.Cookie{Name: "auth_token", Value: "invalid-token"})
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_Verify_ValidToken(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
// Create user
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "test@example.com",
|
|
Name: "Test User",
|
|
Role: "user",
|
|
Enabled: true,
|
|
}
|
|
user.SetPassword("password123")
|
|
db.Create(user)
|
|
|
|
// Generate token
|
|
token, _ := handler.authService.GenerateToken(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/verify", handler.Verify)
|
|
|
|
req := httptest.NewRequest("GET", "/verify", http.NoBody)
|
|
req.AddCookie(&http.Cookie{Name: "auth_token", Value: token})
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Equal(t, "test@example.com", w.Header().Get("X-Forwarded-User"))
|
|
assert.Equal(t, "user", w.Header().Get("X-Forwarded-Groups"))
|
|
}
|
|
|
|
func TestAuthHandler_Verify_BearerToken(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "bearer@example.com",
|
|
Name: "Bearer User",
|
|
Role: "admin",
|
|
Enabled: true,
|
|
}
|
|
user.SetPassword("password123")
|
|
db.Create(user)
|
|
|
|
token, _ := handler.authService.GenerateToken(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/verify", handler.Verify)
|
|
|
|
req := httptest.NewRequest("GET", "/verify", http.NoBody)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Equal(t, "bearer@example.com", w.Header().Get("X-Forwarded-User"))
|
|
}
|
|
|
|
func TestAuthHandler_Verify_DisabledUser(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "disabled@example.com",
|
|
Name: "Disabled User",
|
|
Role: "user",
|
|
}
|
|
user.SetPassword("password123")
|
|
db.Create(user)
|
|
// Explicitly disable after creation to bypass GORM's default:true behavior
|
|
db.Model(user).Update("enabled", false)
|
|
|
|
token, _ := handler.authService.GenerateToken(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/verify", handler.Verify)
|
|
|
|
req := httptest.NewRequest("GET", "/verify", http.NoBody)
|
|
req.AddCookie(&http.Cookie{Name: "auth_token", Value: token})
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_Verify_ForwardAuthDenied(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
// Create proxy host with forward auth enabled
|
|
proxyHost := &models.ProxyHost{
|
|
UUID: uuid.NewString(),
|
|
Name: "Protected App",
|
|
DomainNames: "app.example.com",
|
|
ForwardAuthEnabled: true,
|
|
Enabled: true,
|
|
}
|
|
db.Create(proxyHost)
|
|
|
|
// Create user with deny_all permission
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "denied@example.com",
|
|
Name: "Denied User",
|
|
Role: "user",
|
|
Enabled: true,
|
|
PermissionMode: models.PermissionModeDenyAll,
|
|
}
|
|
user.SetPassword("password123")
|
|
db.Create(user)
|
|
|
|
token, _ := handler.authService.GenerateToken(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/verify", handler.Verify)
|
|
|
|
req := httptest.NewRequest("GET", "/verify", http.NoBody)
|
|
req.AddCookie(&http.Cookie{Name: "auth_token", Value: token})
|
|
req.Header.Set("X-Forwarded-Host", "app.example.com")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_VerifyStatus_NotAuthenticated(t *testing.T) {
|
|
handler, _ := setupAuthHandlerWithDB(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/status", handler.VerifyStatus)
|
|
|
|
req := httptest.NewRequest("GET", "/status", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
assert.Equal(t, false, resp["authenticated"])
|
|
}
|
|
|
|
func TestAuthHandler_VerifyStatus_InvalidToken(t *testing.T) {
|
|
handler, _ := setupAuthHandlerWithDB(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/status", handler.VerifyStatus)
|
|
|
|
req := httptest.NewRequest("GET", "/status", http.NoBody)
|
|
req.AddCookie(&http.Cookie{Name: "auth_token", Value: "invalid"})
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
assert.Equal(t, false, resp["authenticated"])
|
|
}
|
|
|
|
func TestAuthHandler_VerifyStatus_Authenticated(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "status@example.com",
|
|
Name: "Status User",
|
|
Role: "user",
|
|
Enabled: true,
|
|
}
|
|
user.SetPassword("password123")
|
|
db.Create(user)
|
|
|
|
token, _ := handler.authService.GenerateToken(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/status", handler.VerifyStatus)
|
|
|
|
req := httptest.NewRequest("GET", "/status", http.NoBody)
|
|
req.AddCookie(&http.Cookie{Name: "auth_token", Value: token})
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
assert.Equal(t, true, resp["authenticated"])
|
|
userObj := resp["user"].(map[string]interface{})
|
|
assert.Equal(t, "status@example.com", userObj["email"])
|
|
}
|
|
|
|
func TestAuthHandler_VerifyStatus_DisabledUser(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "disabled2@example.com",
|
|
Name: "Disabled User 2",
|
|
Role: "user",
|
|
}
|
|
user.SetPassword("password123")
|
|
db.Create(user)
|
|
// Explicitly disable after creation to bypass GORM's default:true behavior
|
|
db.Model(user).Update("enabled", false)
|
|
|
|
token, _ := handler.authService.GenerateToken(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/status", handler.VerifyStatus)
|
|
|
|
req := httptest.NewRequest("GET", "/status", http.NoBody)
|
|
req.AddCookie(&http.Cookie{Name: "auth_token", Value: token})
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
assert.Equal(t, false, resp["authenticated"])
|
|
}
|
|
|
|
func TestAuthHandler_GetAccessibleHosts_Unauthorized(t *testing.T) {
|
|
handler, _ := setupAuthHandlerWithDB(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/hosts", handler.GetAccessibleHosts)
|
|
|
|
req := httptest.NewRequest("GET", "/hosts", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_GetAccessibleHosts_AllowAll(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
// Create proxy hosts
|
|
host1 := &models.ProxyHost{UUID: uuid.NewString(), Name: "Host 1", DomainNames: "host1.example.com", Enabled: true}
|
|
host2 := &models.ProxyHost{UUID: uuid.NewString(), Name: "Host 2", DomainNames: "host2.example.com", Enabled: true}
|
|
db.Create(host1)
|
|
db.Create(host2)
|
|
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "allowall@example.com",
|
|
Name: "Allow All User",
|
|
Role: "user",
|
|
Enabled: true,
|
|
PermissionMode: models.PermissionModeAllowAll,
|
|
}
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Next()
|
|
})
|
|
r.GET("/hosts", handler.GetAccessibleHosts)
|
|
|
|
req := httptest.NewRequest("GET", "/hosts", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
hosts := resp["hosts"].([]interface{})
|
|
assert.Len(t, hosts, 2)
|
|
}
|
|
|
|
func TestAuthHandler_GetAccessibleHosts_DenyAll(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
// Create proxy hosts
|
|
host1 := &models.ProxyHost{UUID: uuid.NewString(), Name: "Host 1", DomainNames: "host1.example.com", Enabled: true}
|
|
db.Create(host1)
|
|
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "denyall@example.com",
|
|
Name: "Deny All User",
|
|
Role: "user",
|
|
Enabled: true,
|
|
PermissionMode: models.PermissionModeDenyAll,
|
|
}
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Next()
|
|
})
|
|
r.GET("/hosts", handler.GetAccessibleHosts)
|
|
|
|
req := httptest.NewRequest("GET", "/hosts", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
hosts := resp["hosts"].([]interface{})
|
|
assert.Len(t, hosts, 0)
|
|
}
|
|
|
|
func TestAuthHandler_GetAccessibleHosts_PermittedHosts(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
// Create proxy hosts
|
|
host1 := &models.ProxyHost{UUID: uuid.NewString(), Name: "Host 1", DomainNames: "host1.example.com", Enabled: true}
|
|
host2 := &models.ProxyHost{UUID: uuid.NewString(), Name: "Host 2", DomainNames: "host2.example.com", Enabled: true}
|
|
db.Create(host1)
|
|
db.Create(host2)
|
|
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "permitted@example.com",
|
|
Name: "Permitted User",
|
|
Role: "user",
|
|
Enabled: true,
|
|
PermissionMode: models.PermissionModeDenyAll,
|
|
PermittedHosts: []models.ProxyHost{*host1}, // Only host1
|
|
}
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Next()
|
|
})
|
|
r.GET("/hosts", handler.GetAccessibleHosts)
|
|
|
|
req := httptest.NewRequest("GET", "/hosts", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
hosts := resp["hosts"].([]interface{})
|
|
assert.Len(t, hosts, 1)
|
|
}
|
|
|
|
func TestAuthHandler_GetAccessibleHosts_UserNotFound(t *testing.T) {
|
|
handler, _ := setupAuthHandlerWithDB(t)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", uint(99999))
|
|
c.Next()
|
|
})
|
|
r.GET("/hosts", handler.GetAccessibleHosts)
|
|
|
|
req := httptest.NewRequest("GET", "/hosts", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_CheckHostAccess_Unauthorized(t *testing.T) {
|
|
handler, _ := setupAuthHandlerWithDB(t)
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.GET("/hosts/:hostId/access", handler.CheckHostAccess)
|
|
|
|
req := httptest.NewRequest("GET", "/hosts/1/access", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_CheckHostAccess_InvalidHostID(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
user := &models.User{UUID: uuid.NewString(), Email: "check@example.com", Enabled: true}
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Next()
|
|
})
|
|
r.GET("/hosts/:hostId/access", handler.CheckHostAccess)
|
|
|
|
req := httptest.NewRequest("GET", "/hosts/invalid/access", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestAuthHandler_CheckHostAccess_Allowed(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
host := &models.ProxyHost{UUID: uuid.NewString(), Name: "Test Host", DomainNames: "test.example.com", Enabled: true}
|
|
db.Create(host)
|
|
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "checkallowed@example.com",
|
|
Enabled: true,
|
|
PermissionMode: models.PermissionModeAllowAll,
|
|
}
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Next()
|
|
})
|
|
r.GET("/hosts/:hostId/access", handler.CheckHostAccess)
|
|
|
|
req := httptest.NewRequest("GET", "/hosts/1/access", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
assert.Equal(t, true, resp["can_access"])
|
|
}
|
|
|
|
func TestAuthHandler_CheckHostAccess_Denied(t *testing.T) {
|
|
handler, db := setupAuthHandlerWithDB(t)
|
|
|
|
host := &models.ProxyHost{UUID: uuid.NewString(), Name: "Protected Host", DomainNames: "protected.example.com", Enabled: true}
|
|
db.Create(host)
|
|
|
|
user := &models.User{
|
|
UUID: uuid.NewString(),
|
|
Email: "checkdenied@example.com",
|
|
Enabled: true,
|
|
PermissionMode: models.PermissionModeDenyAll,
|
|
}
|
|
db.Create(user)
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Next()
|
|
})
|
|
r.GET("/hosts/:hostId/access", handler.CheckHostAccess)
|
|
|
|
req := httptest.NewRequest("GET", "/hosts/1/access", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
assert.Equal(t, false, resp["can_access"])
|
|
}
|