diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go
index 589a9ce..dba0f5a 100644
--- a/internal/handlers/auth.go
+++ b/internal/handlers/auth.go
@@ -70,7 +70,6 @@ type UpdateProfileRequest struct {
// Register handles POST /api/auth/register
func (h *AuthHandler) Register(c echo.Context) error {
- // Try form data first (HTMX), then JSON (Bruno)
email := c.FormValue("email")
username := c.FormValue("username")
password := c.FormValue("password")
@@ -79,7 +78,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
role := c.FormValue("role")
if email == "" || username == "" || password == "" {
- // Fallback to JSON binding
req := RegisterRequest{}
if err := c.Bind(&req); err != nil {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -109,7 +107,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
- // Additional validation: Trim whitespace from username
req.Username = strings.TrimSpace(req.Username)
if req.Username == "" {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -118,12 +115,10 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "username cannot be empty or whitespace"})
}
- // Normalize role to lowercase
if req.Role != "" {
req.Role = strings.ToLower(req.Role)
}
- // Check if user already exists
if _, err := h.db.GetUserByEmail(c.Request().Context(), req.Email); err == nil {
if c.Request().Header.Get("HX-Request") == "true" {
return c.HTML(http.StatusConflict, `
Email already exists
`)
@@ -138,7 +133,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusConflict, map[string]string{"error": "username already exists"})
}
- // Check if this is the first user - if so, make them admin regardless of request
users, err := h.db.ListUsers(c.Request().Context())
if err != nil {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -147,7 +141,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to check existing users: " + err.Error()})
}
- // Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -156,7 +149,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to hash password"})
}
- // Check if any admin users already exist
adminExists := false
for _, u := range users {
if u.Role == "admin" {
@@ -165,17 +157,15 @@ func (h *AuthHandler) Register(c echo.Context) error {
}
}
- // Set role - first user is always admin, otherwise validate requested role based on existing admins
var userRole string
if len(users) == 0 {
- userRole = "admin" // First user is always admin
+ userRole = "admin"
} else {
userRole = req.Role
if userRole == "" {
- userRole = "user" // Default to regular user if not specified
+ userRole = "user"
}
- // Validate role for subsequent users
if userRole != "user" && userRole != "admin" {
if c.Request().Header.Get("HX-Request") == "true" {
return c.HTML(http.StatusBadRequest, `Invalid role. Must be 'user' or 'admin'
`)
@@ -183,12 +173,9 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid role. must be 'user' or 'admin'"})
}
- // Role-based restrictions: only admins can create admin users if any admin already exists
if userRole == "admin" && adminExists {
- // Check if current user is admin (requires authentication)
user, ok := c.Get("user").(database.Users)
if !ok || user.Role != "admin" {
- // Not authenticated or not admin - cannot create admin user if admins exist
if c.Request().Header.Get("HX-Request") == "true" {
return c.HTML(http.StatusForbidden, `Only existing administrators can create admin accounts
`)
}
@@ -197,14 +184,13 @@ func (h *AuthHandler) Register(c echo.Context) error {
}
}
- // Create user
user, err := h.db.CreateUser(c.Request().Context(), database.CreateUserParams{
Email: req.Email,
Username: req.Username,
PasswordHash: string(hashedPassword),
FirstName: pgtype.Text{String: req.FirstName, Valid: req.FirstName != ""},
LastName: pgtype.Text{String: req.LastName, Valid: req.LastName != ""},
- Theme: pgtype.Text{String: "tokyo-night", Valid: true}, // default theme
+ Theme: pgtype.Text{String: "tokyo-night", Valid: true},
Role: userRole,
})
if err != nil {
@@ -214,7 +200,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
- // Generate JWT with user details
accessToken, err := h.generateJWTWithAllClaims(
uuid.UUID(user.ID.Bytes).String(),
user.Role,
@@ -228,7 +213,6 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate token"})
}
- // Create refresh token
_, refreshToken, err := h.CreateRefreshToken(uuid.UUID(user.ID.Bytes))
if err != nil {
if c.Request().Header.Get("HX-Request") == "true" {
@@ -237,9 +221,7 @@ func (h *AuthHandler) Register(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate refresh token"})
}
- // Check if request is from HTMX
if c.Request().Header.Get("HX-Request") == "true" {
- // Return HTML with script to set token and redirect
html := fmt.Sprintf(`Registration successful! Redirecting...