118 lines
3.3 KiB
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})
|
|
}
|