Secure Data Connect с авторизацией и аттестацией

Firebase Data Connect обеспечивает надежную безопасность на стороне клиента с помощью:

  • Авторизация мобильных и веб-клиентов
  • Индивидуальный контроль авторизации на уровне запросов и мутаций
  • Аттестация приложений с помощью Firebase App Check .

Data Connect расширяет эту безопасность с помощью:

  • Авторизация на стороне сервера
  • Проект Firebase и безопасность пользователей Cloud SQL с помощью IAM.

Авторизация клиентских запросов и мутаций

Data Connect полностью интегрирован с Firebase Authentication , поэтому вы можете использовать подробные данные о пользователях, которые получают доступ к вашим данным (аутентификация), в своем проекте для определения того, к каким данным эти пользователи могут получить доступ (авторизация).

Data Connect предоставляет директиву @auth для запросов и мутаций, которая позволяет вам установить уровень аутентификации, необходимый для авторизации операции. В этом руководстве представлена ​​директива @auth с примерами .

Кроме того, Data Connect поддерживает выполнение запросов, встроенных в мутации, поэтому вы можете извлекать дополнительные критерии авторизации, которые вы сохранили в своей базе данных, и использовать эти критерии в директивах @check , чтобы решить, авторизованы ли включающие мутации. Для этого случая авторизации директива @redact позволяет вам контролировать, возвращаются ли результаты запроса клиентам в протоколе Wire, а встроенный запрос опускается в сгенерированных SDK. Найдите введение в эти директивы с примерами .

Понять директиву @auth

Вы можете параметризовать директиву @auth , чтобы следовать одному из нескольких предустановленных уровней доступа, которые охватывают множество распространенных сценариев доступа. Эти уровни варьируются от PUBLIC (который разрешает запросы и мутации от всех клиентов без какой-либо аутентификации) до NO_ACCESS (который запрещает запросы и мутации за пределами привилегированных серверных сред с использованием Firebase Admin SDK ). Каждый из этих уровней коррелирует с потоками аутентификации, предоставляемыми Firebase Authentication .

Уровень Определение
PUBLIC Операцию может выполнить любой человек, прошедший аутентификацию или нет.
PUBLIC Операцию может выполнить любой человек, прошедший аутентификацию или нет.
USER_ANON Любой идентифицированный пользователь, включая тех, кто вошел в систему анонимно с помощью Firebase Authentication , имеет право выполнить запрос или мутацию.
USER Любой пользователь, вошедший в систему с использованием Firebase Authentication имеет право выполнять запрос или мутацию, за исключением анонимных пользователей, вошедших в систему.
USER_EMAIL_VERIFIED Любой пользователь, вошедший в систему с помощью Firebase Authentication и подтвержденного адреса электронной почты, имеет право выполнить запрос или мутацию.
NO_ACCESS Эту операцию невозможно выполнить вне контекста Admin SDK.

Используя эти предустановленные уровни доступа в качестве отправной точки, вы можете определить сложные и надежные проверки авторизации в директиве @auth , используя фильтры where и выражения Common Expression Language (CEL), оцениваемые на сервере.

Используйте директиву @auth для реализации общих сценариев авторизации.

Предустановленные уровни доступа являются отправной точкой для авторизации.

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

Полностью безопасный доступ будет построен на уровне USER плюс фильтры и выражения, которые проверяют атрибуты пользователя, атрибуты ресурсов, роли и другие проверки. Уровни USER_ANON и USER_EMAIL_VERIFIED являются вариациями случая USER .

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

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

В этом руководстве теперь приводятся примеры того, как строить на USER и PUBLIC .

Мотивирующий пример

Следующие примеры передовой практики относятся к следующей схеме для блог-платформы, в которой определенный контент заблокирован по тарифному плану.

Такая платформа, скорее всего, будет моделировать Users и Posts .

type User @table(key: "uid") {
  uid: String!
  name: String
  birthday: Date
  createdAt: Timestamp! @default(expr: "request.time")
}

type Post @table {
  author: User!
  text: String!
  # "one of 'draft', 'public', or 'pro'"
  visibility: String! @default(value: "draft")
  # "the time at which the post should be considered published. defaults to
  # immediately"
  publishedAt: Timestamp! @default(expr: "request.time")
  createdAt: Timestamp! @default(expr: "request.time")
  updatedAt: Timestamp! @default(expr: "request.time")
}

