Полдень. Я стою в подъезде у своей двери и задерживаюсь на секунду дольше обычного: замок поворачивается только со второго раза, и я, как всякий, кто живёт с таким замком, уже не помню, когда это началось. Дальше будет история про дверь, которая не открылась совсем, и про проверку, которая на вопрос «работает ли ключ» отвечала «да», потому что спрашивала не то.
Что случилось тридцать первого октября
Тридцать первого октября, после взлома сервера сборки, меняли секреты. При этой смене новый ключ WorkOS выпустили в окружении Production — в том, где не было ни одного пользователя. Клиент, к которому этот ключ должен подходить, остался из Staging, где живут все сто восемь аккаунтов.
WorkOS пишет об этом прямо в документации: staging и production — два полностью раздельных окружения, а ключи, организации, подключения, пользователи, вебхуки и оформление привязаны к одному окружению и между ними не переносятся. Это не тонкость настройки, а устройство продукта.
С этого дня обмен кода в двух точках входа — при возврате с Google и при подтверждении кода из письма — отвечал отказом. В записи приложения это выглядит как строка invalid_client; для человека это страница входа с общей ошибкой и возврат на тот же экран. Аккаунт цел, пароль не менялся, письмо не пришло — а войти нельзя.
Проверка, которая отвечала «да»
При смене ключ проверяли запросом к списку пользователей: ответ 200 — ключ принят. Такой ответ даёт ключ любого окружения, потому что список пуст, а не потому, что он верный. Скрипт спрашивал «тебя пускают внутрь», а спросить надо было «ты от этого клиента».
Разница — в одном слове и в одном коде ошибки. Обмен заведомо неверного кода с идентификатором клиента из настроек отвечает либо invalid_grant — ключ от этого клиента, либо invalid_client — от чужого. Первый ответ означает, что дверь та; второй, что ключ от соседней.
Почему это читается человеком как «неверный пароль»: система именно так и отвечает — общей ошибкой входа. Ни одна страница не сказала «вход сломан, дело не в тебе, мы знаем». Это уже мой вывод, а не запись из журнала: в разборе описан отказ, а не то, что видел пользователь. Но сто восемь человек, у которых с той субботы перестал работать вход, не имели способа отличить одно от другого.
Двое с половиной суток, потом ещё три дня
Починили третьего ноября, руками на сервере: выпустили новый ключ в Staging, скрипт ответил invalid_grant и записал его, приложение пересобрали, проверка состояния ответила 200, строк об отказе входа после этого не появлялось. С этого момента вход работал — а правки, которая приводит скрипт в такое состояние, ещё не существовало.
Её открыли четвёртого ноября, в восемь с небольшим утра по Москве: пять файлов, из них один скрипт и тесты к нему, шестьдесят восемь добавленных строк и три удалённых. Судья в тестах простой: на старом скрипте три новые проверки падают, на новом проходят все сорок восемь тестов файла. Шестого ноября, в тринадцать четырнадцать, правка вошла в основную ветку в общей сборке из четырнадцати.
Итого: шесть дней от начала отказа до правки в основной ветке, из них двое с половиной — пока вход не работал. Три дня между починкой на сервере и починкой в репозитории — не халатность, а обычное следствие того, что сервер можно поправить руками раньше, чем откроется окно для правки.
Цена
Цена здесь не в железе и не в часах разработки. Двое с половиной суток и сто восемь человек, каждый из которых, скорее всего, решил, что дело в нём, и пошёл менять пароль. Плюс проверка, которая всё это время отвечала «да» — и была формально права: ключ действительно работал.
Скрипт теперь отказывается работать без идентификатора клиента в настройках, то есть без ответа на вопрос, от кого ключ. Стоит это отдельного условия и одной тестовой проверки на обмен, которого не должно быть. Дешевле двух с половиной суток — но это видно только после.
