Secure Data Connect con autorización y certificación

Firebase Data Connect proporciona una seguridad sólida del cliente con lo siguiente:

  • Autorización de clientes web y para dispositivos móviles
  • Controles de autorización individuales a nivel de mutación y consulta
  • Atestación de la app con Firebase App Check

Data Connect extiende esta seguridad con lo siguiente:

  • Autorización del servidor
  • Seguridad del proyecto de Firebase y del usuario de Cloud SQL con IAM

Autoriza las consultas y mutaciones del cliente

Data Connect se integra por completo con Firebase Authentication, por lo que puedes usar datos enriquecidos sobre los usuarios que acceden a tus datos (autenticación) en tu diseño para determinar a qué datos pueden acceder esos usuarios (autorización).

Data Connect proporciona una directiva @auth para consultas y mutaciones que te permite establecer el nivel de autenticación requerido para autorizar la operación. En esta guía, se presenta la directiva @auth con ejemplos.

Además, Data Connect admite la ejecución de consultas incorporadas en mutaciones, por lo que puedes recuperar criterios de autorización adicionales que hayas almacenado en tu base de datos y usarlos en directivas @check para decidir si se autorizan las mutaciones adjuntas. En este caso de autorización, la directiva @redact te permite controlar si los resultados de la búsqueda se devuelven a los clientes en el protocolo de transferencia y si la búsqueda incorporada se omite en los SDKs generados. Consulta la introducción a estas directivas, con ejemplos.

Información sobre la directiva @auth

Puedes parametrizar la directiva @auth para que siga uno de los varios niveles de acceso predeterminados que abarcan muchas situaciones de acceso comunes. Estos niveles varían desde PUBLIC (que permite consultas y mutaciones de todos los clientes sin ningún tipo de autenticación) hasta NO_ACCESS (que no permite consultas ni mutaciones fuera de los entornos de servidor privilegiados que usan el SDK de Firebase Admin). Cada uno de estos niveles se correlaciona con los flujos de autenticación que proporciona Firebase Authentication.

Nivel Definición
PUBLIC Cualquier persona puede ejecutar la operación, con o sin autenticación.
PUBLIC Cualquier persona puede ejecutar la operación, con o sin autenticación.
USER_ANON Cualquier usuario identificado, incluidos aquellos que accedieron de forma anónima con Firebase Authentication, está autorizado para realizar la consulta o mutación.
USER Cualquier usuario que haya accedido con Firebase Authentication está autorizado a realizar la consulta o mutación, excepto los usuarios que accedieron de forma anónima.
USER_EMAIL_VERIFIED Cualquier usuario que haya accedido con Firebase Authentication con una dirección de correo electrónico verificada está autorizado para realizar la consulta o la mutación.
NO_ACCESS Esta operación no se puede ejecutar fuera de un contexto del SDK de Admin.

Con estos niveles de acceso predeterminados como punto de partida, puedes definir verificaciones de autorización complejas y sólidas en la directiva @auth con filtros where y expresiones de Common Expression Language (CEL) que se evalúan en el servidor.

Usa la directiva @auth para implementar situaciones de autorización comunes

Los niveles de acceso predeterminados son el punto de partida para la autorización.

El nivel de acceso USER es el nivel básico más útil para comenzar.

El acceso completamente seguro se basará en el nivel USER, además de filtros y expresiones que verifiquen los atributos del usuario, los atributos del recurso, los roles y otras verificaciones. Los niveles USER_ANON y USER_EMAIL_VERIFIED son variaciones del caso USER.

La sintaxis de expresiones te permite evaluar datos con un objeto auth que representa los datos de autenticación que se pasan con las operaciones, tanto los datos estándar en los tokens de autenticación como los datos personalizados en los tokens. Para obtener la lista de campos disponibles en el objeto auth, consulta la sección de referencia.

Por supuesto, hay casos de uso en los que PUBLIC es el nivel de acceso correcto para comenzar. Una vez más, un nivel de acceso siempre es un punto de partida, y se necesitan filtros y expresiones adicionales para una seguridad sólida.

