@conveo/conveo-sdk 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # @conveo/conveo-sdk
2
+
3
+ **Easily add Conveo authentication to your app. @conveo.ai sign-in for free.**
4
+
5
+ Vibe-coding an internal tool? You don't need to build login, sessions,
6
+ or user management. Conveo's **agent gateway** signs people in with their
7
+ normal Conveo account (Okta) *before* they ever reach your app. This SDK
8
+ is the small piece your app needs to know **who** is calling.
9
+
10
+ ## Why you need this
11
+
12
+ Without it, every internal app reinvents auth, and usually gets it wrong
13
+ (client-side-only checks, shared API keys, "@conveo.ai only" rules that aren't
14
+ actually enforced). With it:
15
+
16
+ - 🔒 **Your app is unreachable except through the gateway.** No login screen
17
+ to build, no passwords or tokens to store, nothing to leak.
18
+ - 👤 **Your code just knows the user.** `user.email`, verified
19
+ cryptographically, on every request.
20
+ - 🔁 **You can call Conveo data and APIs *as that user*.** The gateway
21
+ enforces per-user permissions and keeps an audit trail.
22
+
23
+ ## Quick start (Next.js on Vercel)
24
+
25
+ **1. Install.** The package is on the public npm registry, so there's no
26
+ registry config or auth to set up — on Vercel, in CI, or locally:
27
+
28
+ ```bash
29
+ pnpm add @conveo/conveo-sdk
30
+ ```
31
+
32
+ **2. Add one file:** `proxy.ts` at your project root (Next.js 16; call it
33
+ `middleware.ts` on older Next):
34
+
35
+ ```ts
36
+ import { createGatewayProxy } from "@conveo/conveo-sdk/next";
37
+
38
+ export default createGatewayProxy();
39
+
40
+ export const config = {
41
+ matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
42
+ };
43
+ ```
44
+
45
+ That's the entire auth setup. Every page and API route is now protected,
46
+ including ones you add next month and forget to think about.
47
+
48
+ **3. Use the user**, anywhere in your server code:
49
+
50
+ ```ts
51
+ import { getGatewayUser, gatewayFetch } from "@conveo/conveo-sdk/server";
52
+
53
+ const user = await getGatewayUser();
54
+ user.email; // "you@conveo.ai"
55
+
56
+ // call Conveo data through the gateway, as this user:
57
+ const res = await gatewayFetch("/postgrest/Organization?select=id,name", { user });
58
+ ```
59
+
60
+ The `user` you get back looks like this:
61
+
62
+ ```ts
63
+ {
64
+ email: "you@conveo.ai", // who's calling (verified, safe to trust)
65
+ sub: "eae61a46-3101-…", // stable user id, use this as your database key
66
+ emailVerified: true,
67
+ token: "eyJhbGciOi…", // the user's token, gatewayFetch uses it for you
68
+ claims: { name: "…", exp: 1765486800, /* …all raw token claims */ },
69
+ }
70
+ ```
71
+
72
+ Day to day you'll only touch `email` (display, allowlists) and `sub` (storing
73
+ per-user data, since emails can change and `sub` never does).
74
+
75
+ **Local dev:** get yourself a real token (opens Okta in your browser, valid 8h):
76
+
77
+ ```bash
78
+ pnpm exec agentgateway-dev-token # writes AGENTGATEWAY_DEV_TOKEN to .env.local
79
+ pnpm dev # localhost now behaves as you, for real
80
+ ```
81
+
82
+ Want a full working example? See the
83
+ [template app](https://github.com/conveo/agentgateway-template-app). Clone
84
+ it and you're done.
85
+
86
+ ## How it works
87
+
88
+ ![How it works](docs/how-it-works.svg)
89
+
90
+ In words: users visit your app at `yourapp.internal.conveo.ai`, which is the
91
+ **gateway**, not Vercel. The gateway checks who they are (Okta), then forwards
92
+ the request to your Vercel deployment with two things attached: its own
93
+ *service token* (the key past Vercel's deployment protection — your app never
94
+ sees it) and the user's *identity token*. Your `proxy.ts` — this SDK —
95
+ double-checks that identity token against Keycloak's public keys and hands
96
+ your code the user. Anyone who tries to reach your `*.vercel.app` URL directly
97
+ hits a wall, and anyone who somehow got through still can't fake an identity:
98
+ that would require Keycloak's signing key.
99
+
100
+ You never see a password, never store a token in the browser, and there are
101
+ no shared secrets anywhere in your app.
102
+
103
+ ---
104
+
105
+ ## The details
106
+
107
+ Everything below is reference material — you don't need it to get started.
108
+
109
+ ### Package layout
110
+
111
+ The SDK is a pure, environment-free core with thin adapters per runtime:
112
+
113
+ | Import | Use from |
114
+ |---|---|
115
+ | `@conveo/conveo-sdk` | core: `createVerifier`, `checkPolicy`, types |
116
+ | `…/next` | `proxy.ts` / `middleware.ts` in a Next.js app |
117
+ | `…/vercel` | root `middleware.ts` of any non-Next Vercel app (Vite SPA etc.) |
118
+ | `…/supabase` | `requireGatewayUser(req)` in Deno edge functions |
119
+ | `…/server` | `getGatewayUser()`, `gatewayFetch()` in Next server code |
120
+
121
+ ### What the middleware verifies
122
+
123
+ Signature against Keycloak's JWKS (cached, auto-rotating — public keys, no
124
+ secrets), issuer, expiry (30s clock tolerance), `email_verified`, and your
125
+ optional domain/allowlist policy. `aud` is verified only when configured —
126
+ conveo-realm user tokens carry no `aud` claim today.
127
+
128
+ ### Configuration (env-first; code options win)
129
+
130
+ | Env var | Default | Purpose |
131
+ |---|---|---|
132
+ | `AGENTGATEWAY_ISSUER` | `https://keycloak.ops.conveo.ai/realms/conveo` | Token issuer |
133
+ | `AGENTGATEWAY_AUDIENCE` | not enforced | Required `aud` when set |
134
+ | `AGENTGATEWAY_JWKS_URL` | derived from issuer | Override for non-Keycloak issuers |
135
+ | `AGENTGATEWAY_USER_HEADER` | `authorization` (Bearer) | Where the gateway puts the user token |
136
+ | `AGENTGATEWAY_REQUIRE_EMAIL_DOMAIN` | — | e.g. `conveo.ai` (exact domain match) |
137
+ | `AGENTGATEWAY_ALLOWED_EMAILS` | — | CSV allowlist |
138
+ | `AGENTGATEWAY_SIGNIN_URL` | — | Browser 302 target; unset → always 401 |
139
+ | `AGENTGATEWAY_AUTH_MODE` | `required` | `optional` verifies but never rejects |
140
+ | `AGENTGATEWAY_API_URL` / `_MCP_URL` | gateway prod hosts | `gatewayFetch` bases |
141
+ | `AGENTGATEWAY_DEV_TOKEN` / `_DEV_MOCK_USER` | — | Local dev only; honored only when `NODE_ENV` or `VERCEL_ENV` is `development` |
142
+
143
+ Code options: `createGatewayProxy({ publicPaths: ["/api/health"], ... })` —
144
+ public paths skip the user check (they're still behind Vercel's edge wall).
145
+
146
+ ### Supabase Edge Functions (Deno)
147
+
148
+ Supabase functions are reachable directly at `*.supabase.co` — they bypass
149
+ Vercel's protection entirely, so they must verify tokens themselves:
150
+
151
+ ```ts
152
+ import { requireGatewayUser } from "../_shared/agentgateway-supabase.bundle.js";
153
+
154
+ Deno.serve(async (req) => {
155
+ try {
156
+ const user = await requireGatewayUser(req);
157
+ return Response.json({ hello: user.email });
158
+ } catch (error) {
159
+ if (error instanceof Response) return error; // 401/403 from the adapter
160
+ throw error;
161
+ }
162
+ });
163
+ ```
164
+
165
+ That relative import is the **vendored bundle**: a self-contained
166
+ `dist/deno/agentgateway-supabase.bundle.js` (jose inlined, minified). Copy it
167
+ from `node_modules` into `supabase/functions/_shared/` with a `postinstall`
168
+ script — the copy always matches your lockfile-pinned version, so deploys are
169
+ reproducible and need no network fetch. (Now that the package is on the public
170
+ registry you can also `import … from "npm:@conveo/conveo-sdk/supabase"`
171
+ directly; the vendored bundle just keeps deploys pinned and offline-friendly.)
172
+
173
+ ### Behavior guarantees
174
+
175
+ - `x-gateway-user-*` headers are stripped from every inbound request before
176
+ anything else — app code can trust them.
177
+ - Fail closed: JWKS unreachable / malformed token / policy failure → 401/403,
178
+ never pass-through. Rejections: 302 to sign-in for browser navigations (when
179
+ configured), otherwise JSON `401` with `WWW-Authenticate` / `403`.
180
+ - `getGatewayUser()` re-verifies the forwarded token rather than trusting
181
+ headers, so routes missed by the matcher can't be fed forged identity.
182
+
183
+ ### Gateway contract
184
+
185
+ The gateway-side contract (Trusted Sources gate token, `Authorization`
186
+ passthrough of the user JWT) is defined in
187
+ [`conveo/agentgateway`](https://github.com/conveo/agentgateway) —
188
+ `chart/VERCEL-TRUSTED-SOURCES.md`. This SDK verifies what
189
+ `backend.auth.passthrough` forwards.
190
+
191
+ ### Releasing
192
+
193
+ CI builds + tests every PR. Releases are automatic: every merge to `main`
194
+ runs the release workflow, which computes the next version from the latest git
195
+ tag (a patch bump, or whatever `package.json` requests if it's higher),
196
+ publishes to the public npm registry, and pushes the version tag. Nothing is
197
+ committed back to `main`.
198
+
199
+ Publishing uses **npm Trusted Publishing** (OIDC) — no `NPM_TOKEN` secret. The
200
+ package's trusted-publisher settings on npmjs.com name this repo and
201
+ `release.yml`; npm build provenance is attached automatically. The published
202
+ JS is minified (`scripts/minify.mjs`); the `.d.ts` types ship readable.
@@ -0,0 +1,11 @@
1
+ import type { GatewayConfigInput } from "../core/types.js";
2
+ export type EnvRecord = Record<string, string | undefined>;
3
+ /** Pure mapping from an env record to config input — explicit options win over env. */
4
+ export declare function configFromEnv(env: EnvRecord, overrides?: GatewayConfigInput): GatewayConfigInput;
5
+ export declare function splitCsv(value: string | undefined): string[] | undefined;
6
+ /**
7
+ * Dev-only features (dev token, mock user) gate on this. Deliberately requires
8
+ * a provable "development" signal rather than the absence of "production":
9
+ * a self-hosted or staging deploy that forgets NODE_ENV must fail closed.
10
+ */
11
+ export declare function isDevelopment(env: EnvRecord): boolean;
@@ -0,0 +1 @@
1
+ function l(E,e={}){return{issuer:e.issuer??E.AGENTGATEWAY_ISSUER,audience:e.audience??E.AGENTGATEWAY_AUDIENCE,jwksUrl:e.jwksUrl??E.AGENTGATEWAY_JWKS_URL,userHeader:e.userHeader??E.AGENTGATEWAY_USER_HEADER,requireEmailDomain:e.requireEmailDomain??E.AGENTGATEWAY_REQUIRE_EMAIL_DOMAIN,allowedEmails:e.allowedEmails??n(E.AGENTGATEWAY_ALLOWED_EMAILS),clockToleranceSec:e.clockToleranceSec}}function n(E){if(!E)return;const e=E.split(",").map(A=>A.trim()).filter(Boolean);return e.length>0?e:void 0}function t(E){return E.VERCEL_ENV==="development"||E.NODE_ENV==="development"}export{l as configFromEnv,t as isDevelopment,n as splitCsv};
@@ -0,0 +1,29 @@
1
+ import { type GatewayUser } from "../core/types.js";
2
+ import type { Verifier } from "../core/verify.js";
3
+ export interface HandleOptions {
4
+ verifier: Verifier;
5
+ /** Paths that skip auth. Exact match, or prefix match when the pattern ends with `*`. */
6
+ publicPaths?: string[];
7
+ /** Absolute URL to redirect browsers to on missing/expired tokens. Unset → always 401. */
8
+ signinUrl?: string;
9
+ /** `optional` verifies when a token is present but never rejects. Default `required`. */
10
+ authMode?: "required" | "optional";
11
+ /** Dev only (caller must gate on non-production): injected when the user header is absent. */
12
+ devToken?: string;
13
+ /** Dev only (caller must gate on non-production): fabricate this user, skip verification. */
14
+ mockUserEmail?: string;
15
+ }
16
+ export type HandleResult = {
17
+ kind: "next";
18
+ requestHeaders: Headers;
19
+ user?: GatewayUser;
20
+ } | {
21
+ kind: "response";
22
+ response: Response;
23
+ };
24
+ /**
25
+ * Runtime-agnostic request flow shared by all adapters (spec §4.3): strip
26
+ * spoofable headers, skip public paths, verify, forward identity or reject.
27
+ */
28
+ export declare function handleRequest(request: Request, opts: HandleOptions): Promise<HandleResult>;
29
+ export declare function matchesPath(pathname: string, patterns: string[]): boolean;
@@ -0,0 +1 @@
1
+ import{GatewayAuthError as c,setIdentityHeaders as s,stripIdentityHeaders as o}from"../core/types.js";async function m(n,e){const{verifier:r}=e,t=new Headers(n.headers);o(t);const a=new URL(n.url).pathname;if(e.publicPaths&&h(a,e.publicPaths))return{kind:"next",requestHeaders:t};if(e.mockUserEmail){const i={sub:"dev-mock-user",email:e.mockUserEmail.toLowerCase(),emailVerified:!0,token:"",claims:{sub:"dev-mock-user",email:e.mockUserEmail}};return s(t,i),{kind:"next",requestHeaders:t,user:i}}e.devToken&&!t.has(r.config.userHeader)&&t.set(r.config.userHeader,r.config.userHeader==="authorization"?`Bearer ${e.devToken}`:e.devToken);try{const i=await r.verifyRequest({headers:t});return s(t,i),{kind:"next",requestHeaders:t,user:i}}catch(i){if(!(i instanceof c))throw i;return e.authMode==="optional"&&i.code==="unauthenticated"?{kind:"next",requestHeaders:t}:{kind:"response",response:u(n,i,e.signinUrl)}}}function u(n,e,r){if(e.code==="unauthenticated"&&r&&d(n)){const a=new URL(r);return a.searchParams.set("redirect_uri",n.url),Response.redirect(a,302)}const t=new Headers({"content-type":"application/json"});return e.code==="unauthenticated"&&t.set("www-authenticate",'Bearer error="invalid_token"'),new Response(JSON.stringify({error:e.code}),{status:e.status,headers:t})}function d(n){return n.headers.get("sec-fetch-mode")==="navigate"?!0:n.headers.get("accept")?.includes("text/html")??!1}function h(n,e){return e.some(r=>r.endsWith("*")?n.startsWith(r.slice(0,-1)):n===r)}export{m as handleRequest,h as matchesPath};
@@ -0,0 +1,22 @@
1
+ import { type NextRequest } from "next/server";
2
+ import type { GatewayConfigInput } from "../core/types.js";
3
+ export interface GatewayMiddlewareOptions extends GatewayConfigInput {
4
+ /** Paths that skip auth. Exact match, or prefix match when the pattern ends with `*`. */
5
+ publicPaths?: string[];
6
+ /** Overrides AGENTGATEWAY_SIGNIN_URL. */
7
+ signinUrl?: string;
8
+ /** Overrides AGENTGATEWAY_AUTH_MODE. Default `required`. */
9
+ authMode?: "required" | "optional";
10
+ }
11
+ /**
12
+ * Next.js adapter. In `proxy.ts` (Next ≥16) or `middleware.ts` (Next <16):
13
+ *
14
+ * ```ts
15
+ * import { createGatewayMiddleware } from "@conveo/conveo-sdk/next";
16
+ * export default createGatewayMiddleware({ publicPaths: ["/api/health"] });
17
+ * export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"] };
18
+ * ```
19
+ */
20
+ export declare function createGatewayMiddleware(options?: GatewayMiddlewareOptions): (request: NextRequest) => Promise<Response>;
21
+ /** Next ≥16 name for the same factory (`middleware.ts` → `proxy.ts` rename). */
22
+ export declare const createGatewayProxy: typeof createGatewayMiddleware;
@@ -0,0 +1 @@
1
+ import{NextResponse as a}from"next/server";import{createVerifier as s}from"../core/verify.js";import{configFromEnv as d,isDevelopment as u}from"./env.js";import{handleRequest as c}from"./handle.js";function E(r={}){let n;return async function(i){const e=process.env;n??=s(d(e,r));const o=u(e),t=await c(i,{verifier:n,publicPaths:r.publicPaths,signinUrl:r.signinUrl??e.AGENTGATEWAY_SIGNIN_URL,authMode:r.authMode??(e.AGENTGATEWAY_AUTH_MODE==="optional"?"optional":"required"),devToken:o?e.AGENTGATEWAY_DEV_TOKEN:void 0,mockUserEmail:o?e.AGENTGATEWAY_DEV_MOCK_USER:void 0});return t.kind==="response"?t.response:a.next({request:{headers:t.requestHeaders}})}}const G=E;export{E as createGatewayMiddleware,G as createGatewayProxy};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Supabase Edge Functions (Deno) adapter. No Node/Next imports — core only.
3
+ *
4
+ * Supabase functions are directly reachable at `*.supabase.co` and bypass
5
+ * Vercel deployment protection entirely, so this adapter enforces the exact
6
+ * same verification — and deliberately offers no shared-secret fallback.
7
+ *
8
+ * ```ts
9
+ * import { requireGatewayUser } from "npm:@conveo/conveo-sdk/supabase";
10
+ *
11
+ * Deno.serve(async (req) => {
12
+ * try {
13
+ * const user = await requireGatewayUser(req);
14
+ * return Response.json({ hello: user.email });
15
+ * } catch (error) {
16
+ * if (error instanceof Response) return error; // 401/403 from the adapter
17
+ * throw error;
18
+ * }
19
+ * });
20
+ * ```
21
+ */
22
+ import { type GatewayConfigInput, type GatewayUser } from "../core/types.js";
23
+ /**
24
+ * Verify the request's user token and return the user. On failure, throws a
25
+ * ready-to-return 401/403 `Response` (catch it and return it — see module doc).
26
+ */
27
+ export declare function requireGatewayUser(request: Request, overrides?: GatewayConfigInput): Promise<GatewayUser>;
@@ -0,0 +1 @@
1
+ import{GatewayAuthError as G}from"../core/types.js";import{createVerifier as E}from"../core/verify.js";import{configFromEnv as n}from"./env.js";function o(){const t=globalThis.Deno,e=r=>{try{return t?.env.get(r)}catch{return}};return{AGENTGATEWAY_ISSUER:e("AGENTGATEWAY_ISSUER"),AGENTGATEWAY_AUDIENCE:e("AGENTGATEWAY_AUDIENCE"),AGENTGATEWAY_JWKS_URL:e("AGENTGATEWAY_JWKS_URL"),AGENTGATEWAY_USER_HEADER:e("AGENTGATEWAY_USER_HEADER"),AGENTGATEWAY_REQUIRE_EMAIL_DOMAIN:e("AGENTGATEWAY_REQUIRE_EMAIL_DOMAIN"),AGENTGATEWAY_ALLOWED_EMAILS:e("AGENTGATEWAY_ALLOWED_EMAILS")}}let i;function a(t){return t?E(n(o(),t)):(i??=E(n(o())),i)}async function s(t,e){try{return await a(e).verifyRequest(t)}catch(r){if(r instanceof G){const A=new Headers({"content-type":"application/json"});throw r.code==="unauthenticated"&&A.set("www-authenticate",'Bearer error="invalid_token"'),new Response(JSON.stringify({error:r.code}),{status:r.status,headers:A})}throw r}}export{s as requireGatewayUser};
@@ -0,0 +1,20 @@
1
+ import type { GatewayConfigInput } from "../core/types.js";
2
+ export interface GatewayRoutingMiddlewareOptions extends GatewayConfigInput {
3
+ /** Paths that skip auth. Exact match, or prefix match when the pattern ends with `*`. */
4
+ publicPaths?: string[];
5
+ /** Overrides AGENTGATEWAY_SIGNIN_URL. */
6
+ signinUrl?: string;
7
+ /** Overrides AGENTGATEWAY_AUTH_MODE. Default `required`. */
8
+ authMode?: "required" | "optional";
9
+ }
10
+ /**
11
+ * Framework-agnostic Vercel Routing Middleware adapter — for non-Next apps
12
+ * (e.g. a Vite SPA like super-G). In `middleware.ts` at the project root:
13
+ *
14
+ * ```ts
15
+ * import { createGatewayRoutingMiddleware } from "@conveo/conveo-sdk/vercel";
16
+ * export default createGatewayRoutingMiddleware({ publicPaths: ["/api/health"] });
17
+ * export const config = { matcher: ["/((?!assets|favicon.ico).*)"] };
18
+ * ```
19
+ */
20
+ export declare function createGatewayRoutingMiddleware(options?: GatewayRoutingMiddlewareOptions): (request: Request) => Promise<Response>;
@@ -0,0 +1 @@
1
+ import{next as a}from"@vercel/functions";import{createVerifier as s}from"../core/verify.js";import{configFromEnv as d,isDevelopment as u}from"./env.js";import{handleRequest as E}from"./handle.js";function p(r={}){let t;return async function(o){const e=process.env;t??=s(d(e,r));const i=u(e),n=await E(o,{verifier:t,publicPaths:r.publicPaths,signinUrl:r.signinUrl??e.AGENTGATEWAY_SIGNIN_URL,authMode:r.authMode??(e.AGENTGATEWAY_AUTH_MODE==="optional"?"optional":"required"),devToken:i?e.AGENTGATEWAY_DEV_TOKEN:void 0,mockUserEmail:i?e.AGENTGATEWAY_DEV_MOCK_USER:void 0});return n.kind==="response"?n.response:a({request:{headers:n.requestHeaders}})}}export{p as createGatewayRoutingMiddleware};
@@ -0,0 +1,3 @@
1
+ export { GatewayAuthError, IDENTITY_HEADERS, setIdentityHeaders, stripIdentityHeaders, type GatewayAuthErrorCode, type GatewayConfig, type GatewayConfigInput, type GatewayUser, } from "./types.js";
2
+ export { checkPolicy } from "./policy.js";
3
+ export { createVerifier, DEFAULTS, extractToken, resolveConfig, type Verifier, type VerifierOptions, } from "./verify.js";
@@ -0,0 +1 @@
1
+ import{GatewayAuthError as t,IDENTITY_HEADERS as o,setIdentityHeaders as i,stripIdentityHeaders as a}from"./types.js";import{checkPolicy as f}from"./policy.js";import{createVerifier as E,DEFAULTS as d,extractToken as n,resolveConfig as p}from"./verify.js";export{d as DEFAULTS,t as GatewayAuthError,o as IDENTITY_HEADERS,f as checkPolicy,E as createVerifier,n as extractToken,p as resolveConfig,i as setIdentityHeaders,a as stripIdentityHeaders};
@@ -0,0 +1,7 @@
1
+ import { type GatewayConfig } from "./types.js";
2
+ /**
3
+ * App-side authorization policy on top of a verified identity. The gateway
4
+ * already applies its own allowlist; this is the belt-and-suspenders layer.
5
+ * Throws `GatewayAuthError("forbidden")` — never reveals which rule failed.
6
+ */
7
+ export declare function checkPolicy(email: string, config: Pick<GatewayConfig, "requireEmailDomain" | "allowedEmails">): void;
@@ -0,0 +1 @@
1
+ import{GatewayAuthError as a}from"./types.js";function m(l,o){const e=l.trim().toLowerCase();if(o.requireEmailDomain&&e.slice(e.lastIndexOf("@")+1)!==o.requireEmailDomain.trim().toLowerCase())throw new a("forbidden","email not allowed for this app");if(o.allowedEmails&&o.allowedEmails.length>0&&!o.allowedEmails.some(r=>r.trim().toLowerCase()===e))throw new a("forbidden","email not allowed for this app")}export{m as checkPolicy};
@@ -0,0 +1,54 @@
1
+ import type { JWTPayload } from "jose";
2
+ /** Fully-resolved configuration. Use {@link resolveConfig} to build one from partial input. */
3
+ export interface GatewayConfig {
4
+ /** OIDC issuer that signs user tokens (the Keycloak realm). */
5
+ issuer: string;
6
+ /**
7
+ * Required `aud` claim. Not enforced when unset — the conveo realm issues user
8
+ * tokens without an `aud` claim (clients get no audience mappers), so there
9
+ * is nothing to pin yet. Set it if the realm gains audience mappers.
10
+ */
11
+ audience?: string;
12
+ /** JWKS endpoint used to verify token signatures. */
13
+ jwksUrl: string;
14
+ /**
15
+ * Lower-cased name of the header carrying the user token. The default,
16
+ * `authorization`, expects a `Bearer ` prefix (this is what the gateway's
17
+ * `backend.auth.passthrough` sets). Any other header is read verbatim.
18
+ */
19
+ userHeader: string;
20
+ /** When set, only emails on exactly this domain pass (e.g. `conveo.ai`). */
21
+ requireEmailDomain?: string;
22
+ /** When non-empty, only these emails pass (case-insensitive). */
23
+ allowedEmails?: string[];
24
+ /** Clock skew tolerance for `exp`/`nbf`, in seconds. */
25
+ clockToleranceSec: number;
26
+ }
27
+ export type GatewayConfigInput = Partial<GatewayConfig>;
28
+ /** A verified, gateway-vouched user. Only ever constructed from a token that passed verification. */
29
+ export interface GatewayUser {
30
+ sub: string;
31
+ email: string;
32
+ emailVerified: true;
33
+ /** The raw verified JWT — reuse it for on-behalf-of calls into the gateway. */
34
+ token: string;
35
+ claims: JWTPayload;
36
+ }
37
+ export type GatewayAuthErrorCode = "unauthenticated" | "forbidden";
38
+ export declare class GatewayAuthError extends Error {
39
+ readonly code: GatewayAuthErrorCode;
40
+ readonly status: 401 | 403;
41
+ constructor(code: GatewayAuthErrorCode, message: string);
42
+ }
43
+ /**
44
+ * Headers this package sets on the request it forwards to the app. They are
45
+ * stripped from every inbound request before anything else happens, so app
46
+ * code can trust them — they can only have been set by the middleware.
47
+ */
48
+ export declare const IDENTITY_HEADERS: {
49
+ readonly sub: "x-gateway-user-sub";
50
+ readonly email: "x-gateway-user-email";
51
+ readonly token: "x-gateway-user-token";
52
+ };
53
+ export declare function stripIdentityHeaders(headers: Headers): void;
54
+ export declare function setIdentityHeaders(headers: Headers, user: GatewayUser): void;
@@ -0,0 +1 @@
1
+ class a extends Error{code;status;constructor(t,o){super(o),this.name="GatewayAuthError",this.code=t,this.status=t==="forbidden"?403:401}}const s={sub:"x-gateway-user-sub",email:"x-gateway-user-email",token:"x-gateway-user-token"};function r(e){for(const t of Object.values(s))e.delete(t)}function n(e,t){e.set(s.sub,t.sub),e.set(s.email,t.email),t.token&&e.set(s.token,t.token)}export{a as GatewayAuthError,s as IDENTITY_HEADERS,n as setIdentityHeaders,r as stripIdentityHeaders};
@@ -0,0 +1,21 @@
1
+ import { type JWTVerifyGetKey } from "jose";
2
+ import { type GatewayConfig, type GatewayConfigInput, type GatewayUser } from "./types.js";
3
+ export declare const DEFAULTS: {
4
+ readonly issuer: "https://keycloak.ops.conveo.ai/realms/conveo";
5
+ readonly userHeader: "authorization";
6
+ readonly clockToleranceSec: 30;
7
+ };
8
+ export declare function resolveConfig(input?: GatewayConfigInput): GatewayConfig;
9
+ export declare function extractToken(headers: Headers, userHeader: string): string | null;
10
+ export interface VerifierOptions {
11
+ /** Override key resolution (tests inject `createLocalJWKSet` here). */
12
+ getKey?: JWTVerifyGetKey;
13
+ }
14
+ export interface Verifier {
15
+ readonly config: GatewayConfig;
16
+ verifyToken(token: string): Promise<GatewayUser>;
17
+ verifyRequest(request: {
18
+ headers: Headers;
19
+ }): Promise<GatewayUser>;
20
+ }
21
+ export declare function createVerifier(input?: GatewayConfigInput, options?: VerifierOptions): Verifier;
@@ -0,0 +1 @@
1
+ import{createRemoteJWKSet as u,jwtVerify as m}from"jose";import{checkPolicy as d}from"./policy.js";import{GatewayAuthError as i}from"./types.js";const a={issuer:"https://keycloak.ops.conveo.ai/realms/conveo",userHeader:"authorization",clockToleranceSec:30};function f(t={}){const o=(t.issuer??a.issuer).replace(/\/+$/,"");return{issuer:o,audience:t.audience,jwksUrl:t.jwksUrl??`${o}/protocol/openid-connect/certs`,userHeader:(t.userHeader??a.userHeader).toLowerCase(),requireEmailDomain:t.requireEmailDomain,allowedEmails:t.allowedEmails,clockToleranceSec:t.clockToleranceSec??a.clockToleranceSec}}function h(t,o){const e=t.get(o);return e?o==="authorization"?/^Bearer /i.test(e)&&e.slice(7).trim()||null:e.trim()||null:null}function p(t={},o={}){const e=f(t),l=o.getKey??u(new URL(e.jwksUrl));async function c(n){let r;try{({payload:r}=await m(n,l,{issuer:e.issuer,...e.audience?{audience:e.audience}:{},algorithms:["RS256"],clockTolerance:e.clockToleranceSec}))}catch{throw new i("unauthenticated","invalid or expired token")}if(typeof r.sub!="string"||r.sub.length===0)throw new i("unauthenticated","token has no subject");if(r.email_verified!==!0||typeof r.email!="string"||!r.email)throw new i("unauthenticated","token has no verified email");return d(r.email,e),{sub:r.sub,email:r.email.toLowerCase(),emailVerified:!0,token:n,claims:r}}async function s(n){const r=h(n.headers,e.userHeader);if(!r)throw new i("unauthenticated",`no token in "${e.userHeader}" header`);return c(r)}return{config:e,verifyToken:c,verifyRequest:s}}export{a as DEFAULTS,p as createVerifier,h as extractToken,f as resolveConfig};
@@ -0,0 +1 @@
1
+ var y=class extends Error{code;status;constructor(t,r){super(r),this.name="GatewayAuthError",this.code=t,this.status=t==="forbidden"?403:401}};var P=new TextEncoder,S=new TextDecoder,ft=2**32;function ae(...e){let t=e.reduce((n,{length:i})=>n+i,0),r=new Uint8Array(t),o=0;for(let n of e)r.set(n,o),o+=n.length;return r}function N(e){let t=new Uint8Array(e.length);for(let r=0;r<e.length;r++){let o=e.charCodeAt(r);if(o>127)throw new TypeError("non-ASCII string encountered in encode()");t[r]=o}return t}function se(e){if(Uint8Array.fromBase64)return Uint8Array.fromBase64(e);let t=atob(e),r=new Uint8Array(t.length);for(let o=0;o<t.length;o++)r[o]=t.charCodeAt(o);return r}function A(e){if(Uint8Array.fromBase64)return Uint8Array.fromBase64(typeof e=="string"?e:S.decode(e),{alphabet:"base64url"});let t=e;t instanceof Uint8Array&&(t=S.decode(t)),t=t.replace(/-/g,"+").replace(/_/g,"/");try{return se(t)}catch{throw new TypeError("The input to be decoded is not correctly encoded.")}}var b=(e,t="algorithm.name")=>new TypeError(`CryptoKey does not support this operation, its ${t} must be ${e}`),K=(e,t)=>e.name===t;function Ge(e){return parseInt(e.name.slice(4),10)}function $(e,t){if(Ge(e.hash)!==t)throw b(`SHA-${t}`,"algorithm.hash")}function Ne(e){switch(e){case"ES256":return"P-256";case"ES384":return"P-384";case"ES512":return"P-521";default:throw new Error("unreachable")}}function Oe(e,t){if(t&&!e.usages.includes(t))throw new TypeError(`CryptoKey does not support this operation, its usages must include ${t}.`)}function ce(e,t,r){switch(t){case"HS256":case"HS384":case"HS512":{if(!K(e.algorithm,"HMAC"))throw b("HMAC");$(e.algorithm,parseInt(t.slice(2),10));break}case"RS256":case"RS384":case"RS512":{if(!K(e.algorithm,"RSASSA-PKCS1-v1_5"))throw b("RSASSA-PKCS1-v1_5");$(e.algorithm,parseInt(t.slice(2),10));break}case"PS256":case"PS384":case"PS512":{if(!K(e.algorithm,"RSA-PSS"))throw b("RSA-PSS");$(e.algorithm,parseInt(t.slice(2),10));break}case"Ed25519":case"EdDSA":{if(!K(e.algorithm,"Ed25519"))throw b("Ed25519");break}case"ML-DSA-44":case"ML-DSA-65":case"ML-DSA-87":{if(!K(e.algorithm,t))throw b(t);break}case"ES256":case"ES384":case"ES512":{if(!K(e.algorithm,"ECDSA"))throw b("ECDSA");let o=Ne(t);if(e.algorithm.namedCurve!==o)throw b(o,"algorithm.namedCurve");break}default:throw new TypeError("CryptoKey does not support this operation")}Oe(e,r)}function fe(e,t,...r){if(r=r.filter(Boolean),r.length>2){let o=r.pop();e+=`one of type ${r.join(", ")}, or ${o}.`}else r.length===2?e+=`one of type ${r[0]} or ${r[1]}.`:e+=`of type ${r[0]}.`;return t==null?e+=` Received ${t}`:typeof t=="function"&&t.name?e+=` Received function ${t.name}`:typeof t=="object"&&t!=null&&t.constructor?.name&&(e+=` Received an instance of ${t.constructor.name}`),e}var de=(e,...t)=>fe("Key must be ",e,...t),V=(e,t,...r)=>fe(`Key for the ${e} algorithm must be `,t,...r);var l=class extends Error{static code="ERR_JOSE_GENERIC";code="ERR_JOSE_GENERIC";constructor(t,r){super(t,r),this.name=this.constructor.name,Error.captureStackTrace?.(this,this.constructor)}},m=class extends l{static code="ERR_JWT_CLAIM_VALIDATION_FAILED";code="ERR_JWT_CLAIM_VALIDATION_FAILED";claim;reason;payload;constructor(t,r,o="unspecified",n="unspecified"){super(t,{cause:{claim:o,reason:n,payload:r}}),this.claim=o,this.reason=n,this.payload=r}},I=class extends l{static code="ERR_JWT_EXPIRED";code="ERR_JWT_EXPIRED";claim;reason;payload;constructor(t,r,o="unspecified",n="unspecified"){super(t,{cause:{claim:o,reason:n,payload:r}}),this.claim=o,this.reason=n,this.payload=r}},O=class extends l{static code="ERR_JOSE_ALG_NOT_ALLOWED";code="ERR_JOSE_ALG_NOT_ALLOWED"},p=class extends l{static code="ERR_JOSE_NOT_SUPPORTED";code="ERR_JOSE_NOT_SUPPORTED"};var d=class extends l{static code="ERR_JWS_INVALID";code="ERR_JWS_INVALID"},T=class extends l{static code="ERR_JWT_INVALID";code="ERR_JWT_INVALID"};var D=class extends l{static code="ERR_JWKS_INVALID";code="ERR_JWKS_INVALID"},x=class extends l{static code="ERR_JWKS_NO_MATCHING_KEY";code="ERR_JWKS_NO_MATCHING_KEY";constructor(t="no applicable key found in the JSON Web Key Set",r){super(t,r)}},U=class extends l{[Symbol.asyncIterator];static code="ERR_JWKS_MULTIPLE_MATCHING_KEYS";code="ERR_JWKS_MULTIPLE_MATCHING_KEYS";constructor(t="multiple matching keys found in the JSON Web Key Set",r){super(t,r)}},H=class extends l{static code="ERR_JWKS_TIMEOUT";code="ERR_JWKS_TIMEOUT";constructor(t="request timed out",r){super(t,r)}},L=class extends l{static code="ERR_JWS_SIGNATURE_VERIFICATION_FAILED";code="ERR_JWS_SIGNATURE_VERIFICATION_FAILED";constructor(t="signature verification failed",r){super(t,r)}};var B=e=>{if(e?.[Symbol.toStringTag]==="CryptoKey")return!0;try{return e instanceof CryptoKey}catch{return!1}},F=e=>e?.[Symbol.toStringTag]==="KeyObject",Y=e=>B(e)||F(e);function q(e,t,r){try{return A(e)}catch{throw new r(`Failed to base64url decode the ${t}`)}}var Ue=e=>typeof e=="object"&&e!==null;function h(e){if(!Ue(e)||Object.prototype.toString.call(e)!=="[object Object]")return!1;if(Object.getPrototypeOf(e)===null)return!0;let t=e;for(;Object.getPrototypeOf(t)!==null;)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t}function ue(...e){let t=e.filter(Boolean);if(t.length===0||t.length===1)return!0;let r;for(let o of t){let n=Object.keys(o);if(!r||r.size===0){r=new Set(n);continue}for(let i of n){if(r.has(i))return!1;r.add(i)}}return!0}var J=e=>h(e)&&typeof e.kty=="string",pe=e=>e.kty!=="oct"&&(e.kty==="AKP"&&typeof e.priv=="string"||typeof e.d=="string"),le=e=>e.kty!=="oct"&&e.d===void 0&&e.priv===void 0,me=e=>e.kty==="oct"&&typeof e.k=="string";function Le(e,t){if(e.startsWith("RS")||e.startsWith("PS")){let{modulusLength:r}=t.algorithm;if(typeof r!="number"||r<2048)throw new TypeError(`${e} requires key modulusLength to be 2048 bits or larger`)}}function Me(e,t){let r=`SHA-${e.slice(-3)}`;switch(e){case"HS256":case"HS384":case"HS512":return{hash:r,name:"HMAC"};case"PS256":case"PS384":case"PS512":return{hash:r,name:"RSA-PSS",saltLength:parseInt(e.slice(-3),10)>>3};case"RS256":case"RS384":case"RS512":return{hash:r,name:"RSASSA-PKCS1-v1_5"};case"ES256":case"ES384":case"ES512":return{hash:r,name:"ECDSA",namedCurve:t.namedCurve};case"Ed25519":case"EdDSA":return{name:"Ed25519"};case"ML-DSA-44":case"ML-DSA-65":case"ML-DSA-87":return{name:e};default:throw new p(`alg ${e} is not supported either by JOSE or your javascript runtime`)}}async function ke(e,t,r){if(t instanceof Uint8Array){if(!e.startsWith("HS"))throw new TypeError(de(t,"CryptoKey","KeyObject","JSON Web Key"));return crypto.subtle.importKey("raw",t,{hash:`SHA-${e.slice(-3)}`,name:"HMAC"},!1,[r])}return ce(t,e,r),t}async function he(e,t,r,o){let n=await ke(e,t,"verify");Le(e,n);let i=Me(e,n.algorithm);try{return await crypto.subtle.verify(i,n,r,o)}catch{return!1}}var M='Invalid or unsupported JWK "alg" (Algorithm) Parameter value';function $e(e){let t,r;switch(e.kty){case"AKP":{switch(e.alg){case"ML-DSA-44":case"ML-DSA-65":case"ML-DSA-87":t={name:e.alg},r=e.priv?["sign"]:["verify"];break;default:throw new p(M)}break}case"RSA":{switch(e.alg){case"PS256":case"PS384":case"PS512":t={name:"RSA-PSS",hash:`SHA-${e.alg.slice(-3)}`},r=e.d?["sign"]:["verify"];break;case"RS256":case"RS384":case"RS512":t={name:"RSASSA-PKCS1-v1_5",hash:`SHA-${e.alg.slice(-3)}`},r=e.d?["sign"]:["verify"];break;case"RSA-OAEP":case"RSA-OAEP-256":case"RSA-OAEP-384":case"RSA-OAEP-512":t={name:"RSA-OAEP",hash:`SHA-${parseInt(e.alg.slice(-3),10)||1}`},r=e.d?["decrypt","unwrapKey"]:["encrypt","wrapKey"];break;default:throw new p(M)}break}case"EC":{switch(e.alg){case"ES256":case"ES384":case"ES512":t={name:"ECDSA",namedCurve:{ES256:"P-256",ES384:"P-384",ES512:"P-521"}[e.alg]},r=e.d?["sign"]:["verify"];break;case"ECDH-ES":case"ECDH-ES+A128KW":case"ECDH-ES+A192KW":case"ECDH-ES+A256KW":t={name:"ECDH",namedCurve:e.crv},r=e.d?["deriveBits"]:[];break;default:throw new p(M)}break}case"OKP":{switch(e.alg){case"Ed25519":case"EdDSA":t={name:"Ed25519"},r=e.d?["sign"]:["verify"];break;case"ECDH-ES":case"ECDH-ES+A128KW":case"ECDH-ES+A192KW":case"ECDH-ES+A256KW":t={name:e.crv},r=e.d?["deriveBits"]:[];break;default:throw new p(M)}break}default:throw new p('Invalid or unsupported JWK "kty" (Key Type) Parameter value')}return{algorithm:t,keyUsages:r}}async function R(e){if(!e.alg)throw new TypeError('"alg" argument is required when "jwk.alg" is not present');let{algorithm:t,keyUsages:r}=$e(e),o={...e};return o.kty!=="AKP"&&delete o.alg,delete o.use,crypto.subtle.importKey("jwk",o,t,e.ext??!(e.d||e.priv),e.key_ops??r)}var C="given KeyObject instance cannot be used for this algorithm",W,ye=async(e,t,r,o=!1)=>{W||=new WeakMap;let n=W.get(e);if(n?.[r])return n[r];let i=await R({...t,alg:r});return o&&Object.freeze(e),n?n[r]=i:W.set(e,{[r]:i}),i},Ve=(e,t)=>{W||=new WeakMap;let r=W.get(e);if(r?.[t])return r[t];let o=e.type==="public",n=!!o,i;if(e.asymmetricKeyType==="x25519"){switch(t){case"ECDH-ES":case"ECDH-ES+A128KW":case"ECDH-ES+A192KW":case"ECDH-ES+A256KW":break;default:throw new TypeError(C)}i=e.toCryptoKey(e.asymmetricKeyType,n,o?[]:["deriveBits"])}if(e.asymmetricKeyType==="ed25519"){if(t!=="EdDSA"&&t!=="Ed25519")throw new TypeError(C);i=e.toCryptoKey(e.asymmetricKeyType,n,[o?"verify":"sign"])}switch(e.asymmetricKeyType){case"ml-dsa-44":case"ml-dsa-65":case"ml-dsa-87":{if(t!==e.asymmetricKeyType.toUpperCase())throw new TypeError(C);i=e.toCryptoKey(e.asymmetricKeyType,n,[o?"verify":"sign"])}}if(e.asymmetricKeyType==="rsa"){let a;switch(t){case"RSA-OAEP":a="SHA-1";break;case"RS256":case"PS256":case"RSA-OAEP-256":a="SHA-256";break;case"RS384":case"PS384":case"RSA-OAEP-384":a="SHA-384";break;case"RS512":case"PS512":case"RSA-OAEP-512":a="SHA-512";break;default:throw new TypeError(C)}if(t.startsWith("RSA-OAEP"))return e.toCryptoKey({name:"RSA-OAEP",hash:a},n,o?["encrypt"]:["decrypt"]);i=e.toCryptoKey({name:t.startsWith("PS")?"RSA-PSS":"RSASSA-PKCS1-v1_5",hash:a},n,[o?"verify":"sign"])}if(e.asymmetricKeyType==="ec"){let s=new Map([["prime256v1","P-256"],["secp384r1","P-384"],["secp521r1","P-521"]]).get(e.asymmetricKeyDetails?.namedCurve);if(!s)throw new TypeError(C);let u={ES256:"P-256",ES384:"P-384",ES512:"P-521"};u[t]&&s===u[t]&&(i=e.toCryptoKey({name:"ECDSA",namedCurve:s},n,[o?"verify":"sign"])),t.startsWith("ECDH-ES")&&(i=e.toCryptoKey({name:"ECDH",namedCurve:s},n,o?[]:["deriveBits"]))}if(!i)throw new TypeError(C);return r?r[t]=i:W.set(e,{[t]:i}),i};async function Ee(e,t){if(e instanceof Uint8Array||B(e))return e;if(F(e)){if(e.type==="secret")return e.export();if("toCryptoKey"in e&&typeof e.toCryptoKey=="function")try{return Ve(e,t)}catch(o){if(o instanceof TypeError)throw o}let r=e.export({format:"jwk"});return ye(e,r,t)}if(J(e))return e.k?A(e.k):ye(e,e,t,!0);throw new Error("unreachable")}async function we(e,t,r){if(!h(e))throw new TypeError("JWK must be an object");let o;switch(t??=e.alg,o??=r?.extractable??e.ext,e.kty){case"oct":if(typeof e.k!="string"||!e.k)throw new TypeError('missing "k" (Key Value) Parameter value');return A(e.k);case"RSA":if("oth"in e&&e.oth!==void 0)throw new p('RSA JWK "oth" (Other Primes Info) Parameter value is not supported');return R({...e,alg:t,ext:o});case"AKP":{if(typeof e.alg!="string"||!e.alg)throw new TypeError('missing "alg" (Algorithm) Parameter value');if(t!==void 0&&t!==e.alg)throw new TypeError("JWK alg and alg option value mismatch");return R({...e,ext:o})}case"EC":case"OKP":return R({...e,alg:t,ext:o});default:throw new p('Unsupported "kty" (Key Type) Parameter value')}}function Se(e,t,r,o,n){if(n.crit!==void 0&&o?.crit===void 0)throw new e('"crit" (Critical) Header Parameter MUST be integrity protected');if(!o||o.crit===void 0)return new Set;if(!Array.isArray(o.crit)||o.crit.length===0||o.crit.some(a=>typeof a!="string"||a.length===0))throw new e('"crit" (Critical) Header Parameter MUST be an array of non-empty strings when present');let i;r!==void 0?i=new Map([...Object.entries(r),...t.entries()]):i=t;for(let a of o.crit){if(!i.has(a))throw new p(`Extension Header Parameter "${a}" is not recognized`);if(n[a]===void 0)throw new e(`Extension Header Parameter "${a}" is missing`);if(i.get(a)&&o[a]===void 0)throw new e(`Extension Header Parameter "${a}" MUST be integrity protected`)}return new Set(o.crit)}function Ae(e,t){if(t!==void 0&&(!Array.isArray(t)||t.some(r=>typeof r!="string")))throw new TypeError(`"${e}" option must be an array of strings`);if(t)return new Set(t)}var _=e=>e?.[Symbol.toStringTag],z=(e,t,r)=>{if(t.use!==void 0){let o;switch(r){case"sign":case"verify":o="sig";break;case"encrypt":case"decrypt":o="enc";break}if(t.use!==o)throw new TypeError(`Invalid key for this operation, its "use" must be "${o}" when present`)}if(t.alg!==void 0&&t.alg!==e)throw new TypeError(`Invalid key for this operation, its "alg" must be "${e}" when present`);if(Array.isArray(t.key_ops)){let o;switch(!0){case(r==="sign"||r==="verify"):case e==="dir":case e.includes("CBC-HS"):o=r;break;case e.startsWith("PBES2"):o="deriveBits";break;case/^A\d{3}(?:GCM)?(?:KW)?$/.test(e):!e.includes("GCM")&&e.endsWith("KW")?o=r==="encrypt"?"wrapKey":"unwrapKey":o=r;break;case(r==="encrypt"&&e.startsWith("RSA")):o="wrapKey";break;case r==="decrypt":o=e.startsWith("RSA")?"unwrapKey":"deriveBits";break}if(o&&t.key_ops?.includes?.(o)===!1)throw new TypeError(`Invalid key for this operation, its "key_ops" must include "${o}" when present`)}return!0},Be=(e,t,r)=>{if(!(t instanceof Uint8Array)){if(J(t)){if(me(t)&&z(e,t,r))return;throw new TypeError('JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present')}if(!Y(t))throw new TypeError(V(e,t,"CryptoKey","KeyObject","JSON Web Key","Uint8Array"));if(t.type!=="secret")throw new TypeError(`${_(t)} instances for symmetric algorithms must be of type "secret"`)}},Fe=(e,t,r)=>{if(J(t))switch(r){case"decrypt":case"sign":if(pe(t)&&z(e,t,r))return;throw new TypeError("JSON Web Key for this operation must be a private JWK");case"encrypt":case"verify":if(le(t)&&z(e,t,r))return;throw new TypeError("JSON Web Key for this operation must be a public JWK")}if(!Y(t))throw new TypeError(V(e,t,"CryptoKey","KeyObject","JSON Web Key"));if(t.type==="secret")throw new TypeError(`${_(t)} instances for asymmetric algorithms must not be of type "secret"`);if(t.type==="public")switch(r){case"sign":throw new TypeError(`${_(t)} instances for asymmetric algorithm signing must be of type "private"`);case"decrypt":throw new TypeError(`${_(t)} instances for asymmetric algorithm decryption must be of type "private"`)}if(t.type==="private")switch(r){case"verify":throw new TypeError(`${_(t)} instances for asymmetric algorithm verifying must be of type "public"`);case"encrypt":throw new TypeError(`${_(t)} instances for asymmetric algorithm encryption must be of type "public"`)}};function be(e,t,r){switch(e.substring(0,2)){case"A1":case"A2":case"di":case"HS":case"PB":Be(e,t,r);break;default:Fe(e,t,r)}}async function ge(e,t,r){if(!h(e))throw new d("Flattened JWS must be an object");if(e.protected===void 0&&e.header===void 0)throw new d('Flattened JWS must have either of the "protected" or "header" members');if(e.protected!==void 0&&typeof e.protected!="string")throw new d("JWS Protected Header incorrect type");if(e.payload===void 0)throw new d("JWS Payload missing");if(typeof e.signature!="string")throw new d("JWS Signature missing or incorrect type");if(e.header!==void 0&&!h(e.header))throw new d("JWS Unprotected Header incorrect type");let o={};if(e.protected)try{let ve=A(e.protected);o=JSON.parse(S.decode(ve))}catch{throw new d("JWS Protected Header is invalid")}if(!ue(o,e.header))throw new d("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");let n={...o,...e.header},i=Se(d,new Map([["b64",!0]]),r?.crit,o,n),a=!0;if(i.has("b64")&&(a=o.b64,typeof a!="boolean"))throw new d('The "b64" (base64url-encode payload) Header Parameter must be a boolean');let{alg:s}=n;if(typeof s!="string"||!s)throw new d('JWS "alg" (Algorithm) Header Parameter missing or invalid');let u=r&&Ae("algorithms",r.algorithms);if(u&&!u.has(s))throw new O('"alg" (Algorithm) Header Parameter value not allowed');if(a){if(typeof e.payload!="string")throw new d("JWS Payload must be a string")}else if(typeof e.payload!="string"&&!(e.payload instanceof Uint8Array))throw new d("JWS Payload must be a string or an Uint8Array instance");let c=!1;typeof t=="function"&&(t=await t(o,e),c=!0),be(s,t,"verify");let f=ae(e.protected!==void 0?N(e.protected):new Uint8Array,N("."),typeof e.payload=="string"?a?N(e.payload):P.encode(e.payload):e.payload),E=q(e.signature,"signature",d),v=await Ee(t,s);if(!await he(s,v,E,f))throw new L;let w;a?w=q(e.payload,"payload",d):typeof e.payload=="string"?w=P.encode(e.payload):w=e.payload;let g={payload:w};return e.protected!==void 0&&(g.protectedHeader=o),e.header!==void 0&&(g.unprotectedHeader=e.header),c?{...g,key:v}:g}async function Ke(e,t,r){if(e instanceof Uint8Array&&(e=S.decode(e)),typeof e!="string")throw new d("Compact JWS must be a string or Uint8Array");let{0:o,1:n,2:i,length:a}=e.split(".");if(a!==3)throw new d("Invalid Compact JWS");let s=await ge({payload:n,protected:o,signature:i},t,r),u={payload:s.payload,protectedHeader:s.protectedHeader};return typeof t=="function"?{...u,key:s.key}:u}var Ye=e=>Math.floor(e.getTime()/1e3),Re=60,Ce=Re*60,X=Ce*24,qe=X*7,ze=X*365.25,Xe=/^(\+|\-)? ?(\d+|\d+\.\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i;function Te(e){let t=Xe.exec(e);if(!t||t[4]&&t[1])throw new TypeError("Invalid time period format");let r=parseFloat(t[2]),o=t[3].toLowerCase(),n;switch(o){case"sec":case"secs":case"second":case"seconds":case"s":n=Math.round(r);break;case"minute":case"minutes":case"min":case"mins":case"m":n=Math.round(r*Re);break;case"hour":case"hours":case"hr":case"hrs":case"h":n=Math.round(r*Ce);break;case"day":case"days":case"d":n=Math.round(r*X);break;case"week":case"weeks":case"w":n=Math.round(r*qe);break;default:n=Math.round(r*ze);break}return t[1]==="-"||t[4]==="ago"?-n:n}var xe=e=>e.includes("/")?e.toLowerCase():`application/${e.toLowerCase()}`,Qe=(e,t)=>typeof e=="string"?t.includes(e):Array.isArray(e)?t.some(Set.prototype.has.bind(new Set(e))):!1;function We(e,t,r={}){let o;try{o=JSON.parse(S.decode(t))}catch{}if(!h(o))throw new T("JWT Claims Set must be a top-level JSON object");let{typ:n}=r;if(n&&(typeof e.typ!="string"||xe(e.typ)!==xe(n)))throw new m('unexpected "typ" JWT header value',o,"typ","check_failed");let{requiredClaims:i=[],issuer:a,subject:s,audience:u,maxTokenAge:c}=r,f=[...i];c!==void 0&&f.push("iat"),u!==void 0&&f.push("aud"),s!==void 0&&f.push("sub"),a!==void 0&&f.push("iss");for(let w of new Set(f.reverse()))if(!(w in o))throw new m(`missing required "${w}" claim`,o,w,"missing");if(a&&!(Array.isArray(a)?a:[a]).includes(o.iss))throw new m('unexpected "iss" claim value',o,"iss","check_failed");if(s&&o.sub!==s)throw new m('unexpected "sub" claim value',o,"sub","check_failed");if(u&&!Qe(o.aud,typeof u=="string"?[u]:u))throw new m('unexpected "aud" claim value',o,"aud","check_failed");let E;switch(typeof r.clockTolerance){case"string":E=Te(r.clockTolerance);break;case"number":E=r.clockTolerance;break;case"undefined":E=0;break;default:throw new TypeError("Invalid clockTolerance option type")}let{currentDate:v}=r,G=Ye(v||new Date);if((o.iat!==void 0||c)&&typeof o.iat!="number")throw new m('"iat" claim must be a number',o,"iat","invalid");if(o.nbf!==void 0){if(typeof o.nbf!="number")throw new m('"nbf" claim must be a number',o,"nbf","invalid");if(o.nbf>G+E)throw new m('"nbf" claim timestamp check failed',o,"nbf","check_failed")}if(o.exp!==void 0){if(typeof o.exp!="number")throw new m('"exp" claim must be a number',o,"exp","invalid");if(o.exp<=G-E)throw new I('"exp" claim timestamp check failed',o,"exp","check_failed")}if(c){let w=G-o.iat,g=typeof c=="number"?c:Te(c);if(w-E>g)throw new I('"iat" claim timestamp check failed (too far in the past)',o,"iat","check_failed");if(w<0-E)throw new m('"iat" claim timestamp check failed (it should be in the past)',o,"iat","check_failed")}return o}async function Q(e,t,r){let o=await Ke(e,t,r);if(o.protectedHeader.crit?.includes("b64")&&o.protectedHeader.b64===!1)throw new T("JWTs MUST NOT use unencoded payload");let i={payload:We(o.protectedHeader,o.payload,r),protectedHeader:o.protectedHeader};return typeof t=="function"?{...i,key:o.key}:i}function Ze(e){switch(typeof e=="string"&&e.slice(0,2)){case"RS":case"PS":return"RSA";case"ES":return"EC";case"Ed":return"OKP";case"ML":return"AKP";default:throw new p('Unsupported "alg" value for a JSON Web Key Set')}}function je(e){return e&&typeof e=="object"&&Array.isArray(e.keys)&&e.keys.every(et)}function et(e){return h(e)}var Z=class{#r;#a=new WeakMap;constructor(t){if(!je(t))throw new D("JSON Web Key Set malformed");this.#r=structuredClone(t)}jwks(){return this.#r}async getKey(t,r){let{alg:o,kid:n}={...t,...r?.header},i=Ze(o),a=this.#r.keys.filter(c=>{let f=i===c.kty;if(f&&typeof n=="string"&&(f=n===c.kid),f&&(typeof c.alg=="string"||i==="AKP")&&(f=o===c.alg),f&&typeof c.use=="string"&&(f=c.use==="sig"),f&&Array.isArray(c.key_ops)&&(f=c.key_ops.includes("verify")),f)switch(o){case"ES256":f=c.crv==="P-256";break;case"ES384":f=c.crv==="P-384";break;case"ES512":f=c.crv==="P-521";break;case"Ed25519":case"EdDSA":f=c.crv==="Ed25519";break}return f}),{0:s,length:u}=a;if(u===0)throw new x;if(u!==1){let c=new U,f=this.#a;throw c[Symbol.asyncIterator]=async function*(){for(let E of a)try{yield await _e(f,E,o)}catch{}},c}return _e(this.#a,s,o)}};async function _e(e,t,r){let o=e.get(t)||e.set(t,{}).get(t);if(o[r]===void 0){let n=await we({...t,ext:!0},r);if(n instanceof Uint8Array||n.type!=="public")throw new D("JSON Web Key Set members must be public keys");o[r]=n}return o[r]}function j(e){let t=new Z(e),r=async(o,n)=>t.getKey(o,n);return Object.defineProperties(r,{jwks:{value:()=>structuredClone(t.jwks()),enumerable:!1,configurable:!1,writable:!1}}),r}function tt(){return typeof WebSocketPair<"u"||typeof navigator<"u"&&navigator.userAgent==="Cloudflare-Workers"||typeof EdgeRuntime<"u"&&EdgeRuntime==="vercel"}var ee;(typeof navigator>"u"||!navigator.userAgent?.startsWith?.("Mozilla/5.0 "))&&(ee="jose/v6.2.3");var Pe=Symbol();async function rt(e,t,r,o=fetch){let n=await o(e,{method:"GET",signal:r,redirect:"manual",headers:t}).catch(i=>{throw i.name==="TimeoutError"?new H:i});if(n.status!==200)throw new l("Expected 200 OK from the JSON Web Key Set HTTP response");try{return await n.json()}catch{throw new l("Failed to parse the JSON Web Key Set HTTP response as JSON")}}var k=Symbol();function ot(e,t){return!(typeof e!="object"||e===null||!("uat"in e)||typeof e.uat!="number"||Date.now()-e.uat>=t||!("jwks"in e)||!h(e.jwks)||!Array.isArray(e.jwks.keys)||!Array.prototype.every.call(e.jwks.keys,h))}var te=class{#r;#a;#c;#s;#o;#e;#t;#f;#n;#i;constructor(t,r){if(!(t instanceof URL))throw new TypeError("url must be an instance of URL");this.#r=new URL(t.href),this.#a=typeof r?.timeoutDuration=="number"?r?.timeoutDuration:5e3,this.#c=typeof r?.cooldownDuration=="number"?r?.cooldownDuration:3e4,this.#s=typeof r?.cacheMaxAge=="number"?r?.cacheMaxAge:6e5,this.#t=new Headers(r?.headers),ee&&!this.#t.has("User-Agent")&&this.#t.set("User-Agent",ee),this.#t.has("accept")||(this.#t.set("accept","application/json"),this.#t.append("accept","application/jwk-set+json")),this.#f=r?.[Pe],r?.[k]!==void 0&&(this.#i=r?.[k],ot(r?.[k],this.#s)&&(this.#o=this.#i.uat,this.#n=j(this.#i.jwks)))}pendingFetch(){return!!this.#e}coolingDown(){return typeof this.#o=="number"?Date.now()<this.#o+this.#c:!1}fresh(){return typeof this.#o=="number"?Date.now()<this.#o+this.#s:!1}jwks(){return this.#n?.jwks()}async getKey(t,r){(!this.#n||!this.fresh())&&await this.reload();try{return await this.#n(t,r)}catch(o){if(o instanceof x&&this.coolingDown()===!1)return await this.reload(),this.#n(t,r);throw o}}async reload(){this.#e&&tt()&&(this.#e=void 0),this.#e||=rt(this.#r.href,this.#t,AbortSignal.timeout(this.#a),this.#f).then(t=>{this.#n=j(t),this.#i&&(this.#i.uat=Date.now(),this.#i.jwks=t),this.#o=Date.now(),this.#e=void 0}).catch(t=>{throw this.#e=void 0,t}),await this.#e}};function re(e,t){let r=new te(e,t),o=async(n,i)=>r.getKey(n,i);return Object.defineProperties(o,{coolingDown:{get:()=>r.coolingDown(),enumerable:!0,configurable:!1},fresh:{get:()=>r.fresh(),enumerable:!0,configurable:!1},reload:{value:()=>r.reload(),enumerable:!0,configurable:!1,writable:!1},reloading:{get:()=>r.pendingFetch(),enumerable:!0,configurable:!1},jwks:{value:()=>r.jwks(),enumerable:!0,configurable:!1,writable:!1}}),o}function Ie(e,t){let r=e.trim().toLowerCase();if(t.requireEmailDomain&&r.slice(r.lastIndexOf("@")+1)!==t.requireEmailDomain.trim().toLowerCase())throw new y("forbidden","email not allowed for this app");if(t.allowedEmails&&t.allowedEmails.length>0&&!t.allowedEmails.some(n=>n.trim().toLowerCase()===r))throw new y("forbidden","email not allowed for this app")}var oe={issuer:"https://keycloak.ops.conveo.ai/realms/conveo",userHeader:"authorization",clockToleranceSec:30};function nt(e={}){let t=(e.issuer??oe.issuer).replace(/\/+$/,"");return{issuer:t,audience:e.audience,jwksUrl:e.jwksUrl??`${t}/protocol/openid-connect/certs`,userHeader:(e.userHeader??oe.userHeader).toLowerCase(),requireEmailDomain:e.requireEmailDomain,allowedEmails:e.allowedEmails,clockToleranceSec:e.clockToleranceSec??oe.clockToleranceSec}}function it(e,t){let r=e.get(t);return r?t==="authorization"?/^Bearer /i.test(r)&&r.slice(7).trim()||null:r.trim()||null:null}function ne(e={},t={}){let r=nt(e),o=t.getKey??re(new URL(r.jwksUrl));async function n(a){let s;try{({payload:s}=await Q(a,o,{issuer:r.issuer,...r.audience?{audience:r.audience}:{},algorithms:["RS256"],clockTolerance:r.clockToleranceSec}))}catch{throw new y("unauthenticated","invalid or expired token")}if(typeof s.sub!="string"||s.sub.length===0)throw new y("unauthenticated","token has no subject");if(s.email_verified!==!0||typeof s.email!="string"||!s.email)throw new y("unauthenticated","token has no verified email");return Ie(s.email,r),{sub:s.sub,email:s.email.toLowerCase(),emailVerified:!0,token:a,claims:s}}async function i(a){let s=it(a.headers,r.userHeader);if(!s)throw new y("unauthenticated",`no token in "${r.userHeader}" header`);return n(s)}return{config:r,verifyToken:n,verifyRequest:i}}function ie(e,t={}){return{issuer:t.issuer??e.AGENTGATEWAY_ISSUER,audience:t.audience??e.AGENTGATEWAY_AUDIENCE,jwksUrl:t.jwksUrl??e.AGENTGATEWAY_JWKS_URL,userHeader:t.userHeader??e.AGENTGATEWAY_USER_HEADER,requireEmailDomain:t.requireEmailDomain??e.AGENTGATEWAY_REQUIRE_EMAIL_DOMAIN,allowedEmails:t.allowedEmails??at(e.AGENTGATEWAY_ALLOWED_EMAILS),clockToleranceSec:t.clockToleranceSec}}function at(e){if(!e)return;let t=e.split(",").map(r=>r.trim()).filter(Boolean);return t.length>0?t:void 0}function De(){let e=globalThis.Deno,t=r=>{try{return e?.env.get(r)}catch{return}};return{AGENTGATEWAY_ISSUER:t("AGENTGATEWAY_ISSUER"),AGENTGATEWAY_AUDIENCE:t("AGENTGATEWAY_AUDIENCE"),AGENTGATEWAY_JWKS_URL:t("AGENTGATEWAY_JWKS_URL"),AGENTGATEWAY_USER_HEADER:t("AGENTGATEWAY_USER_HEADER"),AGENTGATEWAY_REQUIRE_EMAIL_DOMAIN:t("AGENTGATEWAY_REQUIRE_EMAIL_DOMAIN"),AGENTGATEWAY_ALLOWED_EMAILS:t("AGENTGATEWAY_ALLOWED_EMAILS")}}var Je;function st(e){return e?ne(ie(De(),e)):(Je??=ne(ie(De())),Je)}async function Or(e,t){try{return await st(t).verifyRequest(e)}catch(r){if(r instanceof y){let o=new Headers({"content-type":"application/json"});throw r.code==="unauthenticated"&&o.set("www-authenticate",'Bearer error="invalid_token"'),new Response(JSON.stringify({error:r.code}),{status:r.status,headers:o})}throw r}}export{Or as requireGatewayUser};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import{execFile as T}from"node:child_process";import{createHash as $,randomBytes as w}from"node:crypto";import{existsSync as v,mkdirSync as k,readFileSync as g,writeFileSync as p}from"node:fs";import{createServer as O}from"node:http";import{homedir as _}from"node:os";import{join as u}from"node:path";const d=(process.env.AGENTGATEWAY_ISSUER??"https://keycloak.ops.conveo.ai/realms/conveo").replace(/\/+$/,""),h=Number(process.env.AGENTGATEWAY_DEV_TOKEN_PORT??8765),f=`http://localhost:${h}/callback`,E=u(_(),".config","agentgateway","dev-clients.json"),A=300*1e3;function m(e){return e.toString("base64url")}async function N(){const e=await fetch(`${d}/.well-known/openid-configuration`);if(!e.ok)throw new Error(`OIDC discovery failed: ${e.status} for ${d}`);return await e.json()}async function b(e){let t={};try{t=JSON.parse(g(E,"utf8"))}catch{}const o=t[d];if(o)return o;const n=await fetch(e.registration_endpoint,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({client_name:`agentgateway-dev-${process.env.USER??"unknown"}`,redirect_uris:[f],grant_types:["authorization_code"],response_types:["code"],token_endpoint_auth_method:"none"})});if(!n.ok)throw new Error(`client registration failed: ${n.status} ${await n.text()}`);const{client_id:r}=await n.json();return k(u(_(),".config","agentgateway"),{recursive:!0}),p(E,JSON.stringify({...t,[d]:r},null,2)),r}function x(e){return new Promise((t,o)=>{const n=O((i,c)=>{const a=new URL(i.url??"/",`http://localhost:${h}`);if(a.pathname!=="/callback"){c.writeHead(404).end();return}const s=a.searchParams.get("code"),l=a.searchParams.get("state");if(!s||l!==e){c.writeHead(400,{"content-type":"text/html; charset=utf-8"}),c.end("<p>Invalid callback \u2014 check the terminal and retry.</p>");return}c.writeHead(200,{"content-type":"text/html; charset=utf-8"}),c.end("<p>Signed in \u2014 you can close this tab and return to the terminal.</p>"),n.close(),clearTimeout(r),t(s)}),r=setTimeout(()=>{n.close(),o(new Error("timed out waiting for the browser sign-in (5 min)"))},A);n.listen(h,"127.0.0.1")})}function R(e){const t=u(process.cwd(),".env.local"),o=`AGENTGATEWAY_DEV_TOKEN=${e}`;if(!v(t))return p(t,`${o}
3
+ `),t;const n=g(t,"utf8"),r=n.match(/^AGENTGATEWAY_DEV_TOKEN=.*$/m)?n.replace(/^AGENTGATEWAY_DEV_TOKEN=.*$/m,o):`${n.replace(/\n*$/,`
4
+ `)}${o}
5
+ `;return p(t,r),t}async function I(){console.log(`Issuer: ${d}`);const e=await N(),t=await b(e),o=m(w(32)),n=m($("sha256").update(o).digest()),r=m(w(16)),i=new URL(e.authorization_endpoint);i.search=new URLSearchParams({client_id:t,response_type:"code",redirect_uri:f,scope:"openid email profile",state:r,code_challenge:n,code_challenge_method:"S256"}).toString(),console.log(`
6
+ Opening your browser for Okta SSO\u2026 if nothing opens, visit:
7
+ `),console.log(` ${i}
8
+ `),process.platform==="darwin"&&T("open",[i.toString()]);const c=await x(r),a=await fetch(e.token_endpoint,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:t,code:c,redirect_uri:f,code_verifier:o})});if(!a.ok)throw new Error(`token exchange failed: ${a.status} ${await a.text()}`);const{access_token:s}=await a.json(),l=JSON.parse(Buffer.from(s.split(".")[1]??"","base64url").toString()),y=R(s),S=l.exp?new Date(l.exp*1e3).toLocaleString():"unknown";console.log(`
9
+ Token for ${l.email??"?"} written to ${y}`),console.log(`Expires: ${S} \u2014 re-run this command to refresh.`)}I().catch(e=>{console.error(`
10
+ Failed: ${e instanceof Error?e.message:String(e)}`),process.exit(1)});
@@ -0,0 +1,31 @@
1
+ import { type GatewayUser } from "./core/types.js";
2
+ /**
3
+ * The verified user for the current request. Re-verifies the forwarded token
4
+ * (cheap — JWKS is cached) rather than trusting headers, so a route that the
5
+ * matcher accidentally misses cannot be fed forged identity headers.
6
+ *
7
+ * Throws `GatewayAuthError` when there is no authenticated user (public paths,
8
+ * `authMode: "optional"`); catch it where anonymous access is expected.
9
+ */
10
+ export declare function getGatewayUser(): Promise<GatewayUser>;
11
+ declare const DEFAULT_BASES: {
12
+ readonly api: "https://gateway.ops.conveo.ai";
13
+ readonly mcp: "https://mcp.ops.conveo.ai";
14
+ };
15
+ export interface GatewayFetchOptions extends RequestInit {
16
+ /** Call on behalf of this user (their token becomes the bearer). */
17
+ user?: GatewayUser;
18
+ /** `user` (default when `user` is set) or `app` (the app's own VERCEL_OIDC_TOKEN). */
19
+ identity?: "user" | "app";
20
+ /** Which gateway host to target. Default `api` (gateway.ops); `mcp` for MCP/LLM routes. */
21
+ base?: keyof typeof DEFAULT_BASES;
22
+ }
23
+ /**
24
+ * Fetch a gateway route with the right credential:
25
+ *
26
+ * ```ts
27
+ * const res = await gatewayFetch("/postgrest/Organization?select=id,name&limit=3", { user });
28
+ * ```
29
+ */
30
+ export declare function gatewayFetch(path: string, options?: GatewayFetchOptions): Promise<Response>;
31
+ export { GatewayAuthError, type GatewayUser } from "./core/types.js";
package/dist/server.js ADDED
@@ -0,0 +1 @@
1
+ import{headers as E}from"next/headers";import{configFromEnv as d,isDevelopment as f}from"./adapters/env.js";import{GatewayAuthError as h,IDENTITY_HEADERS as m}from"./core/types.js";import{createVerifier as l}from"./core/verify.js";let w;function v(){return w??=l(d(process.env)),w}async function k(){const e=(await E()).get(m.token);if(!e){const t=process.env.AGENTGATEWAY_DEV_MOCK_USER;if(t&&f(process.env)){const r=t.toLowerCase();return{sub:"dev-mock-user",email:r,emailVerified:!0,token:"",claims:{sub:"dev-mock-user",email:r}}}throw new h("unauthenticated","no gateway user on this request \u2014 public path, optional auth, or middleware not applied")}return v().verifyToken(e)}const y={api:"https://gateway.ops.conveo.ai",mcp:"https://mcp.ops.conveo.ai"};async function G(s,e={}){const{user:t,identity:r=e.user?"user":"app",base:c="api",...u}=e,n=process.env,o=(c==="mcp"?n.AGENTGATEWAY_MCP_URL:n.AGENTGATEWAY_API_URL)??y[c],a=new URL(s,o.endsWith("/")?o:`${o}/`);if(a.origin!==new URL(o).origin)throw new Error(`gatewayFetch path must stay on the gateway origin (resolved to ${a.origin})`);const i=new Headers(u.headers);if(r==="user"){if(!t?.token)throw new h("unauthenticated",'gatewayFetch with identity "user" needs a verified user with a token (mock users have none)');i.set("authorization",`Bearer ${t.token}`)}else{const p=n.VERCEL_OIDC_TOKEN;if(!p)throw new Error("VERCEL_OIDC_TOKEN is not available \u2014 enable OIDC federation on the Vercel project (or `vercel env pull` for local dev)");i.set("authorization",`Bearer ${p}`)}return fetch(a,{...u,headers:i})}import{GatewayAuthError as L}from"./core/types.js";export{L as GatewayAuthError,G as gatewayFetch,k as getGatewayUser};
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@conveo/conveo-sdk",
3
+ "version": "0.3.0",
4
+ "description": "SDK for Conveo apps behind the agent gateway: drop-in auth middleware (Next.js / Vercel / Supabase), server helpers, and a dev-token CLI.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/conveo/conveo-sdk.git"
10
+ },
11
+ "publishConfig": {
12
+ "registry": "https://registry.npmjs.org",
13
+ "access": "public"
14
+ },
15
+ "packageManager": "pnpm@11.5.3",
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/core/index.d.ts",
25
+ "default": "./dist/core/index.js"
26
+ },
27
+ "./next": {
28
+ "types": "./dist/adapters/next.d.ts",
29
+ "default": "./dist/adapters/next.js"
30
+ },
31
+ "./vercel": {
32
+ "types": "./dist/adapters/vercel.d.ts",
33
+ "default": "./dist/adapters/vercel.js"
34
+ },
35
+ "./supabase": {
36
+ "types": "./dist/adapters/supabase.d.ts",
37
+ "default": "./dist/adapters/supabase.js"
38
+ },
39
+ "./server": {
40
+ "types": "./dist/server.d.ts",
41
+ "default": "./dist/server.js"
42
+ }
43
+ },
44
+ "bin": {
45
+ "agentgateway-dev-token": "./dist/dev/token.js"
46
+ },
47
+ "scripts": {
48
+ "build": "tsc -p tsconfig.build.json && pnpm minify && pnpm build:deno",
49
+ "minify": "node scripts/minify.mjs",
50
+ "build:deno": "esbuild src/adapters/supabase.ts --bundle --minify --format=esm --platform=browser --target=es2022 --outfile=dist/deno/agentgateway-supabase.bundle.js",
51
+ "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
52
+ "test": "vitest run",
53
+ "typecheck": "tsc -p tsconfig.json --noEmit"
54
+ },
55
+ "dependencies": {
56
+ "jose": "^6.2.3"
57
+ },
58
+ "peerDependencies": {
59
+ "@vercel/functions": ">=2",
60
+ "next": ">=14"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@vercel/functions": {
64
+ "optional": true
65
+ },
66
+ "next": {
67
+ "optional": true
68
+ }
69
+ },
70
+ "devDependencies": {
71
+ "@types/node": "^24.0.0",
72
+ "@vercel/functions": "^3.7.1",
73
+ "esbuild": "^0.28.1",
74
+ "next": "^16.2.9",
75
+ "typescript": "^5.9.3",
76
+ "vitest": "^4.1.8"
77
+ }
78
+ }