Usar o SDK Admin com o SQL Connect

O Firebase Admin SDK é um conjunto de bibliotecas de servidor que permite interagir com o Firebase usando ambientes privilegiados para executar ações como consultas e mutações em um serviço Firebase SQL Connect para gerenciamento de dados em massa e outras operações com privilégios elevados e credenciais personificadas.

O Admin SDK fornece uma API para chamar operações nos modos de leitura/gravação e modo somente leitura. Com as operações somente leitura, você tem a tranquilidade de implementar funções administrativas que não podem modificar dados nos seus bancos de dados.

Configuração do SDK Admin

Para começar a usar o Firebase SQL Connect no seu servidor, primeiro instale e configure o Admin SDK para Node.js.

Inicializar o SDK Admin nos seus scripts

Para inicializar o SDK, importe as extensões SQL Connect e declare o ID e o local do serviço do projeto.


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

Projetar consultas e mutações para usar com o Admin SDK

O Admin SDK é útil para executar operações SQL Connect, considerando as seguintes considerações.

Entender o SDK e a diretiva de operação @auth(level: NO_ACCESS)

Como o Admin SDK opera com privilégios, ele pode executar qualquer uma das suas consultas e mutações, independentemente dos níveis de acesso definidos usando as diretivas @auth, incluindo o nível NO_ACCESS.

Se, além das operações do cliente, você organizar as consultas e mutações administrativas em arquivos de origem .gql para importação em scripts administrativos, o Firebase recomenda marcar as operações administrativas sem nenhum nível de acesso de autorização ou, talvez, ser mais explícito e defini-las como NO_ACCESS. De qualquer forma, isso impede que essas operações sejam executadas por clientes ou em outros contextos não privilegiados.

Usar o SDK com o emulador do SQL Connect

Em ambientes de protótipo e teste, pode ser útil realizar a propagação de dados e outras operações em dados locais. O Admin SDK permite simplificar seus fluxos de trabalho, já que ele pode ignorar a autenticação e a autorização para fluxos locais. Também é possível ativar explicitamente a conformidade com a configuração de autenticação e autorização das suas operações com a falsificação de identidade do usuário.

Os SDKs Admin do Firebase se conectam automaticamente ao emulador do SQL Connect quando a variável de ambiente DATA_CONNECT_EMULATOR_HOST está definida:

export DATA_CONNECT_EMULATOR_HOST="127.0.0.1:9399"

Para mais informações, consulte:

Executar operações de administrador

O Admin SDK é fornecido para operações privilegiadas nos dados críticos.

O SDK Admin oferece três conjuntos de APIs:

  • SDKs Admin gerados, que são SDKs com segurança de tipos gerados com base nas suas definições de gql, da mesma forma que você gera SDKs de cliente.
  • Uma interface geral para executar operações arbitrárias do GraphQL, em que o código implementa consultas e mutações e as transmite para o método de leitura e gravação executeGraphql ou o método somente leitura executeGraphqlRead.
  • Uma interface especializada para operações de dados em massa que, em vez de métodos executeGraphql genéricos, expõe métodos dedicados para operações de mutação: insert, insertMany, upsert e upsertMany.

Gerenciar dados com SDKs gerados

Você gera SDKs Admin com base nas definições de gql da mesma forma que gera SDKs de cliente.

O SDK Admin gerado contém interfaces e funções que correspondem às definições de gql, que podem ser usadas para realizar operações no banco de dados. Por exemplo, suponha que você tenha gerado um SDK para um banco de dados de músicas, com uma 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 } }
);

Ou, para especificar uma configuração de conector:

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

Fingir ser um usuário não autenticado

Os SDKs Admin são destinados à execução em ambientes confiáveis e, portanto, têm acesso irrestrito aos seus bancos de dados.

Ao executar operações públicas com o SDK Admin, evite usar privilégios de administrador completos (seguindo o princípio de privilégio mínimo). Em vez disso, execute a operação como um usuário personificado (consulte a próxima seção) ou como um usuário não autenticado personificado. Os usuários não autenticados só podem executar operações marcadas como PUBLIC.

No exemplo acima, a consulta getSongs é executada como um usuário não autenticado.

Falsificação de identidade de um usuário

Também é possível realizar operações em nome de usuários específicos transmitindo parte ou todo um token Firebase Authentication na opção impersonate. No mínimo, é necessário especificar o ID do usuário na declaração secundária. Esse é o mesmo valor do valor do servidor auth.uid que pode ser referenciado nas operações do GraphQL SQL Connect.