En esta guía, ahora se proporcionan ejemplos de cómo compilar en USER y PUBLIC.

Un ejemplo motivador

Los siguientes ejemplos de prácticas recomendadas hacen referencia al siguiente esquema de una plataforma de blogs con cierto contenido bloqueado detrás de un plan de pagos.

Es probable que una plataforma de este tipo modelaría Users y 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")
}

Recursos propiedad del usuario

Firebase recomienda que escribas filtros y expresiones que prueben la propiedad del usuario de un recurso, en los siguientes casos, la propiedad de Posts.

En los siguientes ejemplos, se leen y comparan los datos de los tokens de autorización con expresiones. El patrón típico es usar expresiones como where: {authorUid: {eq_expr: "auth.uid"}} para comparar un authorUid almacenado con el auth.uid (ID de usuario) que se pasa en el token de autenticación.

Crear

Esta práctica de autorización comienza agregando el auth.uid del token de autorización a cada Post nuevo como un campo authorUid para permitir la comparación en pruebas de autorización posteriores.

# 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
  })
}
Actualizar

Cuando un cliente intenta actualizar un Post, puedes probar el auth.uid pasado en comparación con el authorUid almacenado.

# 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"
    }
  )
}
Borrar

La misma técnica se usa para autorizar las operaciones de eliminación.

# 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 }
}
Lista
# 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
  }
}
Obtener
# 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
  }
}

Filtrar datos

El sistema de autorización de Data Connect te permite escribir filtros sofisticados combinados con niveles de acceso preestablecidos, como PUBLIC, y también usar datos de tokens de autorización.

El sistema de autorización también te permite usar solo expresiones, sin un nivel de acceso base, como se muestra en algunos de los siguientes ejemplos.

Filtrar por atributos de recursos

Aquí, la autorización no se basa en tokens de autenticación, ya que el nivel de seguridad base se establece en PUBLIC. Sin embargo, podemos establecer registros de forma explícita en nuestra base de datos como aptos para el acceso público. Supongamos que tenemos Post registros en nuestra base de datos con visibility establecido como "público".

# 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
  }
}
Filtrar por reclamos del usuario

Aquí, supón que configuraste reclamaciones personalizadas del usuario que pasan tokens de autenticación para identificar a los usuarios en un plan "pro" para tu app, marcados con un campo auth.token.plan en el token de autenticación. Tus expresiones pueden realizar pruebas en comparación con este campo.

# 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
  }
}
Filtrar por orden y límite

O bien, es posible que hayas establecido visibility en los registros de Post para identificar que son contenido disponible para los usuarios "profesionales", pero, para una vista previa o un avance de los datos, limita aún más la cantidad de registros que se devuelven.

# 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
  }
}
Filtrar por rol

Si tu reclamo personalizado define un rol de admin, puedes probar y autorizar operaciones según corresponda.

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

Agrega las directivas @check y @redact para buscar datos de autorización

Un caso de uso común de la autorización implica almacenar roles de autorización personalizados en tu base de datos, por ejemplo, en una tabla de permisos especiales, y usar esos roles para autorizar mutaciones para crear, actualizar o borrar datos.

Con las búsquedas de datos de autorización, puedes consultar roles según un userID y usar expresiones de CEL para decidir si la mutación está autorizada. Por ejemplo, es posible que desees escribir una mutación UpdateMovieTitle que permita que un cliente autorizado actualice los títulos de las películas.

Para el resto de este análisis, supón que la base de datos de la app de reseñas de películas almacena un rol de autorización en una tabla 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!
}

Uso en mutaciones

En la siguiente implementación de ejemplo, la mutación UpdateMovieTitle incluye un campo query para recuperar datos de MoviePermission y las siguientes directivas para garantizar que la operación sea segura y sólida:

  • Una directiva @transaction para garantizar que todas las consultas y verificaciones de autorización se completen o fallen de forma atómica.
  • La directiva @redact para omitir los resultados de la búsqueda de la respuesta, lo que significa que nuestra verificación de autorización se realiza en el servidor Data Connect, pero los datos sensibles no se exponen al cliente.
  • Un par de directivas @check para evaluar la lógica de autorización en los resultados de la búsqueda, como probar que un ID de usuario determinado tenga un rol adecuado para realizar modificaciones.

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
  })
}

