Secure Data Connect с авторизацией и аттестацией, Secure Data Connect с авторизацией и аттестацией, 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 позволяет контролировать, возвращаются ли результаты запроса клиентам по проводному протоколу, а встроенный запрос опускается в сгенерированных 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

Директива @check проверяет наличие указанных полей в результатах запроса. Выражение Common Expression Language (CEL) используется для проверки значений полей. Поведение директивы по умолчанию — проверять и отклонять узлы с null значением.

Директива @redact редактирует часть ответа клиента. Отредактированные поля по-прежнему оцениваются на наличие побочных эффектов (включая изменения данных и @check ), а результаты по-прежнему доступны для последующих шагов в выражениях CEL.

В Data Connect директивы @check и @redact чаще всего используются в контексте проверок авторизации; обратитесь к обсуждению поиска данных авторизации .

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

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

Используя поиск данных авторизации, вы можете запрашивать роли на основе идентификатора пользователя и использовать выражения 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 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) и @check(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:) могут оценивать следующее:

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

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

  • this (значение текущего поля)

Объект request.operationName

Объект request.operarationName хранит тип операции: запрос или мутацию.

Объект 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

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

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

Объект аутентификации содержит следующую информацию:

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

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

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, числом, строкой, списком, картой, меткой времени или длительностью. слева направо
a==ba!=b Операторы сравнения слева направо
a && b Условное И слева направо
a || b Условное ИЛИ слева направо
a ? true_value : false_value Тернарное выражение слева направо

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

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

Поле Описание
email Адрес электронной почты, связанный с учетной записью, если таковой имеется.
email_verified true если пользователь подтвердил, что у него есть доступ к адресу email . Некоторые провайдеры автоматически проверяют принадлежащие им адреса электронной почты.
phone_number Номер телефона, связанный с учетной записью, если он присутствует.
name Отображаемое имя пользователя, если оно установлено.
sub UID пользователя Firebase. Это уникально в рамках проекта.
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

Вы также можете получить доступ к следующим полям 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 .

Что дальше?

,

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

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

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

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

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

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

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

Кроме того, Data Connect поддерживает выполнение запросов, встроенных в мутации, поэтому вы можете получить дополнительные критерии авторизации, которые вы сохранили в своей базе данных, и использовать эти критерии в директивах @check , чтобы решить, разрешены ли включающие мутации. В этом случае авторизации директива @redact позволяет контролировать, возвращаются ли результаты запроса клиентам по проводному протоколу, а встроенный запрос опускается в сгенерированных 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

Директива @check проверяет наличие указанных полей в результатах запроса. Выражение Common Expression Language (CEL) используется для проверки значений полей. Поведение директивы по умолчанию — проверять и отклонять узлы с null значением.

Директива @redact редактирует часть ответа клиента. Отредактированные поля по-прежнему оцениваются на наличие побочных эффектов (включая изменения данных и @check ), а результаты по-прежнему доступны для последующих шагов в выражениях CEL.

В Data Connect директивы @check и @redact чаще всего используются в контексте проверок авторизации; обратитесь к обсуждению поиска данных авторизации .

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

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

Используя поиск данных авторизации, вы можете запрашивать роли на основе идентификатора пользователя и использовать выражения 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 App Check для аттестации приложения

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

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

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

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

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

Уровень Определение
PUBLIC Операция может быть выполнена всеми с аутентификацией или без него.

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

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

@auth Фильтры и выражения не могут быть использованы в сочетании с этим уровнем доступа. Любые такие выражения потерпят неудачу с ошибкой плохого запроса 400.
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 Эта операция не может быть выполнена вне контекста SDK администратора.

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

Справочник CEL для @auth(expr) и @check(expr)

Как показано в примерах в другом месте этого руководства, вы можете и должны использовать выражения, определенные на языке общего выражения (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)")

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

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

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

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

  • this (значение текущего поля)

Объект request.operationname

Объект request.operarationName сохраняет тип операции, либо запрос, либо мутацию.

Объект 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

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

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

Объект Auth содержит следующую информацию:

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

Для получения более подробной информации о Contenst of auth.token см. Данные в токенах AUTH

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, TimeStam слева направо
a==ba!=b Операторы сравнения слева направо
a && b Условно и слева направо
a || b Условное или слева направо
a ? true_value : false_value Тройное выражение слева направо

Данные в токенах AUTH

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