Ресурсы, принадлежащие пользователю

Firebase рекомендует вам писать фильтры и выражения, которые проверяют право собственности пользователя на ресурс, в следующих случаях: право собственности на Posts .

В следующих примерах данные из токенов аутентификации считываются и сравниваются с использованием выражений. Типичный шаблон — использовать выражения типа where: {authorUid: {eq_expr: "auth.uid"}} для сравнения сохраненного authorUid с auth.uid (идентификатором пользователя), переданным в токене аутентификации.

Создавать

Эта практика авторизации начинается с добавления auth.uid из токена авторизации к каждому новому Post в качестве поля authorUid , чтобы обеспечить сравнение в последовательных тестах авторизации.

# Create a new post as the current user
mutation CreatePost($text: String!, $visibility: String) @auth(level: USER) {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}
Обновлять

Когда клиент пытается обновить Post , вы можете проверить переданный auth.uid на соответствие сохраненному authorUid .

# Update one of the current user's posts
mutation UpdatePost($id: UUID!, $text: String, $visibility: String) @auth(level:USER) {
  post_update(
    # only update posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
    data: {
      text: $text
      visibility: $visibility
      # insert the current server time for updatedAt
      updatedAt_expr: "request.time"
    }
  )
}
Удалить

Тот же метод используется для авторизации операций удаления.

# Delete one of the current user's posts
mutation DeletePost($id: UUID!) @auth(level: USER) {
  post_delete(
    # only delete posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
  )
}
# Common display information for a post
fragment DisplayPost on Post {
  id, text, createdAt, updatedAt
  author { uid, name }
}
Список
# List all posts belonging to the current user
query ListMyPosts @auth(level: USER) {
  posts(where: {
    userUid: {eq_expr: "auth.uid"}
  }) {
    # See the fragment above
    ...DisplayPost
    # also show visibility since it is user-controlled
    visibility
  }
}
Получать
# Get a post only if it belongs to the current user
query GetMyPost($id: UUID!) @auth(level: USER) {
  post(key: {id: $id},
    first: {where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}}
      }}, {
      # See the fragment above
      ...DisplayPost
      # also show visibility since it is user-controlled
      visibility
  }
}

Фильтровать данные

Система авторизации Data Connect позволяет создавать сложные фильтры в сочетании с предустановленными уровнями доступа, такими как PUBLIC а также использовать данные из токенов аутентификации.

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

Фильтр по атрибутам ресурса

Здесь авторизация не основана на токенах аутентификации, поскольку базовый уровень безопасности установлен на PUBLIC . Но мы можем явно задать записи в нашей базе данных как подходящие для публичного доступа; предположим, что у нас есть записи Post в нашей базе данных с visibility установленной на «публичный».

# List all posts marked as 'public' visibility
query ListPublicPosts @auth(level: PUBLIC) {
  posts(where: {
    # Test that visibility is "public"
    visibility: {eq: "public"}
    # Only display articles that are already published
    publishedAt: {lt_expr: "request.time"}
  }) {
    # see the fragment above
    ...DisplayPost
  }
}
Фильтр по заявлениям пользователей

Здесь предположим, что вы настроили пользовательские утверждения, которые передают токены аутентификации для идентификации пользователей в плане "pro" для вашего приложения, помеченные полем auth.token.plan в токене аутентификации. Ваши выражения могут тестироваться по этому полю.

# List all public or pro posts, only permitted if user has "pro" plan claim
query ProListPosts @auth(expr: "auth.token.plan == 'pro'") {
  posts(where: {
    # display both public posts and "pro" posts
    visibility: {in: ['public', 'pro']},
    # only display articles that are already published
    publishedAt: {lt_expr: "request.time"},
  }) {
    # see the fragment above
    ...DisplayPost
    # show visibility so pro users can see which posts are pro\
    visibility
  }
}
Фильтр по заказу + лимит

Или, опять же, вы можете настроить visibility записей Post , чтобы указать, что это контент, доступный для «профессиональных» пользователей, но для предварительного просмотра или тизерного списка данных дополнительно ограничить количество возвращаемых записей.

