package authentication import ( "context" "crypto/rand" "encoding/base32" "errors" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "golang.org/x/crypto/bcrypt" ) var ( ErrInvalidPassword = errors.New("incorrect password") ErrUserNotFound = errors.New("user not found") ErrExpiredToken = errors.New("token expired") ErrRefreshTokenNotFound = errors.New("refresh token not found") // i.e login again ) type LoginSuccess struct { UserId int64 Role domain.Role RfToken string BranchId int64 } func (s *Service) Login(ctx context.Context, email, phone string, password string) (LoginSuccess, error) { user, err := s.userStore.GetUserByEmailPhone(ctx, email, phone) if err != nil { return LoginSuccess{}, err } err = matchPassword(password, user.Password) if err != nil { return LoginSuccess{}, err } refreshToken, err := generateRefreshToken() if err != nil { return LoginSuccess{}, err } err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{ Token: refreshToken, UserID: user.ID, CreatedAt: time.Now(), ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second), }) if err != nil { return LoginSuccess{}, err } return LoginSuccess{ UserId: user.ID, Role: user.Role, RfToken: refreshToken, BranchId: user.BranchID, }, nil } func (s *Service) RefreshToken(ctx context.Context, refToken string) error { token, err := s.tokenStore.GetRefreshToken(ctx, refToken) if err != nil { return err } if token.Revoked { return ErrRefreshTokenNotFound } if token.ExpiresAt.Before(time.Now()) { return ErrExpiredToken } // newRefToken, err := generateRefreshToken() // if err != nil { // return "", err // } // err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{ // Token: newRefToken, // UserID: token.UserID, // CreatedAt: time.Now(), // ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second), // }) return nil } func (s *Service) Logout(ctx context.Context, refToken string) error { token, err := s.tokenStore.GetRefreshToken(ctx, refToken) if err != nil { return err } if token.Revoked { return ErrRefreshTokenNotFound } if token.ExpiresAt.Before(time.Now()) { return ErrExpiredToken } return s.tokenStore.RevokeRefreshToken(ctx, refToken) } func matchPassword(plaintextPassword string, hash []byte) error { err := bcrypt.CompareHashAndPassword(hash, []byte(plaintextPassword)) if err != nil { switch { case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword): return ErrInvalidPassword default: return err } } return err } func generateRefreshToken() (string, error) { randomBytes := make([]byte, 32) _, err := rand.Read(randomBytes) if err != nil { return "", err } plaintext := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(randomBytes) return plaintext, nil }