Skip to content

Commit

Permalink
Fix esm stuff; update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
inlined committed Feb 6, 2025
1 parent 73afde8 commit 118bf91
Show file tree
Hide file tree
Showing 24 changed files with 207 additions and 622 deletions.
136 changes: 78 additions & 58 deletions docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Alternatively, Firebase also provides auth context into the flow where it can
do its own checks. For non-Functions flows, auth can be managed and set
through middleware.

## Basic flow authorization
## Authorizing within a Flow

Flows can check authorization in two ways: either the request binding
(e.g. `onCallGenkit` for Cloud Functions for Firebase or `express`) can enforce
Expand All @@ -24,7 +24,7 @@ where the flow has access to the information for auth managed within the
flow.

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

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

Expand All @@ -34,10 +34,10 @@ export const selfSummaryFlow = ai.defineFlow( {
outputSchema: z.string(),
}, async (input, { context }) => {
if (!context.auth) {
throw new Error('Authorization required.');
throw new UserFacingErrorError('UNAUTHENTICATED', 'Unauthenticated');
}
if (input.uid !== context.auth.uid) {
throw new Error('You may only summarize your own profile data.');
throw new UserFacingError('PERMISSION_DENIED', 'You may only summarize your own profile data.');
}
// Flow logic here...
});
Expand Down Expand Up @@ -112,14 +112,13 @@ object in the UI, or on the command line with the `--context` flag:
genkit flow:run selfSummaryFlow '{"uid": "abc-def"}' --context '{"auth": {"email_verified": true}}'
```

## Cloud Functions for Firebase integration
## Authorizing with Cloud Functions for Firebase

The Cloud Functions for Firebase SDKs support Genkit including
integration with Firebase Auth / Google Cloud Identity Platform, as well as
built-in Firebase App Check support.

### Authorization

### User authentication
The `onCallGenkit()` wrapper provided by the Firebase Functions library works
natively with the Cloud Functions for Firebase
[client SDKs](https://firebase.google.com/docs/functions/callable?gen=2nd#call_the_function).
Expand Down Expand Up @@ -192,7 +191,7 @@ export const selfSummary = onCallGenkit({
}, selfSummaryFlow);
```

### Client integrity
#### Client integrity

