package handlers import ( "log" "net/http" "github.com/gin-gonic/gin" "gorm.io/gorm" "smoop-api/internal/crypto" "smoop-api/internal/dto" "smoop-api/internal/models" ) type AuthHandler struct { db *gorm.DB jwtMgr *crypto.JWTManager } func NewAuthHandler(db *gorm.DB, jwt *crypto.JWTManager) *AuthHandler { return &AuthHandler{db: db, jwtMgr: jwt} } func (h *AuthHandler) SignUp(c *gin.Context) { var req dto.AuthDto if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } hash, err := crypto.Hash(req.Password, crypto.DefaultArgon2) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "hash failed"}) return } u := models.User{Username: req.Username, Password: hash, Role: models.RoleUser} if err := h.db.Create(&u).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "username exists"}) return } tok, _ := h.jwtMgr.Generate(u.ID, u.Username, string(u.Role)) c.JSON(http.StatusCreated, dto.AccessTokenDto{AccessToken: tok}) } func (h *AuthHandler) SignIn(c *gin.Context) { var req dto.AuthDto if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var u models.User if err := h.db.Where("username = ?", req.Username).First(&u).Error; err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials - login"}) return } ok, verr := crypto.Verify(req.Password, u.Password) if verr != nil { log.Printf("verify error: %v", verr) // keep log-only in prod } if !ok { c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials - password"}) return } tok, _ := h.jwtMgr.Generate(u.ID, u.Username, string(u.Role)) c.JSON(http.StatusCreated, dto.AccessTokenDto{AccessToken: tok}) } func (h *AuthHandler) ChangePassword(c *gin.Context) { var req dto.ChangePasswordDto if err := c.ShouldBindJSON(&req); err != nil || req.NewPassword == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) return } claims := MustClaims(c) currentUID := ClaimUserID(claims) isAdmin := ClaimRole(claims) == "admin" targetID := currentUID if req.UserID != 0 { if !isAdmin && req.UserID != currentUID { c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"}) return } targetID = req.UserID } var u models.User if err := h.db.First(&u, targetID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } if !isAdmin { ok, _ := crypto.Verify(req.OldPassword, u.Password) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid old password"}) return } } hash, _ := crypto.Hash(req.NewPassword, crypto.DefaultArgon2) u.Password = hash if err := h.db.Save(&u).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"}) return } c.Status(http.StatusCreated) } func (h *AuthHandler) CheckToken(c *gin.Context) { var req dto.AccessTokenDto if err := c.ShouldBindJSON(&req); err != nil || req.AccessToken == "" { c.JSON(http.StatusBadRequest, gin.H{"isValid": false}) return } _, err := h.jwtMgr.Parse(req.AccessToken) c.JSON(http.StatusCreated, gin.H{"isValid": err == nil}) }