core.login_attempts наповнювалась на кожній спробі входу й читалась
рівно в одному місці — для стримування підбору. Ні ендпойнта, ні
сторінки. На питання «хто заходив у мій моніторинг» продукт мав
відповідь у базі й не мав способу її показати.
ГОЛОВНЕ БУЛА НЕ ВЕРСТКА. У таблиці немає tenant_id — саме тому ці
події й не в аудиті. Показати «як є» означало б віддати одному
кабінету спроби входу чужих людей. Прив'язка непряма: спроба ->
користувач -> членство, трьома шляхами одночасно (user_id, username,
email) в одному JOIN LATERAL з tenant_id усередині. Саме JOIN, а не
LEFT JOIN: без збігу рядок ПРИБИРАЄТЬСЯ, а не лишається без імені.
На бойових даних це не теорія: у власника немає пошти, тож за email
не прив'язується ЖОДНА з 94 спроб. Шлях через username дає 93.
Невдала спроба з неіснуючим логіном не належить нікому: рядком не
показується, але рахується числом — сигнал «логіни перебирають»
лишається, чужа людина в чужий кабінет не потрапляє.
Ізоляцію перевірено проти справжньої бази (TestLoginAttemptsTenantIsolation).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>