Usar o SDK Admin com o Data Connect

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

O Admin SDK oferece uma API para chamar operações nos modos de leitura/gravação e 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 Data 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 Data 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 de Data 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 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 ser mais explícito e defini-las como NO_ACCESS. De qualquer forma, isso impede que essas operações sejam executadas em clientes ou em outros contextos não privilegiados.

Usar o SDK com o emulador Data Connect

Em ambientes de protótipo e teste, pode ser útil realizar a geração de dados e outras operações em dados locais. O Admin SDK simplifica seus fluxos de trabalho porque 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 operações com simulação de usuário.

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

export DATA_CONNECT_EMULATOR_HOST="127.0.0.1:9399"

Confira mais informações:

Executar operações de administrador

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

O SDK Admin oferece três conjuntos de APIs:

  • SDKs Admin gerados, que são SDKs com segurança de tipo gerados das suas definições de gql da mesma forma que você gera SDKs do cliente.
  • Uma interface geral para executar operações arbitrárias do GraphQL, em que seu código implementa consultas e mutações e as transmite para o método executeGraphql de leitura e gravação ou o método executeGraphqlRead somente leitura.
  • Uma interface especializada para operações de dados em massa que, em vez de métodos genéricos executeGraphql, 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 das suas definições de gql da mesma forma que gera SDKs do cliente.

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

Representar um usuário não autenticado

Os SDKs Admin são projetados para serem executados 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 do privilégio mínimo). Em vez disso, execute a operação como um usuário representado (consulte a próxima seção) ou como um usuário não autenticado representado. 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.

Representar 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, você precisa especificar o ID do usuário na subdeclaração. Esse é o mesmo valor do auth.uid do servidor que você pode referenciar nas operações do GraphQL do Data Connect.

Ao simular um usuário, a operação só será concluída se os dados fornecidos passarem nas 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 simular um usuário.

Ao usar Cloud Functions invocável, o token de autenticação é verificado automaticamente, e você pode usá-lo 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 } }
    );

    // ...
});

Só especifique um ID de usuário que não tenha origem em uma fonte verificável ao realizar tarefas administrativas reais, como migração de dados, em um ambiente seguro e não acessível ao público:

// 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 nível de administrador, omita o parâmetro "impersonate" 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 a fins administrativos, defina-as com a diretiva @auth(level: NO_ACCESS). Assim, apenas chamadores no nível de administrador podem 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.

Representar 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 do privilégio mínimo). Em vez disso, execute a operação como um usuário representado (consulte a próxima seção) ou como um usuário representado não autenticado. 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);

Representar um usuário

Há também casos de uso em que você quer que seus scripts modifiquem os dados do usuário com base em credenciais limitadas, em nome de um usuário específico. Essa abordagem segue o princípio do 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 nível de administrador, omita o parâmetro "impersonate" 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 a fins administrativos, defina-as com a diretiva @auth(level: NO_ACCESS). Assim, apenas chamadores no nível de administrador podem executar essas operações.

Realizar operações de dados em massa

O Firebase recomenda usar o 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 Data 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);

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

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

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

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

A seguir