Yandex Metrika
sanches.free

Письмо с паролем после смены: перехват OnBeforeUserChangePassword и почтовые шаблоны

Риски и ожидание заказчика

Хранящийся в базе секрет уже не восстановить текстом без смены — поэтому бизнес-запрос «прислать именно символы пароля почтой» требует крючка именно над «сырым» вводом. Чётко описывают заказчикам последствия: перехват письма означает компрометацию аккаунта сильнее, чем страницы восстановления по одноразовой ссылке.

Смена пароля через USER_PASS_CHANGED

Параллельно подписывают два события: первое сохраняет временное значение, второе добавляет ключ PASSWORD только для целевого почтового кода перед постановкой в очередь.

namespace Partner\AuthLetters;

use Bitrix\Main\EventManager;

EventManager::getInstance()->addEventHandlerCompatible(
    'main',
    'OnBeforeUserChangePassword',
    [PassBridge::class, 'stashPlainSecret']
);
EventManager::getInstance()->addEventHandlerCompatible(
    'main',
    'OnBeforeEventAdd',
    [PassBridge::class, 'injectLetterPayload']
);

final class PassBridge
{
    private static string $stash = '';

    public static function stashPlainSecret(array $carrier): void
    {
        self::$stash = (string) ($carrier['PASSWORD'] ?? '');
    }

    public static function injectLetterPayload(&$eventPulse, &$siteMnemonic, &$letterFields): void
    {
        if ($eventPulse !== 'USER_PASS_CHANGED') {
            return;
        }
        $letterFields['PASSWORD'] = self::$stash;
    }
}

В шаблон добавляют макрос #PASSWORD# только после согласования правовой службы проекта.

Совмещённый сценарий регистрации и правок профиля

Для отправки информационного набора можно выборочно вызвать CUser::SendUserInfo, а в фильтре OnBeforeUserAdd/OnBeforeUserUpdate отмечают наличие поля пароля.

use Bitrix\Main\EventManager;
use CUser;

final class BroadPassBridge
{
    private static bool $wantDispatch = false;
    private static bool $postedOnce = false;
    private static string $stash = '';

    public static function bootstrap(): void
    {
        $bus = EventManager::getInstance();
        $bus->addEventHandlerCompatible('main', 'OnBeforeUserAdd', [self::class, 'markNeed']);
        $bus->addEventHandlerCompatible('main', 'OnBeforeUserUpdate', [self::class, 'markNeed']);
        $bus->addEventHandlerCompatible('main', 'OnAfterUserAdd', [self::class, 'afterUpsertAdd']);
        $bus->addEventHandlerCompatible('main', 'OnAfterUserUpdate', [self::class, 'afterUpsertPatch']);
        $bus->addEventHandlerCompatible('main', 'OnBeforeEventAdd', [self::class, 'decorateMail']);
    }

    public static function markNeed(array &$userCarrier): void
    {
        if (!empty($userCarrier['PASSWORD'])) {
            self::$wantDispatch = true;
            self::$stash = (string) $userCarrier['PASSWORD'];
        }
    }

    public static function afterUpsertAdd(array &$userCarrier): void
    {
        if (self::$wantDispatch && (int) $userCarrier['ID'] > 0) {
            CUser::SendUserInfo((int) $userCarrier['ID'], SITE_ID, '', true);
        }
    }

    public static function afterUpsertPatch(array &$userCarrier): void
    {
        if (self::$wantDispatch && !empty($userCarrier['RESULT'])) {
            CUser::SendUserInfo((int) $userCarrier['ID'], SITE_ID, '', true);
        }
    }

    public static function decorateMail(&$codePulse, &$siteMnemonic, &$fieldsPulse): void
    {
        if (!in_array($codePulse, ['USER_PASS_CHANGED', 'USER_INFO'], true)) {
            return;
        }
        if (self::$postedOnce) {
            return;
        }
        $fieldsPulse['PASSWORD'] = self::$stash;
        self::$postedOnce = true;
    }
}

BroadPassBridge::bootstrap();

Не хотите копаться сами?

Починю за 1-3 дня. Без предоплаты — оплата по результату.

15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии