Files
NewSmoop/server/internal/handlers/auth.go

118 lines
3.3 KiB
Go

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})
}