Usar SDKs Admin gerados

Os SDKs Admin do Firebase Data Connect permitem chamar consultas e mutações de ambientes confiáveis, como o Cloud Functions, back-ends personalizados ou sua própria estação de trabalho. Da mesma forma que você gera SDKs para apps clientes, é possível gerar um SDK Admin personalizado em paralelo ao criar os esquemas, consultas e mutações que você implanta no serviço Data Connect. Em seguida, integre métodos desse SDK à lógica de back-end ou aos scripts de administração.

Como já mencionamos, é importante observar que as Data Connect consultas e mutações não são enviadas pelos clientes no momento da solicitação. Em vez disso, quando implantadas, as operações Data Connect são armazenadas no servidor, como o Cloud Functions. Isso significa que, sempre que você implantar mudanças nas consultas e mutações, também será necessário regenerar os SDKs Admin e reimplantar todos os serviços que dependem deles.

Antes de começar

Gerar SDKs Admin

Depois de criar os esquemas, consultas e mutações do Data Connect, você pode gerar um SDK Admin correspondente:

  1. Abra ou crie um arquivo connector.yaml e adicione uma definição adminNodeSdk:

    connectorId: default
    generate:
      adminNodeSdk:
        outputDir: ../../dataconnect-generated/admin-generated
        package: "@dataconnect/admin-generated"
        packageJsonDir: ../..
    

    O arquivo connector.yaml geralmente é encontrado no mesmo diretório que os arquivos GraphQL (.gql) que contêm as definições de consulta e mutação. Se você já gerou SDKs do cliente, esse arquivo já foi criado.

  2. Gere o SDK.

    Se você tiver a extensão do Data Connect para VS Code instalada, ela sempre manterá os SDKs gerados atualizados.

    Caso contrário, use a CLI do Firebase:

    firebase dataconnect:sdk:generate

    Ou, para regenerar automaticamente os SDKs quando você atualizar os arquivos gql:

    firebase dataconnect:sdk:generate --watch

Executar operações de um SDK Admin

O SDK Admin gerado contém interfaces e funções que correspondem às definições gql, que podem ser usadas para realizar operações no 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 destinados a 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 executar a operação com 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 Firebase Authentication token na opção impersonate. No mínimo, é necessário especificar o ID do usuário na declaração "sub". Esse é o mesmo valor do auth.uid valor do servidor que pode ser referenciado nas Data Connect operações do GraphQL.

Ao representar um usuário, a operação só será bem-sucedida se os dados do usuário 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 representar um usuário.

Ao usar o Cloud Functions chamá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 Admin SDK's verifyIdToken método para validar e decodificar o token de autenticação. Por exemplo, suponha que o 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, especifique um ID de usuário que não tenha origem em uma fonte verificável:

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

Executar com acesso irrestrito

Se você estiver realizando uma operação que exige permissões de nível 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 a fins de administração, defina-as com a diretiva @auth(level: NO_ACCESS). Isso garante que apenas autores de chamadas de nível de administrador possam executar essas operações.