Uso en consultas

Las búsquedas de datos de autorización también son útiles para restringir las consultas según los roles o cualquier otra restricción.

En el siguiente ejemplo, que también usa el esquema MoviePermission, la consulta verifica si un solicitante tiene un rol de "administrador" adecuado para ver a los usuarios que pueden editar una película.

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
    }
  }
}

Antipatrones que se deben evitar en la autorización

En la sección anterior, se describen los patrones que se deben seguir cuando se usa la directiva @auth.

También debes tener en cuenta los antipatrones importantes que debes evitar.

Evita pasar los IDs de atributos del usuario y los parámetros del token de autorización en los argumentos de consultas y mutaciones

Firebase Authentication es una herramienta eficaz para presentar flujos de autenticación y capturar de forma segura datos de autenticación, como IDs de usuarios registrados y numerosos campos almacenados en tokens de autenticación.

No se recomienda pasar IDs de usuario ni datos de tokens de autenticación en argumentos de consultas y mutaciones.

# 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
  }
}

Evita usar el nivel de acceso USER sin filtros

Como se mencionó varias veces en la guía, los niveles de acceso principales, como USER, USER_ANON y USER_EMAIL_VERIFIED, son puntos de partida y valores de referencia para las verificaciones de autorización, que se pueden mejorar con filtros y expresiones. Usar estos niveles sin un filtro o una expresión correspondientes que verifiquen qué usuario realiza la solicitud es esencialmente equivalente a usar el nivel PUBLIC.

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

Evita usar el nivel de acceso PUBLIC o USER para la creación de prototipos

Para acelerar el desarrollo, puede ser tentador establecer todas las operaciones en el nivel de acceso PUBLIC o en el nivel de acceso USER sin más mejoras para autorizar todas las operaciones y permitirte probar tu código rápidamente.

Cuando hayas realizado un prototipo inicial de esta manera, comienza a cambiar de NO_ACCESS a la autorización lista para producción con niveles de PUBLIC y USER. Sin embargo, no los implementes como PUBLIC o USER sin agregar lógica adicional, como se muestra en esta guía.

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

Evita basar la autorización en direcciones de correo electrónico no verificadas

Otorgar acceso a los usuarios de un dominio en particular es una excelente manera de limitar el acceso. Sin embargo, cualquier persona puede afirmar que es propietaria de un correo electrónico durante el acceso. Asegúrate de otorgar acceso solo a las direcciones de correo electrónico que se verificaron a través de Firebase Authentication.

# 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
  })
}

También consulta auth.token.email_verified

mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email_veri&&fied  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
  })
}

Autoriza la auditoría con Firebase CLI

Como se indicó anteriormente, los niveles de acceso preestablecidos, como PUBLIC y USER, son el punto de partida para una autorización sólida y se deben usar con verificaciones de autorización adicionales basadas en filtros y expresiones. No deben usarse por sí solas sin considerar cuidadosamente el caso de uso.

Data Connect te ayuda a auditar tu estrategia de autorización analizando el código del conector cuando implementas en el servidor con firebase deploy desde la CLI de Firebase. Puedes usar esta auditoría para revisar tu base de código.

Cuando implementes tus conectores, la CLI generará evaluaciones para el código de operación existente, modificado y nuevo en tu conector.

En el caso de las operaciones nuevas y modificadas, la CLI emite advertencias y te solicita confirmación cuando usas ciertos niveles de acceso en tus operaciones nuevas o cuando modificas operaciones existentes para usar esos niveles de acceso.

Las advertencias y los mensajes siempre aparecen en los siguientes casos:

  • PUBLIC

Además, se muestran advertencias y mensajes en los siguientes niveles de acceso cuando no los aumentas con filtros usando auth.uid:

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

Cómo suprimir las advertencias de operaciones no seguras con el argumento @auth(insecureReason:)

