Сегодня мы рассмотрим настройку GraphQL API без необходимости написания схемы GraphQL с помощью PostGraphile.

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

Затем мы настроим Postgres Row Level Locks, чтобы пользователи могли получать доступ или изменять данные только в своей организации.

И, наконец, мы создадим очень простое приложение React, которое может запрашивать данные с помощью Apollo.

Что мы строим?

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

Мы сосредоточимся в первую очередь на бэкенде и оставим красивые интерфейсы на другой день.

Разветвляемый начальный шаблон

Самая крутая часть всего этого проекта — его расширяемость. Вы можете разветвить стартовый шаблон, отредактировать и применить файл schema.sql, ввести несколько переменных среды, и у вас будет полностью безопасный многопользовательский GraphQL API и приложение Next.js, где вы можете писать свои запросы.

Любые изменения, внесенные вами в схему, автоматически обновят ваш GraphQL API.

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

Делаем начальную схему

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

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

Запуск PostGraphile как библиотеки

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

Для настройки нашего проекта мы устанавливаем Express, PostGraphile и pg-simlify-inflector, который PostGraphile рекомендует для всех новых проектов.

$ yarn add express postgraphile @graphile-contrib/pg-simplify-inflector

Затем мы создадим файл index.js, который создаст для нас полный API GraphQL на основе нашей схемы:

Вы также можете указать дополнительные флаги вместе с appendPlugins, например graphiql: true или enhanceGraphiql: true, которые предоставляют вам удобные пользовательские интерфейсы для создания запросов и просмотра ваша схема:

Добавление мультитенантной аутентификации в наш GraphQL API

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

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

В новый файл propelauth.js мы экспортируем все наши функции аутентификации:

Затем мы можем импортировать именно то, что нам нужно, в данном случае промежуточное ПО requireOrgMember:

Это создает промежуточное ПО Express, которому требуется токен доступа в запросе (что-то, что мы получим, когда создадим интерфейс), и оно гарантирует, что токен доступа действителен. Кроме того, он убедится, что этот пользователь является членом организации, указанной в заголовке x-org-id, в противном случае запрос будет отклонен.

Что произойдет, если мы обновим наше промежуточное ПО PostGraphile таким образом?

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

Безопасность Postgres на уровне строк

Поскольку запросы GraphQL могут быть довольно сложными, наложение ограничений на сам запрос будет очень сложной задачей. Вместо этого мы можем гораздо проще применять ограничения на уровне Postgres.

Однако для этого нам нужен какой-то способ передать метаинформацию (например, идентификатор пользователя и идентификатор организации) в Postgres. К счастью, в PostGraphile есть простой способ сделать это:

Все без точки (роль) интерпретируется как параметр Postgres, поэтому наш запрос будет выполняться ролью member в Postgres.

Для других настроек (propelauth.user_id и propelauth.org_id) мы можем получить их в наших запросах с помощью current_setting('propelauth.org_id')

Давайте разберем, что нам нужно для защиты этой таблицы:

  1. Это включает RLS для таблицы, позволяя нам ограничить доступ.
  2. Мы создаем новую роль в Postgres member и явно предоставляем им доступ к wiki_page. Позже мы увидим, как мы можем использовать разные роли для разных шаблонов доступа.
  3. Политика, состоящая из двух важных компонентов:
  4. USING позволяет нам определить, доступен ли доступ к существующей строке.
  5. WITH CHECK позволяет определить, следует ли создать новую или обновленную строку. Вот почему у нас есть дополнительная проверка, чтобы убедиться, что они могут указать только свой собственный идентификатор пользователя.

Удивительно, но это все, что нам нужно, чтобы все заработало. Давайте теперь протестируем наш код.

Создание простого приложения React с провайдером Authentication GraphQL

$ npx create-react-app postgraphile-frontend
$ cd postgraphile-frontend
$ yarn add @propelauth/react @apollo/client graphql
$ yarn start

Мы используем @propelauth/react, чтобы проверить, вошли ли наши пользователи в систему, и @apollo/client и graphql, чтобы отправлять запросы.