# Show 2 oldest Pro post as a preview
query ProTeaser @auth(level: USER) {
  posts(
    where: {
      # show only pro posts
      visibility: {eq: "pro"}
      # that have already been published more than 30 days ago
      publishedAt: {lt_time: {now: true, sub: {days: 30}}}
    },
    # order by publish time
    orderBy: [{publishedAt: DESC}],
    # only return two posts
    limit: 2
  ) {
    # See the fragment above
    ...DisplayPost
  }
}
Фильтр по роли

Если ваше пользовательское утверждение определяет роль admin , вы можете протестировать и авторизовать операции соответствующим образом.

# List all posts unconditionally iff the current user has an admin claim
query AdminListPosts @auth(expr: "auth.token.admin == true") {
  posts { ...DisplayPost }
}

Добавьте директивы @check и @redact для поиска данных авторизации.

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

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

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

# MoviePermission
# Suppose a user has an authorization role with respect to records in the Movie table
type MoviePermission @table(key: ["movie", "user"]) {
  movie: Movie! # implies another field: movieId: UUID!
  user: User!
  role: String!
}

Использование в мутациях

В следующем примере реализации мутация UpdateMovieTitle включает поле query для извлечения данных из MoviePermission и следующие директивы для обеспечения безопасности и надежности операции:

  • Директива @transaction , гарантирующая, что все запросы и проверки авторизации будут выполнены или не будут выполнены автоматически.
  • Директива @redact исключает результаты запроса из ответа, что означает, что наша проверка авторизации выполняется на сервере Data Connect но конфиденциальные данные не предоставляются клиенту.
  • Пара директив @check для оценки логики авторизации по результатам запроса, например, проверка того, что заданный идентификатор пользователя имеет соответствующую роль для внесения изменений.

