From 0afae205eb610d58ba853c54afe7ff2ef8c19eb2 Mon Sep 17 00:00:00 2001 From: Emad Malekpour - Backend Date: Fri, 9 Jun 2023 17:20:06 +0330 Subject: [PATCH] [FEATURE] Implement otp and jwt authentication --- app/console/commands/serve.go | 15 ++- app/http/handlers/api/otp.go | 93 +++++++++++++++++++ app/http/middlewares/jwt.go | 49 ++++++++++ app/http/routes/api.go | 18 +++- app/listeners/otp/created.go | 18 ++++ app/models/otp_tokens.go | 18 ++++ app/models/user.go | 12 ++- app/repositories/otp/repository.go | 57 ++++++++++++ app/repositories/user/repository.go | 32 +++++++ app/services/otp/service.go | 47 ++++++++++ app/services/user/service.go | 17 ++++ database/migrations/otp_tokens/init.go | 17 ++++ .../migrations/user/01_add_remember_token.go | 17 ++++ database/migrations/user/02_add_mobile.go | 17 ++++ go.mod | 1 + go.sum | 2 + internal/config/config.go | 16 ++++ internal/container/container.go | 8 ++ internal/event/provider.go | 4 + internal/jwt/jwt.go | 14 +++ internal/migrator/migrator.go | 4 + internal/otp/otp.go | 13 +++ internal/response/response.go | 4 +- wire/wire.go | 4 + wire/wire_gen.go | 10 +- 25 files changed, 484 insertions(+), 23 deletions(-) create mode 100644 app/http/handlers/api/otp.go create mode 100644 app/http/middlewares/jwt.go create mode 100644 app/listeners/otp/created.go create mode 100644 app/models/otp_tokens.go create mode 100644 app/repositories/otp/repository.go create mode 100644 app/services/otp/service.go create mode 100644 database/migrations/otp_tokens/init.go create mode 100644 database/migrations/user/01_add_remember_token.go create mode 100644 database/migrations/user/02_add_mobile.go create mode 100644 internal/jwt/jwt.go create mode 100644 internal/otp/otp.go diff --git a/app/console/commands/serve.go b/app/console/commands/serve.go index 6e4fcca..c1c503b 100644 --- a/app/console/commands/serve.go +++ b/app/console/commands/serve.go @@ -8,7 +8,6 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/spf13/cobra" - csrf "github.com/utrack/gin-csrf" ) type ServeCommand struct { @@ -20,13 +19,13 @@ func (s *ServeCommand) RunE(cmd *cobra.Command, args []string) error { router.Use(middlewares.MaintenanceMode(s.container.Config)) router.Use(sessions.Sessions("mysession", s.container.Session.Store)) - router.Use(csrf.Middleware(csrf.Options{ - Secret: "SHsHZ28711587148418", - ErrorFunc: func(c *gin.Context) { - c.String(400, "CSRF token mismatch") - c.Abort() - }, - })) + //router.Use(csrf.Middleware(csrf.Options{ + // Secret: "SHsHZ28711587148418", + // ErrorFunc: func(c *gin.Context) { + // c.String(400, "CSRF token mismatch") + // c.Abort() + // }, + //})) routes.SetupApiRoutes(router, s.container) diff --git a/app/http/handlers/api/otp.go b/app/http/handlers/api/otp.go new file mode 100644 index 0000000..0393f92 --- /dev/null +++ b/app/http/handlers/api/otp.go @@ -0,0 +1,93 @@ +package api + +import ( + "GoGinStarter/app/services/otp" + "GoGinStarter/app/services/user" + "GoGinStarter/internal/config" + "GoGinStarter/internal/container" + "GoGinStarter/internal/event" + "GoGinStarter/internal/jwt" + "GoGinStarter/internal/log" + "GoGinStarter/internal/response" + "github.com/gin-gonic/gin" +) + +type OTPApiHandler struct { + userService user.Service + otpService otp.Service + response response.Response + log log.Log + config *config.Config + eventDispatcher event.Dispatcher +} + +func NewTOPApiHandler(container *container.Container) *OTPApiHandler { + return &OTPApiHandler{ + container.UserService, + container.OTPService, + container.Response, + container.Log, + container.Config, + container.EventDispatcher, + } +} + +type SentRequest struct { + Mobile string `json:"mobile"` +} + +func (o *OTPApiHandler) SentHandler(ctx *gin.Context) { + var data SentRequest + if err := ctx.BindJSON(&data); err != nil { + o.response.BadRequest(ctx, err.Error(), nil) + return + } + + _, err := o.otpService.Create(ctx, data.Mobile) + if err != nil { + o.response.Error(ctx, 500, err.Error()) + return + } + + o.response.Success(ctx, nil, "OTP successfully sent") +} + +type VerifyRequest struct { + Mobile string `json:"mobile"` + Token string `json:"token"` +} + +func (o *OTPApiHandler) VerifyHandler(ctx *gin.Context) { + var data VerifyRequest + if err := ctx.BindJSON(&data); err != nil { + o.response.BadRequest(ctx, err.Error(), nil) + return + } + + if err := o.otpService.VerifyByToken(ctx, data.Mobile, data.Token); err != nil { + o.response.Error(ctx, 500, err.Error()) + return + } + + user, err := o.userService.FirstOrCreate(ctx, data.Mobile) + if err != nil { + o.response.Error(ctx, 500, err.Error()) + return + } + + tokenString, err := jwt.CreateToken(user.ID, o.config.Auth.JWT.Secret, o.config.Auth.JWT.ExpirationTime) + if err != nil { + o.response.Error(ctx, 500, err.Error()) + return + } + + user, err = o.userService.AddRememberToken(ctx, user.ID, tokenString) + if err != nil { + o.response.Error(ctx, 500, err.Error()) + return + } + + ctx.Header("Authorization", "Bearer "+tokenString) + + o.response.Success(ctx, nil, "Authentication successfully") +} diff --git a/app/http/middlewares/jwt.go b/app/http/middlewares/jwt.go new file mode 100644 index 0000000..4e23502 --- /dev/null +++ b/app/http/middlewares/jwt.go @@ -0,0 +1,49 @@ +package middlewares + +import ( + "GoGinStarter/app/services/user" + "GoGinStarter/internal/config" + "GoGinStarter/internal/response" + "fmt" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "strings" +) + +func JWT(response response.Response, config *config.Config, userService user.Service) gin.HandlerFunc { + return func(ctx *gin.Context) { + authHeader := ctx.GetHeader("Authorization") + fmt.Println(authHeader) + if authHeader == "" { + response.Unauthorized(ctx, "Unauthorized 401") + ctx.Abort() + return + } + + tokenString := strings.Replace(authHeader, "Bearer ", "", 1) + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, jwt.ErrSignatureInvalid + } + return []byte(config.Auth.JWT.Secret), nil + }) + + if err != nil { + response.Unauthorized(ctx, err.Error()) + ctx.Abort() + return + } + + if !token.Valid { + response.Unauthorized(ctx, "Invalid token") + ctx.Abort() + return + } + + user, _ := userService.FindByRememberToken(ctx, token.Raw) + + ctx.Set("remember_token", token) + ctx.Set("user", user) + ctx.Next() + } +} diff --git a/app/http/routes/api.go b/app/http/routes/api.go index d80797b..90728ef 100644 --- a/app/http/routes/api.go +++ b/app/http/routes/api.go @@ -8,11 +8,19 @@ import ( ) func SetupApiRoutes(router *gin.Engine, container *container.Container) { + OTPApiHandler := api.NewTOPApiHandler(container) userApiHandler := api.NewUserApiHandler(container) + + jwtMiddleware := middlewares.JWT(container.Response, container.Config, container.UserService) + apiRouter := router.Group("/api/v1", middlewares.RequestLogger(container.Log)) - { - apiRouter.GET("/users", userApiHandler.UsersHandler) - apiRouter.GET("/users/:id", userApiHandler.SingleHandler) - apiRouter.POST("/users", userApiHandler.StoreHandler) - } + + authRouter := apiRouter.Group("/auth") + authRouter.POST("/sent-otp", OTPApiHandler.SentHandler) + authRouter.POST("/verify-otp", OTPApiHandler.VerifyHandler) + + userRouter := apiRouter.Group("/users", jwtMiddleware) + userRouter.GET("/index", userApiHandler.UsersHandler) + userRouter.POST("/store", userApiHandler.StoreHandler) + userRouter.GET("/:id/single", userApiHandler.SingleHandler) } diff --git a/app/listeners/otp/created.go b/app/listeners/otp/created.go new file mode 100644 index 0000000..34d7df6 --- /dev/null +++ b/app/listeners/otp/created.go @@ -0,0 +1,18 @@ +package otp + +import ( + "fmt" +) + +type CreatedListener struct{} + +func (l CreatedListener) HandleEvent(data interface{}) { + if d, ok := data.(map[string]interface{}); ok { + token := d["token"].(string) + mobile := d["mobile"].(string) + + // Implement OTP send + + fmt.Println("OTP created is " + token + " for mobile " + mobile) + } +} diff --git a/app/models/otp_tokens.go b/app/models/otp_tokens.go new file mode 100644 index 0000000..c33dbea --- /dev/null +++ b/app/models/otp_tokens.go @@ -0,0 +1,18 @@ +package models + +import ( + "gorm.io/gorm" + "time" +) + +type OtpTokens struct { + gorm.Model + Mobile string `json:"mobile" gorm:"index:not null"` + Token string `json:"token" gorm:"size:6;not null"` + SentAt time.Time `json:"sent_at"` + ExpiresAt time.Time `json:"expires_at"` +} + +func (o *OtpTokens) TableName() string { + return "otp_tokens" +} diff --git a/app/models/user.go b/app/models/user.go index a668a99..a0f8013 100644 --- a/app/models/user.go +++ b/app/models/user.go @@ -6,11 +6,13 @@ import ( type User struct { gorm.Model - FirstName string `json:"first_name" gorm:"not null"` - LastName string `json:"last_name" gorm:"not null"` - Email string `json:"email" gorm:"unique_index;not null"` - Password string `json:"-" gorm:"not null"` - DeletedAt gorm.DeletedAt `json:"deleted_at"` + FirstName string `json:"first_name" gorm:"not null"` + LastName string `json:"last_name" gorm:"not null"` + Mobile string `json:"mobile;" gorm:"unique_index"` + Email string `json:"email" gorm:"unique_index;not null"` + Password string `json:"-" gorm:"not null"` + RememberToken string `json:"-" gorm:"unique"` + DeletedAt gorm.DeletedAt `json:"deleted_at"` } func (u *User) TableName() string { diff --git a/app/repositories/otp/repository.go b/app/repositories/otp/repository.go new file mode 100644 index 0000000..6989732 --- /dev/null +++ b/app/repositories/otp/repository.go @@ -0,0 +1,57 @@ +package otp + +import ( + "GoGinStarter/app/models" + "GoGinStarter/internal/config" + "GoGinStarter/internal/log" + "GoGinStarter/internal/otp" + "fmt" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + time "time" +) + +type Repository interface { + Create(ctx *gin.Context, mobile string) (*models.OtpTokens, error) + VerifyByToken(ctx *gin.Context, mobile string, token string) error +} + +type repository struct { + db *gorm.DB + log log.Log + config *config.Config +} + +func (r *repository) Create(ctx *gin.Context, mobile string) (*models.OtpTokens, error) { + otpToken := models.OtpTokens{ + Mobile: mobile, + Token: otp.GenerateOTP(r.config.Auth.OTP.TokenLength), + SentAt: time.Now(), + ExpiresAt: time.Now().Add(time.Second * time.Duration(r.config.Auth.OTP.ExpirationTime)), + } + r.db.Create(&otpToken) + + return &otpToken, nil +} + +func (r *repository) VerifyByToken(ctx *gin.Context, mobile string, token string) error { + otpToken := models.OtpTokens{} + db := r.db.Where("mobile = ? AND token = ?", mobile, token).First(&otpToken) + if err := db.Error; err != nil { + return err + } + + if time.Now().After(otpToken.ExpiresAt) { + return fmt.Errorf("token has exipred") + } + + if err := db.Delete(&otpToken).Error; err != nil { + return err + } + + return nil +} + +func ProvideOtpRepository(db *gorm.DB, log log.Log, config *config.Config) Repository { + return &repository{db, log, config} +} diff --git a/app/repositories/user/repository.go b/app/repositories/user/repository.go index e39b67c..8cefb68 100644 --- a/app/repositories/user/repository.go +++ b/app/repositories/user/repository.go @@ -12,6 +12,9 @@ import ( type Repository interface { Index(ctx *gin.Context) (*paginator.Paginator, error) FindById(ctx *gin.Context, id int) (*models.User, error) + FirstOrCreate(ctx *gin.Context, mobile string) (*models.User, error) + AddRememberToken(ctx *gin.Context, id uint, rememberToken string) (*models.User, error) + FindByRememberToken(ctx *gin.Context, rememberToken string) (*models.User, error) } type repository struct { @@ -44,6 +47,35 @@ func (r *repository) FindById(ctx *gin.Context, id int) (*models.User, error) { return &user, nil } +func (r *repository) FirstOrCreate(ctx *gin.Context, mobile string) (*models.User, error) { + var user models.User + if err := r.db.Where("mobile = ?", mobile).FirstOrCreate(&user, models.User{ + Mobile: mobile, + }).Error; err != nil { + return nil, err + } + + return &user, nil +} + +func (r *repository) AddRememberToken(ctx *gin.Context, id uint, rememberToken string) (*models.User, error) { + var user models.User + if err := r.db.Model(&user).Where("id = ?", id).Update("remember_token", rememberToken).Error; err != nil { + return nil, err + } + + return &user, nil +} + +func (r *repository) FindByRememberToken(ctx *gin.Context, rememberToken string) (*models.User, error) { + var user models.User + if err := r.db.Where("remember_token = ?", rememberToken).First(&user).Error; err != nil { + return nil, err + } + + return &user, nil +} + func ProvideUserRepository(db *gorm.DB, log log.Log) Repository { return &repository{db, log} } diff --git a/app/services/otp/service.go b/app/services/otp/service.go new file mode 100644 index 0000000..0d11686 --- /dev/null +++ b/app/services/otp/service.go @@ -0,0 +1,47 @@ +package otp + +import ( + "GoGinStarter/app/repositories/otp" + "GoGinStarter/internal/cache" + "GoGinStarter/internal/event" + "GoGinStarter/internal/log" + "github.com/gin-gonic/gin" +) + +type Service struct { + repository otp.Repository + log log.Log + cache cache.Cache + eventDispatcher event.Dispatcher +} + +func (s Service) Create(ctx *gin.Context, mobile string) (string, error) { + otpToken, err := s.repository.Create(ctx, mobile) + if err != nil { + return "", err + } + s.eventDispatcher.Dispatch("otp.created", map[string]any{ + "token": otpToken.Token, + "mobile": otpToken.Mobile, + }) + + return otpToken.Token, nil +} + +func (s Service) VerifyByToken(ctx *gin.Context, mobile string, token string) error { + return s.repository.VerifyByToken(ctx, mobile, token) +} + +func ProvideOTPService( + Repository otp.Repository, + Log log.Log, + Cache cache.Cache, + EventDispatcher event.Dispatcher, +) Service { + return Service{ + repository: Repository, + log: Log, + cache: Cache, + eventDispatcher: EventDispatcher, + } +} diff --git a/app/services/user/service.go b/app/services/user/service.go index a144a85..111d652 100644 --- a/app/services/user/service.go +++ b/app/services/user/service.go @@ -34,6 +34,23 @@ func (s Service) FindById(ctx *gin.Context, id int) (*models.User, error) { return s.repository.FindById(ctx, utils.IStoI(ctx.Param("id"))) } +func (s Service) FirstOrCreate(ctx *gin.Context, mobile string) (*models.User, error) { + user, err := s.repository.FirstOrCreate(ctx, mobile) + if err != nil { + return nil, err + } + + return user, nil +} + +func (s Service) AddRememberToken(ctx *gin.Context, id uint, token string) (*models.User, error) { + return s.repository.AddRememberToken(ctx, id, token) +} + +func (s Service) FindByRememberToken(ctx *gin.Context, token string) (*models.User, error) { + return s.repository.FindByRememberToken(ctx, token) +} + func ProvideUserService( Repository user.Repository, Log log.Log, diff --git a/database/migrations/otp_tokens/init.go b/database/migrations/otp_tokens/init.go new file mode 100644 index 0000000..7ced903 --- /dev/null +++ b/database/migrations/otp_tokens/init.go @@ -0,0 +1,17 @@ +package otp_tokens + +import ( + "GoGinStarter/app/models" + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +var InitMigration = gormigrate.Migration{ + ID: "otp_tokens_init", + Migrate: func(tx *gorm.DB) error { + return tx.Migrator().CreateTable(&models.OtpTokens{}) + }, + Rollback: func(tx *gorm.DB) error { + return tx.Migrator().DropTable("otp_tokens") + }, +} diff --git a/database/migrations/user/01_add_remember_token.go b/database/migrations/user/01_add_remember_token.go new file mode 100644 index 0000000..892047f --- /dev/null +++ b/database/migrations/user/01_add_remember_token.go @@ -0,0 +1,17 @@ +package user + +import ( + "GoGinStarter/app/models" + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +var AddRememberTokenMigration = gormigrate.Migration{ + ID: "add_remember_token", + Migrate: func(tx *gorm.DB) error { + return tx.Migrator().AddColumn(&models.User{}, "remember_token") + }, + Rollback: func(tx *gorm.DB) error { + return tx.Migrator().DropColumn(&models.User{}, "remember_token") + }, +} diff --git a/database/migrations/user/02_add_mobile.go b/database/migrations/user/02_add_mobile.go new file mode 100644 index 0000000..3f27aae --- /dev/null +++ b/database/migrations/user/02_add_mobile.go @@ -0,0 +1,17 @@ +package user + +import ( + "GoGinStarter/app/models" + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +var AddMobileMigration = gormigrate.Migration{ + ID: "add_mobile", + Migrate: func(tx *gorm.DB) error { + return tx.Migrator().AddColumn(&models.User{}, "mobile") + }, + Rollback: func(tx *gorm.DB) error { + return tx.Migrator().DropColumn(&models.User{}, "mobile") + }, +} diff --git a/go.mod b/go.mod index 47c4a08..32ee595 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/go-playground/validator/v10 v10.11.2 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/goccy/go-json v0.10.0 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect diff --git a/go.sum b/go.sum index 4999dd5..ea5e62f 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/internal/config/config.go b/internal/config/config.go index d4c8669..1290f89 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,6 +12,7 @@ type Config struct { Cache Cache `yaml:"cache"` Session Session `yaml:"session"` Mail Mail `yaml:"mail"` + Auth Auth `yaml:"auth"` } type App struct { @@ -53,6 +54,21 @@ type Mail struct { FromName string `yaml:"from_name"` } +type Auth struct { + JWT JWT `yaml:"jwt"` + OTP OTP `yaml:"otp"` +} + +type JWT struct { + Secret string `yaml:"secret"` + ExpirationTime int `yaml:"expiration_time"` +} + +type OTP struct { + TokenLength int `yaml:"token_length"` + ExpirationTime int `yaml:"expiration_time"` +} + func ProvideConfig(log log.Log) *Config { // Open the YAML file file, err := os.Open(".yaml") diff --git a/internal/container/container.go b/internal/container/container.go index 2ce517e..356bf9a 100644 --- a/internal/container/container.go +++ b/internal/container/container.go @@ -1,7 +1,9 @@ package container import ( + otpr "GoGinStarter/app/repositories/otp" ur "GoGinStarter/app/repositories/user" + otps "GoGinStarter/app/services/otp" us "GoGinStarter/app/services/user" "GoGinStarter/internal/cache" "GoGinStarter/internal/config" @@ -15,6 +17,8 @@ import ( ) type Container struct { + OTPService otps.Service + OTPRepository otpr.Repository UserService us.Service UserRepository ur.Repository Cache cache.Cache @@ -29,6 +33,8 @@ type Container struct { } func ProvideContainer( + OTPService otps.Service, + OTPRepository otpr.Repository, UserService us.Service, UserRepository ur.Repository, Cache cache.Cache, @@ -42,6 +48,8 @@ func ProvideContainer( Scheduler scheduler.Schedule, ) *Container { return &Container{ + OTPService: OTPService, + OTPRepository: OTPRepository, UserService: UserService, UserRepository: UserRepository, Cache: Cache, diff --git a/internal/event/provider.go b/internal/event/provider.go index 3abed55..0d26cca 100644 --- a/internal/event/provider.go +++ b/internal/event/provider.go @@ -1,6 +1,7 @@ package event import ( + "GoGinStarter/app/listeners/otp" "GoGinStarter/app/listeners/user" ) @@ -8,4 +9,7 @@ var Listeners = map[string][]Listener{ "user.created": { user.CreatedListener{}, }, + "otp.created": { + otp.CreatedListener{}, + }, } diff --git a/internal/jwt/jwt.go b/internal/jwt/jwt.go new file mode 100644 index 0000000..d87a35a --- /dev/null +++ b/internal/jwt/jwt.go @@ -0,0 +1,14 @@ +package jwt + +import ( + "github.com/golang-jwt/jwt/v5" + "time" +) + +func CreateToken(userID uint, secret string, expirationAt int) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "user_id": userID, + "exp": time.Now().Add(time.Second * time.Duration(expirationAt)).Unix(), + }) + return token.SignedString([]byte(secret)) +} diff --git a/internal/migrator/migrator.go b/internal/migrator/migrator.go index 2b4633c..40cff79 100644 --- a/internal/migrator/migrator.go +++ b/internal/migrator/migrator.go @@ -1,6 +1,7 @@ package migrator import ( + "GoGinStarter/database/migrations/otp_tokens" "GoGinStarter/database/migrations/user" "github.com/go-gormigrate/gormigrate/v2" "gorm.io/gorm" @@ -8,6 +9,9 @@ import ( var Migrations = []*gormigrate.Migration{ &user.InitMigration, + &otp_tokens.InitMigration, + &user.AddRememberTokenMigration, + &user.AddMobileMigration, } func NewMigrator(db *gorm.DB) *gormigrate.Gormigrate { diff --git a/internal/otp/otp.go b/internal/otp/otp.go new file mode 100644 index 0000000..b4e0629 --- /dev/null +++ b/internal/otp/otp.go @@ -0,0 +1,13 @@ +package otp + +import ( + "crypto/rand" + "encoding/base32" +) + +func GenerateOTP(length int) string { + randomBytes := make([]byte, 4) + _, _ = rand.Read(randomBytes) + otp := base32.StdEncoding.EncodeToString(randomBytes) + return otp[:length] +} diff --git a/internal/response/response.go b/internal/response/response.go index bc6d36d..216705f 100644 --- a/internal/response/response.go +++ b/internal/response/response.go @@ -17,7 +17,7 @@ type Response interface { Error(ctx *gin.Context, status int, error string) BadRequest(ctx *gin.Context, error string, data any) NotFound(ctx *gin.Context, error string) - Unauthorized(ctx *gin.Context, statusCode int, error string) + Unauthorized(ctx *gin.Context, error string) Render(ctx *gin.Context, name string, obj any) WithPaginate(ctx *gin.Context, pagination *paginator.Paginator, message string) } @@ -79,7 +79,7 @@ func (r *response) NotFound(ctx *gin.Context, error string) { r.general(ctx, http.StatusNotFound, false, error, nil, nil) } -func (r *response) Unauthorized(ctx *gin.Context, statusCode int, error string) { +func (r *response) Unauthorized(ctx *gin.Context, error string) { r.general(ctx, http.StatusUnauthorized, false, error, nil, nil) } diff --git a/wire/wire.go b/wire/wire.go index 8d2fb0c..713841a 100644 --- a/wire/wire.go +++ b/wire/wire.go @@ -4,7 +4,9 @@ package wire import ( + otpr "GoGinStarter/app/repositories/otp" ur "GoGinStarter/app/repositories/user" + otps "GoGinStarter/app/services/otp" us "GoGinStarter/app/services/user" "GoGinStarter/internal/cache" "GoGinStarter/internal/config" @@ -32,6 +34,8 @@ func InitializeContainer() *container.Container { seeder.ProvideSeeder, ur.ProvideUserRepository, us.ProvideUserService, + otpr.ProvideOtpRepository, + otps.ProvideOTPService, container.ProvideContainer, ) diff --git a/wire/wire_gen.go b/wire/wire_gen.go index 81159ee..0363898 100644 --- a/wire/wire_gen.go +++ b/wire/wire_gen.go @@ -7,7 +7,9 @@ package wire import ( + "GoGinStarter/app/repositories/otp" "GoGinStarter/app/repositories/user" + otp2 "GoGinStarter/app/services/otp" user2 "GoGinStarter/app/services/user" "GoGinStarter/internal/cache" "GoGinStarter/internal/config" @@ -27,14 +29,16 @@ func InitializeContainer() *container.Container { logLog := log.ProvideLog() configConfig := config.ProvideConfig(logLog) gormDB := db.ProvideDB(configConfig, logLog) - repository := user.ProvideUserRepository(gormDB, logLog) + repository := otp.ProvideOtpRepository(gormDB, logLog, configConfig) cacheCache := cache.ProvideCache(configConfig, logLog) dispatcher := event.ProvideDispatcher() - service := user2.ProvideUserService(repository, logLog, cacheCache, dispatcher) + service := otp2.ProvideOTPService(repository, logLog, cacheCache, dispatcher) + userRepository := user.ProvideUserRepository(gormDB, logLog) + userService := user2.ProvideUserService(userRepository, logLog, cacheCache, dispatcher) responseResponse := response.ProvideResponse() seederSeeder := seeder.ProvideSeeder(gormDB) sessionSession := session.ProvideSession(gormDB) schedule := scheduler.ProvideSchedule() - containerContainer := container.ProvideContainer(service, repository, cacheCache, configConfig, gormDB, responseResponse, logLog, dispatcher, seederSeeder, sessionSession, schedule) + containerContainer := container.ProvideContainer(service, repository, userService, userRepository, cacheCache, configConfig, gormDB, responseResponse, logLog, dispatcher, seederSeeder, sessionSession, schedule) return containerContainer }