Следуя документации для React, мы настроим наш RequiredAuthProvider. Он управляет информацией об аутентификации и всеми другими компонентами, получаемыми от него. Мы используем RequiredAuthProvider, поскольку он автоматически перенаправляет незарегистрированных пользователей. Это помогает упростить остальную часть приложения, потому что мы знаем, что имеем дело только с вошедшими в систему пользователями. Если вам нужна возможность обработки незарегистрированных пользователей, вы можете использовать AuthProvider.

Затем нам нужно настроить ApolloProvider, который позволит нам выполнять запросы GraphQL в любом месте нашего приложения. Однако мы хотим добавить токен доступа нашего пользователя и организацию, членом которой он является (на которую, как мы видели, полагается серверная часть). Мы сделаем это, заключив ApolloProvider в наш собственный AuthenticatedApolloProvider.

А потом возвращаемся и настраиваем и этого провайдера:

Теперь всякий раз, когда мы делаем запрос GraphQL, он автоматически включает в себя как токен доступа нашего пользователя, так и организацию, указанную в orgHelper.getSelectedOrg(). Если наш пользователь выйдет из системы, он будет автоматически перенаправлен обратно на экран входа в систему.

Выполнение аутентифицированных вызовов GraphQL

Мы не будем тратить слишком много времени на внешний интерфейс, но давайте посмотрим, как получить все вики-страницы в нашей организации:

Без каких-либо данных это просто вернет пустой список:

Для нашего следующего теста давайте зайдем в нашу базу данных и вставим данные для выдуманной организации:

INSERT INTO wiki_page (org_id, author_user_id, title, body)
VALUES ('nonsense', 'alsononsense', 'hello', 'you should not see this');

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

Если мы отправим эту форму, она вызовет мутацию GraphQL, которая передается в наш заголовок, тело, userId и orgId. Поскольку данные находятся в нашей организации, мы можем просмотреть эти данные из нашего исходного запроса:

Но ждать! Что произойдет, если злоумышленник укажет в запросе другой orgId? Вы можете проверить это, жестко закодировав организацию в приведенном выше фрагменте кода, и вы получите эту ошибку:

Подтягиваем нашу схему

Один небольшой момент, на который стоит обратить внимание, заключается в том, что PostGraphile предоставляет нам GraphQL API для всей нашей схемы. Если у вас есть какая-либо конфиденциальная информация в вашей базе данных, вам нужно быть очень осторожным, раскрывая ее.

PostGraphile предлагает использовать пространства имен для скрытия конфиденциальных таблиц. Вы также можете использовать представления для предоставления подмножеств этих конфиденциальных данных.

Однако, если вы посмотрите на нашу схему выше, у нас действительно есть досадная ошибка: наши пользователи могут указывать свои собственные идентификаторы или временные метки createdAt. Чтобы решить эту проблему, PostGraphile включает умные теги, чтобы мы могли убедиться, что ID/createdAt не могут быть созданы или изменены нашими пользователями.

Краткая заметка об управлении доступом на основе ролей (RBAC)

В приведенном выше примере мы создали только одну роль в Postgres, а затем создали политику, которая применялась ко всем. Однако мы могли бы создать несколько ролей в Postgres, чтобы они соответствовали нашим ролям в PropelAuth.

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

Краткое содержание

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

  • API-интерфейсы GraphQL, автоматически сгенерированные на основе нашей схемы Postgres.
  • Многопользовательская система аутентификации пользователей, которая позволяет нашим пользователям отправлять приглашения, устанавливать соединения SAML, управлять ролями и т. д.
  • Postgres RLS обеспечивает многопользовательскую авторизацию на уровне базы данных.
  • Приложение React, в котором мы можем написать запрос GraphQL в любом месте, и он будет автоматически привязан к организации и аутентифицирован.

Вы можете разветвить стартовый шаблон, отредактировать и применить файл schema.sql, ввести несколько переменных среды, и у вас будет полностью безопасный многопользовательский API GraphQL и приложение Next.js, где вы можете писать свои запросы.