Utilizzare l'SDK Admin con SQL Connect

Il Firebase Admin SDK è un insieme di librerie server che ti consente di interagire con Firebase da ambienti con privilegi per eseguire azioni come query e mutazioni su un Firebase SQL Connect servizio per la gestione collettiva dei dati e altre operazioni con privilegi elevati e credenziali impersonate.

Il Admin SDK fornisce un'API per chiamare le operazioni in modalità di lettura/scrittura e di sola lettura. Con le operazioni di sola lettura, puoi implementare le funzioni amministrative che non possono modificare i dati nei database.

Configurazione dell'SDK Admin

Per iniziare a utilizzare con Firebase SQL Connect sul server, devi prima installare e configurare Admin SDK per Node.js.

Inizializzare l'SDK Admin negli script

Per inizializzare l'SDK, importa le estensioni SQL Connect e dichiara l'ID servizio e la località del progetto.


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

Progettare query e mutazioni da utilizzare con il Admin SDK

Il Admin SDK è utile per eseguire le operazioni di SQL Connect, tenendo conto delle seguenti considerazioni.

Comprendere l'SDK e la direttiva dell'operazione @auth(level: NO_ACCESS)

Poiché il Admin SDK opera con privilegi, può eseguire qualsiasi query e mutazione indipendentemente dai livelli di accesso impostati utilizzando le direttive @auth, incluso il livello NO_ACCESS.

Se, oltre alle operazioni client, organizzi le query e le mutazioni amministrative nei file di origine .gql per l'importazione negli script amministrativi, Firebase ti consiglia di contrassegnare le operazioni amministrative senza alcun livello di accesso di autorizzazione o, in alternativa, di impostarle in modo più esplicito come NO_ACCESS. In entrambi i casi, ciò impedisce l'esecuzione di queste operazioni da parte dei client o in altri contesti non privilegiati.

Utilizzare l'SDK con l'emulatore SQL Connect

Negli ambienti di prototipazione e test, può essere utile eseguire il seeding dei dati e altre operazioni sui dati locali. Il Admin SDK semplifica i flussi di lavoro perché può ignorare l'autenticazione e l'autorizzazione per i flussi locali. (Puoi anche scegliere esplicitamente di rispettare la configurazione di autenticazione e autorizzazione delle operazioni con l'impersonificazione di un utente.)

Gli SDK Admin Firebase si connettono automaticamente all'SQL Connect emulatore quando è impostata la variabile di ambiente DATA_CONNECT_EMULATOR_HOST:

export DATA_CONNECT_EMULATOR_HOST="127.0.0.1:9399"

Per ulteriori informazioni, vedi:

Eseguire operazioni di amministrazione

L'Admin SDK viene fornito per le operazioni con privilegi sui dati critici.

L'SDK Admin fornisce tre insiemi di API:

  • SDK Admin generati, che sono SDK con sicurezza dei tipi generati dalle definizioni gql nello stesso modo in cui generi gli SDK client.
  • Un'interfaccia generale per l'esecuzione di operazioni GraphQL arbitrarie, in cui il codice implementa query e mutazioni e le passa al metodo di lettura/scrittura executeGraphql o al metodo di sola lettura executeGraphqlRead.
  • Un'interfaccia specializzata per le operazioni collettive sui dati, che, anziché metodi executeGraphql generici, espone metodi dedicati per le operazioni di mutazione: insert, insertMany, upsert e upsertMany.

Gestire i dati con gli SDK generati

Generi gli SDK Admin dalle definizioni gql nello stesso modo in cui generi gli SDK client.

L'SDK Admin generato contiene interfacce e funzioni che corrispondono alle definizioni gql, che puoi utilizzare per eseguire operazioni sul 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 } }
);

Utilizzare l'identità di un utente non autenticato

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

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

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

