Usa el SDK de Admin con SQL Connect

El Firebase Admin SDK es un conjunto de bibliotecas de servidor que te permiten interactuar con Firebase desde entornos con privilegios para realizar acciones como consultas y mutaciones en un servicio de Firebase SQL Connect para la administración de datos masivos y otras operaciones con privilegios elevados y credenciales de identidad temporal.

La Admin SDK te proporciona una API para llamar a operaciones en los modos de lectura y escritura, y de solo lectura. Con las operaciones de solo lectura, tienes la tranquilidad de implementar funciones administrativas que no pueden modificar los datos en tus bases de datos.

Configuración del SDK de Admin

Para comenzar a usar Firebase SQL Connect en tu servidor, primero deberás instalar y configurar Admin SDK para Node.js.

Inicializa el SDK de Admin en tus secuencias de comandos

Para inicializar el SDK, importa las extensiones de SQL Connect y declara el ID y la ubicación del servicio de tu proyecto.


import { initializeApp } from 'firebase-admin/app';
import { getDataConnect } from 'firebase-admin/data-connect';

// If you'd like to use OAuth2 flows and other credentials to log in,
// visit https://firebase.google.com/docs/admin/setup#initialize-sdk
// for alternative ways to initialize the SDK.

const app = initializeApp();

const dataConnect = getDataConnect({
    serviceId: 'serviceId',
    location: 'us-west2'
});

Diseña consultas y mutaciones para usarlas con el Admin SDK

Admin SDK es útil para ejecutar operaciones SQL Connect, dadas las siguientes consideraciones.

Comprende el SDK y la directiva de operación @auth(level: NO_ACCESS)

Como Admin SDK opera con privilegios, puede ejecutar cualquiera de tus consultas y mutaciones, independientemente de los niveles de acceso establecidos con las directivas @auth, incluido el nivel NO_ACCESS.

Si, junto con las operaciones del cliente, organizas tus consultas y mutaciones administrativas en archivos de origen .gql para importarlas a secuencias de comandos administrativas, Firebase recomienda que marques las operaciones administrativas sin ningún nivel de acceso de autorización o, tal vez, que seas más explícito y las configures como NO_ACCESS. De cualquier manera, esto evita que esas operaciones se ejecuten desde clientes o en otros contextos sin privilegios.

Usa el SDK con el emulador de SQL Connect

En los entornos de prototipo y de pruebas, puede ser útil realizar la propagación de datos y otras operaciones en los datos locales. Admin SDK te permite simplificar tus flujos de trabajo, ya que puede ignorar la autenticación y la autorización para los flujos locales. (También puedes aceptar de forma explícita cumplir con la configuración de autenticación y autorización de tus operaciones con la identidad temporal de usuarios).

Los SDKs de Firebase Admin se conectan automáticamente al emulador de SQL Connect cuando se configura la variable de entorno DATA_CONNECT_EMULATOR_HOST de la siguiente manera:

export DATA_CONNECT_EMULATOR_HOST="127.0.0.1:9399"

Para obtener más información, consulte:

Ejecutar operaciones de administrador

La Admin SDK se proporciona para operaciones con privilegios en tus datos críticos.

El SDK de Admin proporciona tres conjuntos de APIs:

  • SDKs de administrador generados, que son SDKs con seguridad de tipos generados a partir de tus definiciones de gql de la misma manera que generas SDKs de cliente.
  • Una interfaz general para ejecutar operaciones arbitrarias de GraphQL, en la que tu código implementa consultas y mutaciones, y las pasa al método de lectura y escritura executeGraphql o al método de solo lectura executeGraphqlRead.
  • Es una interfaz especializada para operaciones de datos masivas que, en lugar de métodos executeGraphql genéricos, expone métodos dedicados para operaciones de mutación: insert, insertMany, upsert y upsertMany.

Administra datos con los SDKs generados

