298 lines
8.0 KiB
Go
298 lines
8.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"gobeyhan/app/account/services"
|
|
settingsServices "gobeyhan/app/settings/services"
|
|
"gobeyhan/config"
|
|
"gobeyhan/database/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"golang.org/x/oauth2"
|
|
"golang.org/x/oauth2/github"
|
|
"golang.org/x/oauth2/google"
|
|
)
|
|
|
|
type OAuthHandler struct {
|
|
userService *services.UserService
|
|
socialAccountService *services.SocialAccountService
|
|
jwtService *settingsServices.JWTService
|
|
googleOAuthConfig *oauth2.Config
|
|
githubOAuthConfig *oauth2.Config
|
|
}
|
|
|
|
func NewOAuthHandler(
|
|
userService *services.UserService,
|
|
socialAccountService *services.SocialAccountService,
|
|
jwtService *settingsServices.JWTService,
|
|
) *OAuthHandler {
|
|
// Google OAuth config
|
|
googleConfig := &oauth2.Config{
|
|
ClientID: config.AppConfig.GoogleClientID,
|
|
ClientSecret: config.AppConfig.GoogleClientSecret,
|
|
RedirectURL: config.AppConfig.GoogleRedirectURL,
|
|
Scopes: []string{
|
|
"https://www.googleapis.com/auth/userinfo.email",
|
|
"https://www.googleapis.com/auth/userinfo.profile",
|
|
},
|
|
Endpoint: google.Endpoint,
|
|
}
|
|
|
|
// GitHub OAuth config
|
|
githubConfig := &oauth2.Config{
|
|
ClientID: config.AppConfig.GithubClientID,
|
|
ClientSecret: config.AppConfig.GithubClientSecret,
|
|
RedirectURL: config.AppConfig.GithubRedirectURL,
|
|
Scopes: []string{"user:email"},
|
|
Endpoint: github.Endpoint,
|
|
}
|
|
|
|
return &OAuthHandler{
|
|
userService: userService,
|
|
socialAccountService: socialAccountService,
|
|
jwtService: jwtService,
|
|
googleOAuthConfig: googleConfig,
|
|
githubOAuthConfig: githubConfig,
|
|
}
|
|
}
|
|
|
|
// GoogleLogin godoc
|
|
// @Summary Google OAuth login
|
|
// @Description Redirect to Google OAuth
|
|
// @Tags auth,oauth
|
|
// @Produce json
|
|
// @Router /api/v1/auth/google [get]
|
|
func (h *OAuthHandler) GoogleLogin(c *gin.Context) {
|
|
url := h.googleOAuthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline)
|
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
|
}
|
|
|
|
// GoogleCallback godoc
|
|
// @Summary Google OAuth callback
|
|
// @Description Handle Google OAuth callback
|
|
// @Tags auth,oauth
|
|
// @Produce json
|
|
// @Param code query string true "Authorization code"
|
|
// @Success 200 {object} object{token=string,user=models.User}
|
|
// @Failure 400 {object} object{error=string}
|
|
// @Router /api/v1/auth/google/callback [get]
|
|
func (h *OAuthHandler) GoogleCallback(c *gin.Context) {
|
|
code := c.Query("code")
|
|
if code == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Code not found"})
|
|
return
|
|
}
|
|
|
|
// Exchange code for token
|
|
token, err := h.googleOAuthConfig.Exchange(context.Background(), code)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token"})
|
|
return
|
|
}
|
|
|
|
// Get user info from Google
|
|
client := h.googleOAuthConfig.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"})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
data, _ := io.ReadAll(resp.Body)
|
|
var googleUser struct {
|
|
ID string `json:"id"`
|
|
Email string `json:"email"`
|
|
VerifiedEmail bool `json:"verified_email"`
|
|
Name string `json:"name"`
|
|
Picture string `json:"picture"`
|
|
}
|
|
json.Unmarshal(data, &googleUser)
|
|
|
|
// Find or create user
|
|
user, accessToken, refreshToken, err := h.findOrCreateOAuthUser(
|
|
googleUser.Email,
|
|
googleUser.Name,
|
|
"google",
|
|
googleUser.ID,
|
|
)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"token": accessToken,
|
|
"refresh_token": refreshToken,
|
|
"user": user,
|
|
})
|
|
}
|
|
|
|
// GithubLogin godoc
|
|
// @Summary GitHub OAuth login
|
|
// @Description Redirect to GitHub OAuth
|
|
// @Tags auth,oauth
|
|
// @Produce json
|
|
// @Router /api/v1/auth/github [get]
|
|
func (h *OAuthHandler) GithubLogin(c *gin.Context) {
|
|
url := h.githubOAuthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline)
|
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
|
}
|
|
|
|
// GithubCallback godoc
|
|
// @Summary GitHub OAuth callback
|
|
// @Description Handle GitHub OAuth callback
|
|
// @Tags auth,oauth
|
|
// @Produce json
|
|
// @Param code query string true "Authorization code"
|
|
// @Success 200 {object} object{token=string,user=models.User}
|
|
// @Failure 400 {object} object{error=string}
|
|
// @Router /api/v1/auth/github/callback [get]
|
|
func (h *OAuthHandler) GithubCallback(c *gin.Context) {
|
|
code := c.Query("code")
|
|
if code == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Code not found"})
|
|
return
|
|
}
|
|
|
|
// Exchange code for token
|
|
token, err := h.githubOAuthConfig.Exchange(context.Background(), code)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token"})
|
|
return
|
|
}
|
|
|
|
// Get user info from GitHub
|
|
client := h.githubOAuthConfig.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"})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
data, _ := io.ReadAll(resp.Body)
|
|
var githubUser struct {
|
|
ID int `json:"id"`
|
|
Login string `json:"login"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
}
|
|
json.Unmarshal(data, &githubUser)
|
|
|
|
// If email is not public, fetch it separately
|
|
if githubUser.Email == "" {
|
|
emailResp, _ := client.Get("https://api.github.com/user/emails")
|
|
if emailResp != nil {
|
|
defer emailResp.Body.Close()
|
|
emailData, _ := io.ReadAll(emailResp.Body)
|
|
var emails []struct {
|
|
Email string `json:"email"`
|
|
Primary bool `json:"primary"`
|
|
Verified bool `json:"verified"`
|
|
}
|
|
json.Unmarshal(emailData, &emails)
|
|
for _, e := range emails {
|
|
if e.Primary && e.Verified {
|
|
githubUser.Email = e.Email
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
username := githubUser.Name
|
|
if username == "" {
|
|
username = githubUser.Login
|
|
}
|
|
|
|
// Find or create user
|
|
user, accessToken, refreshToken, err := h.findOrCreateOAuthUser(
|
|
githubUser.Email,
|
|
username,
|
|
"github",
|
|
strconv.Itoa(githubUser.ID),
|
|
)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"token": accessToken,
|
|
"refresh_token": refreshToken,
|
|
"user": user,
|
|
})
|
|
}
|
|
|
|
// findOrCreateOAuthUser finds existing user or creates new one for OAuth
|
|
func (h *OAuthHandler) findOrCreateOAuthUser(
|
|
email, username, provider, providerUserID string,
|
|
) (*models.User, string, string, error) {
|
|
// Try to find existing user by email
|
|
user, err := h.userService.GetUserByEmail(email)
|
|
if err != nil {
|
|
return nil, "", "", err
|
|
}
|
|
|
|
// If user doesn't exist, create new one
|
|
if user == nil {
|
|
user = &models.User{
|
|
Email: email,
|
|
UserName: username,
|
|
}
|
|
// Create user with empty password
|
|
if err := h.userService.CreateUser(user, ""); err != nil {
|
|
return nil, "", "", fmt.Errorf("failed to create user: %w", err)
|
|
}
|
|
|
|
// Assign default role
|
|
if err := h.userService.AssignDefaultRole(user.ID); err != nil {
|
|
// Log error but continue
|
|
// fmt.Printf("Failed to assign default role: %v\n", err)
|
|
}
|
|
}
|
|
|
|
// Check if social account exists
|
|
accounts, err := h.socialAccountService.GetSocialAccountsByUser(user.ID)
|
|
if err != nil {
|
|
return nil, "", "", err
|
|
}
|
|
|
|
// Create social account if it doesn't exist
|
|
found := false
|
|
for _, acc := range accounts {
|
|
if acc.Provider == provider && acc.ProviderID == providerUserID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
socialAccount := &models.SocialAccount{
|
|
UserID: user.ID,
|
|
Provider: provider,
|
|
ProviderID: providerUserID,
|
|
}
|
|
if err := h.socialAccountService.CreateSocialAccount(socialAccount); err != nil {
|
|
return nil, "", "", fmt.Errorf("failed to create social account: %w", err)
|
|
}
|
|
}
|
|
|
|
// Generate JWT tokens
|
|
accessToken, refreshToken, err := h.jwtService.GenerateTokenPair(*user)
|
|
if err != nil {
|
|
return nil, "", "", fmt.Errorf("failed to generate tokens: %w", err)
|
|
}
|
|
|
|
// Clear password before returning
|
|
user.Password = ""
|
|
return user, accessToken, refreshToken, nil
|
|
}
|