Utilizzare l'identità di un altro 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 secondaria. (Questo è lo stesso valore del auth.uid valore del server a cui puoi fare riferimento nelle operazioni GraphQL SQL Connect.)

Quando utilizzi l'identità di un altro utente, l'operazione avrà esito positivo solo se i dati utente forniti 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 utilizzare l'identità di un altro utente.

Quando utilizzi le Cloud Functions chiamabili, il token di autenticazione viene verificato automaticamente e puoi utilizzarlo come nell'esempio seguente:

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 Admin SDK's verifyIdToken method 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 standard:

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 vere e proprie, come la migrazione dei dati, da un ambiente sicuro e non accessibile pubblicamente, devi specificare un ID utente che non proviene da un'origine verificabile:

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

Eseguire con accesso illimitato

Se stai eseguendo un'operazione che richiede autorizzazioni a livello di amministratore, 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 solo a scopi di amministrazione, devi definirle con la direttiva @auth(level: NO_ACCESS). In questo modo, solo i chiamanti a livello di amministratore possono eseguire queste operazioni.

Gestire i dati con i metodi executeGraphql

Se devi eseguire operazioni una tantum per le quali non hai definito gql mutazioni o query, puoi utilizzare il metodo executeGraphql o il metodo executeGraphqlRead di sola lettura.

Eseguire operazioni relazionali nidificate personalizzate (uno-a-molti) utilizzando executeGraphql

Quando chiami le operazioni dall'SDK Admin in un ambiente con privilegi, non devi specificare la direttiva @allow sulle variabili per gli inserimenti flat standard (non nidificati), ad esempio l'inserimento di singoli record Movie o Actor.

Tuttavia, per gli inserimenti relazionali nidificati (ad esempio l'inserimento di un film insieme alle recensioni iniziali), devi comunque dichiarare la direttiva @allow nella definizione della variabile per consentire staticamente i campi relazionali nidificati all'interno dello schema della variabile compilata.

// Custom GraphQL mutation with nested relational insert
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 });

Utilizzare l'identità di un utente non autenticato

Quando esegui operazioni pubbliche con l'SDK Admin, devi evitare di eseguire l'operazione con privilegi di amministratore completi (seguendo il principio del privilegio minimo). In alternativa, devi eseguire l'operazione come utente impersonato (vedi la sezione successiva) o come utente non autenticato impersonato. Gli utenti non autenticati possono eseguire solo le operazioni contrassegnate come 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);

Utilizzare l'identità di un altro utente

Esistono anche casi d'uso in cui vuoi che gli script modifichino i dati utente in base a credenziali limitate, per conto di un utente specifico. Questo approccio rispetta il principio del privilegio minimo.

Per utilizzare questa interfaccia, raccogli le informazioni da un token di autenticazione JWT personalizzato che segue il formato del token Authentication. Consulta anche la guida sui token personalizzati.

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

Utilizzare le credenziali amministrative

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

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

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

Eseguire operazioni di inserimento collettivo dei dati

Firebase ti consiglia di utilizzare il Admin SDK per le operazioni collettive sui dati nei database di produzione.

L'SDK fornisce i seguenti metodi per lavorare con i dati collettivi. Dai parametri forniti, ogni metodo costruisce ed esegue una mutazione 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);

Operazioni relazionali nidificate (uno-a-molti)

L'Admin SDK supporta i dati relazionali nelle operazioni collettive:

// Example of inserting a movie with nested reviews
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);

Note sul rendimento per le operazioni collettive

Ogni richiesta al backend comporterà un'andata e ritorno a Cloud SQL, quindi più batch esegui, maggiore sarà la velocità effettiva.

Tuttavia, maggiore è la dimensione del batch, più lunga è l'istruzione SQL generata. Quando viene raggiunto il limite di lunghezza dell'istruzione SQL PostgreSQL, si verifica un errore.

In pratica, sperimenta per trovare la dimensione del batch appropriata per il tuo carico di lavoro.

Passaggi successivi