package service import ( "context" "time" "datarush/internal/auth/domain" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) const ( tokenExpiration = 24 * time.Hour bcryptCost = 10 ) type UserRepository interface { Create(ctx context.Context, user *domain.User) error GetByEmail(ctx context.Context, email string) (*domain.User, error) GetByID(ctx context.Context, id domain.ID) (*domain.User, error) Update(ctx context.Context, user *domain.User) error Delete(ctx context.Context, id domain.ID) error } type AuthService struct { repo UserRepository jwtSecret string } func NewAuthService(repo UserRepository, jwtSecret string) *AuthService { return &AuthService{ repo: repo, jwtSecret: jwtSecret, } } func (s *AuthService) SignUp(ctx context.Context, email, username, password string) (string, error) { // Check if user already exists _, err := s.repo.GetByEmail(ctx, email) if err == nil { return "", domain.ErrUserAlreadyExists } if err != domain.ErrUserNotFound { return "", err } // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) if err != nil { return "", err } // Create user user := &domain.User{ ID: domain.NewID(), Email: email, Username: username, Password: string(hashedPassword), } if err := s.repo.Create(ctx, user); err != nil { return "", err } // Generate token token, err := s.generateToken(user) if err != nil { return "", err } return token, nil } func (s *AuthService) SignIn(ctx context.Context, email, password string) (string, error) { // Get user by email user, err := s.repo.GetByEmail(ctx, email) if err != nil { if err == domain.ErrUserNotFound { return "", domain.ErrInvalidPassword } return "", err } // Check password if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { return "", domain.ErrInvalidPassword } // Generate token token, err := s.generateToken(user) if err != nil { return "", err } return token, nil } func (s *AuthService) ValidateToken(ctx context.Context, tokenString string) (*domain.UserWithoutPassword, error) { userID, err := s.parseToken(tokenString) if err != nil { return nil, err } user, err := s.repo.GetByID(ctx, userID) if err != nil { return nil, err } return &domain.UserWithoutPassword{ ID: user.ID, Email: user.Email, Username: user.Username, CreatedAt: user.CreatedAt, }, nil } func (s *AuthService) generateToken(user *domain.User) (string, error) { claims := jwt.MapClaims{ "user_id": user.ID.String(), "email": user.Email, "username": user.Username, "exp": time.Now().Add(tokenExpiration).Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString([]byte(s.jwtSecret)) if err != nil { return "", err } return tokenString, nil } func (s *AuthService) parseToken(tokenString string) (domain.ID, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return []byte(s.jwtSecret), nil }) if err != nil { return domain.ID{}, domain.ErrInvalidToken } if !token.Valid { return domain.ID{}, domain.ErrInvalidToken } claims, ok := token.Claims.(jwt.MapClaims) if !ok { return domain.ID{}, domain.ErrInvalidToken } userIDStr, ok := claims["user_id"].(string) if !ok { return domain.ID{}, domain.ErrInvalidToken } userID, err := domain.ParseID(userIDStr) if err != nil { return domain.ID{}, domain.ErrInvalidToken } return userID, nil }