En muchos casos, concluirás que usar los niveles de acceso PUBLIC y USER* es perfectamente adecuado.

Cuando tu conector contiene muchas operaciones, es posible que desees una salida de auditoría de seguridad más clara y pertinente que omita las operaciones que normalmente activarían una advertencia, pero que sabes que tienen el nivel de acceso correcto.

Puedes suprimir las advertencias para estas operaciones con @auth(insecureReason:). Por ejemplo:

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

Usa Firebase App Check para la certificación de apps

La autenticación y la autorización son componentes fundamentales de la seguridad de Data Connect. La autenticación y la autorización combinadas con la certificación de apps conforman una solución de seguridad muy sólida.

Con la certificación a través de Firebase App Check, los dispositivos que ejecutan tu app usarán un proveedor de certificación de apps o de dispositivos que certifique que las operaciones de Data Connect se originan en tu app auténtica y que las solicitudes se originan en un dispositivo auténtico y no adulterado. Esta certificación se adjunta a cada solicitud que tu app realiza a Data Connect.

Para obtener información sobre cómo habilitar App Check para Data Connect y cómo incluir su SDK de cliente en tu app, consulta el resumen de App Check.

Niveles de autenticación para la directiva @auth(level)

En la siguiente tabla, se enumeran todos los niveles de acceso estándar y sus equivalentes en CEL. Los niveles de autenticación se enumeran de general a específico. Cada nivel abarca a todos los usuarios que coinciden con los niveles siguientes.

Nivel Definición
PUBLIC Cualquier persona puede ejecutar la operación, con o sin autenticación.

Consideraciones: Cualquier usuario puede leer o modificar los datos. Firebase recomienda este nivel de autorización para los datos que se pueden explorar públicamente, como las fichas de productos o medios. Consulta los ejemplos y las alternativas de prácticas recomendadas.

Equivalente a @auth(expr: "true")

Los filtros y las expresiones @auth no se pueden usar en combinación con este nivel de acceso. Cualquier expresión de este tipo fallará con un error de solicitud incorrecta 400.
USER_ANON Cualquier usuario identificado, incluidos aquellos que accedieron de forma anónima con Firebase Authentication, está autorizado para realizar la consulta o mutación.

Nota: USER_ANON es un superconjunto de USER.

Consideraciones: Ten en cuenta que debes diseñar cuidadosamente tus consultas y mutaciones para este nivel de autorización. Este nivel permite que el usuario acceda de forma anónima (acceso automático vinculado solo a un dispositivo del usuario) con Authentication y, por sí solo, no realiza otras verificaciones, por ejemplo, si los datos pertenecen al usuario. Consulta los ejemplos y las alternativas de prácticas recomendadas.

Dado que los flujos de acceso anónimo de Authentication emiten un uid, el nivel de USER_ANON es equivalente a
@auth(expr: "auth.uid != nil")
USER Cualquier usuario que haya accedido con Firebase Authentication está autorizado a realizar la consulta o mutación, excepto los usuarios que accedieron de forma anónima.

Consideraciones: Ten en cuenta que debes diseñar cuidadosamente tus consultas y mutaciones para este nivel de autorización. Este nivel solo verifica que el usuario haya accedido con Authentication y no realiza por sí solo otras verificaciones, por ejemplo, si los datos pertenecen al usuario. Consulta los ejemplos de prácticas recomendadas y alternativas.

Equivale a @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"
USER_EMAIL_VERIFIED Cualquier usuario que haya accedido con Firebase Authentication con una dirección de correo electrónico verificada está autorizado para realizar la consulta o la mutación.

Consideraciones: Dado que la verificación por correo electrónico se realiza con Authentication, se basa en un método de Authentication más sólido, por lo que este nivel proporciona seguridad adicional en comparación con USER o USER_ANON. Este nivel solo verifica que el usuario haya accedido con Authentication con un correo electrónico verificado y no realiza por sí solo otras verificaciones, por ejemplo, si los datos pertenecen al usuario. Consulta los ejemplos y las alternativas de prácticas recomendadas.

Equivale a @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS Esta operación no se puede ejecutar fuera de un contexto del SDK de Admin.

