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