first commit
This commit is contained in:
615
app/controllers/AuthControllers.go
Normal file
615
app/controllers/AuthControllers.go
Normal file
@@ -0,0 +1,615 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
database "goGin/app/database/config"
|
||||
"goGin/app/database/models"
|
||||
"goGin/app/middlewares"
|
||||
"goGin/app/services"
|
||||
configs "goGin/config"
|
||||
utils "goGin/pkg/utis"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/github"
|
||||
"golang.org/x/oauth2/google"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AuthResponse
|
||||
type AuthResponse struct {
|
||||
User UserResponse `json:"user"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// RegisterPayload
|
||||
type RegisterPayload struct {
|
||||
UserName string `json:"username" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
}
|
||||
|
||||
// LoginPayload
|
||||
type LoginPayload struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// RefreshPayload
|
||||
type RefreshPayload struct {
|
||||
RefreshToken string `json:"refresh_token" binding:"required"`
|
||||
}
|
||||
|
||||
// Helper to generate secure token for email verification
|
||||
func generateSecureToken() string {
|
||||
b := make([]byte, 32)
|
||||
rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// Register godoc
|
||||
// @Summary Register a new user
|
||||
// @Description Register a new user. Sends verification email. Does NOT return tokens.
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param register body RegisterPayload true "Register payload"
|
||||
// @Success 201 {object} controllers.AuthResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/auth/register [post]
|
||||
func Register(c *gin.Context) {
|
||||
if database.DB == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
||||
return
|
||||
}
|
||||
|
||||
var payload RegisterPayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Check existing email
|
||||
var existing models.User
|
||||
if err := database.DB.Where("email = ?", payload.Email).First(&existing).Error; err == nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "email already registered"})
|
||||
return
|
||||
}
|
||||
|
||||
hashedPwd, err := utils.HashPassword(payload.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"})
|
||||
return
|
||||
}
|
||||
|
||||
// Email Verification Token
|
||||
verificationToken := generateSecureToken()
|
||||
emailVerified := false
|
||||
|
||||
user := models.User{
|
||||
UserName: payload.UserName,
|
||||
Email: payload.Email,
|
||||
Password: hashedPwd,
|
||||
EmailVerified: &emailVerified,
|
||||
EmailVerifyToken: verificationToken,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Send Verification Email
|
||||
go func() {
|
||||
if err := utils.SendVerificationEmail(user.Email, verificationToken); err != nil {
|
||||
fmt.Printf("Failed to send verification email to %s: %v\n", user.Email, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Response
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Registration successful. Please check your email to verify your account.",
|
||||
"user": toUserResponse(user),
|
||||
})
|
||||
}
|
||||
|
||||
// VerifyEmail godoc
|
||||
// @Summary Verify email address
|
||||
// @Description Verify email using token
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param token query string true "Verification Token"
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Router /api/v1/auth/verify-email [get]
|
||||
func VerifyEmail(c *gin.Context) {
|
||||
token := c.Query("token")
|
||||
if token == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "token is required"})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := database.DB.Where("email_verify_token = ?", token).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid or expired token"})
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
verified := true
|
||||
user.EmailVerified = &verified
|
||||
user.EmailVerifiedAt = &now
|
||||
user.EmailVerifyToken = "" // Clear token
|
||||
|
||||
if err := database.DB.Save(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to verify email"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Email verified successfully"})
|
||||
}
|
||||
|
||||
// Login godoc
|
||||
// @Summary Login user
|
||||
// @Description Login with email and password, returns tokens
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param login body LoginPayload true "Login payload"
|
||||
// @Success 200 {object} controllers.AuthResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 401 {object} map[string]string
|
||||
// @Router /api/v1/auth/login [post]
|
||||
func Login(c *gin.Context) {
|
||||
if database.DB == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
||||
return
|
||||
}
|
||||
|
||||
var payload LoginPayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := database.DB.Where("email = ?", payload.Email).First(&user).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.CheckPasswordHash(payload.Password, user.Password) {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if email is verified
|
||||
if user.EmailVerified != nil && !*user.EmailVerified {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "email not verified"})
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin := false
|
||||
if user.IsAdmin != nil && *user.IsAdmin {
|
||||
isAdmin = true
|
||||
}
|
||||
|
||||
jwtService := services.NewJWTService()
|
||||
accessToken, err := jwtService.GenerateToken(user.ID, isAdmin)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate access token"})
|
||||
return
|
||||
}
|
||||
refreshToken, err := jwtService.GenerateRefreshToken(user.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate refresh token"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"user": toUserResponse(user),
|
||||
"access_token": accessToken,
|
||||
"refresh_token": refreshToken,
|
||||
})
|
||||
}
|
||||
|
||||
// Refresh godoc
|
||||
// @Summary Refresh access token
|
||||
// @Description usage: send refresh token to get new access token and refresh token
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param refresh body RefreshPayload true "Refresh token payload"
|
||||
// @Success 200 {object} map[string]string "Returns both access_token and refresh_token"
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 401 {object} map[string]string
|
||||
// @Router /api/v1/auth/refresh [post]
|
||||
func Refresh(c *gin.Context) {
|
||||
var payload RefreshPayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
jwtService := services.NewJWTService()
|
||||
claims, err := jwtService.ValidateToken(payload.RefreshToken)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid refresh token"})
|
||||
return
|
||||
}
|
||||
|
||||
if claims.TokenType != "refresh" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not a refresh token"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get User
|
||||
var userID uint
|
||||
switch v := claims.UserID.(type) {
|
||||
case float64:
|
||||
userID = uint(v)
|
||||
case uint:
|
||||
userID = v
|
||||
default:
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid user claim"})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin := false
|
||||
if user.IsAdmin != nil && *user.IsAdmin {
|
||||
isAdmin = true
|
||||
}
|
||||
|
||||
newAccessToken, err := jwtService.GenerateToken(user.ID, isAdmin)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"})
|
||||
return
|
||||
}
|
||||
|
||||
newRefreshToken, err := jwtService.GenerateRefreshToken(user.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate refresh token"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"access_token": newAccessToken,
|
||||
"refresh_token": newRefreshToken,
|
||||
})
|
||||
}
|
||||
|
||||
// Me godoc
|
||||
// @Summary Get current user (me)
|
||||
// @Description Get current authenticated user information
|
||||
// @Tags auth
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 401 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/auth/me [get]
|
||||
func Me(c *gin.Context) {
|
||||
claims, ok := middlewares.GetAuthClaims(c)
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var userID uint
|
||||
switch v := claims.UserID.(type) {
|
||||
case float64:
|
||||
userID = uint(v)
|
||||
case uint:
|
||||
userID = v
|
||||
default:
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid user claim"})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin := false
|
||||
if user.IsAdmin != nil && *user.IsAdmin {
|
||||
isAdmin = true
|
||||
}
|
||||
|
||||
isVerified := false
|
||||
if user.EmailVerified != nil && *user.EmailVerified {
|
||||
isVerified = true
|
||||
}
|
||||
|
||||
// Frontend'in beklediği formata göre response döndür
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.UserName,
|
||||
"email": user.Email,
|
||||
"email_verified": isVerified,
|
||||
"is_admin": isAdmin,
|
||||
})
|
||||
}
|
||||
|
||||
// OAuth Helpers
|
||||
var (
|
||||
googleOauthConfig = &oauth2.Config{
|
||||
RedirectURL: "", // Will be set in init or handler
|
||||
ClientID: "",
|
||||
ClientSecret: "",
|
||||
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
|
||||
Endpoint: google.Endpoint,
|
||||
}
|
||||
githubOauthConfig = &oauth2.Config{
|
||||
RedirectURL: "",
|
||||
ClientID: "",
|
||||
ClientSecret: "",
|
||||
Scopes: []string{"user:email"},
|
||||
Endpoint: github.Endpoint,
|
||||
}
|
||||
)
|
||||
|
||||
func getGoogleConfig() *oauth2.Config {
|
||||
googleOauthConfig.ClientID = configs.AppConfig.GoogleClientID
|
||||
googleOauthConfig.ClientSecret = configs.AppConfig.GoogleClientSecret
|
||||
googleOauthConfig.RedirectURL = configs.AppConfig.GoogleRedirectURL
|
||||
return googleOauthConfig
|
||||
}
|
||||
|
||||
func getGithubConfig() *oauth2.Config {
|
||||
githubOauthConfig.ClientID = configs.AppConfig.GithubClientID
|
||||
githubOauthConfig.ClientSecret = configs.AppConfig.GithubClientSecret
|
||||
githubOauthConfig.RedirectURL = configs.AppConfig.GithubRedirectURL
|
||||
return githubOauthConfig
|
||||
}
|
||||
|
||||
// GoogleLogin godoc
|
||||
// @Summary Google OAuth2 Login
|
||||
// @Description Redirects to Google for authentication
|
||||
// @Tags auth
|
||||
// @Success 302
|
||||
// @Router /api/v1/auth/google [get]
|
||||
func GoogleLogin(c *gin.Context) {
|
||||
url := getGoogleConfig().AuthCodeURL("state_google", oauth2.AccessTypeOffline)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
}
|
||||
|
||||
// GoogleCallback godoc
|
||||
// @Summary Google OAuth2 Callback
|
||||
// @Description Handles Google OAuth2 callback
|
||||
// @Tags auth
|
||||
// @Success 200 {object} controllers.AuthResponse
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/auth/google/callback [get]
|
||||
func GoogleCallback(c *gin.Context) {
|
||||
code := c.Query("code")
|
||||
token, err := getGoogleConfig().Exchange(context.Background(), code)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
client := getGoogleConfig().Client(context.Background(), token)
|
||||
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user info: " + err.Error()})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
userData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read user info"})
|
||||
return
|
||||
}
|
||||
|
||||
var googleUser struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
VerifiedEmail bool `json:"verified_email"`
|
||||
Name string `json:"name"`
|
||||
Picture string `json:"picture"`
|
||||
}
|
||||
if err := json.Unmarshal(userData, &googleUser); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse user info"})
|
||||
return
|
||||
}
|
||||
|
||||
handleSocialLogin(c, "google", googleUser.ID, googleUser.Email, googleUser.Name, googleUser.Picture)
|
||||
}
|
||||
|
||||
// GithubLogin godoc
|
||||
// @Summary GitHub OAuth2 Login
|
||||
// @Description Redirects to GitHub for authentication
|
||||
// @Tags auth
|
||||
// @Success 302
|
||||
// @Router /api/v1/auth/github [get]
|
||||
func GithubLogin(c *gin.Context) {
|
||||
url := getGithubConfig().AuthCodeURL("state_github", oauth2.AccessTypeOffline)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
}
|
||||
|
||||
// GithubCallback godoc
|
||||
// @Summary GitHub OAuth2 Callback
|
||||
// @Description Handles GitHub OAuth2 callback
|
||||
// @Tags auth
|
||||
// @Success 200 {object} controllers.AuthResponse
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/auth/github/callback [get]
|
||||
func GithubCallback(c *gin.Context) {
|
||||
code := c.Query("code")
|
||||
token, err := getGithubConfig().Exchange(context.Background(), code)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
client := getGithubConfig().Client(context.Background(), token)
|
||||
resp, err := client.Get("https://api.github.com/user")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user info: " + err.Error()})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
userData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read user info"})
|
||||
return
|
||||
}
|
||||
|
||||
var githubUser struct {
|
||||
ID int64 `json:"id"`
|
||||
Login string `json:"login"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
if err := json.Unmarshal(userData, &githubUser); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse user info"})
|
||||
return
|
||||
}
|
||||
|
||||
// GitHub email might be private, need to fetch separately if empty
|
||||
email := githubUser.Email
|
||||
if email == "" {
|
||||
// Fetch emails
|
||||
emailResp, err := client.Get("https://api.github.com/user/emails")
|
||||
if err == nil {
|
||||
defer emailResp.Body.Close()
|
||||
var emails []struct {
|
||||
Email string `json:"email"`
|
||||
Primary bool `json:"primary"`
|
||||
Verified bool `json:"verified"`
|
||||
}
|
||||
if body, err := io.ReadAll(emailResp.Body); err == nil {
|
||||
json.Unmarshal(body, &emails)
|
||||
for _, e := range emails {
|
||||
if e.Primary && e.Verified {
|
||||
email = e.Email
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if email == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Could not retrieve email from GitHub"})
|
||||
return
|
||||
}
|
||||
|
||||
handleSocialLogin(c, "github", fmt.Sprintf("%d", githubUser.ID), email, githubUser.Name, githubUser.AvatarURL)
|
||||
}
|
||||
|
||||
func handleSocialLogin(c *gin.Context, provider, providerID, email, name, avatarURL string) {
|
||||
var user models.User
|
||||
var socialAccount models.SocialAccount
|
||||
|
||||
// Check if social account exists
|
||||
err := database.DB.Where("provider = ? AND provider_id = ?", provider, providerID).First(&socialAccount).Error
|
||||
|
||||
if err == nil {
|
||||
// Found social account, find user
|
||||
if err := database.DB.First(&user, socialAccount.UserID).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "User record missing for social account"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Social account not found. Check if email exists
|
||||
if err := database.DB.Where("email = ?", email).First(&user).Error; err == nil {
|
||||
// User exists, add social account
|
||||
newSocial := models.SocialAccount{
|
||||
UserID: uint64(user.ID),
|
||||
Provider: provider,
|
||||
ProviderID: providerID,
|
||||
Email: email,
|
||||
Name: name,
|
||||
AvatarURL: avatarURL,
|
||||
}
|
||||
database.DB.Create(&newSocial)
|
||||
} else {
|
||||
// Create new user
|
||||
verified := true
|
||||
now := time.Now()
|
||||
// Generate random password
|
||||
randomPass := generateSecureToken()
|
||||
hashedPwd, _ := utils.HashPassword(randomPass)
|
||||
|
||||
user = models.User{
|
||||
UserName: name, // Handle duplicate usernames?
|
||||
Email: email,
|
||||
Password: hashedPwd,
|
||||
EmailVerified: &verified,
|
||||
EmailVerifiedAt: &now,
|
||||
}
|
||||
// Fallback username if empty
|
||||
if user.UserName == "" {
|
||||
user.UserName = strings.Split(email, "@")[0]
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
newSocial := models.SocialAccount{
|
||||
UserID: uint64(user.ID),
|
||||
Provider: provider,
|
||||
ProviderID: providerID,
|
||||
Email: email,
|
||||
Name: name,
|
||||
AvatarURL: avatarURL,
|
||||
}
|
||||
database.DB.Create(&newSocial)
|
||||
}
|
||||
}
|
||||
|
||||
// Login logic
|
||||
isAdmin := false
|
||||
if user.IsAdmin != nil && *user.IsAdmin {
|
||||
isAdmin = true
|
||||
}
|
||||
|
||||
jwtService := services.NewJWTService()
|
||||
accessToken, err := jwtService.GenerateToken(user.ID, isAdmin)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate access token"})
|
||||
return
|
||||
}
|
||||
refreshToken, err := jwtService.GenerateRefreshToken(user.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate refresh token"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"user": toUserResponse(user),
|
||||
"access_token": accessToken,
|
||||
"refresh_token": refreshToken,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user