Как я oauth proxy навайбокодил
- вторник, 15 апреля 2025 г. в 00:00:05
Чем хорош TRMNL -- так это возможностью выводить что-то своё за считанные минуты.
С помощью "Private Plugins" можно взять ссылку, что отдаёт JSON (недавно добавили поддержку XML и сырого текста), рисуем простой шаблон на HTML с Liquid, готово! Для пущей динамики возможе неще и javascript, что позволяет выводить графики.
Можно опубликовать для других пользователей, можно брать опубликованное другими и либо использовать как есть -- либо подправить под себя. Раздолье!
График сахара -- пять минут, используя приведённый в документации шаблон графиков и два взмаха жаваскриптом для конвертации данных.
График шагов... А вот тут сложнее. Сервер позволяет простые GET и POST запросы, можно сунуть что-то в заголовки, URL, тело запроса и т.п. -- но вот хранить состояние между запросами нельзя, то есть в лоб OAuth2 не реализуешь. Оно и понятно: они не очень хотят хранить данные авторизации для всех.
Что ж. Решим задачу.
Дано:
есть платформа, которая может запрашивать урлы, но не хранит состояние;
есть источники данных, которые отдают по REST что попросишь -- но требуют OAuth2 авторизации;
OAuth2 запросы авторизуется простыми ключами, но ключи требуют изначальной авторизации для выдачи прав, а потом постоянной ротации, чтоб поддерживать в актуальном состоянии;
нет желания поднимать и обслуживать виртуалку где бы то ни было;
нагрузка АЖ запрос раз в 15 минут. Может, если вдруг захочется, десяток запросов.
Решение:
берём бесплатный CloudFlare;
рисуем прокси, который будет проверять входящие запросы по статическому токену (без авторотации), и отсылать запросы дальше подписав динамическим токеном;
рисуем крон, который будет поддерживать динамические токены в актуальном состоянии.
Так как задача простая как пробка, что справится кто угодно, почему бы не попытаться заставить это сделать машину?
Так что дополнительное развлечение: будем вайбокодить.
Устанавливаем курсор. Запускаем. Пытаемся "перенести" плагины -- получается нерабочий ужас.
Сносим курсор, чистим, устанавливаем заново, говорим оставить только рекомендованные плагины. Пытаемся доставить нужные, не находим в маркете. Просто забиваем на это дело, "и так сойдёт" и будем ковыряться с деплоем вручную.
Активируем триальную лицензию (заодно узнаем, стоит ли за неё платить). Создаём новый Git репозиторий, устанавливаем nodejs и npm внутри wsl и на винде.
В общем и целом, часть команд он запускать может, интеграция с Wsl сломана, интеграция через ssh внутрь wsl работает но как-то косо. Я просто открыл папку проекта и держу открытый отдельный terminator, благо монитор достаточно широкий.
Всё, более-менее всё дышит, можно начинать вайбокодить.
Попытка "в лоб" сформулировать задачу как бы я её задавал человеку оказалась катастрофически провальной, но это я знал заранее, так как уже игрался с сеточками для поиска решений.
Дао "заставить одну сеть составить план, который скармливать потом курсору" я не постиг: я сломался на попытке получить план, с которым я был бы согласен. Этот навык набивать можно будет позднее, перейдём к практике.
Кодить будем по шагам, и план простой: создать админку => добавить oauth авторизацию и хранение => добавить прокси => добавить ротацию => деплоим.
(1) Говорим агенту, что мы пишем под CloudFlare workers на Javascript и просим создать скелет c двумя endpoint'ами: для настройки и для админки, админке нужен логин-пароль который хранится в KV базе.
Он создаёт какой-то код, который оч похож на правду. Восторгаемся пассажу про
<div class="credentials-info">
<p>Development credentials:</p>
<p>Username: admin</p>
<p>Password: password123</p>
</div>
Но игнорируем, так как нигде внутри кода это не захардкожено.
Спрашиваем как его запустить для пощупать (я ничего не знаю про CloudFlare), его ответ не работает (wrangler в лоб не запускается ни на винде ни в wsl), вопрос в gemini объяснил недостающее, ставлю пакеты, через npx wrangler dev
запускаю, не работает копируют ошибку про KV курсору, он прави тnamespace на local, оно запускается.
Прошу создать .gitignore, он его создаёт, и вроде даже похоже на правду. Коммитим, первый шаг готов.
(2) Просим в админке создать форму создания нового приложения и список приложений, в котором есть кнопка удалить и отредактировать. Для каждого приложения задаются его название, client id, пути для авторизации и API. Client ID и название редактировать нельзя, пути - можно.
Тут я познакомился с его подходом "простите, что-то не получилось, я сейчас еще раз". Раза с третьего он смог интегрировать своё решение в редактор. Выглядит так, что он иногда пытается сгенерировать дифф, а иногда полный файл.
В любом случае, он создал нечто, от чего я был в полном восторге: он добавил управление сессией! Сам !После проверки логина и пароля, он создал сессию и больше не надо вводить пароль, всё работает!
Очень внимательно полюбуйтесь на это. Пароль больше не надо вводить, да. Нужен только логин, который сохраняется в куку, очень удобно.
Просим переделать на использование рандомной куки, которая будет жить минут 15. Переделывает.
Спрашиваем "а не может ли KV проверять срок жизни?"... Может, говорит, и переделывает.
Уф, теперь ничего сходу страшного не обнаруживается, коммитим.
(3) Пришла пора проверить его догадливость. Просим для каждого приложения добавить кнопку oauth авторизации, которую сохранять в базе приложения.
И он справляется! Сам добавляет хранение токенов, отдельный endpoint для callback'а при авторизации, генератор рандомного кода для верификации, причем реюзнул тот же что для сессий сделал на прошлом шаге. Ручные эксперименты с fitbit web api потребовали только прописать scope не "read write" а "activity".
Вот только редиректы -- не работают. Несколько жалоб с копированием ошибки таки привели к кое-как рабочему решению.
Коммитим, пока не поломалось.
(4) Просим сделать scope параметром для приложения. Коммитим.
(5) Так как сервера часто проверяют callback url и требуют его ввести при настройке, просим вывести его на форму.
Он выводит и добавляет, как он сам сказал, для удобства, кнопочку копирования. Коммитим.
(6) Просим для приложений добавить уникальные прокси токены, с возможностью перегенерировать их если вдруг захочется -- и вывести их для каждого приложения. Коммитим.
(7) Добрались до второй части нашего балета: GET прокси. Просим сделать так, чтоб /get/
эндпоинт брал первое слово после /get/ как имя приложения, а всё что дальше -- использовал как путь для API. Проверял, что в запросе передали правильный proxyToken
и если да -- добавлял остаток ссылки у API адресу и пересылал подписав текущим OAuth ключом.
Неплохо справляется с первого раза. Вторым запросом просим сохранить любые GET параметры запроса (кроме proxyToken). Коммитим.
// Construct the target URL with all query parameters except proxyToken
const targetUrl = new URL(path, app.apiPath);
// Copy all query parameters except proxyToken
for (const [key, value] of url.searchParams.entries()) {
if (key !== 'proxyToken') {
targetUrl.searchParams.set(key, value);
}
}
(Неплохо же для первого раза в жизни?)
(8) В смысле -- просим создать workflow, который будет рефрешить ключи для всех приложений, у которых осталось меньше 3х часов свежести. И добавить крон, который раз в 2 часа будет запускать воркфлоу.
Он создаёт новый файл с новым кодом, который на мой неопытный глаз вообще выглядит не так, как документация говорит. Даём ему ссылку на документацию и просим сделать согласно актуальной спеки. Он переделывает, код похож, а вот техническая обёртка вокруг -- нет. Просим еще раз переделать, сложив всё вместе. Переделывает, поломав и новый и старый код. А я не коммитил! К счастью, после известной истории, "откатить текущий шаг" доступен после каждой рекомендации (причем иногда при редактировании запроса -- срабатывает, так как какой-то из хоткеев редактирования текста работает как "отменить всё"). Откатываем, сливаем файлы вручную. Просим еще раз привести к актуальному виду (дав ссылку). Приводит, поломав всего ничего по дороге. Чиним вручную.
Вручную локальный код работает, workflow локально тестировать невозможно. Фиг с ним, код на первый взгляд делает что просили -- коммитим.
(9) Поскольку нельзя нормально Workflow протестировать локально, несмотря на то, что документация говорит обратное (о чем разные сетки врут по-разному: те, что умеют искать в гугле -- находят что нельзя, те, что не умеют -- как только ни галлюцинируют) -- деплоим.
Спрашиваем как, пробуем, не работает, гуглим, находим, побеждаем. Обновляем вранглер, просим еще раз обновить код (ранее вручную смерженный в один файл) согласно последней документации (даём ссылку на неё) и еще раз полируем после него вручную (он запутался выдал смесь TypeScript и JavaScript).
Просим как быть с реквизитами (созданные для key-value), после трёх итераций сдаёмся, нагугливаем ответ.
(Ответ, кстати, мне не нравится: надо wrangler.toml
внести в .gitignore, и положить пример под другим именем. То есть мержить и держить их в актуальном состоянии -- боль -- но неважно, это делается на "раз поставил и забыл".)
Окей, как только деплой прошел на ура и приложение ожило -- обновляем инструкции и коммитим.
(10) Первое, что бросилось в глаза на тестах: в процессе авторизации приложения слетает сессия админки. Курсор сломался решить проблему: предлагал пучок разных решений, но так и не догадался, что проблема в secure cookie, которые он сам же и проставил с самого начала. Но даже когда я ему объяснил в чем дело -- не понял. Пришлось прямо сказать чтоб воткнул в середине процесса восстановление куки и редирект через HTML (то есть не через 302 Location а через 200 + meta-refresh). В процессе хоть вспомнил зачем HTML редирект делался :))).
(11) Пароли плейнтекстом.
Нет, я всё понимаю, но не надо так. Попросил его. Без обиняков. Просто, мол, сделай пароли солёными.
Он решил пойти простым путём, и если есть логин но нет пароля -- первое же использование задаёт пароль. Что примечательно: он сам догадался поправить README, но форму, которая говорит про password123
не стал менять. А я тем более не буду: не барское это дело! Я уже за ним вручную линтеры прогонял и копипастил.
(12) Позже выяснилось, что если Fitbit пофиг, то вот Netatmo таки требует проверки clientSecret
. Просим добавить clientSecret
к приложениям -- он добавляет. Просим пописать еще и к рефрешу. Добавляет, радуемся.
Итого, за несколько присестов в выходные удалось создать желаемое: trmnl-oauth-proxy.
Приложение позволяет быстро развернуть на бесплатном cloudflare тарифе персональный oauth прокси, так что можно создавать и авторизовывать источники данных для своих нужд и легко выводить их в своём TRMNL терминале.
Задача решена? Да -- плюс. Умеет читать документацию, если ему её показать. Тоже плюс.
Как средство решения задач -- еще слишком юно. Требует постоянный глаз да глаз. Норовит пропустить что-то. Добавить от себя. Решить ну очень не правильным методом. Забывает, что попросили. Истории нет. Часто пытается перегенерировать всё заново, ломая по дороге то, что уже починил. Объём контекста ограничен. Путается в показаниях.
Короче очень юный джун. Но, к сожалению, джун, который не учится. Когда-нибудь, когда он станет лучше... Возможно. Но сейчас -- нет. Работы по проверке за ним больше, чем экономия времени.
Основной плюс AI для программирования для себя: избавиться от чистого листа. Попросить скелет / набросок решения. Выкинуть потом оттуда всё и переписать под себя зачастую быстрее, чем пытаться совсем писать с нуля. Ну, так я уже могу -- и тут курсор не единственное решение. Хоть они и были более-менее первыми -- таки платить ему мне не за чем.
Но сколько сеток -- столько мнений, так что у читателя может сложиться своё мнение. С удовольствием послушаю в комментария о другом личном опыте!