authentication service added

master
nima 2024-06-17 18:46:27 +03:30
parent 41d5fda851
commit 380a4570d9
4 changed files with 286 additions and 0 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}