הרשאה ותקינות

כשאתם מפתחים אפליקציה גלויה לכולם, חשוב מאוד להגן על הנתונים שמאוחסנים במערכת. כשמדובר ב-LLM, צריך להקפיד במיוחד לוודא שהמודל ניגש רק לנתונים שהוא אמור לגשת אליהם, שהקריאות לכלי מוגדרות כראוי בהתאם למשתמש שמפעיל את ה-LLM, ושרק אפליקציות לקוח מאומתות מפעילות את התהליך.

Firebase Genkit מספק מנגנונים לניהול הקשרים וכללי המדיניות של ההרשאות. תהליכים שפועלים ב-Firebase יכולים להשתמש בקריאה חוזרת (או בעזרה) של מדיניות אימות. לחלופין, Firebase מספקת גם הקשר אימות לתהליך, שבו היא יכולה לבצע בדיקות משלה. בתהליכים שאינם של Functions, אפשר לנהל את האימות ולהגדיר אותו באמצעות תוכנה לעיבוד נתונים ביניים.

מתן הרשאה בתוך תהליך

תהליכים יכולים לבדוק הרשאה בשתי דרכים: הקישור של הבקשה (למשל onCallGenkit ל-Cloud Functions for Firebase או express) יכול לאכוף הרשאה, או שהמסגרות האלה יכולות להעביר את מדיניות האימות לתהליך עצמו, שבו לתהליך יש גישה למידע לאימות שמנוהל בתוך התהליך.

import { genkit, z, UserFacingError } from 'genkit';

const ai = genkit({ ... });

export const selfSummaryFlow = ai.defineFlow( {
  name: 'selfSummaryFlow',
  inputSchema: z.object({ uid: z.string() }),
  outputSchema: z.string(),
}, async (input, { context }) => {
  if (!context.auth) {
    throw new UserFacingErrorError('UNAUTHENTICATED', 'Unauthenticated');
  }
  if (input.uid !== context.auth.uid) {
    throw new UserFacingError('PERMISSION_DENIED', 'You may only summarize your own profile data.');
  }
  // Flow logic here...
});

במקרה כזה, המערכת שתקשר את הבקשה היא זו שצריכה לאכלס את context.auth. לדוגמה, onCallGenkit מאכלס באופן אוטומטי את context.auth (אימות ב-Firebase), את context.app (בדיקת אפליקציות של Firebase) ואת context.instanceIdToken (Firebase Cloud Messaging). כשקוראים לתהליך באופן ידני, אפשר להוסיף את הקשר האימות שלכם באופן ידני.

// Error: Authorization required.
await selfSummaryFlow({ uid: 'abc-def' });

// Error: You may only summarize your own profile data.
await selfSummaryFlow.run(
  { uid: 'abc-def' },
  {
    context: { auth: { uid: 'hij-klm' } },
  }
);

// Success
await selfSummaryFlow(
  { uid: 'abc-def' },
  {
    context: { auth: { uid: 'abc-def' } },
  }
);

כשמריצים את הקוד באמצעות ממשק המשתמש של Genkit Development, אפשר להעביר את אובייקט האימות על ידי הזנת JSON בכרטיסייה Auth JSON: ‏ {"uid": "abc-def"}.

אפשר גם לאחזר את הקשר האימות של התהליך בכל שלב בתוך התהליך, על ידי קריאה לפונקציה ai.currentContext(), כולל בפונקציות שהתהליך מפעיל:

import { genkit, z } from 'genkit';

const ai = genkit({ ... });;

async function readDatabase(uid: string) {
  const auth = ai.currentContext()?.auth;
  // Note: the shape of context.auth depends on the provider. onCallGenkit puts
  // claims information in auth.token
  if (auth?.token?.admin) {
    // Do something special if the user is an admin
  } else {
    // Otherwise, use the `uid` variable to retrieve the relevant document
  }
}

export const selfSummaryFlow = ai.defineFlow(
  {
    name: 'selfSummaryFlow',
    inputSchema: z.object({ uid: z.string() }),
    outputSchema: z.string(),
    authPolicy: ...
  },
  async (input) => {
    await readDatabase(input.uid);
  }
);

כשבודקים תהליכים באמצעות כלי הפיתוח של Genkit, אפשר לציין את אובייקט האימות הזה בממשק המשתמש או בשורת הפקודה באמצעות הדגל --context:

genkit flow:run selfSummaryFlow '{"uid": "abc-def"}' --context '{"auth": {"email_verified": true}}'

מתן הרשאה באמצעות Cloud Functions for Firebase

ערכות ה-SDK של Cloud Functions for Firebase תומכות ב-Genkit, כולל שילוב עם Firebase Auth או Google Cloud Identity Platform, וגם תמיכה מובנית ב-Firebase App Check.

אימות משתמשים

למעטפת onCallGenkit() שסופקת על ידי ספריית Firebase Functions יש תמיכה מובנית בערכות ה-SDK של הלקוח של Cloud Functions for Firebase. כשמשתמשים ב-SDK האלה, הכותרת של Firebase Auth נכללת באופן אוטומטי כל עוד לקוח האפליקציה משתמש גם ב-Firebase Auth SDK. אפשר להשתמש ב-Firebase Auth כדי להגן על התהליכים שהוגדרו באמצעות onCallGenkit():

import { genkit } from 'genkit';
import { onCallGenkit } from 'firebase-functions/https';

const ai = genkit({ ... });;

const selfSummaryFlow = ai.defineFlow({
  name: 'selfSummaryFlow',
  inputSchema: z.string(),
  outputSchema: z.string(),
}, async (input) => {
  // Flow logic here...
});