Quando você personifica um usuário, a operação só será bem-sucedida se os dados do usuário fornecidos passarem pelas verificações de autenticação especificadas na definição do GraphQL.

Se você estiver chamando o SDK gerado de um endpoint acessível publicamente, é fundamental que o endpoint exija autenticação e que você valide a integridade do token de autenticação antes de usá-lo para personificar um usuário.

Ao usar o Cloud Functions chamável, o token de autenticação é verificado automaticamente e pode ser usado como no exemplo a seguir:

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

    // ...
});

Caso contrário, use o método verifyIdToken do Admin SDK para validar e decodificar o token de autenticação. Por exemplo, suponha que seu endpoint seja implementado como uma função HTTP simples e que você tenha transmitido o token Firebase Authentication para o endpoint usando o cabeçalho authorization, como é padrão:

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

    // ...
});

Somente ao realizar tarefas administrativas verdadeiras, como migração de dados, em um ambiente seguro e não acessível publicamente, você deve especificar um ID do usuário que não tenha sido originado de uma fonte verificável:

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

Execução com acesso irrestrito

Se você estiver realizando uma operação que exige permissões de administrador, omita o parâmetro de representação da chamada:

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

Uma operação chamada dessa maneira tem acesso completo ao banco de dados. Se você tiver consultas ou mutações destinadas apenas para fins de administração, defina-as com a diretiva @auth(level: NO_ACCESS). Isso garante que apenas os autores de chamadas no nível de administrador possam executar essas operações.

Gerenciar dados com métodos executeGraphql

Se você precisar executar operações únicas para as quais não definiu mutações ou consultas gql, use o método executeGraphql ou o método executeGraphqlRead somente leitura.

Realizar operações relacionais aninhadas personalizadas (um para muitos e um para um) usando executeGraphql

Ao chamar operações do SDK Admin em um ambiente privilegiado, não é necessário especificar a diretiva @allow nas variáveis para inserções simples padrão (não aninhadas), como inserir registros individuais de filmes ou atores.

No entanto, para inserções relacionais aninhadas (como inserir um filme com as avaliações iniciais ou um usuário e o perfil dele), ainda é necessário declarar a diretiva @allow na definição da variável para permitir estaticamente os campos relacionais aninhados dentro do esquema de variável compilado.

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

Fingir ser um usuário não autenticado

Ao executar operações públicas com o SDK Admin, evite usar privilégios de administrador completos (seguindo o princípio de privilégio mínimo). Em vez disso, execute a operação como um usuário personificado (consulte a próxima seção) ou como um usuário não autenticado personificado. Os usuários não autenticados só podem executar operações 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);

Falsificação de identidade de um usuário

Também há casos de uso em que você quer que seus scripts modifiquem dados do usuário com base em credenciais limitadas, em nome de um usuário específico. Essa abordagem segue o princípio de privilégio mínimo.

Para usar essa interface, colete informações de um token de autenticação JWT personalizado que segue o formato de token Authentication. Consulte também o guia 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" } } }

Usar credenciais administrativas

Se você estiver realizando uma operação que exige permissões de administrador, omita o parâmetro de representação da chamada:

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

Uma operação chamada dessa maneira tem acesso completo ao banco de dados. Se você tiver consultas ou mutações destinadas apenas para fins de administração, defina-as com a diretiva @auth(level: NO_ACCESS). Isso garante que apenas os autores de chamadas no nível de administrador possam executar essas operações.

Realizar operações de inserção de dados em massa

O Firebase recomenda o uso do Admin SDK para operações de dados em massa em bancos de dados de produção.

O SDK oferece os seguintes métodos para trabalhar com dados em massa. Com base nos argumentos fornecidos, cada método cria e executa uma mutação do 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);

Operações relacionais aninhadas (um para muitos e um para um)

O Admin SDK oferece suporte a dados relacionais em operações em massa. Assim como nas mutações personalizadas, use uma matriz de objetos para relações de um para muitos e um único objeto para relações de um para um:

// 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);

Observações de desempenho para operações em massa

Cada solicitação ao back-end vai gerar uma ida e volta para Cloud SQL. Portanto, quanto mais você agrupar, maior será a capacidade de processamento.

No entanto, quanto maior o tamanho do lote, mais longa será a instrução SQL gerada. Quando o limite de comprimento da instrução SQL do PostgreSQL for atingido, você vai encontrar um erro.

Na prática, faça testes para encontrar o tamanho de lote adequado para sua carga de trabalho.

A seguir