Equivale a @auth(expr: "false")

Referencia de CEL para @auth(expr)

Como se muestra en los ejemplos de otras partes de esta guía, puedes y debes usar expresiones definidas en Common Expression Language (CEL) para controlar la autorización de Data Connect con las directivas @auth(expr:) y @check.

En esta sección, se abarca la sintaxis de CEL pertinente para crear expresiones para estas directivas.

En la especificación de CEL, se proporciona información de referencia completa sobre este lenguaje.

Variables de prueba que se pasan en consultas y mutaciones

La sintaxis de @auth(expr) te permite acceder a las variables de las consultas y mutaciones, y probarlas.

Por ejemplo, puedes incluir una variable de operación, como $status, con vars.status.

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

Datos disponibles para las expresiones: request, response, this

Usas los datos para lo siguiente:

  • Evaluación con expresiones de CEL en las directivas @auth(expr:) y @check(expr:)
  • Asignación con expresiones del servidor, <field>_expr.

Ambas expresiones CEL @auth(expr:) y @check(expr:) pueden evaluar lo siguiente:

  • request.operationName
  • vars (alias de request.variables)
  • auth (alias de request.auth)

En las mutaciones, puedes acceder a los contenidos de los siguientes elementos y asignarlos:

  • response (para verificar los resultados parciales en la lógica de varios pasos)

Además, las expresiones @check(expr:) pueden evaluar lo siguiente:

  • this (el valor del campo actual)
  • response (para verificar los resultados parciales en la lógica de varios pasos)

La vinculación request.operationName

La vinculación request.operarationName almacena el tipo de operación, ya sea consulta o mutación.

La vinculación de vars (request.vars)

La vinculación vars permite que tus expresiones accedan a todas las variables que se pasan en tu consulta o mutación.

Puedes usar vars.<variablename> en una expresión como alias de request.variables.<variablename> completamente calificado:

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

La vinculación de auth (request.auth)

Authentication identifica a los usuarios que solicitan acceso a tus datos y proporciona esa información como una vinculación que puedes usar en tus expresiones.

En tus filtros y expresiones, puedes usar auth como alias de request.auth.

La vinculación de autorización contiene la siguiente información:

  • uid: Es un ID de usuario único, asignado al usuario solicitante.
  • token: Es un mapa de valores recopilados por Authentication.

Para obtener más detalles sobre el contenido de auth.token, consulta Datos en tokens de autorización.

La vinculación de response

La vinculación response contiene los datos que el servidor ensambla en respuesta a una consulta o mutación a medida que se ensamblan esos datos.

A medida que avanza la operación y se completa cada paso correctamente, response contiene datos de respuesta de los pasos completados correctamente.

La vinculación de response se estructura según la forma de su operación asociada, incluidos los campos anidados (múltiples) y las consultas incorporadas (si corresponde).

Ten en cuenta que, cuando accedes a los datos de respuesta de la consulta incorporada, los campos pueden contener cualquier tipo de datos, según los datos solicitados en la consulta incorporada. Cuando accedes a los datos que devuelven los campos de mutación, como _inserts y _deletes, pueden contener claves de UUID, cantidad de eliminaciones, valores nulos (consulta la referencia de mutaciones).

Por ejemplo:

  • En una mutación que contiene una consulta incorporada, la vinculación response contiene datos de búsqueda en response.query.<fieldName>.<fieldName>...., en este caso, response.query.todoList y 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
    }
  }
}
  • En una mutación de varios pasos, por ejemplo, con varios campos _insert, la vinculación response contiene datos parciales en response.<fieldName>.<fieldName>...., en este caso, 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.todoLis<t_insert.id" # -- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

La vinculación de this

La vinculación this se evalúa en el campo al que se adjunta la directiva @check. En un caso básico, puedes evaluar los resultados de la búsqueda de un solo valor.

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
  })
}

Si el campo devuelto aparece varias veces porque algún elemento superior es una lista, cada aparición se prueba con this vinculado a cada valor.