Generas SDKs de administrador a partir de tus definiciones de gql de la misma manera que generas SDKs de cliente.

El SDK de Admin generado contiene interfaces y funciones que corresponden a tus definiciones de gql, que puedes usar para realizar operaciones en tu base de datos. Por ejemplo, supongamos que generaste un SDK para una base de datos de canciones, junto con una consulta, getSongs:

import { initializeApp } from "firebase-admin/app";
import { getSongs } from "@dataconnect/admin-generated";

const adminApp = initializeApp();

const songs = await getSongs(
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);

O bien, para especificar una configuración de conector, haz lo siguiente:

import { initializeApp } from "firebase-admin/app";
import { getDataConnect } from "firebase-admin/data-connect";
import {
  connectorConfig,
  getSongs,
} from "@dataconnect/admin-generated";

const adminApp = initializeApp();
const adminDc = getDataConnect(connectorConfig);

const songs = await getSongs(
  adminDc,
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);

Suplantar la identidad de un usuario no autenticado

Los SDK de Admin están diseñados para ejecutarse desde entornos de confianza y, por lo tanto, tienen acceso sin restricciones a tus bases de datos.

Cuando ejecutes operaciones públicas con el SDK de Admin, debes evitar ejecutar la operación con privilegios de administrador completos (siguiendo el principio de privilegio mínimo). En su lugar, debes ejecutar la operación como un usuario con identidad suplantada (consulta la siguiente sección) o como un usuario no autenticado con identidad suplantada. Los usuarios no autenticados solo pueden ejecutar operaciones marcadas como PUBLIC.

En el ejemplo anterior, la consulta getSongs se ejecuta como un usuario no autenticado.

Suplantar la identidad de un usuario

También puedes realizar operaciones en nombre de usuarios específicos pasando parte o todo un token Firebase Authentication en la opción impersonate; como mínimo, debes especificar el ID de usuario del usuario en la reclamación sub. (Este es el mismo valor que el valor del servidor auth.uid al que puedes hacer referencia en las operaciones de GraphQL de SQL Connect).

Cuando actúas en nombre de un usuario, la operación se realizará correctamente solo si los datos del usuario que proporcionaste pasan las verificaciones de autenticación especificadas en tu definición de GraphQL.

Si llamas al SDK generado desde un extremo de acceso público, es fundamental que el extremo requiera autenticación y que valides la integridad del token de autenticación antes de usarlo para actuar en nombre de un usuario.

Cuando usas Cloud Functions que admiten llamadas, el token de autenticación se verifica automáticamente y puedes usarlo como en el siguiente ejemplo:

import { HttpsError, onCall } from "firebase-functions/https";