export const selfSummary = onCallGenkit({
  authPolicy: (auth) => auth?.token?.['email_verified'] && auth?.token?.['admin'],
}, selfSummaryFlow);

כשמשתמשים ב-onCallGenkit, הערך context.auth מוחזר כאובייקט עם uid שמכיל את מזהה המשתמש, ועם token שהוא DecodedIdToken. תמיד אפשר לאחזר את האובייקט הזה באמצעות ai.currentContext(), כפי שצוין למעלה. כשמריצים את התהליך הזה במהלך הפיתוח, מעבירים את אובייקט המשתמש באותו אופן:

genkit flow:run selfSummaryFlow '{"uid": "abc-def"}' --context '{"auth": {"admin": true}}'

בכל פעם שחשופים את Cloud Function לאינטרנט הרחב, חשוב מאוד להשתמש במנגנון אימות כלשהו כדי להגן על הנתונים שלכם ועל נתוני הלקוחות. עם זאת, יש מקרים שבהם צריך לפרוס Cloud Function ללא בדיקות הרשאה מבוססות-קוד (לדוגמה, הפונקציה לא ניתנת לקריאה לכולם, אלא מוגנת על ידי Cloud IAM). אפשר לעשות זאת באמצעות Cloud Functions for Firebase באמצעות המאפיין invoker, שמאפשר לשלוט בגישה ל-IAM. הערך המיוחד 'private' משאיר את הפונקציה עם הגדרת ברירת המחדל של IAM, כלומר רק מבצעים עם תפקיד Cloud Run Invoker יכולים להריץ את הפונקציה. במקום זאת, אפשר לספק את כתובת האימייל של משתמש או של חשבון שירות שצריך להעניק לו הרשאה לקרוא לפונקציה הזו.

import { onCallGenkit } from 'firebase-functions/https'

const selfSummaryFlow = ai.defineFlow({
  name: 'selfSummaryFlow',
  inputSchema: z.string(),
  outputSchema: z.string(),
}, async (input) => {
  // Flow logic here...
});

export const selfSummary = onCallGenkit({
  invoker: 'private',
}, selfSummaryFlow);

תקינות הלקוח

אימות בפני עצמו עוזר מאוד להגן על האפליקציה, אבל חשוב גם לוודא שרק אפליקציות הלקוח שלכם קוראות לפונקציות שלכם. הפלאגין של Firebase ל-genkit כולל תמיכה ברמה הגבוהה ביותר ב-Firebase App Check. כדי לעשות זאת, מוסיפים את אפשרויות התצורה הבאות לקובץ onCallGenkit():

import { onCallGenkit } from 'firebase-functions/https';

const selfSummaryFlow = ai.defineFlow({
  name: 'selfSummaryFlow',
  inputSchema: z.string(),
  outputSchema: z.string(),
}, async (input) => {
  // Flow logic here...
});

export const selfSummary = onCallGenkit({
  // These two fields for app check. The consumeAppCheckToken option is for
  // replay protection, and requires additional client configuration. See the
  // App Check docs.
  enforceAppCheck: true,
  consumeAppCheckToken: true,

  authPolicy: ...,
}, selfSummaryFlow);

הרשאת HTTP שאינה של Firebase

כשפורסים תהליכים בהקשר של שרת מחוץ ל-Cloud Functions for Firebase, כדאי שתהיה לכם דרך להגדיר בדיקות הרשאה משלכם לצד התהליכים המובנים.

משתמשים ב-ContextProvider כדי לאכלס ערכים של הקשר כמו auth, וכדי לספק מדיניות מצהירה או קריאה חוזרת (callback) של מדיניות. ‏Genkit SDK מספק ContextProvider כמו apiKey, וגם פלאגינים יכולים לחשוף אותם. לדוגמה, הפלאגין @genkit-ai/firebase/context חושף ספק הקשר לאימות פרטי הכניסה של Firebase Auth ולמילוי שלהם בהקשר.

באמצעות קוד כמו זה, שעשוי להופיע במגוון אפליקציות:

// Express app with a simple API key
import { genkit, z } from 'genkit';

const ai = genkit({ ... });;

export const selfSummaryFlow = ai.defineFlow(
  {
    name: 'selfSummaryFlow',
    inputSchema: z.object({ uid: z.string() }),
    outputSchema: z.string(),
  },
  async (input) => {
    // Flow logic here...
  }
);

אפשר לאבטח אפליקציית Express פשוטה מסוג 'שרת תהליך' על ידי כתיבת הקוד הבא:

import { apiKey } from "genkit";
import { startFlowServer, withContext } from "@genkit-ai/express";

startFlowServer({
  flows: [
    withContext(selfSummaryFlow, apiKey(process.env.REQUIRED_API_KEY))
  ],
});

לחלופין, אפשר ליצור אפליקציית Express בהתאמה אישית באמצעות אותם כלים:

import { apiKey } from "genkit";
import * as express from "express";
import { expressHandler } from "@genkit-ai/express;

const app = express();
// Capture but don't validate the API key (or its absence)
app.post('/summary', expressHandler(selfSummaryFlow, { contextProvider: apiKey()}))

app.listen(process.env.PORT, () => {
  console.log(`Listening on port ${process.env.PORT}`);
})

ContextProvider מספק רכיב מופשט של מסגרת האינטרנט, כך שהכלים האלה פועלים גם במסגרות אחרות כמו Next.js. זו דוגמה לאפליקציית Firebase שנוצרה באמצעות Next.js.

import { appRoute } from "@genkit-ai/express";
import { firebaseContext } from "@genkit-ai/firebase";

export const POST = appRoute(selfSummaryFlow, { contextProvider: firebaseContext })

למידע נוסף על השימוש ב-Express, תוכלו לעיין בהוראות של Cloud Run.