auth_handler.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package handlers
  2. import (
  3. "errors"
  4. "net/http"
  5. "strings"
  6. "time"
  7. "github.com/celestia-trace/backend/config"
  8. "github.com/celestia-trace/backend/models"
  9. "github.com/gin-gonic/gin"
  10. "github.com/golang-jwt/jwt/v5"
  11. "golang.org/x/crypto/bcrypt"
  12. "gorm.io/gorm"
  13. )
  14. type AuthHandler struct {
  15. DB *gorm.DB
  16. Cfg *config.Config
  17. }
  18. func respondError(c *gin.Context, status int, code int, message string) {
  19. c.JSON(status, models.ErrorResponse{Code: code, Message: message})
  20. }
  21. func respondSuccess(c *gin.Context, data interface{}) {
  22. c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": data})
  23. }
  24. func (h *AuthHandler) generateToken(userID string) (string, error) {
  25. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
  26. "sub": userID,
  27. "exp": time.Now().Add(7 * 24 * time.Hour).Unix(),
  28. })
  29. return token.SignedString([]byte(h.Cfg.JWTSecret))
  30. }
  31. func isEmail(identifier string) bool {
  32. return strings.Contains(identifier, "@")
  33. }
  34. // Register handler
  35. func (h *AuthHandler) Register(c *gin.Context) {
  36. var req models.RegisterRequest
  37. if err := c.ShouldBindJSON(&req); err != nil {
  38. respondError(c, http.StatusBadRequest, 400, "invalid input data")
  39. return
  40. }
  41. hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10)
  42. if err != nil {
  43. respondError(c, http.StatusInternalServerError, 500, "failed to hash password")
  44. return
  45. }
  46. user := models.User{
  47. Username: req.Username,
  48. PasswordHash: string(hash),
  49. }
  50. if isEmail(req.Identifier) {
  51. user.Email = &req.Identifier
  52. } else {
  53. user.PhoneNumber = &req.Identifier
  54. }
  55. if err := h.DB.Create(&user).Error; err != nil {
  56. if strings.Contains(err.Error(), "duplicate key") || strings.Contains(err.Error(), "UNIQUE constraint") {
  57. respondError(c, http.StatusConflict, 409, "username or identifier already exists")
  58. return
  59. }
  60. respondError(c, http.StatusInternalServerError, 500, "failed to create user")
  61. return
  62. }
  63. token, err := h.generateToken(user.ID)
  64. if err != nil {
  65. respondError(c, http.StatusInternalServerError, 500, "failed to generate token")
  66. return
  67. }
  68. respondSuccess(c, models.AuthResponse{User: user, Token: token})
  69. }
  70. // Login handler
  71. func (h *AuthHandler) Login(c *gin.Context) {
  72. var req models.LoginRequest
  73. if err := c.ShouldBindJSON(&req); err != nil {
  74. respondError(c, http.StatusBadRequest, 400, "invalid input data")
  75. return
  76. }
  77. var user models.User
  78. if err := h.DB.Where("username = ? OR email = ? OR phone_number = ?", req.Identifier, req.Identifier, req.Identifier).First(&user).Error; err != nil {
  79. if errors.Is(err, gorm.ErrRecordNotFound) {
  80. respondError(c, http.StatusUnauthorized, 401, "invalid credentials")
  81. return
  82. }
  83. respondError(c, http.StatusInternalServerError, 500, "database error")
  84. return
  85. }
  86. if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil {
  87. respondError(c, http.StatusUnauthorized, 401, "invalid credentials")
  88. return
  89. }
  90. token, err := h.generateToken(user.ID)
  91. if err != nil {
  92. respondError(c, http.StatusInternalServerError, 500, "failed to generate token")
  93. return
  94. }
  95. respondSuccess(c, models.AuthResponse{User: user, Token: token})
  96. }
  97. // Logout handler
  98. func (h *AuthHandler) Logout(c *gin.Context) {
  99. // Client side discards the token
  100. respondSuccess(c, nil)
  101. }
  102. // GetProfile handler
  103. func (h *AuthHandler) GetProfile(c *gin.Context) {
  104. userID := c.GetString("userID")
  105. var user models.User
  106. if err := h.DB.First(&user, "id = ?", userID).Error; err != nil {
  107. respondError(c, http.StatusNotFound, 404, "user not found")
  108. return
  109. }
  110. respondSuccess(c, user)
  111. }
  112. // UpdateProfile handler
  113. func (h *AuthHandler) UpdateProfile(c *gin.Context) {
  114. userID := c.GetString("userID")
  115. var req models.UpdateProfileRequest
  116. if err := c.ShouldBindJSON(&req); err != nil {
  117. respondError(c, http.StatusBadRequest, 400, "invalid input data")
  118. return
  119. }
  120. var user models.User
  121. if err := h.DB.First(&user, "id = ?", userID).Error; err != nil {
  122. respondError(c, http.StatusNotFound, 404, "user not found")
  123. return
  124. }
  125. if req.Username != nil && len(*req.Username) >= 2 {
  126. user.Username = *req.Username
  127. }
  128. if req.Email != nil {
  129. user.Email = req.Email
  130. }
  131. if req.PhoneNumber != nil {
  132. user.PhoneNumber = req.PhoneNumber
  133. }
  134. if req.AvatarURL != nil {
  135. user.AvatarURL = req.AvatarURL
  136. }
  137. if err := h.DB.Save(&user).Error; err != nil {
  138. respondError(c, http.StatusInternalServerError, 500, "failed to update profile")
  139. return
  140. }
  141. respondSuccess(c, user)
  142. }
  143. // ChangePassword handler
  144. func (h *AuthHandler) ChangePassword(c *gin.Context) {
  145. userID := c.GetString("userID")
  146. var req models.ChangePasswordRequest
  147. if err := c.ShouldBindJSON(&req); err != nil {
  148. respondError(c, http.StatusBadRequest, 400, "invalid input data")
  149. return
  150. }
  151. var user models.User
  152. if err := h.DB.First(&user, "id = ?", userID).Error; err != nil {
  153. respondError(c, http.StatusNotFound, 404, "user not found")
  154. return
  155. }
  156. if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.OldPassword)); err != nil {
  157. respondError(c, http.StatusUnauthorized, 401, "invalid old password")
  158. return
  159. }
  160. hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), 10)
  161. if err != nil {
  162. respondError(c, http.StatusInternalServerError, 500, "failed to hash new password")
  163. return
  164. }
  165. user.PasswordHash = string(hash)
  166. if err := h.DB.Save(&user).Error; err != nil {
  167. respondError(c, http.StatusInternalServerError, 500, "failed to change password")
  168. return
  169. }
  170. respondSuccess(c, nil)
  171. }