С 1 июля 2025 года хранить персональные данные россиян в иностранных сервисах больше нельзя. Назрел вопрос, что же делать, например, с регистрациями на мероприятия, которые так удобно складывались из формочек Tilda напрямую в Google Sheets без СМС и регистрации. В статье расскажу, как я эту задачу решил с помощью бесплатного open-source сервиса Grist, платного, но копеечного Яндекс Облака и сомнительного Go кода.

Задача — натараканить self-hosted сервис для замены Google Sheets, чтобы также легко и непринужденно сохранять данные из формочек Tilda, но уже куда-то себе за пазуху. В идеале решение должно быть open-source. На то есть две причины:
1. Это бесплатно. Значит, будет просто согласовать и внедрить;
2. Гипотетические ИБ-шники смогут проверить код на все уязвимости.

Исходные требования к сервису 

Во-первых, сервис точно должен быть бесплатным, так как оплатить что-то из РФ сейчас довольно проблематично, а любое решение с припиской enterprise настолько дорогое, что на сайте даже цену не пишут.

Во-вторых, должен быть удобный API, так как иначе данные из формочек Tilda автоматизировано никуда не положишь. Удобный, потому что хочется работать как разработчик, а не как исследователь далеких документаций.

В-третьих, нужна авторизация в каком-то виде. Исходно проблема в том, что данные нужно хранить секурно, все дела. Бонусом хочется иметь возможность кастомизировать авторизацию. Нужно уметь использовать существующие в компании методы, чтобы не генерить логин-пароль для конкретного сервиса. Таким пользоваться никто просто не будет. Конкретно в нашем случае планировалось подключать старый добрый ldap.

В-четвертых, должен быть субъективно красивый UI и как можно больше кастомизации под себя.

В-пятых, нужна возможность разграничивать доступ на просмотр и редактирование табличек.

Кандидаты

Я рассматривал исключительно self-hosted и open-source варианты. Вот что нашел. 

  • Nocodb — выпадает сразу и в поисковике и GPT, если ищешь аналоги Google Sheets. Сразу не прошел по пункту с авторизацией, так как сторонняя авторизация доступна только в enterprise-версии.

  • Baserow — тоже самое, но по UI больше понравился.

  • OnlyOffice — пушка с точки зрения UI, ldap тоже есть. Поначалу показалось, что все прекрасно, но, побороздив океаны их сомнительно документации, я не нашел нормальных API-методов для взаимодействия с таблицами. Даже за деньги нет, хотя казалось бы.

  • Grist — вот этот касатик с ходу показался самым хорошим. В UI чуть странновато, но минут через 20 работа с таблицами становится понятной. Есть возможности писать формулы, но не как в Excel. По всем пунктам требований Grist проходил, кроме авторизации через ldap но там бабуля надвое сказала.

    Изучил и другие аналогичные сервисы. Не вижу смысла их упоминать из-за ранней стадии разработки и аналогичных проблем с авторизацией.

Погружение в Grist

Сам по себе кор Grist поднимать смысла нет. У него есть старший брат под названием grist-omnibus, который сразу в себя включает кор, сервис для авторизации – Dex, Traefik и некий Middleware, чтобы связать все это дело вместе.

Этот кофе 4-в-1 разворачивается в docker одной командой и с порога дает возможность авторизации через учетки Google и Microsoft, если прописать в нужном месте ключики. Под нужным местом подразумеваю dex.yaml, который дан как шаблон с репозиторием Omnibus. Переключение Dex на ldap не потребовало много усилий, так как у него есть ничего такая документация (и даже с примерами).

И вот, казалось бы, все отлично, все хорошо, все требы выполняются, можно бежать за пивом и звонить маме, чтобы рассказать, какой я молодец, но нет.

Вампирский нюанс

Dex — это просто сервис, который разными способами может или пустить тебя или не пустить. Grist же — самодостаточный сервис, и в целом может жить и работать без Dex. Но, правда, и без ldap тогда тоже. 

