package user 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 ( errInvalidUserID = errors.New("invalid id") errAdminUserNotFound = errors.New("admin not found") errCannotResetPassword = errors.New("cannot reset password for this user") errResetPasswordFailed = errors.New("failed to reset password") errResetPasswordRateHit = errors.New("too many password reset requests") errResetPasswordForbidden = errors.New("you are not allowed to reset this password") ) const adminPasswordResetRateLimit = 10 // AdminResetPassword generates a new password for the target admin user. func (s *userService) AdminResetPassword(ctx context.Context, actorID, actorRole, targetID string) (*AdminResetPasswordResponse, error) { if UserRole(actorRole) != UserRoleAdmin { return nil, errResetPasswordForbidden } if _, err := bson.ObjectIDFromHex(targetID); err != nil { return nil, errInvalidUserID } if err := s.checkPasswordResetRateLimit(ctx, actorID, "admin"); err != nil { return nil, err } user, err := s.repository.GetByID(ctx, targetID) if err != nil { if errors.Is(err, ErrUserNotFound) { return nil, errAdminUserNotFound } s.logger.Error("Failed to load user for password reset", map[string]interface{}{ "error": err.Error(), "target_id": targetID, }) return nil, errResetPasswordFailed } if user.Status != UserStatusActive { return nil, errCannotResetPassword } plainPassword, err := password.GenerateSecure() if err != nil { s.logger.Error("Failed to generate password", map[string]interface{}{ "error": err.Error(), "target_id": targetID, }) return nil, errResetPasswordFailed } 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(), "target_id": targetID, }) return nil, errResetPasswordFailed } if err := s.repository.UpdatePasswordReset(ctx, targetID, string(hashedPassword), actorID); err != nil { if errors.Is(err, ErrUserNotFound) { return nil, errAdminUserNotFound } s.logger.Error("Failed to persist reset password", map[string]interface{}{ "error": err.Error(), "target_id": targetID, }) return nil, errResetPasswordFailed } if err := s.authService.InvalidateUserSessions(targetID); err != nil { s.logger.Error("Failed to invalidate user sessions after password reset", map[string]interface{}{ "error": err.Error(), "target_id": targetID, }) } s.auditLogger.Log(ctx, audit.Entry{ ActorID: actorID, TargetType: audit.TargetTypeAdmin, TargetID: targetID, Action: audit.ActionPasswordReset, }) s.logger.Info("Admin password reset completed", map[string]interface{}{ "actor_id": actorID, "target_id": targetID, }) return &AdminResetPasswordResponse{Password: plainPassword}, nil } func (s *userService) 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, adminPasswordResetRateLimit, 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 errResetPasswordRateHit } return nil }