Utiliser les SDK Admin générés

Les SDK Admin Firebase Data Connect vous permettent d'appeler vos requêtes et mutations à partir d'environnements fiables tels que Cloud Functions, des backends personnalisés ou votre propre poste de travail. De la même manière que vous générez des SDK pour vos applications clientes, vous pouvez générer un SDK Admin personnalisé en parallèle lorsque vous concevez les schémas, les requêtes et les mutations que vous déployez sur votre service Data Connect. Ensuite, vous intégrez les méthodes de ce SDK dans votre logique de backend ou vos scripts d'administration.

Comme nous l'avons mentionné ailleurs, il est important de noter que les requêtes et les mutations Data Connect ne sont pas envoyées par les clients au moment de la requête. En revanche, une fois déployées, les opérations Data Connect sont stockées sur le serveur, comme les fonctions Cloud Functions. Cela signifie que chaque fois que vous déployez des modifications apportées à vos requêtes et mutations, vous devez également régénérer les SDK Admin et redéployer tous les services qui en dépendent.

Avant de commencer

Générer des SDK Admin

Une fois que vous avez créé vos schémas, requêtes et mutations Data Connect, vous pouvez générer un SDK d'administration correspondant :

  1. Ouvrez ou créez un fichier connector.yaml et ajoutez une définition adminNodeSdk :

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

    Le fichier connector.yaml se trouve généralement dans le même répertoire que les fichiers GraphQL (.gql) qui contiennent vos définitions de requêtes et de mutations. Si vous avez déjà généré des SDK clients, ce fichier a déjà été créé.

  2. Générez le SDK.

    Si l'extension VS Code Data Connect est installée, elle mettra toujours à jour les SDK générés.

    Sinon, utilisez la CLI Firebase :

    firebase dataconnect:sdk:generate

    Vous pouvez également régénérer automatiquement les SDK lorsque vous mettez à jour vos fichiers gql :

    firebase dataconnect:sdk:generate --watch

Exécuter des opérations à partir d'un SDK Admin

Le SDK Admin généré contient des interfaces et des fonctions qui correspondent à vos définitions gql. Vous pouvez les utiliser pour effectuer des opérations sur votre base de données. Par exemple, supposons que vous ayez généré un SDK pour une base de données de titres, ainsi qu'une requête, 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 } }
);

Vous pouvez également spécifier une configuration de connecteur :

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

Emprunter l'identité d'un utilisateur non authentifié

Les SDK Admin sont conçus pour être exécutés à partir d'environnements fiables. Ils ont donc un accès illimité à vos bases de données.

Lorsque vous exécutez des opérations publiques avec le SDK Admin, vous devez éviter de les exécuter avec des droits d'administrateur complets (en suivant le principe du moindre privilège). Vous devez plutôt exécuter l'opération en tant qu'utilisateur usurpé (voir la section suivante) ou en tant qu'utilisateur non authentifié usurpé. Les utilisateurs non authentifiés ne peuvent exécuter que les opérations marquées comme PUBLIC.

Dans l'exemple ci-dessus, la requête getSongs est exécutée en tant qu'utilisateur non authentifié.

Usurpation d'identité d'un utilisateur

Vous pouvez également effectuer des opérations au nom d'utilisateurs spécifiques en transmettant une partie ou la totalité d'un jeton Firebase Authentication dans l'option impersonate. Vous devez au moins spécifier l'ID utilisateur de l'utilisateur dans la revendication "sub". (Il s'agit de la même valeur que la valeur du serveur auth.uid que vous pouvez référencer dans les opérations GraphQL de Data Connect.)

Lorsque vous usurpez l'identité d'un utilisateur, l'opération ne réussit que si les données utilisateur que vous avez fournies passent les vérifications d'authentification spécifiées dans votre définition GraphQL.

Si vous appelez le SDK généré à partir d'un point de terminaison accessible au public, il est essentiel que le point de terminaison nécessite une authentification et que vous validiez l'intégrité du jeton d'authentification avant de l'utiliser pour emprunter l'identité d'un utilisateur.

Lorsque vous utilisez Cloud Functions appelable, le jeton d'authentification est automatiquement vérifié et vous pouvez l'utiliser comme dans l'exemple suivant :

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

    // ...
});

Sinon, utilisez la méthode verifyIdToken de Admin SDK pour valider et décoder le jeton d'authentification. Par exemple, supposons que votre point de terminaison soit implémenté en tant que fonction HTTP simple et que vous ayez transmis le jeton Firebase Authentication à votre point de terminaison à l'aide de l'en-tête authorization, comme c'est la norme :

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

    // ...
});

Vous ne devez spécifier un ID utilisateur qui ne provient pas d'une source vérifiable que lorsque vous effectuez de véritables tâches administratives, telles que la migration de données, à partir d'un environnement sécurisé et non accessible au public :

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

Exécution avec un accès non restreint

Si vous effectuez une opération qui nécessite des autorisations d'administrateur, omettez le paramètre "impersonate" de l'appel :

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

Une opération appelée de cette manière a un accès complet à la base de données. Si vous avez des requêtes ou des mutations destinées uniquement à des fins d'administration, vous devez les définir avec la directive @auth(level: NO_ACCESS). Cela permet de s'assurer que seuls les appelants de niveau administrateur peuvent exécuter ces opérations.