diff --git a/backend/internal/api/handlers/auth_handler.go b/backend/internal/api/handlers/auth_handler.go
index 5bb42718..841468a9 100644
--- a/backend/internal/api/handlers/auth_handler.go
+++ b/backend/internal/api/handlers/auth_handler.go
@@ -69,7 +69,19 @@ func (h *AuthHandler) Logout(c *gin.Context) {
func (h *AuthHandler) Me(c *gin.Context) {
userID, _ := c.Get("userID")
role, _ := c.Get("role")
- c.JSON(http.StatusOK, gin.H{"user_id": userID, "role": role})
+
+ u, err := h.authService.GetUserByID(userID.(uint))
+ if err != nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "user_id": userID,
+ "role": role,
+ "name": u.Name,
+ "email": u.Email,
+ })
}
type ChangePasswordRequest struct {
diff --git a/backend/internal/api/handlers/auth_handler_test.go b/backend/internal/api/handlers/auth_handler_test.go
index d27a6229..90305cbf 100644
--- a/backend/internal/api/handlers/auth_handler_test.go
+++ b/backend/internal/api/handlers/auth_handler_test.go
@@ -124,14 +124,23 @@ func TestAuthHandler_Logout(t *testing.T) {
}
func TestAuthHandler_Me(t *testing.T) {
- handler, _ := setupAuthHandler(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", uint(1))
- c.Set("role", "admin")
+ c.Set("userID", user.ID)
+ c.Set("role", user.Role)
c.Next()
})
r.GET("/me", handler.Me)
@@ -143,8 +152,10 @@ func TestAuthHandler_Me(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
- assert.Equal(t, float64(1), resp["user_id"])
+ 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_ChangePassword(t *testing.T) {
diff --git a/backend/internal/api/handlers/user_handler.go b/backend/internal/api/handlers/user_handler.go
index d5761277..fa2d0f68 100644
--- a/backend/internal/api/handlers/user_handler.go
+++ b/backend/internal/api/handlers/user_handler.go
@@ -2,6 +2,7 @@ package handlers
import (
"net/http"
+ "strings"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
@@ -70,7 +71,7 @@ func (h *UserHandler) Setup(c *gin.Context) {
user := models.User{
UUID: uuid.New().String(),
Name: req.Name,
- Email: req.Email,
+ Email: strings.ToLower(req.Email),
Role: "admin",
Enabled: true,
}
@@ -176,6 +177,7 @@ func (h *UserHandler) UpdateProfile(c *gin.Context) {
}
// Check if email is already taken by another user
+ req.Email = strings.ToLower(req.Email)
var count int64
if err := h.DB.Model(&models.User{}).Where("email = ? AND id != ?", req.Email, userID).Count(&count).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check email availability"})
diff --git a/backend/internal/api/handlers/user_integration_test.go b/backend/internal/api/handlers/user_integration_test.go
new file mode 100644
index 00000000..745ab30f
--- /dev/null
+++ b/backend/internal/api/handlers/user_integration_test.go
@@ -0,0 +1,117 @@
+package handlers
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/config"
+ "github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/models"
+ "github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/services"
+ "github.com/gin-gonic/gin"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "gorm.io/driver/sqlite"
+ "gorm.io/gorm"
+)
+
+func TestUserLoginAfterEmailChange(t *testing.T) {
+ // Setup 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{})
+
+ // Setup Services and Handlers
+ cfg := config.Config{}
+ authService := services.NewAuthService(db, cfg)
+ authHandler := NewAuthHandler(authService)
+ userHandler := NewUserHandler(db)
+
+ // Setup Router
+ gin.SetMode(gin.TestMode)
+ r := gin.New()
+
+ // Register Routes
+ r.POST("/auth/login", authHandler.Login)
+
+ // Mock Auth Middleware for UpdateProfile
+ r.POST("/user/profile", func(c *gin.Context) {
+ // Simulate authenticated user
+ var user models.User
+ db.First(&user)
+ c.Set("userID", user.ID)
+ c.Set("role", user.Role)
+ c.Next()
+ }, userHandler.UpdateProfile)
+
+ // 1. Create Initial User
+ initialEmail := "initial@example.com"
+ password := "password123"
+ user, err := authService.Register(initialEmail, password, "Test User")
+ require.NoError(t, err)
+ require.NotNil(t, user)
+
+ // 2. Login with Initial Credentials (Verify it works)
+ loginBody := map[string]string{
+ "email": initialEmail,
+ "password": password,
+ }
+ jsonBody, _ := json.Marshal(loginBody)
+ req, _ := http.NewRequest("POST", "/auth/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, "Initial login should succeed")
+
+ // 3. Update Profile (Change Email)
+ newEmail := "updated@example.com"
+ updateBody := map[string]string{
+ "name": "Test User Updated",
+ "email": newEmail,
+ }
+ jsonUpdate, _ := json.Marshal(updateBody)
+ req, _ = http.NewRequest("POST", "/user/profile", bytes.NewBuffer(jsonUpdate))
+ req.Header.Set("Content-Type", "application/json")
+ w = httptest.NewRecorder()
+ r.ServeHTTP(w, req)
+ assert.Equal(t, http.StatusOK, w.Code, "Update profile should succeed")
+
+ // Verify DB update
+ var updatedUser models.User
+ db.First(&updatedUser, user.ID)
+ assert.Equal(t, newEmail, updatedUser.Email, "Email should be updated in DB")
+
+ // 4. Login with New Email
+ loginBodyNew := map[string]string{
+ "email": newEmail,
+ "password": password,
+ }
+ jsonBodyNew, _ := json.Marshal(loginBodyNew)
+ req, _ = http.NewRequest("POST", "/auth/login", bytes.NewBuffer(jsonBodyNew))
+ req.Header.Set("Content-Type", "application/json")
+ w = httptest.NewRecorder()
+ r.ServeHTTP(w, req)
+
+ // This is where the user says it fails
+ assert.Equal(t, http.StatusOK, w.Code, "Login with new email should succeed")
+ if w.Code != http.StatusOK {
+ t.Logf("Response Body: %s", w.Body.String())
+ }
+
+ // 5. Login with New Email (Different Case)
+ loginBodyCase := map[string]string{
+ "email": "Updated@Example.com", // Different case
+ "password": password,
+ }
+ jsonBodyCase, _ := json.Marshal(loginBodyCase)
+ req, _ = http.NewRequest("POST", "/auth/login", bytes.NewBuffer(jsonBodyCase))
+ req.Header.Set("Content-Type", "application/json")
+ w = httptest.NewRecorder()
+ r.ServeHTTP(w, req)
+
+ // If this fails, it confirms case sensitivity issue
+ assert.Equal(t, http.StatusOK, w.Code, "Login with mixed case email should succeed")
+}
diff --git a/backend/internal/services/auth_service.go b/backend/internal/services/auth_service.go
index e07aeb5e..cde2c3a8 100644
--- a/backend/internal/services/auth_service.go
+++ b/backend/internal/services/auth_service.go
@@ -2,6 +2,7 @@ package services
import (
"errors"
+ "strings"
"time"
"github.com/Wikid82/CaddyProxyManagerPlus/backend/internal/config"
@@ -27,6 +28,7 @@ type Claims struct {
}
func (s *AuthService) Register(email, password, name string) (*models.User, error) {
+ email = strings.ToLower(email)
var count int64
s.db.Model(&models.User{}).Count(&count)
@@ -57,6 +59,7 @@ func (s *AuthService) Register(email, password, name string) (*models.User, erro
}
func (s *AuthService) Login(email, password string) (string, error) {
+ email = strings.ToLower(email)
var user models.User
if err := s.db.Where("email = ?", email).First(&user).Error; err != nil {
return "", errors.New("invalid credentials")
@@ -138,3 +141,11 @@ func (s *AuthService) ValidateToken(tokenString string) (*Claims, error) {
return claims, nil
}
+
+func (s *AuthService) GetUserByID(id uint) (*models.User, error) {
+ var user models.User
+ if err := s.db.First(&user, id).Error; err != nil {
+ return nil, err
+ }
+ return &user, nil
+}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 56c44a07..c595e9a0 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -11,7 +11,7 @@ import ImportCaddy from './pages/ImportCaddy'
import Certificates from './pages/Certificates'
import SettingsLayout from './pages/SettingsLayout'
import SystemSettings from './pages/SystemSettings'
-import Security from './pages/Security'
+import Account from './pages/Account'
import Backups from './pages/Backups'
import Logs from './pages/Logs'
import Login from './pages/Login'
@@ -41,14 +41,15 @@ export default function App() {
{/* Settings Routes */}