-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from prodemmi/feature/implement-auth
[FEATURE] Implement otp and jwt authentication
- Loading branch information
Showing
25 changed files
with
484 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.