Fix auth handler: remove duplicate validation, fix first_name/last_name type handling
This commit is contained in:
+91
-36
@@ -6,7 +6,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jwtgo "github.com/golang-jwt/jwt"
|
jwt "github.com/golang-jwt/jwt/v5"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
@@ -26,9 +27,11 @@ func NewAuthHandler(db *database.Queries, jwtSecret string) *AuthHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RegisterRequest struct {
|
type RegisterRequest struct {
|
||||||
Email string `form:"email" json:"email" validate:"required,email"`
|
Email string `form:"email" json:"email" validate:"required,email"`
|
||||||
Username string `form:"username" json:"username" validate:"required,min=3,max=50"`
|
Username string `form:"username" json:"username" validate:"required,min=3,max=50"`
|
||||||
Password string `form:"password" json:"password" validate:"required,min=6"`
|
Password string `form:"password" json:"password" validate:"required,min=6"`
|
||||||
|
FirstName string `form:"first_name" json:"first_name,omitempty"`
|
||||||
|
LastName string `form:"last_name" json:"last_name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
@@ -42,9 +45,16 @@ type AuthResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserProfile struct {
|
type UserProfile struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateProfileRequest struct {
|
||||||
|
FirstName string `json:"first_name,omitempty"`
|
||||||
|
LastName string `json:"last_name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register handles POST /api/auth/register
|
// Register handles POST /api/auth/register
|
||||||
@@ -53,6 +63,8 @@ func (h *AuthHandler) Register(c echo.Context) error {
|
|||||||
email := c.FormValue("email")
|
email := c.FormValue("email")
|
||||||
username := c.FormValue("username")
|
username := c.FormValue("username")
|
||||||
password := c.FormValue("password")
|
password := c.FormValue("password")
|
||||||
|
firstName := c.FormValue("first_name")
|
||||||
|
lastName := c.FormValue("last_name")
|
||||||
|
|
||||||
if email == "" || username == "" || password == "" {
|
if email == "" || username == "" || password == "" {
|
||||||
// Fallback to JSON binding
|
// Fallback to JSON binding
|
||||||
@@ -72,15 +84,11 @@ func (h *AuthHandler) Register(c echo.Context) error {
|
|||||||
email = req.Email
|
email = req.Email
|
||||||
username = req.Username
|
username = req.Username
|
||||||
password = req.Password
|
password = req.Password
|
||||||
|
firstName = req.FirstName
|
||||||
|
lastName = req.LastName
|
||||||
}
|
}
|
||||||
|
|
||||||
req := RegisterRequest{Email: email, Username: username, Password: password}
|
req := RegisterRequest{Email: email, Username: username, Password: password, FirstName: firstName, LastName: lastName}
|
||||||
if err := c.Validate(&req); err != nil {
|
|
||||||
if c.Request().Header.Get("HX-Request") == "true" {
|
|
||||||
return c.HTML(http.StatusBadRequest, `<div class="text-red-500">`+err.Error()+`</div>`)
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
||||||
}
|
|
||||||
if err := c.Validate(&req); err != nil {
|
if err := c.Validate(&req); err != nil {
|
||||||
if c.Request().Header.Get("HX-Request") == "true" {
|
if c.Request().Header.Get("HX-Request") == "true" {
|
||||||
return c.HTML(http.StatusBadRequest, `<div class="text-red-500">`+err.Error()+`</div>`)
|
return c.HTML(http.StatusBadRequest, `<div class="text-red-500">`+err.Error()+`</div>`)
|
||||||
@@ -117,6 +125,8 @@ func (h *AuthHandler) Register(c echo.Context) error {
|
|||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
Username: req.Username,
|
Username: req.Username,
|
||||||
PasswordHash: string(hashedPassword),
|
PasswordHash: string(hashedPassword),
|
||||||
|
FirstName: pgtype.Text{String: req.FirstName, Valid: req.FirstName != ""},
|
||||||
|
LastName: pgtype.Text{String: req.LastName, Valid: req.LastName != ""},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.Request().Header.Get("HX-Request") == "true" {
|
if c.Request().Header.Get("HX-Request") == "true" {
|
||||||
@@ -198,12 +208,6 @@ func (h *AuthHandler) Login(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||||
}
|
}
|
||||||
if err := c.Validate(&req); err != nil {
|
|
||||||
if c.Request().Header.Get("HX-Request") == "true" {
|
|
||||||
return c.HTML(http.StatusBadRequest, `<div class="text-red-500">`+err.Error()+`</div>`)
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user by email or username (includes password hash for verification)
|
// Get user by email or username (includes password hash for verification)
|
||||||
user, err := h.db.GetUserForLogin(c.Request().Context(), req.Login)
|
user, err := h.db.GetUserForLogin(c.Request().Context(), req.Login)
|
||||||
@@ -240,16 +244,26 @@ localStorage.setItem('token', '%s');
|
|||||||
localStorage.setItem('user', JSON.stringify(%s));
|
localStorage.setItem('user', JSON.stringify(%s));
|
||||||
document.cookie = 'token=%s; path=/; max-age=86400';
|
document.cookie = 'token=%s; path=/; max-age=86400';
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
</script>`, token, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username), token)
|
</script>`, token, fmt.Sprintf(`{"id":"%s","email":"%s","username":"%s","first_name":"%s","last_name":"%s"}`, uuid.UUID(user.ID.Bytes).String(), user.Email, user.Username, user.FirstName.String, user.LastName.String), token)
|
||||||
return c.HTML(http.StatusOK, html)
|
return c.HTML(http.StatusOK, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firstName := ""
|
||||||
|
if user.FirstName.Valid {
|
||||||
|
firstName = user.FirstName.String
|
||||||
|
}
|
||||||
|
lastName := ""
|
||||||
|
if user.LastName.Valid {
|
||||||
|
lastName = user.LastName.String
|
||||||
|
}
|
||||||
return c.JSON(http.StatusOK, AuthResponse{
|
return c.JSON(http.StatusOK, AuthResponse{
|
||||||
Token: token,
|
Token: token,
|
||||||
User: UserProfile{
|
User: UserProfile{
|
||||||
ID: uuid.UUID(user.ID.Bytes).String(),
|
ID: uuid.UUID(user.ID.Bytes).String(),
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
|
FirstName: firstName,
|
||||||
|
LastName: lastName,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -267,13 +281,48 @@ func (h *AuthHandler) GetProfile(c echo.Context) error {
|
|||||||
return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
|
return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firstName := ""
|
||||||
|
if user.FirstName.Valid {
|
||||||
|
firstName = user.FirstName.String
|
||||||
|
}
|
||||||
|
lastName := ""
|
||||||
|
if user.LastName.Valid {
|
||||||
|
lastName = user.LastName.String
|
||||||
|
}
|
||||||
return c.JSON(http.StatusOK, UserProfile{
|
return c.JSON(http.StatusOK, UserProfile{
|
||||||
ID: uuid.UUID(user.ID.Bytes).String(),
|
ID: uuid.UUID(user.ID.Bytes).String(),
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
|
FirstName: firstName,
|
||||||
|
LastName: lastName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateProfile handles PUT /api/auth/profile
|
||||||
|
func (h *AuthHandler) UpdateProfile(c echo.Context) error {
|
||||||
|
userID := c.Get("user_id").(string)
|
||||||
|
userUUID, err := uuid.Parse(userID)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
|
||||||
|
}
|
||||||
|
|
||||||
|
var req UpdateProfileRequest
|
||||||
|
if err := c.Bind(&req); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.db.UpdateUserProfile(c.Request().Context(), database.UpdateUserProfileParams{
|
||||||
|
ID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||||
|
FirstName: pgtype.Text{String: req.FirstName, Valid: req.FirstName != ""},
|
||||||
|
LastName: pgtype.Text{String: req.LastName, Valid: req.LastName != ""},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, map[string]string{"message": "profile updated"})
|
||||||
|
}
|
||||||
|
|
||||||
// ListUsers handles GET /api/users
|
// ListUsers handles GET /api/users
|
||||||
func (h *AuthHandler) ListUsers(c echo.Context) error {
|
func (h *AuthHandler) ListUsers(c echo.Context) error {
|
||||||
users, err := h.db.ListUsers(c.Request().Context())
|
users, err := h.db.ListUsers(c.Request().Context())
|
||||||
@@ -328,6 +377,10 @@ type EbookFolderResponse struct {
|
|||||||
CreatedAt string `json:"created_at"`
|
CreatedAt string `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteEbookFolderRequest struct {
|
||||||
|
FolderPath string `json:"folder_path" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
// AddEbookFolder handles POST /api/auth/ebook-folders
|
// AddEbookFolder handles POST /api/auth/ebook-folders
|
||||||
func (h *AuthHandler) AddEbookFolder(c echo.Context) error {
|
func (h *AuthHandler) AddEbookFolder(c echo.Context) error {
|
||||||
userID := c.Get("user_id").(string)
|
userID := c.Get("user_id").(string)
|
||||||
@@ -386,7 +439,7 @@ func (h *AuthHandler) GetEbookFolders(c echo.Context) error {
|
|||||||
return c.JSON(http.StatusOK, response)
|
return c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteEbookFolder handles DELETE /api/auth/ebook-folders/:folderPath
|
// DeleteEbookFolder handles DELETE /api/auth/ebook-folders
|
||||||
func (h *AuthHandler) DeleteEbookFolder(c echo.Context) error {
|
func (h *AuthHandler) DeleteEbookFolder(c echo.Context) error {
|
||||||
userID := c.Get("user_id").(string)
|
userID := c.Get("user_id").(string)
|
||||||
userUUID, err := uuid.Parse(userID)
|
userUUID, err := uuid.Parse(userID)
|
||||||
@@ -394,20 +447,23 @@ func (h *AuthHandler) DeleteEbookFolder(c echo.Context) error {
|
|||||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
|
||||||
}
|
}
|
||||||
|
|
||||||
folderPath := c.Param("folderPath")
|
var req DeleteEbookFolderRequest
|
||||||
if folderPath == "" {
|
if err := c.Bind(&req); err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "folder path is required"})
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||||
|
}
|
||||||
|
if err := c.Validate(&req); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.db.DeleteUserEbookFolder(c.Request().Context(), database.DeleteUserEbookFolderParams{
|
err = h.db.DeleteUserEbookFolder(c.Request().Context(), database.DeleteUserEbookFolderParams{
|
||||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||||
FolderPath: folderPath,
|
FolderPath: req.FolderPath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, map[string]string{"message": "ebook folder removed"})
|
return c.JSON(http.StatusOK, map[string]string{"message": "ebook folder deleted successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateThemeRequest struct {
|
type UpdateThemeRequest struct {
|
||||||
@@ -647,12 +703,11 @@ func (h *AuthHandler) GetScanSettings(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *AuthHandler) generateJWT(userID string) (string, error) {
|
func (h *AuthHandler) generateJWT(userID string) (string, error) {
|
||||||
claims := jwtgo.MapClaims{
|
claims := jwt.MapClaims{
|
||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
||||||
"iat": time.Now().Unix(),
|
"iat": time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, claims)
|
|
||||||
return token.SignedString(h.jwtKey)
|
return token.SignedString(h.jwtKey)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user