init
This commit is contained in:
162
internal/service/app_lock_service.go
Normal file
162
internal/service/app_lock_service.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"accounting-app/internal/models"
|
||||
"accounting-app/internal/repository"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxFailedAttempts is the maximum number of failed login attempts before locking
|
||||
MaxFailedAttempts = 5
|
||||
// LockDuration is how long the app remains locked after max failed attempts
|
||||
LockDuration = 5 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAppLocked = errors.New("app is locked due to too many failed attempts")
|
||||
ErrAppLockInvalidPassword = errors.New("invalid password")
|
||||
ErrAppLockNotEnabled = errors.New("app lock is not enabled")
|
||||
ErrPasswordRequired = errors.New("password is required")
|
||||
)
|
||||
|
||||
// AppLockService handles business logic for app lock
|
||||
type AppLockService struct {
|
||||
repo *repository.AppLockRepository
|
||||
}
|
||||
|
||||
// NewAppLockService creates a new app lock service
|
||||
func NewAppLockService(repo *repository.AppLockRepository) *AppLockService {
|
||||
return &AppLockService{repo: repo}
|
||||
}
|
||||
|
||||
// GetStatus returns the current app lock status
|
||||
func (s *AppLockService) GetStatus(userID uint) (*models.AppLock, error) {
|
||||
return s.repo.GetOrCreate(userID)
|
||||
}
|
||||
|
||||
// SetPassword sets or updates the app lock password
|
||||
func (s *AppLockService) SetPassword(userID uint, password string) error {
|
||||
if password == "" {
|
||||
return ErrPasswordRequired
|
||||
}
|
||||
|
||||
// Hash the password using bcrypt
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appLock, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appLock.PasswordHash = string(hashedPassword)
|
||||
appLock.IsEnabled = true
|
||||
appLock.FailedAttempts = 0
|
||||
appLock.LockedUntil = nil
|
||||
appLock.LastFailedAttempt = nil
|
||||
|
||||
return s.repo.Update(appLock)
|
||||
}
|
||||
|
||||
// VerifyPassword verifies the provided password against the stored hash
|
||||
func (s *AppLockService) VerifyPassword(userID uint, password string) error {
|
||||
appLock, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !appLock.IsEnabled {
|
||||
return ErrAppLockNotEnabled
|
||||
}
|
||||
|
||||
// Check if app is currently locked
|
||||
if appLock.IsLocked() {
|
||||
return ErrAppLocked
|
||||
}
|
||||
|
||||
// Verify password
|
||||
err = bcrypt.CompareHashAndPassword([]byte(appLock.PasswordHash), []byte(password))
|
||||
if err != nil {
|
||||
// Password is incorrect, increment failed attempts
|
||||
return s.handleFailedAttempt(appLock)
|
||||
}
|
||||
|
||||
// Password is correct, reset failed attempts
|
||||
if appLock.FailedAttempts > 0 {
|
||||
if err := s.repo.ResetFailedAttempts(appLock); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleFailedAttempt handles a failed password attempt
|
||||
func (s *AppLockService) handleFailedAttempt(appLock *models.AppLock) error {
|
||||
now := time.Now()
|
||||
appLock.FailedAttempts++
|
||||
appLock.LastFailedAttempt = &now
|
||||
|
||||
// Lock the app if max attempts reached
|
||||
if appLock.FailedAttempts >= MaxFailedAttempts {
|
||||
lockUntil := now.Add(LockDuration)
|
||||
appLock.LockedUntil = &lockUntil
|
||||
}
|
||||
|
||||
if err := s.repo.IncrementFailedAttempts(appLock); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if appLock.FailedAttempts >= MaxFailedAttempts {
|
||||
return ErrAppLocked
|
||||
}
|
||||
|
||||
return ErrAppLockInvalidPassword
|
||||
}
|
||||
|
||||
// DisableLock disables the app lock (requires password verification first)
|
||||
func (s *AppLockService) DisableLock(userID uint) error {
|
||||
appLock, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appLock.IsEnabled = false
|
||||
appLock.FailedAttempts = 0
|
||||
appLock.LockedUntil = nil
|
||||
appLock.LastFailedAttempt = nil
|
||||
|
||||
return s.repo.Update(appLock)
|
||||
}
|
||||
|
||||
// ChangePassword changes the app lock password (requires old password verification first)
|
||||
func (s *AppLockService) ChangePassword(userID uint, oldPassword, newPassword string) error {
|
||||
// Verify old password first
|
||||
if err := s.VerifyPassword(userID, oldPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set new password
|
||||
return s.SetPassword(userID, newPassword)
|
||||
}
|
||||
|
||||
// GetRemainingLockTime returns the remaining lock time in seconds, or 0 if not locked
|
||||
func (s *AppLockService) GetRemainingLockTime(userID uint) (int, error) {
|
||||
appLock, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !appLock.IsLocked() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
remaining := time.Until(*appLock.LockedUntil)
|
||||
return int(remaining.Seconds()), nil
|
||||
}
|
||||
Reference in New Issue
Block a user