Utilizzare gli SDK Admin generati

Gli SDK Firebase Data Connect Admin ti consentono di chiamare le query e le mutazioni da ambienti attendibili come Cloud Functions, backend personalizzati o la tua workstation. Proprio come generi gli SDK per le tue app client, puoi generare un SDK Admin personalizzato in parallelo mentre progetti gli schemi, le query e le mutazioni che implementi nel servizio Data Connect. Poi, integra i metodi di questo SDK nella logica di backend o negli script di amministrazione.

Come abbiamo già detto, è importante notare che le query e le modifiche di Data Connect non vengono inviate dai client al momento della richiesta. Quando vengono implementate, le operazioni di Data Connect vengono memorizzate sul server come Cloud Functions. Ciò significa che ogni volta che implementi modifiche alle query e alle mutazioni, devi anche rigenerare gli SDK Admin e reimplementare tutti i servizi che si basano su di essi.

Prima di iniziare

Genera SDK Admin

Dopo aver creato schemi, query e mutazioni di Data Connect, puoi generare un SDK amministratore corrispondente:

  1. Apri o crea un file connector.yaml e aggiungi una definizione adminNodeSdk:

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

    Il file connector.yaml si trova in genere nella stessa directory dei file GraphQL (.gql) che contengono le definizioni di query e mutazione. Se hai già generato gli SDK client, questo file è già stato creato.

  2. Genera l'SDK.

    Se hai installato l'estensione Data Connect VS Code, gli SDK generati saranno sempre aggiornati.

    Altrimenti, utilizza l'interfaccia a riga di comando di Firebase:

    firebase dataconnect:sdk:generate

    In alternativa, per rigenerare automaticamente gli SDK quando aggiorni i file gql:

    firebase dataconnect:sdk:generate --watch

Eseguire operazioni da un SDK Admin

L'SDK Admin generato contiene interfacce e funzioni che corrispondono alle tue definizioni di gql, che puoi utilizzare per eseguire operazioni sul tuo database. Ad esempio, supponiamo di aver generato un SDK per un database di brani, insieme a una query, 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 } }
);

In alternativa, per specificare una configurazione del connettore:

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

Impersonare un utente non autenticato

Gli SDK Admin sono progettati per essere eseguiti da ambienti attendibili e pertanto hanno accesso illimitato ai tuoi database.

Quando esegui operazioni pubbliche con l'SDK Admin, devi evitare di eseguire l'operazione con privilegi amministrativi completi (seguendo il principio del privilegio minimo). Devi invece eseguire l'operazione come utente rappresentato (vedi la sezione successiva) o come utente non autenticato rappresentato. Gli utenti non autenticati possono eseguire solo le operazioni contrassegnate come PUBLIC.

Nell'esempio precedente, la query getSongs viene eseguita come utente non autenticato.

Impersonare un utente

Puoi anche eseguire operazioni per conto di utenti specifici passando parte o tutto un token Firebase Authentication nell'opzione impersonate; come minimo, devi specificare l'ID utente dell'utente nella rivendicazione sub. (Si tratta dello stesso valore del valore del server auth.uid a cui puoi fare riferimento nelle operazioni GraphQL di Data Connect.)

Quando esegui l'impersonificazione di un utente, l'operazione andrà a buon fine solo se i dati utente che hai fornito superano i controlli di autenticazione specificati nella definizione GraphQL.

Se chiami l'SDK generato da un endpoint accessibile pubblicamente, è fondamentale che l'endpoint richieda l'autenticazione e che tu convalidi l'integrità del token di autenticazione prima di utilizzarlo per rappresentare un utente.

Quando utilizzi Cloud Functions richiamabile, il token di autenticazione viene verificato automaticamente e puoi utilizzarlo come nel seguente esempio:

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

    // ...
});

In caso contrario, utilizza il metodo verifyIdToken di Admin SDK per convalidare e decodificare il token di autenticazione. Ad esempio, supponiamo che l'endpoint sia implementato come una semplice funzione HTTP e che tu abbia passato il token Firebase Authentication all'endpoint utilizzando l'intestazione authorization, come di consueto:

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 quando esegui attività amministrative reali, come la migrazione dei dati, da un ambiente sicuro e non accessibile pubblicamente, devi specificare un ID utente che non provenga da un'origine verificabile:

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

Esecuzione con accesso illimitato

Se stai eseguendo un'operazione che richiede autorizzazioni a livello amministrativo, ometti il parametro impersonate dalla chiamata:

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

Un'operazione chiamata in questo modo ha accesso completo al database. Se hai query o mutazioni destinate esclusivamente a scopi amministrativi, devi definirle con la direttiva @auth(level: NO_ACCESS). In questo modo, solo i chiamanti a livello amministrativo possono eseguire queste operazioni.