authentication service added
parent
41d5fda851
commit
380a4570d9
|
@ -0,0 +1,93 @@
|
|||
package authentication
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"netina/models"
|
||||
"time"
|
||||
|
||||
"netina/validation"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type GenerateCode struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewGenerateCode(db *gorm.DB) *GenerateCode {
|
||||
return &GenerateCode{DB: db}
|
||||
}
|
||||
|
||||
// Generate a random 6-digit code
|
||||
func generateCode() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return fmt.Sprintf("%06d", rand.Intn(1000000))
|
||||
}
|
||||
|
||||
// CreateLoginCode generates a login code and stores it in the database
|
||||
func (s *GenerateCode) CreateLoginCode(phoneNumber string) (string, error) {
|
||||
var code string
|
||||
var isUnique bool
|
||||
|
||||
// Try to generate a unique code
|
||||
for !isUnique {
|
||||
code = generateCode()
|
||||
isUnique, _ = s.isCodeUnique(code)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
expiresAt := now.Add(2 * time.Minute)
|
||||
|
||||
loginCode := &models.LoginCode{
|
||||
PhoneNumber: phoneNumber,
|
||||
Code: code,
|
||||
CreatedAt: now,
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
|
||||
if err := validation.ValidateStruct(loginCode); err != nil {
|
||||
return "" , err
|
||||
}
|
||||
|
||||
if err := s.DB.Create(loginCode).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return code, nil
|
||||
}
|
||||
|
||||
// Check if the generated code is unique
|
||||
func (s *GenerateCode) isCodeUnique(code string) (bool, error) {
|
||||
var loginCode models.LoginCode
|
||||
if err := s.DB.Where("code = ?", code).First(&loginCode).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// VerifyLoginCode verifies the login code and checks if it is still valid
|
||||
func (s *GenerateCode) VerifyLoginCode(phoneNumber, code string) error {
|
||||
var loginCode models.LoginCode
|
||||
|
||||
// Find the login code
|
||||
if err := s.DB.Where("phone_number = ? AND code = ?", phoneNumber, code).First(&loginCode).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("code not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if the code has expired
|
||||
if time.Now().After(loginCode.ExpiresAt) {
|
||||
return errors.New("code expired")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package authentication
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"netina/database"
|
||||
Role_repository "netina/repositories/role"
|
||||
User_repository "netina/repositories/user"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// JWTClaims defines the structure of JWT claims.
|
||||
type JWTClaims struct {
|
||||
ID uint `json:"id"`
|
||||
Role string `json:"role"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
// Load the environment variables from the .env file
|
||||
func loadEnv() {
|
||||
err := godotenv.Load("./config/.env")
|
||||
if err != nil {
|
||||
panic("Error loading .env file")
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateJWT generates a new JWT token.
|
||||
func GenerateJWT(claims *JWTClaims) (string, error) {
|
||||
loadEnv()
|
||||
secretKey := os.Getenv("SECRET_KEY")
|
||||
if secretKey == "" {
|
||||
return "", errors.New("SECRET_KEY is not set in the environment variables")
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenString, err := token.SignedString([]byte(secretKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
// ValidateJWT validates a given JWT token.
|
||||
func ValidateJWT(tokenString string) (*JWTClaims, error) {
|
||||
loadEnv()
|
||||
secretKey := os.Getenv("SECRET_KEY")
|
||||
if secretKey == "" {
|
||||
return nil, errors.New("SECRET_KEY is not set in the environment variables")
|
||||
}
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("unexpected signing method")
|
||||
}
|
||||
return []byte(secretKey), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
// GenerateClaims creates JWT claims.
|
||||
func GenerateClaims(userID uint) (*JWTClaims , error) {
|
||||
db := database.Db()
|
||||
role_query := Role_repository.RoleQueryRepository{DB: &db}
|
||||
user_query := User_repository.UserQueryRepository{DB: &db}
|
||||
|
||||
user , err := user_query.GetUser(userID)
|
||||
if err != nil {
|
||||
return nil , err
|
||||
}
|
||||
|
||||
role , err := role_query.GetRole(user.Role_id)
|
||||
if err != nil {
|
||||
return nil , err
|
||||
}
|
||||
|
||||
cliams := &JWTClaims{
|
||||
ID: user.User_id,
|
||||
Role: role.Name,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(time.Hour * 2).Unix(),
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
return cliams , nil
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package authentication
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"netina/database"
|
||||
"netina/models"
|
||||
u "netina/queries"
|
||||
user_repository "netina/repositories/user"
|
||||
sms "netina/services"
|
||||
"netina/validation"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func RequestCode(c echo.Context) error {
|
||||
db := database.Db()
|
||||
|
||||
gc := NewGenerateCode(&db)
|
||||
|
||||
phoneNumber := new(models.CreatePhoneNumber)
|
||||
if err := c.Bind(phoneNumber); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request payload"})
|
||||
}
|
||||
|
||||
if err := validation.ValidateStruct(phoneNumber); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
loginCode ,err := gc.CreateLoginCode(phoneNumber.PhoneNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sms.Sms(phoneNumber.PhoneNumber , loginCode)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func Login(c echo.Context) error {
|
||||
db := database.Db()
|
||||
code := new(models.LoginCode)
|
||||
if err := c.Bind(code); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validation.ValidateStruct(code); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
gc := NewGenerateCode(&db)
|
||||
|
||||
if gc.VerifyLoginCode(code.PhoneNumber , code.Code) == nil {
|
||||
ur := user_repository.UserQueryRepository{DB: &db}
|
||||
userHandller := u.GetUserByPhoneNumberHandler{Repository: ur}
|
||||
user , err := userHandller.Handle(code.PhoneNumber)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusNotFound , "user not found")
|
||||
}
|
||||
|
||||
token , err:= GenerateTokenForUser(user.User_id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusAccepted , token)
|
||||
|
||||
} else {
|
||||
return c.JSON(http.StatusBadRequest , "invalid Request")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package authentication
|
||||
|
||||
func GenerateTokenForUser(userID uint) (string, error) {
|
||||
claims, err := GenerateClaims(userID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
token, err := GenerateJWT(claims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func ValidateUserToken(tokenString string) (*JWTClaims, error) {
|
||||
claims, err := ValidateJWT(tokenString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return claims, nil
|
||||
}
|
Loading…
Reference in New Issue