|
| 1 | +package users |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "errors" |
| 6 | + "fmt" |
| 7 | + |
| 8 | + "github.com/android-sms-gateway/server/pkg/cache" |
| 9 | + "github.com/android-sms-gateway/server/pkg/crypto" |
| 10 | + "go.uber.org/zap" |
| 11 | +) |
| 12 | + |
| 13 | +type Service struct { |
| 14 | + users *repository |
| 15 | + |
| 16 | + cache *loginCache |
| 17 | + |
| 18 | + logger *zap.Logger |
| 19 | +} |
| 20 | + |
| 21 | +func NewService( |
| 22 | + users *repository, |
| 23 | + cache *loginCache, |
| 24 | + logger *zap.Logger, |
| 25 | +) *Service { |
| 26 | + return &Service{ |
| 27 | + users: users, |
| 28 | + |
| 29 | + cache: cache, |
| 30 | + |
| 31 | + logger: logger, |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +func (s *Service) Create(username, password string) (*User, error) { |
| 36 | + exists, err := s.users.Exists(username) |
| 37 | + if err != nil { |
| 38 | + return nil, err |
| 39 | + } |
| 40 | + |
| 41 | + if exists { |
| 42 | + return nil, fmt.Errorf("%w: %s", ErrExists, username) |
| 43 | + } |
| 44 | + |
| 45 | + passwordHash, err := crypto.MakeBCryptHash(password) |
| 46 | + if err != nil { |
| 47 | + return nil, fmt.Errorf("failed to hash password: %w", err) |
| 48 | + } |
| 49 | + |
| 50 | + user := &userModel{ |
| 51 | + ID: username, |
| 52 | + PasswordHash: passwordHash, |
| 53 | + } |
| 54 | + |
| 55 | + if err := s.users.Insert(user); err != nil { |
| 56 | + return nil, fmt.Errorf("failed to create user: %w", err) |
| 57 | + } |
| 58 | + |
| 59 | + return newUser(user), nil |
| 60 | +} |
| 61 | + |
| 62 | +func (s *Service) GetByUsername(username string) (*User, error) { |
| 63 | + user, err := s.users.GetByID(username) |
| 64 | + if err != nil { |
| 65 | + return nil, err |
| 66 | + } |
| 67 | + |
| 68 | + return newUser(user), nil |
| 69 | +} |
| 70 | + |
| 71 | +func (s *Service) Login(ctx context.Context, username, password string) (*User, error) { |
| 72 | + cachedUser, err := s.cache.Get(ctx, username, password) |
| 73 | + if err == nil { |
| 74 | + return cachedUser, nil |
| 75 | + } else if !errors.Is(err, cache.ErrKeyNotFound) { |
| 76 | + s.logger.Warn("failed to get user from cache", zap.String("username", username), zap.Error(err)) |
| 77 | + } |
| 78 | + |
| 79 | + user, err := s.users.GetByID(username) |
| 80 | + if err != nil { |
| 81 | + return nil, err |
| 82 | + } |
| 83 | + |
| 84 | + if err := crypto.CompareBCryptHash(user.PasswordHash, password); err != nil { |
| 85 | + return nil, fmt.Errorf("login failed: %w", err) |
| 86 | + } |
| 87 | + |
| 88 | + loggedInUser := newUser(user) |
| 89 | + if err := s.cache.Set(ctx, username, password, *loggedInUser); err != nil { |
| 90 | + s.logger.Error("failed to cache user", zap.String("username", username), zap.Error(err)) |
| 91 | + } |
| 92 | + |
| 93 | + return loggedInUser, nil |
| 94 | +} |
| 95 | + |
| 96 | +func (s *Service) ChangePassword(ctx context.Context, username, currentPassword, newPassword string) error { |
| 97 | + _, err := s.Login(ctx, username, currentPassword) |
| 98 | + if err != nil { |
| 99 | + return err |
| 100 | + } |
| 101 | + |
| 102 | + if err := s.cache.Delete(ctx, username, currentPassword); err != nil { |
| 103 | + return err |
| 104 | + } |
| 105 | + |
| 106 | + passwordHash, err := crypto.MakeBCryptHash(newPassword) |
| 107 | + if err != nil { |
| 108 | + return fmt.Errorf("failed to hash password: %w", err) |
| 109 | + } |
| 110 | + |
| 111 | + return s.users.UpdatePassword(username, passwordHash) |
| 112 | +} |
0 commit comments