Files
vallyenfail 80e5f318a9
All checks were successful
Deploy Smart Search Backend Test / deploy (push) Successful in 1m24s
add service
2026-01-18 01:48:46 +03:00

187 lines
5.4 KiB
Go

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(30 * 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(30 * 24 * time.Hour),
}
if err := s.sessionRepo.Create(ctx, session); err != nil {
return "", "", err
}
return accessToken, refreshToken, nil
}