Files
tm_back/internal/customer/admin_reset_password.go
T
2026-05-30 12:29:45 +03:30

123 lines
3.5 KiB
Go

package customer
import (
"context"
"errors"
"fmt"
"time"
"tm/pkg/audit"
"tm/pkg/password"
"tm/pkg/ratelimit"
"go.mongodb.org/mongo-driver/v2/bson"
"golang.org/x/crypto/bcrypt"
)
var (
errInvalidCustomerID = errors.New("invalid id")
errCustomerNotFound = errors.New("customer not found")
errCannotResetCustPassword = errors.New("cannot reset password for this user")
errCustResetPasswordFailed = errors.New("failed to reset password")
errCustResetPasswordRate = errors.New("too many password reset requests")
)
const customerPasswordResetRateLimit = 10
// AdminResetPassword generates a new password for the target customer.
func (s *customerService) AdminResetPassword(ctx context.Context, actorID, targetID string) (*AdminResetPasswordResponse, error) {
if _, err := bson.ObjectIDFromHex(targetID); err != nil {
return nil, errInvalidCustomerID
}
if err := s.checkPasswordResetRateLimit(ctx, actorID, "customer"); err != nil {
return nil, err
}
customer, err := s.repository.GetByID(ctx, targetID)
if err != nil {
if errors.Is(err, ErrCustomerNotFound) {
return nil, errCustomerNotFound
}
s.logger.Error("Failed to load customer for password reset", map[string]interface{}{
"error": err.Error(),
"customer_id": targetID,
})
return nil, errCustResetPasswordFailed
}
if customer.Status != CustomerStatusActive {
return nil, errCannotResetCustPassword
}
plainPassword, err := password.GenerateSecure()
if err != nil {
s.logger.Error("Failed to generate password", map[string]interface{}{
"error": err.Error(),
"customer_id": targetID,
})
return nil, errCustResetPasswordFailed
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(plainPassword), bcrypt.DefaultCost)
if err != nil {
s.logger.Error("Failed to hash generated password", map[string]interface{}{
"error": err.Error(),
"customer_id": targetID,
})
return nil, errCustResetPasswordFailed
}
if err := s.repository.UpdatePasswordReset(ctx, targetID, string(hashedPassword), actorID); err != nil {
if errors.Is(err, ErrCustomerNotFound) {
return nil, errCustomerNotFound
}
s.logger.Error("Failed to persist reset password", map[string]interface{}{
"error": err.Error(),
"customer_id": targetID,
})
return nil, errCustResetPasswordFailed
}
if err := s.authService.InvalidateUserSessions(targetID); err != nil {
s.logger.Error("Failed to invalidate customer sessions after password reset", map[string]interface{}{
"error": err.Error(),
"customer_id": targetID,
})
}
s.auditLogger.Log(ctx, audit.Entry{
ActorID: actorID,
TargetType: audit.TargetTypeCustomer,
TargetID: targetID,
Action: audit.ActionPasswordReset,
})
s.logger.Info("Customer password reset completed", map[string]interface{}{
"actor_id": actorID,
"customer_id": targetID,
})
return &AdminResetPasswordResponse{Password: plainPassword}, nil
}
func (s *customerService) checkPasswordResetRateLimit(ctx context.Context, actorID, resource string) error {
if s.redisClient == nil {
return nil
}
key := fmt.Sprintf("ratelimit:password_reset:%s:%s", actorID, resource)
allowed, err := ratelimit.Allow(ctx, s.redisClient, key, customerPasswordResetRateLimit, time.Minute)
if err != nil {
s.logger.Warn("Password reset rate limit check failed", map[string]interface{}{
"actor_id": actorID,
"error": err.Error(),
})
return nil
}
if !allowed {
return errCustResetPasswordRate
}
return nil
}