В самом Grist при первом запуске создается некая организация, в которой и будет храниться всякое разное в табличках. Странность заключается в том, что каждого нового пользователя нужно персонально пригласить в эту самую организацию. Без этого ничего нельзя сделать — access denied. Возможно, создатели вдохновлялись вампирами, или сами вампиры, тут не уверен.

Если совместить Grist и Dex, получается интересная ситуация: новый пользователь через ldap без проблем проходит Dex, но тут же упирается в то, что в Grist его никто не пригласил, и поэтому делать он ничего не может. Эта задача висит на форуме Grist с 2023 года, но до сих пор не была решена.

It’s not stupid if it works

Этот маленький вампирский нюанс с приглашениями отделял меня от сервиса, который в остальном полностью устраивал. Я решил разобраться, как устроен этот кофе 4-в-1. Может, есть некая лазейка между Dex и Grist, куда можно влететь и написать категорически дружелюбный код, который будет приглашать всех, кто прошел проверку через ldap?..

Оказывается, все это время в пачке grist-omnibus был traefik-forward-auth, который, по великому совпадению, как раз и находился ровно между Dex и Grist. Вишенкой на торте стало то, что код там написан на Golang и, по счастливой случайности, я на нем уже три года как что-то пишу.

Не придумал ничего умнее, как по API проверять в Grist наличие человека в рядах организации после успешного прохождения ldap. Если бедолаги в организации не нашлось, то с превеликой радостью его добавляем и даем полный доступ к работе с сервисом. Мое решение тут.  

Легкая неидеальность решения заключается в том, что для работы прослойки traefik-forward-auth нужно сообщить API token. Токен же можно получить, только авторизовавшись в Grist. Но без автоприглашения в организацию до конца авторизоваться в Grist нельзя. Попахивает «поправкой 22», но, слава богу, есть админский аккаунт, который при первом запуске становится сразу членом организации. И через него можно добыть желаемый ключ.

Решение

Для того, чтобы все работало, как задумывалось, нужно один раз сделать последовательность действий, детально описанных в моем fork grist-omnibus:

  1. Собрать docker image из fork’а.

  2. Запустить контейнер с Grist.

  3. Залогиниться с логин-паролем, которые были заданы при запуске.

  4. Зайти в настройки и запомнить API token.

  5. Остановить контейнер и запустить заново, но уже с API token в переменной окружения.

А чего там с Тilda-то?

Tilda поддерживает отправку Webhook по регистрации в формочке. Идем в «Настройки сайта» «Формы», там все настраивается максимально просто:

Опять же не стал сильно думать и в URL решил пихать название таблицы (на скрине название «masker»), куда было бы неплохо сохранять полученные данные в Grist. Понадобилось это решение ради того, чтобы подключать разные таблички к разным формам, а не лепить все в одно место. Из обидного — Tilda не дает в URL писать на русском название таблицы.

Если с Google Sheets можно было не переживать за потерю новых регистраций, то со своим Webhook это чуть сложнее. Оптимально использовать связку producer – consumer. 

Producer — это максимально глупый, но стойкий скрипт, единственная задача которого — положить полученные данные из формочки в очередь. Так как скрипт глупый, то для него оформили самый немощный сервер. Но скрипт должен быть стойкий, поэтому он в облаке Яндекса, чтобы была высокая доступность.

В том же облаке Яндекса организовали и Yandex Queue, который работает на библиотеках AWS.

Consumer, в свою очередь (каламбур намерен), — более сложный код, который читает  Yandex Queue и по полученным данным добавляет новые строки в таблички в Grist. В интеграции Google Sheets и Tilda достаточно было указать, в какой файлик класть данные, и далее все как по волшебству создавалось и заполнялось. Интеграция с Grist максимально мимикрирует под исходное решение, чтобы конечный пользователь в лице back-office мог безболезненно перейти на новый сервис и соблюсти законодательство.

В итоге получилась вот такая схема от регистрации до записи в таблице:

Если остались вопросы — отвечу в комментариях.