package auth import ( "context" "errors" "fmt" "time" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "golang.org/x/crypto/bcrypt" ) var ( ErrEmailExists = errors.New("email already registered") ErrInvalidCreds = errors.New("invalid email or password") ErrUserNotFound = errors.New("user not found") ErrInvalidUserID = errors.New("invalid user ID") ) type Service struct { repo *Repository jwtSecret []byte jwtExp time.Duration } func NewService(repo *Repository, jwtSecret string, jwtExp time.Duration) *Service { return &Service{ repo: repo, jwtSecret: []byte(jwtSecret), jwtExp: jwtExp, } } func (s *Service) Register(ctx context.Context, req RegisterRequest) (*UserPublic, error) { existing, err := s.repo.FindByEmail(ctx, req.Email) if err != nil && !errors.Is(err, mongo.ErrNoDocuments) { return nil, fmt.Errorf("failed to check existing user: %w", err) } if existing != nil { return nil, ErrEmailExists } hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { return nil, fmt.Errorf("failed to hash password: %w", err) } user := &User{ Username: req.Username, Email: req.Email, PasswordHash: string(hash), } if err := s.repo.Create(ctx, user); err != nil { return nil, fmt.Errorf("failed to create user: %w", err) } public := NewUserPublic(user) return &public, nil } func (s *Service) Login(ctx context.Context, req LoginRequest) (*AuthResponse, error) { user, err := s.repo.FindByEmail(ctx, req.Email) if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { return nil, ErrInvalidCreds } return nil, fmt.Errorf("failed to find user: %w", err) } if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil { return nil, ErrInvalidCreds } token, err := GenerateToken(user.ID.Hex(), user.Email, s.jwtSecret, s.jwtExp) if err != nil { return nil, fmt.Errorf("failed to generate token: %w", err) } return &AuthResponse{ Token: token, User: NewUserPublic(user), }, nil } func (s *Service) GetUserByID(ctx context.Context, userID string) (*UserPublic, error) { id, err := bson.ObjectIDFromHex(userID) if err != nil { return nil, ErrInvalidUserID } user, err := s.repo.FindByID(ctx, id) if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { return nil, ErrUserNotFound } return nil, fmt.Errorf("failed to find user: %w", err) } public := NewUserPublic(user) return &public, nil }