Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
# Dependency directories (remove the comment below to include it)
# vendor/

.idea
.idea

./config
141 changes: 138 additions & 3 deletions api/controllers/auth.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,72 @@
package controller

import (
"encoding/json"
"erp/config"
dto "erp/dto/auth"
service "erp/service"
"fmt"
"github.com/jinzhu/copier"
"github.com/mitchellh/mapstructure"
"golang.org/x/oauth2"
"golang.org/x/oauth2/facebook"
"golang.org/x/oauth2/google"
"net/http"

"github.com/gin-gonic/gin"
_ "github.com/jinzhu/copier"
"go.uber.org/zap"
)

type AuthController struct {
AuthService service.AuthService
logger *zap.Logger
config *config.Config
BaseController
}

func NewAuthController(c *gin.RouterGroup, authService service.AuthService, logger *zap.Logger) *AuthController {
func NewAuthController(c *gin.RouterGroup, authService service.AuthService, logger *zap.Logger, config *config.Config) *AuthController {
controller := &AuthController{
AuthService: authService,
logger: logger,
config: config,
}
g := c.Group("/auth")
g.POST("/register", controller.Register)
g.POST("/login", controller.Login)

g.GET("/google/login", controller.GoogleLogin)
g.GET("/google/callback", controller.GoogleCallback)
g.GET("/facebook/login", controller.facebookLogin)
g.GET("/facebook/callback", controller.facebookCallback)
return controller
}

func (b *AuthController) getGoogleOAuthConfig() oauth2.Config {
return oauth2.Config{
RedirectURL: b.config.GoogleOAuth.RedirectURL, // Replace with your callback URL
ClientID: b.config.GoogleOAuth.ClientID,
ClientSecret: b.config.GoogleOAuth.ClientSecret,
Scopes: b.config.GoogleOAuth.Scopes,
Endpoint: google.Endpoint,
}
}
func (b *AuthController) getFacebookConfig() oauth2.Config {
return oauth2.Config{
ClientID: b.config.FacebookOAuth.AppID,
ClientSecret: b.config.FacebookOAuth.AppSecret,
Endpoint: facebook.Endpoint,
RedirectURL: b.config.FacebookOAuth.RedirectURL,
Scopes: []string{"email"},
}
}
func (b *AuthController) Register(c *gin.Context) {
var req dto.RegisterRequest

if err := c.ShouldBindJSON(&req); err != nil {
b.ResponseError(c, http.StatusBadRequest, []error{err})
return
}

b.logger.Info(fmt.Sprintf("Request info %+v", req))
_, err := b.AuthService.Register(c.Request.Context(), req)

if err != nil {
Expand All @@ -60,3 +92,106 @@ func (b *AuthController) Login(c *gin.Context) {
}
b.Response(c, http.StatusOK, "success", res)
}

func (b *AuthController) GoogleLogin(c *gin.Context) {
authConfig := b.getGoogleOAuthConfig()
url := authConfig.AuthCodeURL("", oauth2.AccessTypeOffline)
c.Redirect(http.StatusFound, url)
}

func (b *AuthController) GoogleCallback(c *gin.Context) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

callback thường thêm param state để validate request này

code := c.Query("code")
authConfig := b.getGoogleOAuthConfig()
token, err := authConfig.Exchange(c, code)
if err != nil {
b.logger.Error(fmt.Sprintf("Cannot register with google: %+v", err))
b.ResponseError(c, http.StatusInternalServerError, []error{err})
return
}

client := authConfig.Client(c, token)

userInfo, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token" + token.AccessToken)
if err != nil {
b.logger.Error(fmt.Sprintf("Cannot register with google: %+v", err))
b.Response(c, http.StatusInternalServerError, "Cannot login by google", nil)
return
}

defer userInfo.Body.Close()

var data map[string]interface{}
decoder := json.NewDecoder(userInfo.Body)
if err := decoder.Decode(&data); err != nil {
// Handle JSON decoding error
b.logger.Error(fmt.Sprintf("Cannot register with google: %+v", err))
b.Response(c, http.StatusInternalServerError, "Cannot login by google", nil)
return
}
fmt.Println(data)
var response dto.UserGoogleRequest
if err := mapstructure.Decode(data, &response); err != nil {
// Handle JSON unmarshaling error
b.logger.Error(fmt.Sprintf("Cannot unmarshal JSON response: %+v", err))
b.Response(c, http.StatusInternalServerError, "Cannot login by google", nil)
return
}

var req dto.UserGoogleRequest

err = copier.Copy(&req, &response)
if err != nil {
b.logger.Error("Cannot register with google")
b.Response(c, http.StatusInternalServerError, "Cannot login by google", nil)
return
}
_, err = b.AuthService.RegisterByGoogle(c.Request.Context(), req)
if err != nil {
res, err := b.AuthService.LoginByGoogle(c.Request.Context(), dto.LoginByGoogleRequest{
Email: req.Email,
GoogleId: req.GoogleID,
})
if err != nil {
b.Response(c, http.StatusInternalServerError, "Cannot login by google account", nil)
return
}
b.Response(c, http.StatusOK, "success", res)
return
}
b.Response(c, http.StatusOK, "success", nil)
}

func (b *AuthController) facebookLogin(c *gin.Context) {
authConfig := b.getFacebookConfig()
url := authConfig.AuthCodeURL("", oauth2.AccessTypeOffline)
fmt.Println("url la: ", url)
c.Redirect(http.StatusFound, url)
}

func (b *AuthController) facebookCallback(c *gin.Context) {
code := c.DefaultQuery("code", "")
if code == "" {
b.Response(c, http.StatusBadRequest, "Missing code parameter", nil)
return
}
authConfig := b.getFacebookConfig()
token, err := authConfig.Exchange(c, code)
if err != nil {
b.Response(c, http.StatusBadRequest, fmt.Sprintf("Error exchanging code: %s", err.Error()), nil)
return
}
client := authConfig.Client(c, token)
resp, err := client.Get(b.config.FacebookOAuth.GraphAPIURL)
if err != nil {
b.Response(c, http.StatusInternalServerError, fmt.Sprintf("Error fetching user data: %s", err.Error()), nil)
return
}
defer resp.Body.Close()

var userData map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil {
b.Response(c, http.StatusInternalServerError, fmt.Sprintf("Error decoding user data: %s", err.Error()), nil)
return
}
b.Response(c, http.StatusOK, "success", userData)
}
33 changes: 30 additions & 3 deletions api/controllers/user.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package controller

import (
"erp/config"
"erp/dto/dto"
service "erp/service"
"net/http"
"strings"

"github.com/gin-gonic/gin"
"go.uber.org/zap"
Expand All @@ -10,15 +14,38 @@ import (
type UserController struct {
userService service.UserService
logger *zap.Logger
jwtService service.JwtService
BaseController
}

func NewUserController(c *gin.RouterGroup, userService service.UserService, logger *zap.Logger) *UserController {
func NewUserController(c *gin.RouterGroup, userService service.UserService, logger *zap.Logger, config *config.Config) *UserController {
controller := &UserController{
userService: userService,
logger: logger,
}
_ = c.Group("/user")

g := c.Group("/user")
g.PATCH("/change_password", controller.ChangePassword)
return controller
}

func (u *UserController) ChangePassword(c *gin.Context) {
auth := c.Request.Header.Get("Authorization")
jwtToken := strings.Split(auth, " ")[1]
userIdJwt, err := u.jwtService.ValidateToken(jwtToken)

if err != nil {
u.ResponseError(c, http.StatusUnauthorized, []error{err})
return
}
var req dto.ChangePassword
if err = c.ShouldBindJSON(&req); err != nil {
u.ResponseError(c, http.StatusBadRequest, []error{err})
return
}
err = u.userService.ChangePassword(c, req, *userIdJwt)
if err != nil {
u.ResponseError(c, http.StatusBadRequest, []error{err})
return
}
u.Response(c, http.StatusOK, "Change password success", nil)
}
3 changes: 1 addition & 2 deletions api/middlewares/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import (
"erp/api_errors"
config "erp/config"
constants "erp/constants"
"erp/infrastructure"
"net/http"
"strings"

"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
)

func (e *GinMiddleware) JWT(config *config.Config, db *infrastructure.Database) gin.HandlerFunc {
func (e *GinMiddleware) JWT(config *config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
if config.Server.Env != constants.Dev && constants.Local != config.Server.Env {
auth := c.Request.Header.Get("Authorization")
Expand Down
30 changes: 23 additions & 7 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ var (

type (
Config struct {
Debug bool `mapstructure:"debug"`
ContextTimeout int `mapstructure:"contextTimeout"`
Server Server `mapstructure:"server"`
Services Services `mapstructure:"services"`
Database Database `mapstructure:"database"`
Logger Logger `mapstructure:"logger"`
Jwt Jwt `mapstructure:"jwt"`
Debug bool `mapstructure:"debug"`
ContextTimeout int `mapstructure:"contextTimeout"`
Server Server `mapstructure:"server"`
Services Services `mapstructure:"services"`
Database Database `mapstructure:"database"`
Logger Logger `mapstructure:"logger"`
Jwt Jwt `mapstructure:"jwt"`
GoogleOAuth GoogleOAuth `mapstructure:"googleOAuth"`
FacebookOAuth FacebookOAuth `mapstructure:"facebookOAuth"`
}

Server struct {
Expand Down Expand Up @@ -57,6 +59,20 @@ type (

Services struct {
}

GoogleOAuth struct {
RedirectURL string `mapstructure:"redirectURL"`
ClientID string `mapstructure:"clientID"`
ClientSecret string `mapstructure:"clientSecret"`
Scopes []string `mapstructure:"scopes"`
}

FacebookOAuth struct {
RedirectURL string `mapstructure:"redirectURL"`
AppID string `mapstructure:"appID"`
AppSecret string `mapstructure:"appSecret"`
GraphAPIURL string `mapstructure:"graphAPIURL"`
}
)

func NewConfig() *Config {
Expand Down
16 changes: 16 additions & 0 deletions config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,19 @@ jwt:
secret: 'hgG6T23XD25N'
accessTokenExpiresIn: 3600
refreshTokenExpiresIn: 86400


googleOAuth:
redirectURL: 'http://localhost:8080/api/auth/google/callback'
clientID: '555161563716-820tf0ghjucnn9mirti5imlvkai8t6kv.apps.googleusercontent.com'
clientSecret: "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"
scopes:
- "https://www.googleapis.com/auth/userinfo.email"
- "https://www.googleapis.com/auth/userinfo.profile"


facebookOAuth:
graphAPIURL: 'https://graph.facebook.com/v12.0/me?fields=id,email,first_name,last_name'
appID : '652357916991921'
appSecret : '8f0693687f2909ea96f679396228dce5'
redirectURL : 'http://localhost:8080/api/auth/facebook/callback'
5 changes: 5 additions & 0 deletions dto/auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ type LoginResponse struct {
Token TokenResponse `json:"token"`
}

type LoginByGoogleRequest struct {
Email string `json:"email" binding:"required" validate:"email"`
GoogleId string `json:"google_id" binding:"required"`
}

type TokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Expand Down
7 changes: 7 additions & 0 deletions dto/auth/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ type RegisterRequest struct {
LastName string `json:"last_name" binding:"required" validate:"min=1,max=50"`
RequestFrom string `json:"request_from" binding:"required" enums:"erp/,web,app"`
}

type UserGoogleRequest struct {
Email string `mapstructure:"email" json:"email" binding:"required" validate:"email"`
GoogleID string `mapstructure:"id" json:"id" binding:"required"`
FirstName string `mapstructure:"family_name" json:"family_name"`
LastName string `mapstructure:"given_name" json:"given_name"`
}
Loading