Authentication on its own goes a long way to protect your app. But it's also
important to ensure that only your client apps are calling your functions. The
Expand Down Expand Up @@ -228,53 +227,74 @@ export const selfSummary = onCallGenkit({

When deploying flows to a server context outside of Cloud Functions for
Firebase, you'll want to have a way to set up your own authorization checks
alongside the native flows. You have two options:

1. Use whatever server framework you like, and pass the auth context through
using the flow call as noted earlier.

1. Use `startFlowsServer()`, which the `@genkit-ai/express` plugin
exposes, and provide Express auth middleware in the flow server config:

```ts
import { genkit, z } from 'genkit';
import { startFlowServer, withAuth } from '@genkit-ai/express';

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

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

const authProvider = (req, res, next) => {
const token = req.headers['authorization'];
const user = yourVerificationLibrary(token);

// Pass auth information to the flow
req.auth = user;
next();
};

startFlowServer({
flows: [
withAuth(selfSummaryFlow, authProvider, ({ auth, action, input, request }) => {
if (!auth) {
throw new Error('Authorization required.');
}
if (input.uid !== auth.uid) {
throw new Error('You may only summarize your own profile data.');
}
})
],
}); // Registers the auth provider middleware and policy
```

For more information about using Express, see the
[Cloud Run](/genkit/cloud-run) instructions.
alongside the native flows.

Use a `ContextProvider`, which both populates context values such as `auth` as well
as allowing you to provide a declarative policy or a policy callback. The genkit
SDK provides `ContextProvider` such as `apiKey`, and plugins may expose them as well,
such as `@genkit-ai/firebase/context` which can be used to build Firebase apps with
backends that are not Cloud Functions for Firebase.

Given the snippet common to all kinds of applications:

```ts
// 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...
}
);
```

You could secure a simple "flow server" express app with:

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

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

Or you can build a custom express application using the same tools:

```ts
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, { context: apiKey()}))

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

`ContextProvider`s are intended to be abstract of any web framework, so these
tools work in other frameworks like Next.js as well. Here is an example of a
Firebase app built on Next.js.

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

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

<!-- NOTE: Should we provide more docs? E.g. docs into various web frameworks and hosting services? -->
For more information about using Express, see the
[Cloud Run](/genkit/cloud-run) instructions.
1 change: 1 addition & 0 deletions docs/cloud-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ endpoints.

When you make the call, specify the flows you want to serve:

There is also
```ts
import { startFlowServer } from '@genkit-ai/express';

Expand Down
2 changes: 2 additions & 0 deletions docs/deploy-node.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<!-- TODO: Add Next docs too. Maybe we need a web-hosting page that deploy-node
and cloud-run links to, which links to express, next, and maybe cloud functions>
# Deploy flows to any Node.js platform
Firebase Genkit has built-in integrations that help you deploy your flows to
Expand Down
40 changes: 39 additions & 1 deletion js/core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { runInActionRuntimeContext } from './action.js';
import { UserFacingError } from './error.js';
import { HasRegistry, Registry } from './registry.js';

const contextAlsKey = 'core.auth.context';
Expand Down Expand Up @@ -61,7 +62,7 @@ export function getContext(

/**
* A universal type that request handling extensions (e.g. express, next) can map their request to.
* This allows middleware to build consistent interfacese on any web framework.
* This allows ContextProviders to build consistent interfacese on any web framework.
* Headers must be lowercase to ensure portability.
*/
export interface RequestData<T = any> {
Expand All @@ -85,3 +86,40 @@ export type ContextProvider<
C extends ActionContext = ActionContext,
T = any,
> = (request: RequestData<T>) => C | Promise<C>;

export interface ApiKeyContext extends ActionContext {
auth: {
apiKey: string | undefined;
};
}

export function apiKey(
policy: (context: ApiKeyContext) => void | Promise<void>
): ContextProvider<ApiKeyContext>;
export function apiKey(value?: string): ContextProvider<ApiKeyContext>;
export function apiKey(
valueOrPolicy?: ((context: ApiKeyContext) => void | Promise<void>) | string
): ContextProvider<ApiKeyContext> {
return async function (request: RequestData): Promise<ApiKeyContext> {
const context: ApiKeyContext = {
auth: { apiKey: request.headers['authorization'] },
};
if (typeof valueOrPolicy === 'string') {
if (!context.auth?.apiKey) {
console.error('THROWING UNAUTHENTICATED');
throw new UserFacingError('UNAUTHENTICATED', 'Unauthenticated');
}
if (context.auth?.apiKey != valueOrPolicy) {
console.error('Throwing PERMISSION_DENIED');
throw new UserFacingError('PERMISSION_DENIED', 'Permission Denied');
}
} else if (typeof valueOrPolicy === 'function') {
await valueOrPolicy(context);
} else if (typeof valueOrPolicy !== 'undefined') {
throw new Error(
`Invalid type ${typeof valueOrPolicy} passed to apiKey()`
);
}
return context;
};
}
4 changes: 3 additions & 1 deletion js/core/src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
import { Registry } from './registry.js';
import { httpStatusCode, StatusName } from './statusTypes.js';

interface HttpErrorWireFormat {
export { StatusName };

export interface HttpErrorWireFormat {
details?: unknown;
message: string;
status: StatusName;
Expand Down
3 changes: 3 additions & 0 deletions js/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ export const GENKIT_REFLECTION_API_SPEC_VERSION = 1;
export { z } from 'zod';
export * from './action.js';
export {
apiKey,
getContext,
runWithContext,
type ActionContext,
type ApiKeyContext,
type ContextProvider,
type RequestData,
} from './context.js';
Expand All @@ -43,6 +45,7 @@ export {
assertUnstable,
getCallableJSON,
getHttpStatus,
type StatusName,
} from './error.js';
export {
defineFlow,
Expand Down
6 changes: 6 additions & 0 deletions js/genkit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,20 @@ export {
StatusCodes,
StatusSchema,
UserFacingError,
apiKey,
defineJsonSchema,
defineSchema,
getCallableJSON,
getCurrentEnv,
getHttpStatus,
getStreamingCallback,
isDevEnv,
runWithStreamingCallback,
z,
type Action,
type ActionContext,
type ActionMetadata,
type ApiKeyContext,
type ContextProvider,
type Flow,
type FlowConfig,
Expand All @@ -140,8 +144,10 @@ export {
type JSONSchema7,
type Middleware,
type ReflectionServerOptions,
type RequestData,
type RunActionResponse,
type Status,
type StatusName,
type StreamingCallback,
type StreamingResponse,
type TelemetryConfig,
Expand Down
1 change: 0 additions & 1 deletion js/plugins/api-key/.npmignore

This file was deleted.

Loading

0 comments on commit 118bf91

Please sign in to comment.