mutation UpdateMovieTitle($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    # Step 1a: Use @check to test if the user has any role associated with the movie
    # Here the `this` binding refers the lookup result, i.e. a MoviePermission object or null
    # The `this != null` expression could be omitted since rejecting on null is default behavior
    ) @check(expr: "this != null", message: "You do not have access to this movie") {
      # Step 1b: Check if the user has the editor role for the movie
      # Next we execute another @check; now `this` refers to the contents of the `role` field
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

Использовать в запросах

Поиск данных авторизации также полезен для ограничения запросов на основе ролей или других ограничений.

В следующем примере, который также использует схему MoviePermission , запрос проверяет, имеет ли запрашивающая сторона соответствующую роль «администратор» для просмотра пользователей, которые могут редактировать фильм.

query GetMovieEditors($movieId: UUID!) @auth(level: PUBLIC) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

Антишаблоны, которых следует избегать при авторизации

В предыдущем разделе рассматриваются шаблоны, которым следует следовать при использовании директивы @auth .

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

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

Firebase Authentication — это мощный инструмент для представления потоков аутентификации и безопасного сбора данных аутентификации, таких как зарегистрированные идентификаторы пользователей и многочисленные поля, хранящиеся в токенах аутентификации.

Не рекомендуется передавать идентификаторы пользователей и данные токенов аутентификации в аргументах запросов и мутаций.

# Antipattern!
# This incorrectly allows any user to view any other user's posts
query AllMyPosts($userId: String!) @auth(level: USER) {
  posts(where: {authorUid: {eq: $userId}}) {
    id, text, createdAt
  }
}

Избегайте использования уровня доступа USER без каких-либо фильтров.

Как уже неоднократно обсуждалось в руководстве, основные уровни доступа, такие как USER , USER_ANON , USER_EMAIL_VERIFIED являются базовыми и отправными точками для проверок авторизации, которые должны быть улучшены с помощью фильтров и выражений. Использование этих уровней без соответствующего фильтра или выражения, проверяющего, какой пользователь выполняет запрос, по сути эквивалентно использованию уровня PUBLIC .

# Antipattern!
# This incorrectly allows any user to view all documents
query ListDocuments @auth(level: USER) {
  documents {
    id
    title
    text
  }
}

Избегайте использования уровня доступа PUBLIC или USER для создания прототипов.

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

Когда вы сделаете таким образом начальное прототипирование, начните переключаться с NO_ACCESS на готовую к производству авторизацию с уровнями PUBLIC и USER . Однако не развертывайте их как PUBLIC или USER без добавления дополнительной логики, как показано в этом руководстве.

# Antipattern!
# This incorrectly allows anyone to delete any post
mutation DeletePost($id: UUID!) @auth(level: PUBLIC) {
  post: post_delete(
    id: $id,
  )
}

Избегайте авторизации на основе непроверенных адресов электронной почты.

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

# Antipattern!
# Anyone can claim an email address during sign-in
mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email.endsWith('@example.com')") {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}

Также проверьте auth.token.email_verified

mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email_verified && auth.token.email.endsWith('@example.com')") {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}

Аудит авторизации с помощью Firebase CLI

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

Data Connect помогает вам проводить аудит вашей стратегии авторизации, анализируя код коннектора при развертывании на сервере с помощью firebase deploy из Firebase CLI. Вы можете использовать этот аудит для проверки вашей кодовой базы.

При развертывании коннекторов CLI выведет оценки для существующего, измененного и нового кода операций в вашем коннекторе.

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

Предупреждения и подсказки всегда появляются для:

  • PUBLIC

А предупреждения и запросы появляются на следующих уровнях доступа, если вы не дополняете их фильтрами с использованием auth.uid :

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

Подавите предупреждения о небезопасных операциях с помощью аргумента @auth(insecureReason:)

Во многих случаях вы придете к выводу, что использование уровней доступа PUBLIC и USER* вполне уместно.

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

Вы можете отключить предупреждения для таких операций с помощью @auth(insecureReason:) . Например:

query listItem @auth(level: PUBLIC, insecureReason: "This operation is safe to expose to the public.")
  {
    items {
      id name
    }
  }

Используйте Firebase App Check для аттестации приложений

Аутентификация и авторизация являются критически важными компонентами безопасности Data Connect . Аутентификация и авторизация в сочетании с аттестацией приложений создают очень надежное решение безопасности.

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

Чтобы узнать, как включить App Check для Data Connect и включить его клиентский SDK в свое приложение, ознакомьтесь с обзором App Check .

Уровни аутентификации для директивы @auth(level)

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

Уровень Определение
PUBLIC Операцию может выполнить любой человек, прошедший аутентификацию или нет.

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

Эквивалентно @auth(expr: "true")

Фильтры и выражения @auth не могут использоваться в сочетании с этим уровнем доступа. Любые такие выражения завершатся ошибкой 400 bad request.
USER_ANON Любой идентифицированный пользователь, включая тех, кто вошел в систему анонимно с помощью Firebase Authentication , имеет право выполнить запрос или мутацию.

Примечание: USER_ANON — это надмножество USER .

Соображения: Обратите внимание, что вы должны тщательно проектировать свои запросы и мутации для этого уровня авторизации. Этот уровень позволяет пользователю входить в систему анонимно (автоматический вход, привязанный только к устройству пользователя) с Authentication и сам по себе не выполняет другие проверки, например, принадлежат ли данные пользователю. Смотрите примеры передовой практики и альтернативы .

Поскольку потоки анонимного входа Authentication выдают uid , уровень USER_ANON эквивалентен
@auth(expr: "auth.uid != nil")
USER Любой пользователь, вошедший в систему с использованием Firebase Authentication имеет право выполнять запрос или мутацию, за исключением анонимных пользователей, вошедших в систему.

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

Эквивалентно @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"
USER_EMAIL_VERIFIED Любой пользователь, вошедший в систему с помощью Firebase Authentication и подтвержденного адреса электронной почты, имеет право выполнить запрос или мутацию.

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

Эквивалентно @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS Эту операцию невозможно выполнить вне контекста Admin SDK.

Эквивалентно @auth(expr: "false")

Ссылка CEL для @auth(expr)

Как показано в примерах в других частях этого руководства, вы можете и должны использовать выражения, определенные в Common Expression Language (CEL), для управления авторизацией для Data Connect с помощью директив @auth(expr:) и @check .

В этом разделе рассматривается синтаксис CEL, имеющий отношение к созданию выражений для этих директив.

Полная справочная информация по CEL приведена в спецификации CEL .

Тестовые переменные, передаваемые в запросах и мутациях

Синтаксис @auth(expr) позволяет получать доступ к переменным из запросов и мутаций и проверять их.

Например, вы можете включить переменную операции, такую ​​как $status , используя vars.status .

mutation Update($id: UUID!, $status: Any) @auth(expr: "has(vars.status)")

Данные, доступные для выражений: запрос, ответ, это

Вы используете данные для:

  • Оценка с помощью выражений CEL в директивах @auth(expr:) и @check(expr:)
  • Назначение с использованием выражений сервера, <field>_expr .

Оба выражения CEL @auth(expr:) и @check(expr:) могут вычислять следующее:

  • request.operationName
  • vars (псевдоним для request.variables )
  • auth (псевдоним для request.auth )

В мутациях вы можете получить доступ и назначить содержимое:

  • response (для проверки частичных результатов в многошаговой логике)

Кроме того, выражения @check(expr:) могут оценивать:

  • this (значение текущего поля)
  • response (для проверки частичных результатов в многошаговой логике)

Привязка request.operationName

Привязка request.operarationName хранит тип операции: запрос или мутация.

Связывание vars (request.vars)

Привязка vars позволяет вашим выражениям получать доступ ко всем переменным, переданным в вашем запросе или мутации.

Вы можете использовать vars.<variablename> в выражении как псевдоним для полностью квалифицированного request.variables.<variablename> :

# The following are equivalent
mutation StringType($v: String!) @auth(expr: "vars.v == 'hello'")
mutation StringType($v: String!) @auth(expr: "request.variables.v == 'hello'")

Привязка auth (request.auth)

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

В своих фильтрах и выражениях вы можете использовать auth как псевдоним для request.auth .

Привязка аутентификации содержит следующую информацию:

  • uid : уникальный идентификатор пользователя, назначенный запрашивающему пользователю.
  • token : карта значений, собранных при Authentication .

Более подробную информацию о содержимом auth.token см. в разделе Данные в токенах аутентификации.

response привязка

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

По мере выполнения операции, когда каждый шаг завершается успешно, response содержит данные ответа с успешно завершенных шагов.

Привязка response структурируется в соответствии с формой связанной с ней операции, включая (несколько) вложенных полей и (если применимо) встроенные запросы.

Обратите внимание, что при доступе к встроенным данным ответа на запрос поля могут содержать любой тип данных в зависимости от данных, запрошенных во встроенном запросе; при доступе к данным, возвращаемым полями мутации, такими как _insert и _delete , они могут содержать ключи UUID, количество удалений, значения NULL (см. справочник по мутациям ).

Например:

  • В мутации, содержащей встроенный запрос, привязка response содержит данные поиска в response.query.<fieldName>.<fieldName>.... , в данном случае response.query.todoList и response.query.todoList.priority .
mutation CheckTodoPriority(
  $uniqueListName: String!
) {
  # This query is identified as `response.query`
  query @check(expr: "response.query.todoList.priority == 'high'", message: "This list is not for high priority items!") {
    # This field is identified as `response.query.todoList`
    todoList(where: { name: $uniqueListName }) {
      # This field is identified as `response.query.todoList.priority`
      priority
    }
  }
}
  • В многошаговой мутации, например с несколькими полями _insert , привязка response содержит частичные данные в response.<fieldName>.<fieldName>.... , в данном случае response.todoList_insert.id .
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()",
    name: $listName,
  })
  # Step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

