fix(mera/b2c): публичный лэндинг больше не показывает «Не удалось проверить доступ» #2910

Merged
lekss361 merged 1 commit from fix/mera-public-host-bypass into main 2026-08-15 19:17:10 +00:00
Owner

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

Замер на живой странице:

404  /trade-in/api/v1/me                            ×3
404  /trade-in/api/v1/trade-in/support/anon/unread

Причина

Caddy на коротком домене переписывает путь внутренне (rewrite, не redir): посетитель остаётся на meraocenka.ru/, а Next получает /trade-in/mera-public. Поэтому на сервере usePathname() даёт /mera-public и bypass срабатывает, а после гидратации клиент видит / — путь из адресной строки — и bypass молча отваливается. Монтируется GuardedRoute, летит GET /me, а на этом хосте Caddy намеренно не проксирует /trade-in/api/* → 404 → NoAccessScreen. Отсюда и «внизу страницы»: ошибка появлялась после загрузки контента.

Второй запрос (support/anon/unread) — следствие того же: не сработал bypass, смонтировался закрытый контур со своим виджетом чата.

В шапке RouteGuard прямо стояло допущение:

Домен/rewrite для неё настраиваются отдельным PR — на поведение гварда это не влияет, он смотрит на путь.

Оно сломалось ровно в тот момент, когда домен появился. Заменено разбором факта, чтобы следующий читатель не унаследовал ту же ошибку.

Правка

Четыре коротких адреса публичного домена (/, /oferta, /refund, /privacy) добавлены в bypass — точным совпадением, отдельной веткой.

Префиксную проверку для / применять нельзя: "/v2/admin".startsWith("/") истинно, то есть один такой элемент в списке сделал бы публичным всё приложение. Это главный риск правки, поэтому он закрыт тестом, а не комментарием.

Адреса существуют только на публичном домене: на gendsgn.ru их ловит catch-all 404, а / отдаёт server-side redirect на /v2 из next.config — приложение по этому пути не рендерится вовсе, bypass ему не достаётся.

Проверено

Тест пинит оба инварианта: короткие адреса публичны И закрытый контур (/v2, /admin/*, /history, /scrapers/*, /login) — нет. Мутация «точное совпадение → префиксное»:

мутация  → 2 failed, 2 passed
   × НЕ делает публичным закрытый контур — главный риск правки
   × не пропускает пути, лишь начинающиеся с публичного имени
откат    → 4 passed

Плюс vitest 33 passed (7 файлов), tsc --noEmit, isolation guard 31 файл.

Отдельно, не баг

Кнопка «Узнать цену» ничего не открывает намеренно: PUBLIC_ESTIMATE_ENABLED = false, анонимный расчёт закрыт RBAC (#2895). Форма при этом не молчит — показывает блок «Расчёт по адресу мы ещё не открыли для всех». Вероятно, он терялся на фоне экрана ошибки; проверю на проде после выкатки.

Продовый баг, замеченный владельцем: внизу `meraocenka.ru` посетителю показывался экран проверки доступа из закрытого контура. **Замер на живой странице:** ``` 404 /trade-in/api/v1/me ×3 404 /trade-in/api/v1/trade-in/support/anon/unread ``` ## Причина Caddy на коротком домене переписывает путь **внутренне** (`rewrite`, не `redir`): посетитель остаётся на `meraocenka.ru/`, а Next получает `/trade-in/mera-public`. Поэтому на сервере `usePathname()` даёт `/mera-public` и bypass срабатывает, а после гидратации клиент видит `/` — путь из адресной строки — и bypass молча отваливается. Монтируется `GuardedRoute`, летит `GET /me`, а на этом хосте Caddy намеренно не проксирует `/trade-in/api/*` → 404 → `NoAccessScreen`. Отсюда и «внизу страницы»: ошибка появлялась после загрузки контента. Второй запрос (`support/anon/unread`) — следствие того же: не сработал bypass, смонтировался закрытый контур со своим виджетом чата. В шапке `RouteGuard` прямо стояло допущение: > Домен/rewrite для неё настраиваются отдельным PR — на поведение гварда это не влияет, он смотрит на путь. Оно сломалось ровно в тот момент, когда домен появился. Заменено разбором факта, чтобы следующий читатель не унаследовал ту же ошибку. ## Правка Четыре коротких адреса публичного домена (`/`, `/oferta`, `/refund`, `/privacy`) добавлены в bypass — **точным совпадением, отдельной веткой**. Префиксную проверку для `/` применять нельзя: `"/v2/admin".startsWith("/")` истинно, то есть один такой элемент в списке сделал бы публичным всё приложение. Это главный риск правки, поэтому он закрыт тестом, а не комментарием. Адреса существуют только на публичном домене: на `gendsgn.ru` их ловит catch-all 404, а `/` отдаёт server-side redirect на `/v2` из `next.config` — приложение по этому пути не рендерится вовсе, bypass ему не достаётся. ## Проверено Тест пинит оба инварианта: короткие адреса публичны И закрытый контур (`/v2`, `/admin/*`, `/history`, `/scrapers/*`, `/login`) — нет. Мутация «точное совпадение → префиксное»: ``` мутация → 2 failed, 2 passed × НЕ делает публичным закрытый контур — главный риск правки × не пропускает пути, лишь начинающиеся с публичного имени откат → 4 passed ``` Плюс `vitest` 33 passed (7 файлов), `tsc --noEmit`, isolation guard 31 файл. ## Отдельно, не баг Кнопка «Узнать цену» ничего не открывает намеренно: `PUBLIC_ESTIMATE_ENABLED = false`, анонимный расчёт закрыт RBAC (#2895). Форма при этом не молчит — показывает блок «Расчёт по адресу мы ещё не открыли для всех». Вероятно, он терялся на фоне экрана ошибки; проверю на проде после выкатки.
lekss361 added 1 commit 2026-08-15 19:07:28 +00:00
fix(mera/b2c): публичный лэндинг больше не показывает «Не удалось проверить доступ»
All checks were successful
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 54s
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
6348f51ff4
На meraocenka.ru посетитель видел внизу страницы экран проверки доступа из
закрытого контура. Замер на проде: три вызова GET /trade-in/api/v1/me → 404
плюс подтянутый следом клиент чата поддержки.

Причина. Caddy на коротком домене переписывает путь ВНУТРЕННЕ (rewrite, не
redir): посетитель остаётся на meraocenka.ru/, а Next получает
/trade-in/mera-public. Поэтому SSR видит /mera-public и bypass срабатывает, а
после гидратации usePathname() возвращает «/» — путь из адресной строки — и
bypass отваливается. Монтируется GuardedRoute, летит запрос к /me, а на этом
хосте Caddy намеренно не проксирует /trade-in/api/* → 404 → NoAccessScreen.

В шапке RouteGuard прямо стояло допущение «домен/rewrite настраиваются
отдельным PR — на поведение гварда это не влияет, он смотрит на путь». Оно
сломалось ровно в тот момент, когда домен появился; заменено разбором факта.

Правка: четыре коротких адреса публичного домена (/, /oferta, /refund,
/privacy) добавлены в bypass ТОЧНЫМ совпадением, отдельной веткой. Префиксную
проверку для «/» применять нельзя — startsWith("/") истинно для любого пути,
то есть один такой элемент сделал бы публичным всё приложение. Эти адреса
существуют только на публичном домене: на gendsgn.ru их ловит catch-all 404, а
«/» отдаёт server-side redirect на /v2 из next.config, то есть приложение по
этому пути не рендерится вовсе.

Тест на оба инварианта: короткие адреса публичны И закрытый контур (/v2,
/admin/*, /history, /scrapers/*, /login) — нет. Мутация «точное совпадение →
префиксное» роняет два теста из четырёх, проверено прогоном.

Проверено: vitest 33 passed (7 файлов), tsc --noEmit, isolation guard 31 файл.
lekss361 merged commit 7537b54dbd into main 2026-08-15 19:17:10 +00:00
lekss361 deleted branch fix/mera-public-host-bypass 2026-08-15 19:17:10 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2910
No description provided.