Почему ПО промежуточного слоя может быть неподходящей абстракцией для ваших политик данных.

Написание совместимых и безопасных бэкендов — сложная задача. Вот что делают разработчики, чтобы сделать эту проблему более управляемой

Каждый фронтенд-разработчик знает или, по крайней мере, подозревает, что бэкэнд — это сложно. Но с чего бы это?

В бизнес-логике бэкендов нет ничего принципиально сложного: в конце концов, это просто код и алгоритмы. Fizzbuzz есть fizzbuzz: он одинаково бесполезен как для бэкенда, так и для интерфейса.

Что усложняет бэкэнд?

Бэкенды сложны, потому что они имеют дело непосредственно с вашими данными, а к данным предъявляются требования, выходящие за рамки манипулирования данными, которые вы хотите выполнить. Например:.

  • Кто может получить доступ к этим данным и при каких обстоятельствах?
  • Где данные могут храниться (и кэшироваться) в соответствии с такими нормами, как GDPR?
  • Соответствует ли хранение данных всем нормам и требованиям бизнеса? Например, шифруется ли он, когда и где он должен быть?

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

Способы кодирования серверных частей, совместимых с политикой

Реализация политик данных может быть сложной задачей для разработчиков, не имеющих непосредственного опыта в этой области. А цена ошибок огромна. Но если ты должен это сделать… ты должен это сделать! Итак, какие стратегии можно использовать для удовлетворения этих требований?

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

У этого есть очевидные недостатки: по мере развития вашего кода трудно убедиться, что из-за человеческой ошибки политики данных не будут пропущены. Это не только утомительно, но и трудно обнаружить ошибки, особенно в больших базах кода, и в какой-то момент данные выйдут за пределы политики.

Является ли промежуточное ПО решением?

Другой альтернативой является использование общего шаблона в большинстве фреймворков: промежуточного программного обеспечения. Промежуточное ПО — это код, который выполняется до и/или после вызова конечной точки и может сразу же отклонить запрос, а также преобразовать выходные данные в соответствии с политиками данных.

Проблема в том, что промежуточное ПО связано с вашей конкретной конечной точкой. Кроме того, чтобы обеспечить стекирование, они непрозрачны и работают на границе запрос/ответ.

Но в большинстве случаев вам действительно нужно защитить данные в их текущем контексте. Вы хотите иметь возможность выражать такие вещи, как

  • «Если пользователь правильно аутентифицирован, пусть он видит свои собственные сообщения. В противном случае спрячьте их».
  • «Если эти личные данные связаны с европейским пользователем, храните их в Европе»
  • «Убедитесь, что эти данные проходят через зашифрованное соединение»

Простые и сложные конечные точки

Для конечной точки, которая когда-либо касается только одного фрагмента данных, например, API для простого извлечения пользователя по идентификатору, сопоставление политики данных с конечной точкой по-прежнему управляемо: авторизация конечной точки означает авторизацию доступа к этому пользователю!

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

он также может работать не очень хорошо: промежуточное ПО, особенно если оно должно быть посредником между многими типами данных, может не иметь возможности преобразовывать запросы. Он непрозрачно работает с запросами и ответами.

Из-за этого промежуточное ПО имеет тенденцию играть роль в таких вещах, как аутентификация пользователя и предоставление общего доступа к конечной точке. Но эффективный и безопасный доступ к данным по-прежнему осуществляется на уровне бизнес-логики, и разработчики должны убедиться, что запросы правильно фильтруются, маскируются и преобразуются.

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

Нарушение абстракции промежуточного программного обеспечения

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

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

Но чтобы сохранить высокие баллы Lighthouse, часто предпочтительнее ломать абстракцию.

Другим примером является соответствие данных: такие базы данных, как Yugabyte, Mongo Atlas, CockroachDB и другие, позволяют вам устанавливать определенные свойства ваших строк, чтобы убедиться, что они хранятся в определенном географическом месте и соответствуют законам, таким как GDPR, CCPA и т. д.

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

Как политики данных могут преобразовать вашу серверную часть

Гораздо лучший подход — позволить разработчику писать политики данных, которые интегрируются непосредственно в серверную часть, но при этом являются достаточно простыми и изолированными, чтобы их можно было проверить в любое время, давая разработчикам уверенность в том, что правила применяются.

Одним из примеров этого является функция политики данных в ChiselStrike, которая теперь доступна в качестве функции предварительного просмотра в версии 0.13. В ChiselStrike база данных абстрагирована, и все в первую очередь основано на TypeScript. Например, вот как можно смоделировать сущность пользователя:

Политики данных в ChiselStrike знают, какой пользователь вошел в систему. Это делается с помощью стандартных JWT, которые вы можете получить в таких службах, как Okta или Clerk. После правильной расшифровки JWT будет выглядеть так:

Затем вы можете создать исходный файл с именем, соответствующим защищаемому объекту (policies/User.ts) со следующим содержимым:

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

В этом примере никто никогда не сможет создать объект User, если его userId не соответствует содержимому JWT. Это будет применяться автоматически при каждом создании пользователя. Аналогичные политики также доступны для операций чтения и обновления.

Политики также можно использовать для фильтрации экземпляров сущностей, которые будут возвращены запросом:

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

Политики данных могут делать гораздо больше:

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

Что теперь?

Первоначальная версия этой работы доступна в ChiselStrike 0.13, и мы работаем над ее стабилизацией. Вся работа над этим находится в открытом доступе, в нашем репозитории GitHub.

Есть мысли по этому поводу? Мы хотели бы услышать, как вы планируете использовать это в нашем Discord или Twitter.