@supabase/server 1.0.1-rc.57 → 1.1.0-rc.58
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 +11 -10
- package/dist/adapters/h3/index.cjs +1 -1
- package/dist/adapters/h3/index.d.cts +1 -1
- package/dist/adapters/h3/index.d.mts +1 -1
- package/dist/adapters/h3/index.mjs +1 -1
- package/dist/adapters/hono/index.cjs +1 -1
- package/dist/adapters/hono/index.d.cts +1 -1
- package/dist/adapters/hono/index.d.mts +1 -1
- package/dist/adapters/hono/index.mjs +1 -1
- package/dist/core/index.cjs +1 -1
- package/dist/core/index.d.cts +4 -3
- package/dist/core/index.d.mts +4 -3
- package/dist/core/index.mjs +1 -1
- package/dist/{create-supabase-context-BBZtr3D2.mjs → create-supabase-context-BeZJlUBy.mjs} +1 -1
- package/dist/{create-supabase-context-B-2NDJhL.cjs → create-supabase-context-Bmuv6M-5.cjs} +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{types-B2yXZjmG.d.mts → types-Bjy1j07i.d.cts} +14 -3
- package/dist/{types-u7fYLtzC.d.cts → types-DP9l5Cvf.d.mts} +14 -3
- package/dist/{verify-auth-CZQd36s0.mjs → verify-auth-CZ3mB47e.mjs} +83 -8
- package/dist/{verify-auth-BKZK83Y8.cjs → verify-auth-z-iM98dR.cjs} +82 -7
- package/docs/environment-variables.md +36 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -400,19 +400,20 @@ export default {
|
|
|
400
400
|
|
|
401
401
|
Automatically available in Supabase Edge Functions:
|
|
402
402
|
|
|
403
|
-
| Variable | Format | Description
|
|
404
|
-
| --------------------------- | ------------------------------------------------------------- |
|
|
405
|
-
| `SUPABASE_URL` | `https://<ref>.supabase.co` | Your project URL
|
|
406
|
-
| `SUPABASE_PUBLISHABLE_KEYS` | `{"default":"sb_publishable_...","web":"sb_publishable_..."}` | Publishable API keys (named)
|
|
407
|
-
| `SUPABASE_SECRET_KEYS` | `{"default":"sb_secret_...","web":"sb_secret_..."}` | Secret API keys (named)
|
|
408
|
-
| `SUPABASE_JWKS` | `{"keys":[...]}` or `[...]` | JSON Web Key Set for JWT verification |
|
|
403
|
+
| Variable | Format | Description |
|
|
404
|
+
| --------------------------- | ------------------------------------------------------------- | -------------------------------------------- |
|
|
405
|
+
| `SUPABASE_URL` | `https://<ref>.supabase.co` | Your project URL |
|
|
406
|
+
| `SUPABASE_PUBLISHABLE_KEYS` | `{"default":"sb_publishable_...","web":"sb_publishable_..."}` | Publishable API keys (named) |
|
|
407
|
+
| `SUPABASE_SECRET_KEYS` | `{"default":"sb_secret_...","web":"sb_secret_..."}` | Secret API keys (named) |
|
|
408
|
+
| `SUPABASE_JWKS` | `{"keys":[...]}` or `[...]` | Inline JSON Web Key Set for JWT verification |
|
|
409
409
|
|
|
410
410
|
Also supported (for local dev, self-hosted, or other runtimes):
|
|
411
411
|
|
|
412
|
-
| Variable | Format | Description
|
|
413
|
-
| -------------------------- | -------------------- |
|
|
414
|
-
| `SUPABASE_PUBLISHABLE_KEY` | `sb_publishable_...` | Single publishable key
|
|
415
|
-
| `SUPABASE_SECRET_KEY` | `sb_secret_...` | Single secret key
|
|
412
|
+
| Variable | Format | Description |
|
|
413
|
+
| -------------------------- | -------------------- | --------------------------------------------------------- |
|
|
414
|
+
| `SUPABASE_PUBLISHABLE_KEY` | `sb_publishable_...` | Single publishable key |
|
|
415
|
+
| `SUPABASE_SECRET_KEY` | `sb_secret_...` | Single secret key |
|
|
416
|
+
| `SUPABASE_JWKS_URL` | `https://...` | Remote JWKS endpoint (used when `SUPABASE_JWKS` is unset) |
|
|
416
417
|
|
|
417
418
|
When both singular and plural forms are set, plural takes priority.
|
|
418
419
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_create_supabase_context = require('../../create-supabase-context-
|
|
2
|
+
const require_create_supabase_context = require('../../create-supabase-context-Bmuv6M-5.cjs');
|
|
3
3
|
let h3 = require("h3");
|
|
4
4
|
|
|
5
5
|
//#region src/adapters/h3/middleware.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createSupabaseContext } from "../../create-supabase-context-
|
|
1
|
+
import { t as createSupabaseContext } from "../../create-supabase-context-BeZJlUBy.mjs";
|
|
2
2
|
import { HTTPError, defineMiddleware } from "h3";
|
|
3
3
|
|
|
4
4
|
//#region src/adapters/h3/middleware.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_create_supabase_context = require('../../create-supabase-context-
|
|
2
|
+
const require_create_supabase_context = require('../../create-supabase-context-Bmuv6M-5.cjs');
|
|
3
3
|
let hono_http_exception = require("hono/http-exception");
|
|
4
4
|
let hono_factory = require("hono/factory");
|
|
5
5
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as SupabaseContext, m as WithSupabaseConfig } from "../../types-
|
|
1
|
+
import { d as SupabaseContext, m as WithSupabaseConfig } from "../../types-Bjy1j07i.cjs";
|
|
2
2
|
import { MiddlewareHandler } from "hono";
|
|
3
3
|
|
|
4
4
|
//#region src/adapters/hono/middleware.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as SupabaseContext, m as WithSupabaseConfig } from "../../types-
|
|
1
|
+
import { d as SupabaseContext, m as WithSupabaseConfig } from "../../types-DP9l5Cvf.mjs";
|
|
2
2
|
import { MiddlewareHandler } from "hono";
|
|
3
3
|
|
|
4
4
|
//#region src/adapters/hono/middleware.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createSupabaseContext } from "../../create-supabase-context-
|
|
1
|
+
import { t as createSupabaseContext } from "../../create-supabase-context-BeZJlUBy.mjs";
|
|
2
2
|
import { HTTPException } from "hono/http-exception";
|
|
3
3
|
import { createMiddleware } from "hono/factory";
|
|
4
4
|
|
package/dist/core/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_verify_auth = require('../verify-auth-
|
|
2
|
+
const require_verify_auth = require('../verify-auth-z-iM98dR.cjs');
|
|
3
3
|
|
|
4
4
|
exports.createAdminClient = require_verify_auth.createAdminClient;
|
|
5
5
|
exports.createContextClient = require_verify_auth.createContextClient;
|
package/dist/core/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AuthResult, c as CreateContextClientOptions, f as SupabaseEnv, i as AuthModeWithKey, l as Credentials, o as ClientAuth, s as CreateAdminClientOptions } from "../types-
|
|
1
|
+
import { a as AuthResult, c as CreateContextClientOptions, f as SupabaseEnv, i as AuthModeWithKey, l as Credentials, o as ClientAuth, s as CreateAdminClientOptions } from "../types-Bjy1j07i.cjs";
|
|
2
2
|
import { i as EnvError, t as AuthError } from "../errors-CZFEYnV_.cjs";
|
|
3
3
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
4
4
|
|
|
@@ -7,8 +7,9 @@ import { SupabaseClient } from "@supabase/supabase-js";
|
|
|
7
7
|
* Resolves Supabase environment configuration from runtime environment variables.
|
|
8
8
|
*
|
|
9
9
|
* Reads `SUPABASE_URL`, keys (`SUPABASE_PUBLISHABLE_KEYS` / `SUPABASE_SECRET_KEYS`),
|
|
10
|
-
* and `SUPABASE_JWKS
|
|
11
|
-
*
|
|
10
|
+
* and the JWKS source (`SUPABASE_JWKS` for inline keys, or `SUPABASE_JWKS_URL`
|
|
11
|
+
* for a remote endpoint). Works across Deno, Node.js, and Bun. For Cloudflare
|
|
12
|
+
* Workers, use `overrides` or enable node-compat.
|
|
12
13
|
*
|
|
13
14
|
* @param overrides - Partial values that take precedence over env vars.
|
|
14
15
|
* @returns `{ data: SupabaseEnv, error: null }` on success, `{ data: null, error: EnvError }` on failure.
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AuthResult, c as CreateContextClientOptions, f as SupabaseEnv, i as AuthModeWithKey, l as Credentials, o as ClientAuth, s as CreateAdminClientOptions } from "../types-
|
|
1
|
+
import { a as AuthResult, c as CreateContextClientOptions, f as SupabaseEnv, i as AuthModeWithKey, l as Credentials, o as ClientAuth, s as CreateAdminClientOptions } from "../types-DP9l5Cvf.mjs";
|
|
2
2
|
import { i as EnvError, t as AuthError } from "../errors-0dbzn5gA.mjs";
|
|
3
3
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
4
4
|
|
|
@@ -7,8 +7,9 @@ import { SupabaseClient } from "@supabase/supabase-js";
|
|
|
7
7
|
* Resolves Supabase environment configuration from runtime environment variables.
|
|
8
8
|
*
|
|
9
9
|
* Reads `SUPABASE_URL`, keys (`SUPABASE_PUBLISHABLE_KEYS` / `SUPABASE_SECRET_KEYS`),
|
|
10
|
-
* and `SUPABASE_JWKS
|
|
11
|
-
*
|
|
10
|
+
* and the JWKS source (`SUPABASE_JWKS` for inline keys, or `SUPABASE_JWKS_URL`
|
|
11
|
+
* for a remote endpoint). Works across Deno, Node.js, and Bun. For Cloudflare
|
|
12
|
+
* Workers, use `overrides` or enable node-compat.
|
|
12
13
|
*
|
|
13
14
|
* @param overrides - Partial values that take precedence over env vars.
|
|
14
15
|
* @returns `{ data: SupabaseEnv, error: null }` on success, `{ data: null, error: EnvError }` on failure.
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as createAdminClient, i as createContextClient, n as verifyCredentials, o as resolveEnv, r as extractCredentials, t as verifyAuth } from "../verify-auth-
|
|
1
|
+
import { a as createAdminClient, i as createContextClient, n as verifyCredentials, o as resolveEnv, r as extractCredentials, t as verifyAuth } from "../verify-auth-CZ3mB47e.mjs";
|
|
2
2
|
|
|
3
3
|
export { createAdminClient, createContextClient, extractCredentials, resolveEnv, verifyAuth, verifyCredentials };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as createAdminClient, f as Errors, i as createContextClient, l as CreateSupabaseClientError, s as AuthError, t as verifyAuth, u as EnvError } from "./verify-auth-
|
|
1
|
+
import { a as createAdminClient, f as Errors, i as createContextClient, l as CreateSupabaseClientError, s as AuthError, t as verifyAuth, u as EnvError } from "./verify-auth-CZ3mB47e.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/create-supabase-context.ts
|
|
4
4
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_verify_auth = require('./verify-auth-
|
|
3
|
-
const require_create_supabase_context = require('./create-supabase-context-
|
|
2
|
+
const require_verify_auth = require('./verify-auth-z-iM98dR.cjs');
|
|
3
|
+
const require_create_supabase_context = require('./create-supabase-context-Bmuv6M-5.cjs');
|
|
4
4
|
let _supabase_supabase_js_cors = require("@supabase/supabase-js/cors");
|
|
5
5
|
|
|
6
6
|
//#region src/cors.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AuthResult, c as CreateContextClientOptions, d as SupabaseContext, f as SupabaseEnv, i as AuthModeWithKey, l as Credentials, m as WithSupabaseConfig, n as AllowWithKey, o as ClientAuth, p as UserClaims, r as AuthMode, s as CreateAdminClientOptions, t as Allow, u as JWTClaims } from "./types-
|
|
1
|
+
import { a as AuthResult, c as CreateContextClientOptions, d as SupabaseContext, f as SupabaseEnv, i as AuthModeWithKey, l as Credentials, m as WithSupabaseConfig, n as AllowWithKey, o as ClientAuth, p as UserClaims, r as AuthMode, s as CreateAdminClientOptions, t as Allow, u as JWTClaims } from "./types-Bjy1j07i.cjs";
|
|
2
2
|
import { a as EnvGenericError, c as MissingDefaultPublishableKeyError, d as MissingSecretKeyError, f as MissingSupabaseURLError, i as EnvError, l as MissingDefaultSecretKeyError, n as AuthGenericError, o as Errors, r as CreateSupabaseClientError, s as InvalidCredentialsError, t as AuthError, u as MissingPublishableKeyError } from "./errors-CZFEYnV_.cjs";
|
|
3
3
|
|
|
4
4
|
//#region src/with-supabase.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AuthResult, c as CreateContextClientOptions, d as SupabaseContext, f as SupabaseEnv, i as AuthModeWithKey, l as Credentials, m as WithSupabaseConfig, n as AllowWithKey, o as ClientAuth, p as UserClaims, r as AuthMode, s as CreateAdminClientOptions, t as Allow, u as JWTClaims } from "./types-
|
|
1
|
+
import { a as AuthResult, c as CreateContextClientOptions, d as SupabaseContext, f as SupabaseEnv, i as AuthModeWithKey, l as Credentials, m as WithSupabaseConfig, n as AllowWithKey, o as ClientAuth, p as UserClaims, r as AuthMode, s as CreateAdminClientOptions, t as Allow, u as JWTClaims } from "./types-DP9l5Cvf.mjs";
|
|
2
2
|
import { a as EnvGenericError, c as MissingDefaultPublishableKeyError, d as MissingSecretKeyError, f as MissingSupabaseURLError, i as EnvError, l as MissingDefaultSecretKeyError, n as AuthGenericError, o as Errors, r as CreateSupabaseClientError, s as InvalidCredentialsError, t as AuthError, u as MissingPublishableKeyError } from "./errors-0dbzn5gA.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/with-supabase.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as MissingSecretKeyError, c as AuthGenericError, d as EnvGenericError, f as Errors, g as MissingPublishableKeyError, h as MissingDefaultSecretKeyError, l as CreateSupabaseClientError, m as MissingDefaultPublishableKeyError, p as InvalidCredentialsError, s as AuthError, u as EnvError, v as MissingSupabaseURLError } from "./verify-auth-
|
|
2
|
-
import { t as createSupabaseContext } from "./create-supabase-context-
|
|
1
|
+
import { _ as MissingSecretKeyError, c as AuthGenericError, d as EnvGenericError, f as Errors, g as MissingPublishableKeyError, h as MissingDefaultSecretKeyError, l as CreateSupabaseClientError, m as MissingDefaultPublishableKeyError, p as InvalidCredentialsError, s as AuthError, u as EnvError, v as MissingSupabaseURLError } from "./verify-auth-CZ3mB47e.mjs";
|
|
2
|
+
import { t as createSupabaseContext } from "./create-supabase-context-BeZJlUBy.mjs";
|
|
3
3
|
import { corsHeaders } from "@supabase/supabase-js/cors";
|
|
4
4
|
|
|
5
5
|
//#region src/cors.ts
|
|
@@ -73,11 +73,22 @@ interface SupabaseEnv {
|
|
|
73
73
|
*/
|
|
74
74
|
secretKeys: Record<string, string>;
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
76
|
+
* JWKS source used for JWT verification.
|
|
77
|
+
*
|
|
78
|
+
* Sourced from one of (in priority order):
|
|
79
|
+
* - `SUPABASE_JWKS` — inline JSON. Resolves to a {@link JsonWebKeySet}.
|
|
80
|
+
* - `SUPABASE_JWKS_URL` — remote endpoint. Resolves to a {@link URL}; keys
|
|
81
|
+
* are fetched lazily and cached in memory (cooldown / max-age handled by
|
|
82
|
+
* `jose`). `https://` is always accepted; plain `http://` is accepted
|
|
83
|
+
* only for loopback hosts (`localhost`, `127.0.0.0/8`, `::1`) to support
|
|
84
|
+
* the Supabase CLI. Any other `http://` URL is rejected to prevent MITM
|
|
85
|
+
* swap-in of a forged signing key.
|
|
86
|
+
*
|
|
78
87
|
* `null` when no JWKS is configured (JWT verification will be unavailable).
|
|
88
|
+
* Each env var is authoritative when set: a malformed value resolves to
|
|
89
|
+
* `null` rather than falling through to the other variable.
|
|
79
90
|
*/
|
|
80
|
-
jwks: JsonWebKeySet | null;
|
|
91
|
+
jwks: JsonWebKeySet | URL | null;
|
|
81
92
|
}
|
|
82
93
|
/**
|
|
83
94
|
* A JSON Web Key Set as defined by RFC 7517.
|
|
@@ -73,11 +73,22 @@ interface SupabaseEnv {
|
|
|
73
73
|
*/
|
|
74
74
|
secretKeys: Record<string, string>;
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
76
|
+
* JWKS source used for JWT verification.
|
|
77
|
+
*
|
|
78
|
+
* Sourced from one of (in priority order):
|
|
79
|
+
* - `SUPABASE_JWKS` — inline JSON. Resolves to a {@link JsonWebKeySet}.
|
|
80
|
+
* - `SUPABASE_JWKS_URL` — remote endpoint. Resolves to a {@link URL}; keys
|
|
81
|
+
* are fetched lazily and cached in memory (cooldown / max-age handled by
|
|
82
|
+
* `jose`). `https://` is always accepted; plain `http://` is accepted
|
|
83
|
+
* only for loopback hosts (`localhost`, `127.0.0.0/8`, `::1`) to support
|
|
84
|
+
* the Supabase CLI. Any other `http://` URL is rejected to prevent MITM
|
|
85
|
+
* swap-in of a forged signing key.
|
|
86
|
+
*
|
|
78
87
|
* `null` when no JWKS is configured (JWT verification will be unavailable).
|
|
88
|
+
* Each env var is authoritative when set: a malformed value resolves to
|
|
89
|
+
* `null` rather than falling through to the other variable.
|
|
79
90
|
*/
|
|
80
|
-
jwks: JsonWebKeySet | null;
|
|
91
|
+
jwks: JsonWebKeySet | URL | null;
|
|
81
92
|
}
|
|
82
93
|
/**
|
|
83
94
|
* A JSON Web Key Set as defined by RFC 7517.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createClient } from "@supabase/supabase-js";
|
|
2
|
-
import { createLocalJWKSet, jwtVerify } from "jose";
|
|
2
|
+
import { createLocalJWKSet, createRemoteJWKSet, jwtVerify } from "jose";
|
|
3
3
|
|
|
4
4
|
//#region src/errors.ts
|
|
5
5
|
/**
|
|
@@ -141,9 +141,10 @@ function resolveKeys(singularVar, pluralVar) {
|
|
|
141
141
|
return {};
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
|
-
* Parses
|
|
145
|
-
*
|
|
146
|
-
*
|
|
144
|
+
* Parses an inline JWKS JSON string. Accepts `{ keys: [...] }` or a bare
|
|
145
|
+
* array `[...]` (wrapped as `{ keys: [...] }`). Returns `null` for missing
|
|
146
|
+
* or malformed input.
|
|
147
|
+
*
|
|
147
148
|
* @internal
|
|
148
149
|
*/
|
|
149
150
|
function parseJwks(raw) {
|
|
@@ -158,11 +159,63 @@ function parseJwks(raw) {
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
/**
|
|
162
|
+
* Returns true if the hostname is a loopback address — `localhost`,
|
|
163
|
+
* `*.localhost`, `127.0.0.0/8`, or `::1`. Browsers treat these as secure
|
|
164
|
+
* contexts because traffic never leaves the machine.
|
|
165
|
+
*
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
function isLoopbackHost(hostname) {
|
|
169
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
|
|
170
|
+
if (hostname === "[::1]") return true;
|
|
171
|
+
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) return true;
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Parses a JWKS endpoint URL. `https://` is always accepted. Plain `http://`
|
|
176
|
+
* is accepted only for loopback hosts (`localhost`, `127.0.0.0/8`, `::1`) so
|
|
177
|
+
* the Supabase CLI flow works against `http://localhost:54321`. For any
|
|
178
|
+
* other host, http is rejected: a MITM on the JWKS fetch could swap in an
|
|
179
|
+
* attacker-controlled key and forge JWTs that pass verification. Returns
|
|
180
|
+
* `null` for missing or malformed input.
|
|
181
|
+
*
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
function parseJwksUrl(raw) {
|
|
185
|
+
if (!raw) return null;
|
|
186
|
+
const trimmed = raw.trim();
|
|
187
|
+
try {
|
|
188
|
+
const url = new URL(trimmed);
|
|
189
|
+
if (url.protocol === "https:") return url;
|
|
190
|
+
if (url.protocol === "http:" && isLoopbackHost(url.hostname)) return url;
|
|
191
|
+
return null;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Resolves the JWKS source from `SUPABASE_JWKS` (inline JSON) or
|
|
198
|
+
* `SUPABASE_JWKS_URL` (https endpoint). `SUPABASE_JWKS` wins when set;
|
|
199
|
+
* `SUPABASE_JWKS_URL` is only consulted if `SUPABASE_JWKS` is absent. Each
|
|
200
|
+
* variable is treated as authoritative — if set but malformed, the result is
|
|
201
|
+
* `null` and the other variable is *not* consulted as a fallback.
|
|
202
|
+
*
|
|
203
|
+
* @internal
|
|
204
|
+
*/
|
|
205
|
+
function resolveJwks() {
|
|
206
|
+
const rawJwks = getEnvVar("SUPABASE_JWKS");
|
|
207
|
+
if (rawJwks && rawJwks.trim()) return parseJwks(rawJwks);
|
|
208
|
+
const rawJwksUrl = getEnvVar("SUPABASE_JWKS_URL");
|
|
209
|
+
if (rawJwksUrl && rawJwksUrl.trim()) return parseJwksUrl(rawJwksUrl);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
161
213
|
* Resolves Supabase environment configuration from runtime environment variables.
|
|
162
214
|
*
|
|
163
215
|
* Reads `SUPABASE_URL`, keys (`SUPABASE_PUBLISHABLE_KEYS` / `SUPABASE_SECRET_KEYS`),
|
|
164
|
-
* and `SUPABASE_JWKS
|
|
165
|
-
*
|
|
216
|
+
* and the JWKS source (`SUPABASE_JWKS` for inline keys, or `SUPABASE_JWKS_URL`
|
|
217
|
+
* for a remote endpoint). Works across Deno, Node.js, and Bun. For Cloudflare
|
|
218
|
+
* Workers, use `overrides` or enable node-compat.
|
|
166
219
|
*
|
|
167
220
|
* @param overrides - Partial values that take precedence over env vars.
|
|
168
221
|
* @returns `{ data: SupabaseEnv, error: null }` on success, `{ data: null, error: EnvError }` on failure.
|
|
@@ -187,7 +240,7 @@ function resolveEnv(overrides) {
|
|
|
187
240
|
url,
|
|
188
241
|
publishableKeys: overrides?.publishableKeys ?? resolveKeys("SUPABASE_PUBLISHABLE_KEY", "SUPABASE_PUBLISHABLE_KEYS"),
|
|
189
242
|
secretKeys: overrides?.secretKeys ?? resolveKeys("SUPABASE_SECRET_KEY", "SUPABASE_SECRET_KEYS"),
|
|
190
|
-
jwks: overrides?.jwks ??
|
|
243
|
+
jwks: overrides?.jwks ?? resolveJwks()
|
|
191
244
|
},
|
|
192
245
|
error: null
|
|
193
246
|
};
|
|
@@ -420,6 +473,28 @@ function jwtClaimsToUserClaims(jwtClaims) {
|
|
|
420
473
|
};
|
|
421
474
|
}
|
|
422
475
|
const INVALID = Symbol("invalid");
|
|
476
|
+
let remoteJwksResolver = void 0;
|
|
477
|
+
/**
|
|
478
|
+
* Returns a key resolver for the given JWKS source.
|
|
479
|
+
*
|
|
480
|
+
* For a {@link URL}, the underlying `createRemoteJWKSet` resolver is cached
|
|
481
|
+
* across requests so `jose`'s built-in cooldown / max-age caching is
|
|
482
|
+
* preserved. Local JWKS objects are wrapped on every call — they're trivially
|
|
483
|
+
* cheap and the object identity may change across requests.
|
|
484
|
+
*
|
|
485
|
+
* @internal
|
|
486
|
+
*/
|
|
487
|
+
function getJwksResolver(jwks) {
|
|
488
|
+
if (jwks instanceof URL) {
|
|
489
|
+
const url = jwks.toString();
|
|
490
|
+
if (remoteJwksResolver?.url !== url) remoteJwksResolver = {
|
|
491
|
+
url,
|
|
492
|
+
resolver: createRemoteJWKSet(jwks)
|
|
493
|
+
};
|
|
494
|
+
return remoteJwksResolver.resolver;
|
|
495
|
+
}
|
|
496
|
+
return createLocalJWKSet(jwks);
|
|
497
|
+
}
|
|
423
498
|
/**
|
|
424
499
|
* Attempts to authenticate credentials against a single auth mode.
|
|
425
500
|
*
|
|
@@ -492,7 +567,7 @@ async function tryMode(mode, credentials, env) {
|
|
|
492
567
|
if (!credentials.token) return null;
|
|
493
568
|
if (!env.jwks) return null;
|
|
494
569
|
try {
|
|
495
|
-
const jwkSet =
|
|
570
|
+
const jwkSet = getJwksResolver(env.jwks);
|
|
496
571
|
const { payload } = await jwtVerify(credentials.token, jwkSet);
|
|
497
572
|
if (typeof payload.sub !== "string") return INVALID;
|
|
498
573
|
const jwtClaims = payload;
|
|
@@ -141,9 +141,10 @@ function resolveKeys(singularVar, pluralVar) {
|
|
|
141
141
|
return {};
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
|
-
* Parses
|
|
145
|
-
*
|
|
146
|
-
*
|
|
144
|
+
* Parses an inline JWKS JSON string. Accepts `{ keys: [...] }` or a bare
|
|
145
|
+
* array `[...]` (wrapped as `{ keys: [...] }`). Returns `null` for missing
|
|
146
|
+
* or malformed input.
|
|
147
|
+
*
|
|
147
148
|
* @internal
|
|
148
149
|
*/
|
|
149
150
|
function parseJwks(raw) {
|
|
@@ -158,11 +159,63 @@ function parseJwks(raw) {
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
/**
|
|
162
|
+
* Returns true if the hostname is a loopback address — `localhost`,
|
|
163
|
+
* `*.localhost`, `127.0.0.0/8`, or `::1`. Browsers treat these as secure
|
|
164
|
+
* contexts because traffic never leaves the machine.
|
|
165
|
+
*
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
function isLoopbackHost(hostname) {
|
|
169
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
|
|
170
|
+
if (hostname === "[::1]") return true;
|
|
171
|
+
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) return true;
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Parses a JWKS endpoint URL. `https://` is always accepted. Plain `http://`
|
|
176
|
+
* is accepted only for loopback hosts (`localhost`, `127.0.0.0/8`, `::1`) so
|
|
177
|
+
* the Supabase CLI flow works against `http://localhost:54321`. For any
|
|
178
|
+
* other host, http is rejected: a MITM on the JWKS fetch could swap in an
|
|
179
|
+
* attacker-controlled key and forge JWTs that pass verification. Returns
|
|
180
|
+
* `null` for missing or malformed input.
|
|
181
|
+
*
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
function parseJwksUrl(raw) {
|
|
185
|
+
if (!raw) return null;
|
|
186
|
+
const trimmed = raw.trim();
|
|
187
|
+
try {
|
|
188
|
+
const url = new URL(trimmed);
|
|
189
|
+
if (url.protocol === "https:") return url;
|
|
190
|
+
if (url.protocol === "http:" && isLoopbackHost(url.hostname)) return url;
|
|
191
|
+
return null;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Resolves the JWKS source from `SUPABASE_JWKS` (inline JSON) or
|
|
198
|
+
* `SUPABASE_JWKS_URL` (https endpoint). `SUPABASE_JWKS` wins when set;
|
|
199
|
+
* `SUPABASE_JWKS_URL` is only consulted if `SUPABASE_JWKS` is absent. Each
|
|
200
|
+
* variable is treated as authoritative — if set but malformed, the result is
|
|
201
|
+
* `null` and the other variable is *not* consulted as a fallback.
|
|
202
|
+
*
|
|
203
|
+
* @internal
|
|
204
|
+
*/
|
|
205
|
+
function resolveJwks() {
|
|
206
|
+
const rawJwks = getEnvVar("SUPABASE_JWKS");
|
|
207
|
+
if (rawJwks && rawJwks.trim()) return parseJwks(rawJwks);
|
|
208
|
+
const rawJwksUrl = getEnvVar("SUPABASE_JWKS_URL");
|
|
209
|
+
if (rawJwksUrl && rawJwksUrl.trim()) return parseJwksUrl(rawJwksUrl);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
161
213
|
* Resolves Supabase environment configuration from runtime environment variables.
|
|
162
214
|
*
|
|
163
215
|
* Reads `SUPABASE_URL`, keys (`SUPABASE_PUBLISHABLE_KEYS` / `SUPABASE_SECRET_KEYS`),
|
|
164
|
-
* and `SUPABASE_JWKS
|
|
165
|
-
*
|
|
216
|
+
* and the JWKS source (`SUPABASE_JWKS` for inline keys, or `SUPABASE_JWKS_URL`
|
|
217
|
+
* for a remote endpoint). Works across Deno, Node.js, and Bun. For Cloudflare
|
|
218
|
+
* Workers, use `overrides` or enable node-compat.
|
|
166
219
|
*
|
|
167
220
|
* @param overrides - Partial values that take precedence over env vars.
|
|
168
221
|
* @returns `{ data: SupabaseEnv, error: null }` on success, `{ data: null, error: EnvError }` on failure.
|
|
@@ -187,7 +240,7 @@ function resolveEnv(overrides) {
|
|
|
187
240
|
url,
|
|
188
241
|
publishableKeys: overrides?.publishableKeys ?? resolveKeys("SUPABASE_PUBLISHABLE_KEY", "SUPABASE_PUBLISHABLE_KEYS"),
|
|
189
242
|
secretKeys: overrides?.secretKeys ?? resolveKeys("SUPABASE_SECRET_KEY", "SUPABASE_SECRET_KEYS"),
|
|
190
|
-
jwks: overrides?.jwks ??
|
|
243
|
+
jwks: overrides?.jwks ?? resolveJwks()
|
|
191
244
|
},
|
|
192
245
|
error: null
|
|
193
246
|
};
|
|
@@ -420,6 +473,28 @@ function jwtClaimsToUserClaims(jwtClaims) {
|
|
|
420
473
|
};
|
|
421
474
|
}
|
|
422
475
|
const INVALID = Symbol("invalid");
|
|
476
|
+
let remoteJwksResolver = void 0;
|
|
477
|
+
/**
|
|
478
|
+
* Returns a key resolver for the given JWKS source.
|
|
479
|
+
*
|
|
480
|
+
* For a {@link URL}, the underlying `createRemoteJWKSet` resolver is cached
|
|
481
|
+
* across requests so `jose`'s built-in cooldown / max-age caching is
|
|
482
|
+
* preserved. Local JWKS objects are wrapped on every call — they're trivially
|
|
483
|
+
* cheap and the object identity may change across requests.
|
|
484
|
+
*
|
|
485
|
+
* @internal
|
|
486
|
+
*/
|
|
487
|
+
function getJwksResolver(jwks) {
|
|
488
|
+
if (jwks instanceof URL) {
|
|
489
|
+
const url = jwks.toString();
|
|
490
|
+
if (remoteJwksResolver?.url !== url) remoteJwksResolver = {
|
|
491
|
+
url,
|
|
492
|
+
resolver: (0, jose.createRemoteJWKSet)(jwks)
|
|
493
|
+
};
|
|
494
|
+
return remoteJwksResolver.resolver;
|
|
495
|
+
}
|
|
496
|
+
return (0, jose.createLocalJWKSet)(jwks);
|
|
497
|
+
}
|
|
423
498
|
/**
|
|
424
499
|
* Attempts to authenticate credentials against a single auth mode.
|
|
425
500
|
*
|
|
@@ -492,7 +567,7 @@ async function tryMode(mode, credentials, env) {
|
|
|
492
567
|
if (!credentials.token) return null;
|
|
493
568
|
if (!env.jwks) return null;
|
|
494
569
|
try {
|
|
495
|
-
const jwkSet = (
|
|
570
|
+
const jwkSet = getJwksResolver(env.jwks);
|
|
496
571
|
const { payload } = await (0, jose.jwtVerify)(credentials.token, jwkSet);
|
|
497
572
|
if (typeof payload.sub !== "string") return INVALID;
|
|
498
573
|
const jwtClaims = payload;
|
|
@@ -2,25 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
On Supabase Platform and Local Development (CLI), all variables are auto-provisioned — no configuration needed
|
|
4
4
|
|
|
5
|
-
| Variable | Format | Description
|
|
6
|
-
| --------------------------- | ---------------------------------- |
|
|
7
|
-
| `SUPABASE_URL` | `https://<ref>.supabase.co` | Your Supabase project URL
|
|
8
|
-
| `SUPABASE_PUBLISHABLE_KEYS` | `{"default":"sb_publishable_..."}` | Named publishable keys as JSON object
|
|
9
|
-
| `SUPABASE_SECRET_KEYS` | `{"default":"sb_secret_..."}` | Named secret keys as JSON object
|
|
10
|
-
| `SUPABASE_JWKS` | `{"keys":[...]}` or `[...]` | JSON Web Key Set for JWT verification | All |
|
|
11
|
-
| `SUPABASE_PUBLISHABLE_KEY` | `sb_publishable_...` | Single publishable key (fallback)
|
|
12
|
-
| `SUPABASE_SECRET_KEY` | `sb_secret_...` | Single secret key (fallback)
|
|
5
|
+
| Variable | Format | Description | Available in |
|
|
6
|
+
| --------------------------- | ---------------------------------- | -------------------------------------------- | --------------------------------- |
|
|
7
|
+
| `SUPABASE_URL` | `https://<ref>.supabase.co` | Your Supabase project URL | All |
|
|
8
|
+
| `SUPABASE_PUBLISHABLE_KEYS` | `{"default":"sb_publishable_..."}` | Named publishable keys as JSON object | All |
|
|
9
|
+
| `SUPABASE_SECRET_KEYS` | `{"default":"sb_secret_..."}` | Named secret keys as JSON object | All |
|
|
10
|
+
| `SUPABASE_JWKS` | `{"keys":[...]}` or `[...]` | Inline JSON Web Key Set for JWT verification | All |
|
|
11
|
+
| `SUPABASE_PUBLISHABLE_KEY` | `sb_publishable_...` | Single publishable key (fallback) | Self-hosted, if manually exported |
|
|
12
|
+
| `SUPABASE_SECRET_KEY` | `sb_secret_...` | Single secret key (fallback) | Self-hosted, if manually exported |
|
|
13
13
|
|
|
14
14
|
## Non-Supabase environments (Node.js, Bun, Cloudflare, self-hosted)
|
|
15
15
|
|
|
16
16
|
Set these based on which auth modes your app uses:
|
|
17
17
|
|
|
18
|
-
| Variable
|
|
19
|
-
|
|
|
20
|
-
| `SUPABASE_URL`
|
|
21
|
-
| `SUPABASE_SECRET_KEY`
|
|
22
|
-
| `SUPABASE_PUBLISHABLE_KEY`
|
|
23
|
-
| `SUPABASE_JWKS`
|
|
18
|
+
| Variable | Required when |
|
|
19
|
+
| -------------------------------------- | ----------------------------------------- |
|
|
20
|
+
| `SUPABASE_URL` | Always |
|
|
21
|
+
| `SUPABASE_SECRET_KEY` | `auth: 'secret'` or using `supabaseAdmin` |
|
|
22
|
+
| `SUPABASE_PUBLISHABLE_KEY` | `auth: 'publishable'` |
|
|
23
|
+
| `SUPABASE_JWKS` or `SUPABASE_JWKS_URL` | `auth: 'user'` (JWT verification) |
|
|
24
24
|
|
|
25
25
|
### Minimal `.env` example
|
|
26
26
|
|
|
@@ -75,19 +75,34 @@ The singular form is a convenience for the common case where you only have one k
|
|
|
75
75
|
|
|
76
76
|
When both singular and plural forms are set, the plural form takes priority.
|
|
77
77
|
|
|
78
|
-
## JWKS
|
|
78
|
+
## JWKS source
|
|
79
79
|
|
|
80
|
-
`
|
|
80
|
+
JWT verification (`auth: 'user'`) needs a JWKS. There are two ways to provide one:
|
|
81
81
|
|
|
82
82
|
```
|
|
83
|
-
#
|
|
83
|
+
# Inline JSON — standard JWKS format
|
|
84
84
|
SUPABASE_JWKS={"keys":[{"kty":"RSA","n":"...","e":"AQAB"}]}
|
|
85
85
|
|
|
86
|
-
#
|
|
86
|
+
# Inline JSON — bare array (convenience, wrapped as { keys: [...] })
|
|
87
87
|
SUPABASE_JWKS=[{"kty":"RSA","n":"...","e":"AQAB"}]
|
|
88
|
+
|
|
89
|
+
# Remote JWKS endpoint — keys are fetched on demand and cached in memory.
|
|
90
|
+
# HTTPS is required for any non-loopback host; plain http:// is rejected
|
|
91
|
+
# (a MITM on the JWKS fetch could swap in an attacker-controlled key and
|
|
92
|
+
# forge JWTs that verify). http:// is allowed for loopback hosts only —
|
|
93
|
+
# `localhost`, `127.0.0.0/8`, `::1` — to support the local Supabase CLI.
|
|
94
|
+
SUPABASE_JWKS_URL=https://<ref>.supabase.co/auth/v1/.well-known/jwks.json
|
|
95
|
+
|
|
96
|
+
# Local development against `supabase start`:
|
|
97
|
+
SUPABASE_JWKS_URL=http://localhost:54321/auth/v1/.well-known/jwks.json
|
|
88
98
|
```
|
|
89
99
|
|
|
90
|
-
|
|
100
|
+
### Resolution order
|
|
101
|
+
|
|
102
|
+
1. `SUPABASE_JWKS` — when set, treated as authoritative inline JSON.
|
|
103
|
+
2. `SUPABASE_JWKS_URL` — only checked when `SUPABASE_JWKS` is unset or empty.
|
|
104
|
+
Must be `https://`, except loopback hosts may use `http://`.
|
|
105
|
+
3. Otherwise — `null`. JWT verification (`auth: 'user'`) is unavailable.
|
|
91
106
|
|
|
92
107
|
## Runtime-specific behavior
|
|
93
108
|
|
|
@@ -176,7 +191,8 @@ interface SupabaseEnv {
|
|
|
176
191
|
url: string
|
|
177
192
|
publishableKeys: Record<string, string>
|
|
178
193
|
secretKeys: Record<string, string>
|
|
179
|
-
|
|
194
|
+
// `URL` when SUPABASE_JWKS is a remote endpoint, `JsonWebKeySet` for inline keys
|
|
195
|
+
jwks: JsonWebKeySet | URL | null
|
|
180
196
|
}
|
|
181
197
|
```
|
|
182
198
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supabase/server",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0-rc.58",
|
|
4
4
|
"description": "Server-side utilities for Supabase. Handles auth, client creation, and context injection so you write business logic, not boilerplate.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"edge",
|