package service import ( "context" "time" "git.techease.ru/Smart-search/smart-search-back/internal/model" "git.techease.ru/Smart-search/smart-search-back/internal/repository" "git.techease.ru/Smart-search/smart-search-back/pkg/crypto" "git.techease.ru/Smart-search/smart-search-back/pkg/errors" "git.techease.ru/Smart-search/smart-search-back/pkg/jwt" "github.com/jackc/pgx/v5" ) type authService struct { userRepo repository.UserRepository sessionRepo repository.SessionRepository inviteRepo repository.InviteRepository txManager *repository.TxManager jwtSecret string cryptoHelper *crypto.Crypto } func NewAuthService(userRepo repository.UserRepository, sessionRepo repository.SessionRepository, inviteRepo repository.InviteRepository, txManager *repository.TxManager, jwtSecret, cryptoSecret string) AuthService { return &authService{ userRepo: userRepo, sessionRepo: sessionRepo, inviteRepo: inviteRepo, txManager: txManager, jwtSecret: jwtSecret, cryptoHelper: crypto.NewCrypto(cryptoSecret), } } func (s *authService) Login(ctx context.Context, email, password, ip, userAgent string) (accessToken, refreshToken string, err error) { emailHash := s.cryptoHelper.EmailHash(email) user, err := s.userRepo.FindByEmailHash(ctx, emailHash) if err != nil { return "", "", err } passwordHash := crypto.PasswordHash(password) if user.PasswordHash != passwordHash { return "", "", errors.NewBusinessError(errors.AuthInvalidCredentials, "Invalid email or password") } accessToken, err = jwt.GenerateAccessToken(user.ID, s.jwtSecret) if err != nil { return "", "", errors.NewInternalError(errors.InternalError, "failed to generate access token", err) } refreshToken, err = jwt.GenerateRefreshToken(user.ID, s.jwtSecret) if err != nil { return "", "", errors.NewInternalError(errors.InternalError, "failed to generate refresh token", err) } session := &model.Session{ UserID: user.ID, AccessToken: accessToken, RefreshToken: refreshToken, IP: ip, UserAgent: userAgent, ExpiresAt: time.Now().Add(24 * time.Hour), } if err := s.sessionRepo.Create(ctx, session); err != nil { return "", "", err } return accessToken, refreshToken, nil } func (s *authService) Refresh(ctx context.Context, refreshToken string) (string, error) { session, err := s.sessionRepo.FindByRefreshToken(ctx, refreshToken) if err != nil { return "", err } newAccessToken, err := jwt.GenerateAccessToken(session.UserID, s.jwtSecret) if err != nil { return "", errors.NewInternalError(errors.InternalError, "failed to generate access token", err) } if err := s.sessionRepo.UpdateAccessToken(ctx, refreshToken, newAccessToken); err != nil { return "", err } return newAccessToken, nil } func (s *authService) Validate(ctx context.Context, accessToken string) (int, error) { claims, err := jwt.ValidateToken(accessToken, s.jwtSecret) if err != nil { return 0, errors.NewBusinessError(errors.AuthInvalidToken, "Invalid or expired token") } if claims.Type != "access" { return 0, errors.NewBusinessError(errors.AuthInvalidToken, "Token is not an access token") } userID, err := jwt.GetUserIDFromToken(accessToken, s.jwtSecret) if err != nil { return 0, errors.NewBusinessError(errors.AuthInvalidToken, "Invalid user ID in token") } isValid, err := s.sessionRepo.IsAccessTokenValid(ctx, accessToken) if err != nil { return 0, err } if !isValid { return 0, errors.NewBusinessError(errors.AuthInvalidToken, "Token has been revoked or expired") } return userID, nil } func (s *authService) Logout(ctx context.Context, accessToken string) error { return s.sessionRepo.RevokeByAccessToken(ctx, accessToken) } func (s *authService) Register(ctx context.Context, email, password, name, phone string, inviteCode int64, ip, userAgent string) (accessToken, refreshToken string, err error) { _, err = s.inviteRepo.FindActiveByCode(ctx, inviteCode) if err != nil { return "", "", err } emailHash := s.cryptoHelper.EmailHash(email) existingUser, err := s.userRepo.FindByEmailHash(ctx, emailHash) if existingUser != nil { return "", "", errors.NewBusinessError(errors.EmailAlreadyExists, "email already registered") } if err != nil && !errors.IsBusinessError(err, errors.UserNotFound) { return "", "", err } user := &model.User{ Email: email, EmailHash: emailHash, PasswordHash: crypto.PasswordHash(password), Phone: phone, UserName: name, Balance: 0, } err = s.txManager.WithTx(ctx, func(tx pgx.Tx) error { if err := s.userRepo.CreateTx(ctx, tx, user); err != nil { return err } if err := s.inviteRepo.DecrementCanBeUsedCountTx(ctx, tx, inviteCode); err != nil { return err } return nil }) if err != nil { return "", "", err } accessToken, err = jwt.GenerateAccessToken(user.ID, s.jwtSecret) if err != nil { return "", "", errors.NewInternalError(errors.InternalError, "failed to generate access token", err) } refreshToken, err = jwt.GenerateRefreshToken(user.ID, s.jwtSecret) if err != nil { return "", "", errors.NewInternalError(errors.InternalError, "failed to generate refresh token", err) } session := &model.Session{ UserID: user.ID, AccessToken: accessToken, RefreshToken: refreshToken, IP: ip, UserAgent: userAgent, ExpiresAt: time.Now().Add(24 * time.Hour), } if err := s.sessionRepo.Create(ctx, session); err != nil { return "", "", err } return accessToken, refreshToken, nil }