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

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

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

הרשאה בסיסית לתהליך

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

import { genkit, z } from 'genkit';

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

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

כשמבצעים את התהליך הזה, חובה לספק אובייקט אימות באמצעות withLocalAuthContext, אחרת תוצג הודעת שגיאה:

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

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

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

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

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

import { genkit, z } from 'genkit';

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

async function readDatabase(uid: string) {
  const auth = ai.getAuthContext();
  if (auth?.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, אפשר לציין את אובייקט האימות הזה בממשק המשתמש או בשורת הפקודה באמצעות הדגל --auth:

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

שילוב של Cloud Functions for Firebase

הפלאגין של Firebase מספק שילוב נוח עם Firebase Auth או עם Google Cloud Identity Platform, וגם תמיכה מובנית ב-Firebase App Check.

הרשאה

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

import { genkit } from 'genkit';
import { firebaseAuth } from '@genkit-ai/firebase';
import { onFlow } from '@genkit-ai/firebase/functions';

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

export const selfSummaryFlow = onFlow(
  ai,
  {
    name: 'selfSummaryFlow',
    inputSchema: z.string(),
    outputSchema: z.string(),
    authPolicy: firebaseAuth((user) => {
      if (!user.email_verified && !user.admin) {
        throw new Error('Email not verified');
      }
    }),
  },
  async (input) => {
        // Flow logic here...
  }
);

כשמשתמשים בפלאגין של Firebase Auth, הערך של user יוחזר בתור DecodedIdToken. תמיד אפשר לאחזר את האובייקט הזה באמצעות getFlowAuth(), כפי שצוין למעלה. כשמריצים את התהליך הזה במהלך הפיתוח, מעבירים את אובייקט המשתמש באותו אופן:

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

כברירת מחדל, הפלאגין של Firebase Auth מחייב את הלקוח לשלוח את כותרת האימות, אבל במקרים שבהם רוצים לאפשר גישה ללא אימות עם טיפול מיוחד למשתמשים מאומתים (למשל, תכונות של שדרוג למינויים), אפשר להגדיר את המדיניות כך:

authPolicy: firebaseAuth((user) => {
  if (user && !user.email_verified) {
    throw new Error("Logged in users must have verified emails");
  }
}, {required: false}),

בכל פעם שחשופים את Cloud Function לאינטרנט הרחב, חשוב מאוד להשתמש במנגנון אימות כלשהו כדי להגן על הנתונים שלכם ועל נתוני הלקוחות. עם זאת, יש מקרים שבהם צריך לפרוס Cloud Function ללא בדיקות הרשאה מבוססות-קוד (לדוגמה, הפונקציה לא ניתנת לקריאה לכולם, אלא מוגנת על ידי Cloud IAM). השדה authPolicy נדרש תמיד כשמשתמשים ב-onFlow(), אבל אפשר להשתמש בפונקציה noAuth() כדי לציין לספרייה שאתם מוותרים על בדיקות ההרשאה:

import { onFlow, noAuth } from "@genkit-ai/firebase/functions";

export const selfSummaryFlow = onFlow(
  ai,
  {
    name: "selfSummaryFlow",
    inputSchema: z.string(),
    outputSchema: z.string(),
    // WARNING: Only do this if you have some other gatekeeping in place, like
    // Cloud IAM!
    authPolicy: noAuth(),
  },
  async (input) => {
        // Flow logic here...
  }
);

תקינות הלקוח

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

import { onFlow } from "@genkit-ai/firebase/functions";

export const selfSummaryFlow = onFlow(
  ai,
  {
    name: "selfSummaryFlow",
    inputSchema: z.string(),
    outputSchema: z.string(),

    // 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: ...,
  },
  async (input) => {
        // Flow logic here...
  }
);

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

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

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

  2. משתמשים ב-startFlowsServer() המובנה ומספקים תוכנת middleware של Express בתצורת התהליך:

    import { genkit, z } from 'genkit';
    
    const ai = genkit({ ... });;
    
    export const selfSummaryFlow = ai.defineFlow(
      {
        name: 'selfSummaryFlow',
        inputSchema: z.object({ uid: z.string() }),
        outputSchema: z.string(),
        middleware: [
          (req, res, next) => {
            const token = req.headers['authorization'];
            const user = yourVerificationLibrary(token);
    
            // Pass auth information to the flow
            req.auth = user;
            next();
          }
        ],
        authPolicy: (auth, input) => {
          if (!auth) {
            throw new Error('Authorization required.');
          }
          if (input.uid !== auth.uid) {
            throw new Error('You may only summarize your own profile data.');
          }
        }
      },
      async (input) => {
        // Flow logic here...
      }
    );
    
    ai.startFlowServer({
      flows: [selfSummaryFlow],
    });  // Registers the middleware
    

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

חשוב לזכור: אם בוחרים באפשרות (1), המערכת תתעלם מאפשרות התצורה middleware כשהתהליך יופעל ישירות.