Поле Описание
email Адрес электронной почты, связанный с учетной записью, если таковой имеется.
email_verified true если пользователь подтвердил, что у него есть доступ к адресу email . Некоторые провайдеры автоматически проверяют принадлежащие им адреса электронной почты.
phone_number Номер телефона, связанный с учетной записью, если он присутствует.
name Отображаемое имя пользователя, если оно установлено.
sub UID пользователя Firebase. Это уникально в рамках проекта.
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 ) в выражениях. Например, если вы создаете пользовательскую претензию, вы можете получить auth.token.adminClaim adminClaim

Что дальше?

,

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

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

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

  • Авторизация на стороне сервера
  • Firebase Project и Cloud SQL Безопасность пользователей с IAM.

Авторизировать клиентские запросы и мутации

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

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

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

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

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

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

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

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

Уровни заданного доступа являются отправной точкой для разрешения.

Уровень доступа USER является наиболее полезным базовым уровнем для начала.

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

Синтаксис выражения позволяет вам оценивать данные с использованием объекта auth , представляющего данные аутентификации, передаваемые операциями, как стандартные данные в токенах 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 .

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

Создавать

Эта практика авторизации начинается с добавления auth.uid из токена Auth в каждый новый 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 а также с использованием данных из токенов Auth.

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

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

Здесь авторизация не основана на токенах Auth, поскольку базовый уровень безопасности устанавливается на 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
  }
}
Фильтр по требованиям пользователей

Здесь предположим, что вы настроили пользовательские претензии пользователей, которые проходят в токенах Auth, чтобы идентифицировать пользователей в плане «Pro» для вашего приложения, помеченного с поле auth.token.plan в токене Auth. Ваши выражения могут проверить эту область.

# 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 , чтобы определить, что они являются контентом, доступными для пользователей «Pro», но для предварительного просмотра или списка данных тизеры еще больше ограничивают количество возвращенных записей.

# 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

Директива @check проверяет, что указанные поля присутствуют в результатах запроса. Экспрессия общего языка экспрессии (CEL) используется для проверки значений поля. Поведение директивы по умолчанию состоит в том, чтобы проверить и отклонить null узлы.

Директива @redact отредактирует часть ответа от клиента. Отредактированные поля все еще оцениваются по побочным эффектам (включая изменения данных и @check ), и результаты по -прежнему доступны для более поздних шагов в выражениях CEL.

В Data Connect Директивы @check и @redact чаще всего используются в контексте проверки авторизации; Обратитесь к обсуждению поиска данных авторизации .

Добавить директивы @check и @redact , чтобы найти данные авторизации

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

Используя поиск данных авторизации, вы можете запросить роли на основе пользователя и использовать выражения 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 является мощным инструментом для представления потоков аутентификации и надежного сбора данных аутентификации, таких как зарегистрированные идентификаторы пользователей, и многочисленные поля, хранящиеся в токенах Auth.

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

# 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 App Check для аттестации приложения

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

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

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

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

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

Уровень Определение
PUBLIC Операция может быть выполнена всеми с аутентификацией или без него.

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

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

@auth Фильтры и выражения не могут быть использованы в сочетании с этим уровнем доступа. Любые такие выражения потерпят неудачу с ошибкой плохого запроса 400.
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 Эта операция не может быть выполнена вне контекста SDK администратора.

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

Справочник CEL для @auth(expr) и @check(expr)

Как показано в примерах в другом месте этого руководства, вы можете и должны использовать выражения, определенные на языке общего выражения (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)")

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

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

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

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

  • this (значение текущего поля)

Объект request.operationname

Объект request.operarationName сохраняет тип операции, либо запрос, либо мутацию.

Объект 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

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

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

Объект Auth содержит следующую информацию:

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

Для получения более подробной информации о Contenst of auth.token см. Данные в токенах AUTH

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 или продолжительность слева направо
a==ba!=b Операторы сравнения слева направо
a && b Условно и слева направо
a || b Условное или слева направо
a ? true_value : false_value Тройное выражение слева направо

Данные в токенах AUTH

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

Поле Описание
email Адрес электронной почты, связанный с учетной записью, если таковой имеется.
email_verified true если пользователь подтвердил, что у него есть доступ к адресу email . Некоторые провайдеры автоматически проверяют принадлежащие им адреса электронной почты.
phone_number Номер телефона, связанный с учетной записью, если он присутствует.
name Отображаемое имя пользователя, если оно установлено.
sub UID пользователя Firebase. Это уникально в рамках проекта.
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 ) в выражениях. Например, если вы создаете пользовательскую претензию, вы можете получить auth.token.adminClaim adminClaim

Что дальше?