export const callableExample = onCall(async (req) => {
    const authClaims = req.auth?.token;
    if (!authClaims) {
        throw new HttpsError("unauthenticated", "Unauthorized");
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});

De lo contrario, usa el método verifyIdToken de Admin SDK para validar y decodificar el token de autenticación. Por ejemplo, supongamos que tu extremo se implementa como una función HTTP simple y que pasaste el token Firebase Authentication a tu extremo con el encabezado authorization, como es estándar:

import { getAuth } from "firebase-admin/auth";
import { onRequest } from "firebase-functions/https";

const auth = getAuth();

export const httpExample = onRequest(async (req, res) => {
    const token = req.header("authorization")?.replace(/^bearer\s+/i, "");
    if (!token) {
        res.sendStatus(401);
        return;
    }
    let authClaims;
    try {
        authClaims = await auth.verifyIdToken(token);
    } catch {
        res.sendStatus(401);
        return;
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});

Solo cuando realices tareas administrativas reales, como la migración de datos, desde un entorno seguro y no accesible de forma pública, debes especificar un ID de usuario que no se haya originado en una fuente verificable:

// Never do this if end users can initiate execution of the code!
const favoriteSongs = await getMyFavoriteSongs(
  undefined,
  { impersonate: { authClaims } }
);

Se ejecuta con acceso sin restricciones

Si realizas una operación que requiere permisos de nivel de administrador, omite el parámetro de suplantación de identidad de la llamada:

await upsertSong(adminDc, {
  title: songTitle_one,
  instrumentsUsed: [Instrument.VOCAL],
});

Una operación a la que se llama de esta manera tiene acceso completo a la base de datos. Si tienes consultas o mutaciones que solo se usarán con fines de administración, debes definirlas con la directiva @auth(level: NO_ACCESS). De esta manera, se garantiza que solo los emisores con nivel de administrador puedan ejecutar estas operaciones.

Administra datos con métodos de executeGraphql

Si necesitas ejecutar operaciones únicas para las que no definiste mutaciones o consultas de gql, puedes usar el método executeGraphql o el método executeGraphqlRead de solo lectura.

Realizar operaciones relacionales anidadas personalizadas (de uno a varios y de uno a uno) con executeGraphql

Cuando llamas a operaciones desde el SDK de Admin en un entorno con privilegios, no necesitas especificar la directiva @allow en tus variables para inserciones planas estándar (no anidadas), como insertar registros individuales de películas o actores.

Sin embargo, para las inserciones relacionales anidadas (como insertar una película junto con sus opiniones iniciales o un usuario y su perfil), debes declarar la directiva @allow en la definición de la variable para permitir de forma estática los campos relacionales anidados dentro del esquema de la variable compilada.

// Custom GraphQL mutation with nested relational insert (one-to-many)
const customMutation = `
  mutation CustomBatchInsert(
    $data: [Movie_Data!]!
    @allow(fields: "title genre reviews_on_movie { rating reviewText }")
  ) {
    movie_insertMany(data: $data)
  }
`;
const variables = {
  data: [
    {
      title: "Interstellar",
      genre: "Sci-Fi",
      reviews_on_movie: [
        {
          rating: 5,
          reviewText: "Visually stunning and emotionally powerful.",
          user: { id: "user-789" }
        }
      ]
    }
  ]
};
const response = await dataConnect.executeGraphql(customMutation, { variables });
// Custom GraphQL mutation with a one-to-one nested relational insert
const customMutationOneToOne = `
  mutation CustomBatchInsertOneToOne(
    $data: [User_Data!]!
    @allow(fields: "name profile_on_user { bio }")
  ) {
    user_insertMany(data: $data)
  }
`;
const variablesOneToOne = {
  data: [
    {
      name: "Alice",
      profile_on_user: {
        bio: "Avid moviegoer and critic."
      }
    }
  ]
};
const responseOneToOne = await dataConnect.executeGraphql(customMutationOneToOne, { variables: variablesOneToOne });

Suplantar la identidad de un usuario no autenticado

Cuando ejecutes operaciones públicas con el SDK de Admin, debes evitar ejecutar la operación con privilegios de administrador completos (siguiendo el principio de privilegio mínimo). En su lugar, debes ejecutar la operación como un usuario con identidad suplantada (consulta la siguiente sección) o como un usuario no autenticado con identidad suplantada. Los usuarios no autenticados solo pueden ejecutar operaciones marcadas como PUBLIC.

// Query to get posts, with authentication level PUBLIC
const queryGetPostsImpersonation = `
    query getPosts @auth(level: PUBLIC) {
        posts {
          description
        }
    }`;

// Attempt to access data as an unauthenticated user
const optionsUnauthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        unauthenticated: true
    }
};

// executeGraphql with impersonated unauthenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetPostsImpersonation, optionsUnauthenticated);

Suplantar la identidad de un usuario

También hay casos de uso en los que quieres que tus secuencias de comandos modifiquen los datos del usuario en función de credenciales limitadas, en nombre de un usuario específico. Este enfoque respeta el principio de privilegio mínimo.