this привязка

Связывание this оценивается по полю, к которому прикреплена директива @check . В базовом случае вы можете оценить результаты запроса с одним значением.

mutation UpdateMovieTitle (
  $movieId: UUID!,
  $newTitle: String!)
  @auth(level: USER)
  @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    ) {
      # Check if the user has the editor role for the movie. `this` is the string value of `role`.
      # If the parent moviePermission is null, the @check will also fail automatically.
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

Если возвращаемое поле встречается несколько раз, поскольку любой предок является списком, каждое вхождение проверяется с this к каждому значению.

Для любого заданного пути, если предок равен null или [] , поле не будет достигнуто, и оценка CEL будет пропущена для этого пути. Другими словами, оценка происходит только тогда, когда this равен null или не null , но никогда undefined .

Если поле само по себе является списком или объектом, this следует той же структуре (включая всех выбранных потомков в случае объектов), как показано в следующем примере.

mutation UpdateMovieTitle2($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query {
    moviePermissions( # Now we query for a list of all matching MoviePermissions.
      where: {movieId: {eq: $movieId}, userId: {eq_expr: "auth.uid"}}
    # This time we execute the @check on the list, so `this` is the list of objects.
    # We can use the `.exists` macro to check if there is at least one matching entry.
    ) @check(expr: "this.exists(p, p.role == 'editor')", message: "You must be an editor of this movie to update title") {
      role
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

Синтаксис сложного выражения

Вы можете писать более сложные выражения, комбинируя их с операторами && и || .

mutation UpsertUser($username: String!) @auth(expr: "(auth != null) && (vars.username == 'joe')")

В следующем разделе описаны все доступные операторы.

Операторы и приоритет операторов

Используйте следующую таблицу в качестве справочной информации по операторам и их приоритету.

Даны произвольные выражения a и b , поле f и индекс i .

Оператор Описание Ассоциативность
a[i] a() af Индекс, вызов, доступ к полю слева направо
!a -a Унарное отрицание справа налево
a/ba%ba*b Мультипликативные операторы слева направо
a+b ab Аддитивные операторы слева направо
a>b a>=b a<b a<=b Реляционные операторы слева направо
a in b Наличие в списке или на карте слева направо
type(a) == t Сравнение типов, где t может быть bool, int, float, number, string, list, map, timestamp или duration слева направо
a==ba!=b Операторы сравнения слева направо
a && b Условное И слева направо
a || b Условное ИЛИ слева направо
a ? true_value : false_value Троичное выражение слева направо

Данные в токенах аутентификации

Объект auth.token может содержать следующие значения:

Поле Описание
email Адрес электронной почты, связанный с учетной записью, если он есть.
email_verified true если пользователь подтвердил, что у него есть доступ к адресу email . Некоторые провайдеры автоматически проверяют адреса электронной почты, которыми они владеют.
phone_number Номер телефона, связанный с учетной записью, если он имеется.
name Отображаемое имя пользователя, если установлено.
sub Firebase UID пользователя. Он уникален в рамках проекта.
firebase.identities Словарь всех идентификаторов, связанных с учетной записью этого пользователя. Ключи словаря могут быть любыми из следующих: email , phone , google.com , facebook.com , github.com , twitter.com . Значения словаря представляют собой массивы уникальных идентификаторов для каждого поставщика идентификаторов, связанного с учетной записью. Например, auth.token.firebase.identities["google.com"][0] содержит первый идентификатор пользователя Google, связанный с учетной записью.
firebase.sign_in_provider Поставщик входа, используемый для получения этого токена. Может быть одной из следующих строк: custom , password , phone , anonymous , google.com , facebook.com , github.com , twitter.com .
firebase.tenant tenantId, связанный с учетной записью, если присутствует. Например, tenant2-m6tyz

Дополнительные поля в токенах JWT ID

Вы также можете получить доступ к следующим полям auth.token :

Требования к пользовательским токенам
alg Алгоритм "RS256"
iss Эмитент Адрес электронной почты учетной записи службы вашего проекта
sub Предмет Адрес электронной почты учетной записи службы вашего проекта
aud Аудитория "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Выпущено в момент Текущее время в секундах с начала эпохи UNIX
exp Срок годности Время в секундах с эпохи UNIX, когда истекает срок действия токена. Может быть максимум на 3600 секунд позже, чем iat .
Примечание: это контролирует только время истечения срока действия самого пользовательского токена . Но как только вы введете пользователя с помощью signInWithCustomToken() , он останется в системе устройства до тех пор, пока его сеанс не будет аннулирован или пользователь не выйдет из системы.
<claims> (необязательно) Необязательные пользовательские утверждения для включения в токен, к которым можно получить доступ через auth.token (или request.auth.token ) в выражениях. Например, если вы создаете пользовательское утверждение adminClaim , вы можете получить к нему доступ с помощью auth.token.adminClaim .

Что дальше?