6.3 KiB
M-2 фронт — виджет Yandex SmartCaptcha на регистрации
Дата: 21.06.2026. Кодовая фраза стены: «роутер-наставник».
Статус: дизайн одобрен владельцем. Следующий шаг — план → реализация.
Связь: завершает M-2 (backend-spec). Бэкенд-драйвер YandexSmartCaptchaVerifier готов (коммит 12eace36); нужен реальный фронт-виджет, выдающий настоящий токен.
Проблема
RegisterView.vue:70 шлёт фейковый captcha_token: 'dev-captcha-stub' от чекбокса «не робот». С реальным бэкенд-драйвером (CAPTCHA_DRIVER=yandex) этот стаб Yandex отвергнет → регистрация сломается. Нужен настоящий виджет SmartCaptcha, отдающий валидный токен.
Интеграция (сверено по докам Yandex 2025):
- Скрипт:
https://smartcaptcha.cloud.yandex.ru/captcha.js?render=onload&onload=<fn>(defer). window.smartCaptcha.render(container, { sitekey, callback, hl })→ возвращаетwidgetId;callback(token: string)отдаёт токен.window.smartCaptcha.reset(widgetId)— сброс после ошибки.
Решение
1. app/resources/js/components/auth/SmartCaptchaWidget.vue (новый)
Изолированный компонент, v-model = строка-токен.
Props/emits: modelValue: string; emit('update:modelValue', token). expose({ reset }).
Логика:
sitekey = import.meta.env.VITE_YANDEX_SMARTCAPTCHA_SITEKEY ?? ''.- sitekey задан (прод):
onMounted→ еслиcaptcha.jsещё не в DOM, добавить<script defer src="…/captcha.js?render=onload&onload=__lidSmartCaptchaOnload">; иначе сразу рендерить.- В
__lidSmartCaptchaOnload(или при уже-готовомwindow.smartCaptcha):widgetId = window.smartCaptcha.render(container, { sitekey, hl: 'ru', callback: (t) => emit('update:modelValue', t) }). reset()→window.smartCaptcha.reset(widgetId)+emit('update:modelValue', '').onUnmounted→window.smartCaptcha.destroy?.(widgetId)(best-effort).- Контейнер —
<div ref="container" />.
- sitekey пуст (dev/local/тесты): fallback — Vuetify
<v-checkbox>«Подтвердите, что вы не робот»; отмечен →emit('update:modelValue', 'dev-captcha-stub'), снят →''.reset()→ снять. Так dev и существующие сценарии не ломаются.
2. app/resources/js/views/auth/RegisterView.vue (правка)
- Удалить локальный чекбокс
captchaAccepted+ стаб-логику (строки ~23, 161-171, 68-70). const captchaToken = ref('');const captchaRef = ref().- В шаблоне на месте чекбокса:
<SmartCaptchaWidget v-model="captchaToken" ref="captchaRef" :error-messages="errors.captcha_token" />(error прокинуть в fallback-чекбокс; у реального виджета ошибки нет). canSubmit→ заменитьcaptchaAccepted.valueнаcaptchaToken.value.length > 0.register({ …, captcha_token: captchaToken.value }).- В
catchпосле ошибки:captchaRef.value?.reset()(одноразовый токен Yandex истёк/использован — нужен свежий).
3. app/resources/js/vite-env.d.ts (новый)
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_YANDEX_SMARTCAPTCHA_SITEKEY?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
interface SmartCaptchaRenderParams {
sitekey: string;
callback?: (token: string) => void;
hl?: string;
invisible?: boolean;
}
interface SmartCaptcha {
render(container: HTMLElement | string, params: SmartCaptchaRenderParams): number;
reset(widgetId?: number): void;
destroy?(widgetId?: number): void;
execute?(widgetId?: number): void;
}
interface Window {
smartCaptcha?: SmartCaptcha;
__lidSmartCaptchaOnload?: () => void;
}
4. app/.env.example (правка)
Рядом с VITE_APP_NAME: VITE_YANDEX_SMARTCAPTCHA_SITEKEY= (на проде — клиентский ключ ysc1_…).
Границы / YAGNI
- Видимая капча (не invisible) — привычно, как текущий «не робот», проще.
- Без бэкенда/схемы — только фронт; бэкенд-драйвер уже готов.
- Fallback на стаб при пустом sitekey — чтобы dev/local/CI и существующие тесты/прогоны не падали.
- CSP: домен
smartcaptcha.cloud.yandex.ruнужно добавить вscript-src/connect-src/frame-srcпрод-nginx CSP — операционный шаг владельца при активации (вне фронт-кода), отметить в деплое.
Деплой (шаг владельца)
- Создать капчу в Yandex Cloud → взять клиентский (
ysc1_) и серверный (ysc2_) ключи. - Прод-
.env:VITE_YANDEX_SMARTCAPTCHA_SITEKEY=ysc1_…(фронт-сборка) +YANDEX_SMARTCAPTCHA_SERVER_KEY=ysc2_…+CAPTCHA_DRIVER=yandex. - Расширить CSP в nginx доменом
smartcaptcha.cloud.yandex.ru(script-src/connect-src/frame-src). - Пересобрать фронт (
npm run build) +config:cache. Капча активна end-to-end.
Verification (честно)
- vue-tsc
npm run type-check= 0. npm run build(vite) — сборка проходит, бандл собран.- eslint/prettier — чисто.
- vitest в проекте сломан → юнит-тестами компонент не прогнать; корректность fallback-ветки и типов покрыта type-check + build. Реальный виджет проверяется на проде после ключа.