Para usar esta interfaz, recopila información de un token de autenticación de JWT personalizado que siga el formato de token de Authentication. Consulta también la guía de tokens personalizados.

// Get the current user's data
const queryGetUserImpersonation = `
    query getUser @auth(level: USER) {
        user(key: {uid_expr: "auth.uid"}) {
            id,
            name
        }
    }`;

// Impersonate a user with the specified auth claims
const optionsAuthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        authClaims: {
            sub: 'QVBJcy5ndXJ1'
        }
    }
};

// executeGraphql with impersonated authenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetUserImpersonation, optionsAuthenticated);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }

Usa credenciales administrativas

Si realizas una operación que requiere permisos de nivel de administrador, omite el parámetro de suplantación de identidad de la llamada:

// User can be publicly accessible, or restricted to admins
const query = "query getProfile(id: AuthID) { user(id: $id) { id name } }";

interface UserData {
  user: {
    id: string;
    name: string;
  };
}

export interface UserVariables {
  id: string;
}

const options:GraphqlOptions<UserVariables> = { variables: { id: "QVBJcy5ndXJ1" } };

// executeGraphql
const gqlResponse = await dataConnect.executeGraphql<UserData, UserVariables>(query, options);

// executeGraphqlRead (similar to previous sample but only for read operations)
const gqlResponse = await dataConnect.executeGraphqlRead<UserData, UserVariables>(query, options);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }

Una operación a la que se llama de esta manera tiene acceso completo a la base de datos. Si tienes consultas o mutaciones que solo se usarán con fines de administración, debes definirlas con la directiva @auth(level: NO_ACCESS). De esta manera, se garantiza que solo los emisores con nivel de administrador puedan ejecutar estas operaciones.

Realiza operaciones de inserción masiva de datos

Firebase recomienda que uses Admin SDK para las operaciones de datos masivas en las bases de datos de producción.

El SDK proporciona los siguientes métodos para trabajar con datos masivos. A partir de los argumentos proporcionados, cada método construye y ejecuta una mutación de GraphQL.


// Methods of the bulk operations API
// dc is a SQL Connect admin instance from getDataConnect

const resp = await dc.insert("movie" /*table name*/, data[0]);
const resp = await dc.insertMany("movie" /*table name*/, data);
const resp = await dc.upsert("movie" /*table name*/, data[0]);
const resp = await dc.upsertMany("movie" /*table name*/, data);

Operaciones relacionales anidadas (uno a varios y uno a uno)

Admin SDK admite datos relacionales en operaciones masivas. Al igual que con las mutaciones personalizadas, usa un array de objetos para las relaciones de uno a varios y un solo objeto para las relaciones de uno a uno:

// Example of inserting a movie with nested reviews (one-to-many)
const moviesData = [
  {
    title: "Interstellar",
    genre: "Sci-Fi",
    reviews_on_movie: [
      {
        rating: 5,
        reviewText: "Visually stunning and emotionally powerful.",
        user: { id: "user-789" }
      }
    ]
  }
];

// Atomically insert movies and their reviews in a single database round-trip.
const response = await dc.insertMany("movie", moviesData);
// Example of inserting a user with a nested profile (one-to-one)
const usersData = [
  {
    name: "Alice",
    profile_on_user: {
      bio: "Avid moviegoer and critic."
    }
  }
];

// Atomically insert users and their profiles.
const userResponse = await dc.insertMany("user", usersData);

Notas de rendimiento para operaciones masivas

Cada solicitud al backend generará un viaje de ida y vuelta a Cloud SQL, por lo que, cuanto más agrupes, mayor será la capacidad de procesamiento.

Sin embargo, cuanto mayor sea el tamaño del lote, más larga será la sentencia de SQL generada. Cuando se alcance el límite de longitud de la sentencia de SQL de PostgreSQL, se producirá un error.

En la práctica, experimenta para encontrar el tamaño de lote adecuado para tu carga de trabajo.

Próximos pasos