Para cualquier ruta determinada, si un elemento superior es null o [], no se accederá al campo y se omitirá la evaluación de CEL para esa ruta. En otras palabras, la evaluación solo se lleva a cabo cuando this es null o no es null, pero nunca undefined.

Cuando el campo en sí es una lista o un objeto, this sigue la misma estructura (incluidos todos los elementos secundarios seleccionados en el caso de los objetos), como se ilustra en el siguiente ejemplo.

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
  })
}

Sintaxis de expresiones complejas

Puedes escribir expresiones más complejas combinándolas con los operadores && y ||.

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

En la siguiente sección, se describen todos los operadores disponibles.

Operadores y prioridad de operadores

Usa la siguiente tabla como referencia para los operadores y su prioridad correspondiente.

Dadas las expresiones arbitrarias a y b, un campo f y un índice i.

Operador Descripción Asociatividad
a[i] a() a.f Acceso a índice, llamada o campo de izquierda a derecha
!a -a Negación unaria De derecha a izquierda
a/b a%b a*b Operadores multiplicativos de izquierda a derecha
a+b a-b Operadores aditivos de izquierda a derecha
a>b a>=b a<b a<=b Operadores relacionales de izquierda a derecha
a in b Existencia en lista o mapa de izquierda a derecha
type(a) == t Comparación de tipos, en la que t puede ser bool, int, float, number, string, list, map, timestamp o duration de izquierda a derecha
a==b a!=b Operadores de comparación de izquierda a derecha
a && b Condicional AND de izquierda a derecha
a || b Condicional OR de izquierda a derecha
a ? true_value : false_value Expresión ternaria de izquierda a derecha

Datos en tokens de autenticación

El objeto auth.token puede contener los siguientes valores:

Campo Descripción
email Dirección de correo electrónico asociada con la cuenta, si está presente.
email_verified true si el usuario verificó que tiene acceso a la dirección email. Algunos proveedores verifican automáticamente las direcciones de correo electrónico de su propiedad.
phone_number Número de teléfono asociado con la cuenta, si está presente.
name Nombre visible del usuario, si se configuró.
sub UID de Firebase del usuario. Es único dentro de un proyecto.
firebase.identities Diccionario de todas las identidades asociadas con la cuenta del usuario. Las claves del diccionario pueden ser cualquiera de las siguientes: email, phone, google.com, facebook.com, github.com y twitter.com. Los valores del diccionario son arreglos de identificadores únicos para cada proveedor de identidad asociado con la cuenta. Por ejemplo, auth.token.firebase.identities["google.com"][0] contiene el primer ID de usuario de Google asociado a la cuenta.
firebase.sign_in_provider Proveedor de acceso utilizado para obtener este token. Puede ser una de las siguientes strings: custom, password, phone, anonymous, google.com, facebook.com, github.com y twitter.com.
firebase.tenant Es el tenantId asociado con la cuenta, si está presente. Por ejemplo, tenant2-m6tyz

Campos adicionales en los tokens de ID de JWT

También puedes acceder a los siguientes campos de auth.token:

Reclamaciones de tokens personalizados
alg Algoritmo "RS256"
iss Emisor Dirección de correo electrónico de la cuenta de servicio del proyecto
sub Asunto Dirección de correo electrónico de la cuenta de servicio del proyecto
aud Público "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Hora de emisión Hora actual, en segundos transcurridos desde la época UNIX
exp Hora de vencimiento Hora de vencimiento del token, en segundos transcurridos desde la época UNIX Puede ser un máximo de 3,600 segundos más tarde de iat.
Nota: Ten en cuenta que esto solo controla la hora de vencimiento del token personalizado en sí. Sin embargo, cuando haces que un usuario acceda con signInWithCustomToken(), su acceso al dispositivo se mantendrá hasta que esa sesión deje de ser válida o el usuario la cierre.
<claims> (opcional) Son reclamaciones personalizadas opcionales que se pueden incluir en el token y a las que se puede acceder a través de auth.token (o request.auth.token) en las expresiones. Por ejemplo, si creas un reclamo personalizado adminClaim, puedes acceder a él con auth.token.adminClaim.

Próximos pasos