Usar SDKs Admin gerados

Com os SDKs Admin do Firebase Data Connect, é possível 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 seus apps cliente, é possível gerar um SDK Admin personalizado em paralelo ao criar os esquemas, as consultas e as 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 mencionamos em outro lugar, é importante observar que as consultas e mutações do Data Connect não são enviadas pelos clientes no momento da solicitação. Em vez disso, quando implantadas, as operações do 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 de administrador e reimplantar 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 de administrador correspondente:

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

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

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

  2. Gere o SDK.

    Se você tiver a extensão Data Connect do 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 ao 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 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.