Multiple implementations of the same back-end application. The aim is to provide quick, side-by-side comparisons of different technologies (languages, frameworks, libraries) while preserving consistent business logic across all implementations.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

126 lines
3.3 KiB

package auth
import (
"context"
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt/v4"
"autostore/internal/application/interfaces"
"autostore/internal/domain/entities"
)
var (
ErrInvalidCredentials = errors.New("invalid credentials")
ErrInvalidToken = errors.New("invalid token")
ErrTokenExpired = errors.New("token expired")
)
type JWTAuthService struct {
userRepo interfaces.IUserRepository
secretKey string
logger interfaces.ILogger
}
func NewJWTAuthService(
userRepo interfaces.IUserRepository,
secretKey string,
logger interfaces.ILogger,
) *JWTAuthService {
return &JWTAuthService{
userRepo: userRepo,
secretKey: secretKey,
logger: logger,
}
}
func (s *JWTAuthService) Authenticate(ctx context.Context, username string, password string) (string, error) {
user, err := s.userRepo.FindByUsername(ctx, username)
if err != nil {
s.logger.Error(ctx, "Failed to find user by username", "error", err, "username", username)
return "", ErrInvalidCredentials
}
if user == nil {
s.logger.Warn(ctx, "User not found", "username", username)
return "", ErrInvalidCredentials
}
if !user.ValidatePassword(password) {
s.logger.Warn(ctx, "Invalid password", "username", username)
return "", ErrInvalidCredentials
}
token, err := s.generateToken(user)
if err != nil {
s.logger.Error(ctx, "Failed to generate token", "error", err, "username", username)
return "", err
}
s.logger.Info(ctx, "User authenticated successfully", "username", username, "userID", user.GetID().String())
return token, nil
}
func (s *JWTAuthService) ValidateToken(ctx context.Context, tokenString string) (bool, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(s.secretKey), nil
})
if err != nil {
s.logger.Warn(ctx, "Token validation failed", "error", err)
return false, nil
}
return token.Valid, nil
}
func (s *JWTAuthService) GetUserIDFromToken(ctx context.Context, tokenString string) (string, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(s.secretKey), nil
})
if err != nil {
s.logger.Warn(ctx, "Failed to parse token", "error", err)
return "", ErrInvalidToken
}
if !token.Valid {
s.logger.Warn(ctx, "Invalid token")
return "", ErrInvalidToken
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
s.logger.Warn(ctx, "Invalid token claims")
return "", ErrInvalidToken
}
userID, ok := claims["sub"].(string)
if !ok {
s.logger.Warn(ctx, "User ID not found in token claims")
return "", ErrInvalidToken
}
return userID, nil
}
func (s *JWTAuthService) generateToken(user *entities.UserEntity) (string, error) {
claims := jwt.MapClaims{
"sub": user.GetID().String(),
"username": user.GetUsername(),
"iss": "autostore",
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Hour * 24).Unix(), // 24 hours expiration
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(s.secretKey))
}