@supabase/server 0.1.1-rc.26 → 0.1.1-rc.28
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 +5 -2
- 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 +12 -18
- package/dist/core/index.d.mts +12 -18
- package/dist/core/index.mjs +1 -1
- package/dist/{create-supabase-context-CmWaH3s6.mjs → create-supabase-context-Bmwyha9p.mjs} +18 -7
- package/dist/{create-supabase-context-DcVorGKG.cjs → create-supabase-context-DDIAxA8h.cjs} +18 -7
- package/dist/errors-CAH-RRA3.d.mts +109 -0
- package/dist/errors-O2ugIMec.d.cts +109 -0
- package/dist/index.cjs +13 -3
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{types-ClmJ8pi8.d.mts → types-BmWSIuH7.d.mts} +46 -2
- package/dist/{types-CnKoFCMX.d.cts → types-X7xYi2LN.d.cts} +46 -2
- package/dist/{verify-auth-2S7zFfR-.mjs → verify-auth-Bt2uGltH.mjs} +98 -33
- package/dist/{verify-auth-DvRVnjdq.cjs → verify-auth-DrgvEuKo.cjs} +157 -32
- package/package.json +1 -1
- package/dist/errors-5ivL23qo.d.mts +0 -78
- package/dist/errors-BmSsOAvx.d.cts +0 -78
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SupabaseClient } from "@supabase/supabase-js";
|
|
1
|
+
import { SupabaseClient, SupabaseClientOptions } from "@supabase/supabase-js";
|
|
2
2
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
/**
|
|
@@ -204,6 +204,50 @@ interface WithSupabaseConfig {
|
|
|
204
204
|
* @defaultValue `true`
|
|
205
205
|
*/
|
|
206
206
|
cors?: boolean | Record<string, string>;
|
|
207
|
+
/**
|
|
208
|
+
* Options forwarded to both internal `createClient()` calls.
|
|
209
|
+
*
|
|
210
|
+
* `accessToken` is stripped, and auth settings (`persistSession`, `autoRefreshToken`,
|
|
211
|
+
* `detectSessionInUrl`) are force-overwritten to server-safe values.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```ts
|
|
215
|
+
* withSupabase({
|
|
216
|
+
* allow: 'user',
|
|
217
|
+
* supabaseOptions: { db: { schema: 'api' } },
|
|
218
|
+
* }, handler)
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
supabaseOptions?: SupabaseClientOptions<string>;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Auth identity for client creation functions.
|
|
225
|
+
*
|
|
226
|
+
* @see {@link verifyAuth}, {@link verifyCredentials}
|
|
227
|
+
*/
|
|
228
|
+
interface ClientAuth {
|
|
229
|
+
/** The caller's JWT, or `null` for anonymous access. */
|
|
230
|
+
token?: string | null;
|
|
231
|
+
/** Name of the API key to use. Falls back to `"default"`, then first available. */
|
|
232
|
+
keyName?: string | null;
|
|
233
|
+
}
|
|
234
|
+
/** Options for {@link createContextClient}. */
|
|
235
|
+
interface CreateContextClientOptions {
|
|
236
|
+
/** Auth identity — token and key name from the verified request. */
|
|
237
|
+
auth?: ClientAuth;
|
|
238
|
+
/** Override auto-detected environment variables. */
|
|
239
|
+
env?: Partial<SupabaseEnv>;
|
|
240
|
+
/** Options forwarded to `createClient()`. `accessToken` is stripped; auth settings are force-overwritten. */
|
|
241
|
+
supabaseOptions?: SupabaseClientOptions<string>;
|
|
242
|
+
}
|
|
243
|
+
/** Options for {@link createAdminClient}. */
|
|
244
|
+
interface CreateAdminClientOptions {
|
|
245
|
+
/** Auth identity — key name from the verified request. */
|
|
246
|
+
auth?: Pick<ClientAuth, 'keyName'>;
|
|
247
|
+
/** Override auto-detected environment variables. */
|
|
248
|
+
env?: Partial<SupabaseEnv>;
|
|
249
|
+
/** Options forwarded to `createClient()`. `accessToken` is stripped; auth settings are force-overwritten. */
|
|
250
|
+
supabaseOptions?: SupabaseClientOptions<string>;
|
|
207
251
|
}
|
|
208
252
|
/**
|
|
209
253
|
* The Supabase context created for each authenticated request.
|
|
@@ -224,4 +268,4 @@ interface SupabaseContext<Database = unknown> {
|
|
|
224
268
|
authType: Allow;
|
|
225
269
|
}
|
|
226
270
|
//#endregion
|
|
227
|
-
export {
|
|
271
|
+
export { CreateAdminClientOptions as a, JWTClaims as c, UserClaims as d, WithSupabaseConfig as f, ClientAuth as i, SupabaseContext as l, AllowWithKey as n, CreateContextClientOptions as o, AuthResult as r, Credentials as s, Allow as t, SupabaseEnv as u };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SupabaseClient } from "@supabase/supabase-js";
|
|
1
|
+
import { SupabaseClient, SupabaseClientOptions } from "@supabase/supabase-js";
|
|
2
2
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
/**
|
|
@@ -204,6 +204,50 @@ interface WithSupabaseConfig {
|
|
|
204
204
|
* @defaultValue `true`
|
|
205
205
|
*/
|
|
206
206
|
cors?: boolean | Record<string, string>;
|
|
207
|
+
/**
|
|
208
|
+
* Options forwarded to both internal `createClient()` calls.
|
|
209
|
+
*
|
|
210
|
+
* `accessToken` is stripped, and auth settings (`persistSession`, `autoRefreshToken`,
|
|
211
|
+
* `detectSessionInUrl`) are force-overwritten to server-safe values.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```ts
|
|
215
|
+
* withSupabase({
|
|
216
|
+
* allow: 'user',
|
|
217
|
+
* supabaseOptions: { db: { schema: 'api' } },
|
|
218
|
+
* }, handler)
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
supabaseOptions?: SupabaseClientOptions<string>;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Auth identity for client creation functions.
|
|
225
|
+
*
|
|
226
|
+
* @see {@link verifyAuth}, {@link verifyCredentials}
|
|
227
|
+
*/
|
|
228
|
+
interface ClientAuth {
|
|
229
|
+
/** The caller's JWT, or `null` for anonymous access. */
|
|
230
|
+
token?: string | null;
|
|
231
|
+
/** Name of the API key to use. Falls back to `"default"`, then first available. */
|
|
232
|
+
keyName?: string | null;
|
|
233
|
+
}
|
|
234
|
+
/** Options for {@link createContextClient}. */
|
|
235
|
+
interface CreateContextClientOptions {
|
|
236
|
+
/** Auth identity — token and key name from the verified request. */
|
|
237
|
+
auth?: ClientAuth;
|
|
238
|
+
/** Override auto-detected environment variables. */
|
|
239
|
+
env?: Partial<SupabaseEnv>;
|
|
240
|
+
/** Options forwarded to `createClient()`. `accessToken` is stripped; auth settings are force-overwritten. */
|
|
241
|
+
supabaseOptions?: SupabaseClientOptions<string>;
|
|
242
|
+
}
|
|
243
|
+
/** Options for {@link createAdminClient}. */
|
|
244
|
+
interface CreateAdminClientOptions {
|
|
245
|
+
/** Auth identity — key name from the verified request. */
|
|
246
|
+
auth?: Pick<ClientAuth, 'keyName'>;
|
|
247
|
+
/** Override auto-detected environment variables. */
|
|
248
|
+
env?: Partial<SupabaseEnv>;
|
|
249
|
+
/** Options forwarded to `createClient()`. `accessToken` is stripped; auth settings are force-overwritten. */
|
|
250
|
+
supabaseOptions?: SupabaseClientOptions<string>;
|
|
207
251
|
}
|
|
208
252
|
/**
|
|
209
253
|
* The Supabase context created for each authenticated request.
|
|
@@ -224,4 +268,4 @@ interface SupabaseContext<Database = unknown> {
|
|
|
224
268
|
authType: Allow;
|
|
225
269
|
}
|
|
226
270
|
//#endregion
|
|
227
|
-
export {
|
|
271
|
+
export { CreateAdminClientOptions as a, JWTClaims as c, UserClaims as d, WithSupabaseConfig as f, ClientAuth as i, SupabaseContext as l, AllowWithKey as n, CreateContextClientOptions as o, AuthResult as r, Credentials as s, Allow as t, SupabaseEnv as u };
|
|
@@ -5,8 +5,7 @@ import { createLocalJWKSet, jwtVerify } from "jose";
|
|
|
5
5
|
/**
|
|
6
6
|
* Thrown when a required environment variable is missing or malformed.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* configuration issues, not client errors.
|
|
8
|
+
* Always has `status: 500` — environment errors are server-side configuration issues.
|
|
10
9
|
*
|
|
11
10
|
* @example
|
|
12
11
|
* ```ts
|
|
@@ -23,13 +22,32 @@ import { createLocalJWKSet, jwtVerify } from "jose";
|
|
|
23
22
|
* ```
|
|
24
23
|
*/
|
|
25
24
|
var EnvError = class extends Error {
|
|
26
|
-
constructor(message, code =
|
|
25
|
+
constructor(message, code = EnvGenericError) {
|
|
27
26
|
super(message);
|
|
28
27
|
this.status = 500;
|
|
29
28
|
this.name = "EnvError";
|
|
30
29
|
this.code = code;
|
|
31
30
|
}
|
|
32
31
|
};
|
|
32
|
+
/** Generic environment error code. */
|
|
33
|
+
const EnvGenericError = "ENV_ERROR";
|
|
34
|
+
/** `SUPABASE_URL` is not set. */
|
|
35
|
+
const MissingSupabaseURLError = "MISSING_SUPABASE_URL";
|
|
36
|
+
/** Named publishable key not found in `SUPABASE_PUBLISHABLE_KEYS`. */
|
|
37
|
+
const MissingPublishableKeyError = "MISSING_PUBLISHABLE_KEY";
|
|
38
|
+
/** No default publishable key found. */
|
|
39
|
+
const MissingDefaultPublishableKeyError = "MISSING_DEFAULT_PUBLISHABLE_KEY";
|
|
40
|
+
/** Named secret key not found in `SUPABASE_SECRET_KEYS`. */
|
|
41
|
+
const MissingSecretKeyError = "MISSING_SECRET_KEY";
|
|
42
|
+
/** No default secret key found. */
|
|
43
|
+
const MissingDefaultSecretKeyError = "MISSING_DEFAULT_SECRET_KEY";
|
|
44
|
+
const EnvErrorMap = {
|
|
45
|
+
[MissingSupabaseURLError]: () => new EnvError("SUPABASE_URL is required but not set", MissingSupabaseURLError),
|
|
46
|
+
[MissingSecretKeyError]: (name) => new EnvError(`No "${name}" secret key found. Include a "${name}" entry in SUPABASE_SECRET_KEYS.`, MissingSecretKeyError),
|
|
47
|
+
[MissingDefaultSecretKeyError]: () => new EnvError("No default secret key found. Set SUPABASE_SECRET_KEY or include a \"default\" entry in SUPABASE_SECRET_KEYS.", MissingDefaultSecretKeyError),
|
|
48
|
+
[MissingPublishableKeyError]: (name) => new EnvError(`No "${name}" publishable key found. Include a "${name}" entry in SUPABASE_PUBLISHABLE_KEYS.`, MissingPublishableKeyError),
|
|
49
|
+
[MissingDefaultPublishableKeyError]: () => new EnvError("No default publishable key found. Set SUPABASE_PUBLISHABLE_KEY or include a \"default\" entry in SUPABASE_PUBLISHABLE_KEYS.", MissingDefaultPublishableKeyError)
|
|
50
|
+
};
|
|
33
51
|
/**
|
|
34
52
|
* Thrown when authentication or authorization fails.
|
|
35
53
|
*
|
|
@@ -44,20 +62,44 @@ var EnvError = class extends Error {
|
|
|
44
62
|
* if (error) {
|
|
45
63
|
* // error is an AuthError
|
|
46
64
|
* return Response.json(
|
|
47
|
-
* {
|
|
65
|
+
* { message: error.message, code: error.code },
|
|
48
66
|
* { status: error.status },
|
|
49
67
|
* )
|
|
50
68
|
* }
|
|
51
69
|
* ```
|
|
52
70
|
*/
|
|
53
71
|
var AuthError = class extends Error {
|
|
54
|
-
constructor(message, code =
|
|
72
|
+
constructor(message, code = AuthGenericError, status = 401) {
|
|
55
73
|
super(message);
|
|
56
74
|
this.name = "AuthError";
|
|
57
75
|
this.code = code;
|
|
58
76
|
this.status = status;
|
|
59
77
|
}
|
|
60
78
|
};
|
|
79
|
+
/** Generic authentication error code. */
|
|
80
|
+
const AuthGenericError = "AUTH_ERROR";
|
|
81
|
+
/** No credential matched any allowed auth mode. */
|
|
82
|
+
const InvalidCredentialsError = "INVALID_CREDENTIALS";
|
|
83
|
+
/** Failed to create a Supabase client after auth succeeded. */
|
|
84
|
+
const CreateSupabaseClientError = "CREATE_SUPABASE_CLIENT_ERROR";
|
|
85
|
+
const AuthErrorMap = {
|
|
86
|
+
[InvalidCredentialsError]: () => new AuthError("Invalid credentials", InvalidCredentialsError, 401),
|
|
87
|
+
[CreateSupabaseClientError]: () => new AuthError("Failed to create Supabase client", CreateSupabaseClientError, 500)
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Factory map for all error types. Keyed by error code constant, each entry
|
|
91
|
+
* returns a pre-configured {@link EnvError} or {@link AuthError}.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* throw Errors[MissingSupabaseURLError]()
|
|
96
|
+
* throw Errors[MissingPublishableKeyError]('mobile')
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
const Errors = {
|
|
100
|
+
...EnvErrorMap,
|
|
101
|
+
...AuthErrorMap
|
|
102
|
+
};
|
|
61
103
|
|
|
62
104
|
//#endregion
|
|
63
105
|
//#region src/core/resolve-env.ts
|
|
@@ -138,7 +180,7 @@ function resolveEnv(overrides) {
|
|
|
138
180
|
const url = overrides?.url ?? getEnvVar("SUPABASE_URL");
|
|
139
181
|
if (!url) return {
|
|
140
182
|
data: null,
|
|
141
|
-
error:
|
|
183
|
+
error: Errors[MissingSupabaseURLError]()
|
|
142
184
|
};
|
|
143
185
|
return {
|
|
144
186
|
data: {
|
|
@@ -157,11 +199,8 @@ function resolveEnv(overrides) {
|
|
|
157
199
|
* Creates an admin Supabase client that bypasses Row-Level Security.
|
|
158
200
|
*
|
|
159
201
|
* Uses a secret key for authentication, giving full access to all data.
|
|
160
|
-
*
|
|
202
|
+
* Stateless — one client per request.
|
|
161
203
|
*
|
|
162
|
-
* @param env - Optional environment overrides (passed through to {@link resolveEnv}).
|
|
163
|
-
* @param keyName - Name of the secret key to use. Falls back to `"default"`, then first available.
|
|
164
|
-
* @returns A configured {@link SupabaseClient} with admin (service-role) privileges.
|
|
165
204
|
* @throws {@link EnvError} If `SUPABASE_URL` is missing or the specified secret key is not found.
|
|
166
205
|
*
|
|
167
206
|
* @example
|
|
@@ -170,18 +209,32 @@ function resolveEnv(overrides) {
|
|
|
170
209
|
* const { data } = await supabaseAdmin.from('audit_log').insert({ action: 'user_login' })
|
|
171
210
|
* ```
|
|
172
211
|
*/
|
|
173
|
-
function createAdminClient(
|
|
174
|
-
const { data: resolved, error } = resolveEnv(env);
|
|
212
|
+
function createAdminClient(options) {
|
|
213
|
+
const { data: resolved, error } = resolveEnv(options?.env);
|
|
175
214
|
if (error) throw error;
|
|
215
|
+
const keyName = options?.auth?.keyName;
|
|
216
|
+
const supabaseOptions = options?.supabaseOptions;
|
|
176
217
|
const name = keyName ?? "default";
|
|
177
218
|
const keys = resolved.secretKeys;
|
|
178
219
|
const secretKey = keys[name] ?? (keyName == null ? Object.values(keys)[0] : void 0);
|
|
179
|
-
if (!secretKey) throw
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
220
|
+
if (!secretKey) throw name === "default" ? Errors[MissingDefaultSecretKeyError]() : Errors[MissingSecretKeyError](name);
|
|
221
|
+
const safeHeaders = { ...supabaseOptions?.global?.headers };
|
|
222
|
+
delete safeHeaders.Authorization;
|
|
223
|
+
delete safeHeaders.apikey;
|
|
224
|
+
return createClient(resolved.url, secretKey, {
|
|
225
|
+
...supabaseOptions,
|
|
226
|
+
accessToken: void 0,
|
|
227
|
+
global: {
|
|
228
|
+
...supabaseOptions?.global,
|
|
229
|
+
headers: safeHeaders
|
|
230
|
+
},
|
|
231
|
+
auth: {
|
|
232
|
+
...supabaseOptions?.auth,
|
|
233
|
+
persistSession: false,
|
|
234
|
+
autoRefreshToken: false,
|
|
235
|
+
detectSessionInUrl: false
|
|
236
|
+
}
|
|
237
|
+
});
|
|
185
238
|
}
|
|
186
239
|
|
|
187
240
|
//#endregion
|
|
@@ -190,32 +243,44 @@ function createAdminClient(env, keyName) {
|
|
|
190
243
|
* Creates a Supabase client scoped to the caller's context.
|
|
191
244
|
*
|
|
192
245
|
* Configured with a publishable key and (optionally) the caller's JWT,
|
|
193
|
-
* so Row-Level Security policies apply.
|
|
194
|
-
* (stateless, one client per request).
|
|
246
|
+
* so Row-Level Security policies apply. Stateless — one client per request.
|
|
195
247
|
*
|
|
196
|
-
* @param token - The caller's JWT, or `null` for anonymous access.
|
|
197
|
-
* @param env - Optional environment overrides (passed through to {@link resolveEnv}).
|
|
198
|
-
* @param keyName - Name of the publishable key to use. Falls back to `"default"`, then first available.
|
|
199
|
-
* @returns A configured {@link SupabaseClient} with RLS enforced.
|
|
200
248
|
* @throws {@link EnvError} If `SUPABASE_URL` is missing or the specified publishable key is not found.
|
|
201
249
|
*
|
|
202
250
|
* @example
|
|
203
251
|
* ```ts
|
|
204
252
|
* const { data: auth } = await verifyAuth(request, { allow: 'user' })
|
|
205
|
-
* const supabase = createContextClient(
|
|
253
|
+
* const supabase = createContextClient({
|
|
254
|
+
* auth: { token: auth.token, keyName: auth.keyName },
|
|
255
|
+
* })
|
|
206
256
|
* const { data } = await supabase.rpc('get_my_items')
|
|
207
257
|
* ```
|
|
208
258
|
*/
|
|
209
|
-
function createContextClient(
|
|
210
|
-
const { data: resolved, error } = resolveEnv(env);
|
|
259
|
+
function createContextClient(options) {
|
|
260
|
+
const { data: resolved, error } = resolveEnv(options?.env);
|
|
211
261
|
if (error) throw error;
|
|
262
|
+
const token = options?.auth?.token;
|
|
263
|
+
const keyName = options?.auth?.keyName;
|
|
264
|
+
const supabaseOptions = options?.supabaseOptions;
|
|
212
265
|
const name = keyName ?? "default";
|
|
213
266
|
const keys = resolved.publishableKeys;
|
|
214
267
|
const anonKey = keys[name] ?? (keyName == null ? Object.values(keys)[0] : void 0);
|
|
215
|
-
if (!anonKey) throw
|
|
268
|
+
if (!anonKey) throw name === "default" ? Errors[MissingDefaultPublishableKeyError]() : Errors[MissingPublishableKeyError](name);
|
|
269
|
+
const safeHeaders = { ...supabaseOptions?.global?.headers };
|
|
270
|
+
delete safeHeaders.Authorization;
|
|
271
|
+
delete safeHeaders.apikey;
|
|
216
272
|
return createClient(resolved.url, anonKey, {
|
|
217
|
-
|
|
273
|
+
...supabaseOptions,
|
|
274
|
+
accessToken: void 0,
|
|
275
|
+
global: {
|
|
276
|
+
...supabaseOptions?.global,
|
|
277
|
+
headers: {
|
|
278
|
+
...safeHeaders,
|
|
279
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
218
282
|
auth: {
|
|
283
|
+
...supabaseOptions?.auth,
|
|
219
284
|
persistSession: false,
|
|
220
285
|
autoRefreshToken: false,
|
|
221
286
|
detectSessionInUrl: false
|
|
@@ -424,7 +489,7 @@ async function tryMode(mode, credentials, env) {
|
|
|
424
489
|
* allow: ['user', 'public'],
|
|
425
490
|
* })
|
|
426
491
|
* if (error) {
|
|
427
|
-
* return Response.json({
|
|
492
|
+
* return Response.json({ message: error.message }, { status: error.status })
|
|
428
493
|
* }
|
|
429
494
|
* ```
|
|
430
495
|
*/
|
|
@@ -444,7 +509,7 @@ async function verifyCredentials(credentials, options) {
|
|
|
444
509
|
}
|
|
445
510
|
return {
|
|
446
511
|
data: null,
|
|
447
|
-
error:
|
|
512
|
+
error: Errors[InvalidCredentialsError]()
|
|
448
513
|
};
|
|
449
514
|
}
|
|
450
515
|
|
|
@@ -473,7 +538,7 @@ async function verifyCredentials(credentials, options) {
|
|
|
473
538
|
* })
|
|
474
539
|
*
|
|
475
540
|
* if (error) {
|
|
476
|
-
* return Response.json({
|
|
541
|
+
* return Response.json({ message: error.message }, { status: error.status })
|
|
477
542
|
* }
|
|
478
543
|
*
|
|
479
544
|
* console.log(auth.userClaims!.id) // "d0f1a2b3-..."
|
|
@@ -484,4 +549,4 @@ async function verifyAuth(request, options) {
|
|
|
484
549
|
}
|
|
485
550
|
|
|
486
551
|
//#endregion
|
|
487
|
-
export { createAdminClient as a,
|
|
552
|
+
export { MissingSecretKeyError as _, createAdminClient as a, AuthGenericError as c, EnvGenericError as d, Errors as f, MissingPublishableKeyError as g, MissingDefaultSecretKeyError as h, createContextClient as i, CreateSupabaseClientError as l, MissingDefaultPublishableKeyError as m, verifyCredentials as n, resolveEnv as o, InvalidCredentialsError as p, extractCredentials as r, AuthError as s, verifyAuth as t, EnvError as u, MissingSupabaseURLError as v };
|
|
@@ -5,8 +5,7 @@ let jose = require("jose");
|
|
|
5
5
|
/**
|
|
6
6
|
* Thrown when a required environment variable is missing or malformed.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* configuration issues, not client errors.
|
|
8
|
+
* Always has `status: 500` — environment errors are server-side configuration issues.
|
|
10
9
|
*
|
|
11
10
|
* @example
|
|
12
11
|
* ```ts
|
|
@@ -23,13 +22,32 @@ let jose = require("jose");
|
|
|
23
22
|
* ```
|
|
24
23
|
*/
|
|
25
24
|
var EnvError = class extends Error {
|
|
26
|
-
constructor(message, code =
|
|
25
|
+
constructor(message, code = EnvGenericError) {
|
|
27
26
|
super(message);
|
|
28
27
|
this.status = 500;
|
|
29
28
|
this.name = "EnvError";
|
|
30
29
|
this.code = code;
|
|
31
30
|
}
|
|
32
31
|
};
|
|
32
|
+
/** Generic environment error code. */
|
|
33
|
+
const EnvGenericError = "ENV_ERROR";
|
|
34
|
+
/** `SUPABASE_URL` is not set. */
|
|
35
|
+
const MissingSupabaseURLError = "MISSING_SUPABASE_URL";
|
|
36
|
+
/** Named publishable key not found in `SUPABASE_PUBLISHABLE_KEYS`. */
|
|
37
|
+
const MissingPublishableKeyError = "MISSING_PUBLISHABLE_KEY";
|
|
38
|
+
/** No default publishable key found. */
|
|
39
|
+
const MissingDefaultPublishableKeyError = "MISSING_DEFAULT_PUBLISHABLE_KEY";
|
|
40
|
+
/** Named secret key not found in `SUPABASE_SECRET_KEYS`. */
|
|
41
|
+
const MissingSecretKeyError = "MISSING_SECRET_KEY";
|
|
42
|
+
/** No default secret key found. */
|
|
43
|
+
const MissingDefaultSecretKeyError = "MISSING_DEFAULT_SECRET_KEY";
|
|
44
|
+
const EnvErrorMap = {
|
|
45
|
+
[MissingSupabaseURLError]: () => new EnvError("SUPABASE_URL is required but not set", MissingSupabaseURLError),
|
|
46
|
+
[MissingSecretKeyError]: (name) => new EnvError(`No "${name}" secret key found. Include a "${name}" entry in SUPABASE_SECRET_KEYS.`, MissingSecretKeyError),
|
|
47
|
+
[MissingDefaultSecretKeyError]: () => new EnvError("No default secret key found. Set SUPABASE_SECRET_KEY or include a \"default\" entry in SUPABASE_SECRET_KEYS.", MissingDefaultSecretKeyError),
|
|
48
|
+
[MissingPublishableKeyError]: (name) => new EnvError(`No "${name}" publishable key found. Include a "${name}" entry in SUPABASE_PUBLISHABLE_KEYS.`, MissingPublishableKeyError),
|
|
49
|
+
[MissingDefaultPublishableKeyError]: () => new EnvError("No default publishable key found. Set SUPABASE_PUBLISHABLE_KEY or include a \"default\" entry in SUPABASE_PUBLISHABLE_KEYS.", MissingDefaultPublishableKeyError)
|
|
50
|
+
};
|
|
33
51
|
/**
|
|
34
52
|
* Thrown when authentication or authorization fails.
|
|
35
53
|
*
|
|
@@ -44,20 +62,44 @@ var EnvError = class extends Error {
|
|
|
44
62
|
* if (error) {
|
|
45
63
|
* // error is an AuthError
|
|
46
64
|
* return Response.json(
|
|
47
|
-
* {
|
|
65
|
+
* { message: error.message, code: error.code },
|
|
48
66
|
* { status: error.status },
|
|
49
67
|
* )
|
|
50
68
|
* }
|
|
51
69
|
* ```
|
|
52
70
|
*/
|
|
53
71
|
var AuthError = class extends Error {
|
|
54
|
-
constructor(message, code =
|
|
72
|
+
constructor(message, code = AuthGenericError, status = 401) {
|
|
55
73
|
super(message);
|
|
56
74
|
this.name = "AuthError";
|
|
57
75
|
this.code = code;
|
|
58
76
|
this.status = status;
|
|
59
77
|
}
|
|
60
78
|
};
|
|
79
|
+
/** Generic authentication error code. */
|
|
80
|
+
const AuthGenericError = "AUTH_ERROR";
|
|
81
|
+
/** No credential matched any allowed auth mode. */
|
|
82
|
+
const InvalidCredentialsError = "INVALID_CREDENTIALS";
|
|
83
|
+
/** Failed to create a Supabase client after auth succeeded. */
|
|
84
|
+
const CreateSupabaseClientError = "CREATE_SUPABASE_CLIENT_ERROR";
|
|
85
|
+
const AuthErrorMap = {
|
|
86
|
+
[InvalidCredentialsError]: () => new AuthError("Invalid credentials", InvalidCredentialsError, 401),
|
|
87
|
+
[CreateSupabaseClientError]: () => new AuthError("Failed to create Supabase client", CreateSupabaseClientError, 500)
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Factory map for all error types. Keyed by error code constant, each entry
|
|
91
|
+
* returns a pre-configured {@link EnvError} or {@link AuthError}.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* throw Errors[MissingSupabaseURLError]()
|
|
96
|
+
* throw Errors[MissingPublishableKeyError]('mobile')
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
const Errors = {
|
|
100
|
+
...EnvErrorMap,
|
|
101
|
+
...AuthErrorMap
|
|
102
|
+
};
|
|
61
103
|
|
|
62
104
|
//#endregion
|
|
63
105
|
//#region src/core/resolve-env.ts
|
|
@@ -138,7 +180,7 @@ function resolveEnv(overrides) {
|
|
|
138
180
|
const url = overrides?.url ?? getEnvVar("SUPABASE_URL");
|
|
139
181
|
if (!url) return {
|
|
140
182
|
data: null,
|
|
141
|
-
error:
|
|
183
|
+
error: Errors[MissingSupabaseURLError]()
|
|
142
184
|
};
|
|
143
185
|
return {
|
|
144
186
|
data: {
|
|
@@ -157,11 +199,8 @@ function resolveEnv(overrides) {
|
|
|
157
199
|
* Creates an admin Supabase client that bypasses Row-Level Security.
|
|
158
200
|
*
|
|
159
201
|
* Uses a secret key for authentication, giving full access to all data.
|
|
160
|
-
*
|
|
202
|
+
* Stateless — one client per request.
|
|
161
203
|
*
|
|
162
|
-
* @param env - Optional environment overrides (passed through to {@link resolveEnv}).
|
|
163
|
-
* @param keyName - Name of the secret key to use. Falls back to `"default"`, then first available.
|
|
164
|
-
* @returns A configured {@link SupabaseClient} with admin (service-role) privileges.
|
|
165
204
|
* @throws {@link EnvError} If `SUPABASE_URL` is missing or the specified secret key is not found.
|
|
166
205
|
*
|
|
167
206
|
* @example
|
|
@@ -170,18 +209,32 @@ function resolveEnv(overrides) {
|
|
|
170
209
|
* const { data } = await supabaseAdmin.from('audit_log').insert({ action: 'user_login' })
|
|
171
210
|
* ```
|
|
172
211
|
*/
|
|
173
|
-
function createAdminClient(
|
|
174
|
-
const { data: resolved, error } = resolveEnv(env);
|
|
212
|
+
function createAdminClient(options) {
|
|
213
|
+
const { data: resolved, error } = resolveEnv(options?.env);
|
|
175
214
|
if (error) throw error;
|
|
215
|
+
const keyName = options?.auth?.keyName;
|
|
216
|
+
const supabaseOptions = options?.supabaseOptions;
|
|
176
217
|
const name = keyName ?? "default";
|
|
177
218
|
const keys = resolved.secretKeys;
|
|
178
219
|
const secretKey = keys[name] ?? (keyName == null ? Object.values(keys)[0] : void 0);
|
|
179
|
-
if (!secretKey) throw
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
220
|
+
if (!secretKey) throw name === "default" ? Errors[MissingDefaultSecretKeyError]() : Errors[MissingSecretKeyError](name);
|
|
221
|
+
const safeHeaders = { ...supabaseOptions?.global?.headers };
|
|
222
|
+
delete safeHeaders.Authorization;
|
|
223
|
+
delete safeHeaders.apikey;
|
|
224
|
+
return (0, _supabase_supabase_js.createClient)(resolved.url, secretKey, {
|
|
225
|
+
...supabaseOptions,
|
|
226
|
+
accessToken: void 0,
|
|
227
|
+
global: {
|
|
228
|
+
...supabaseOptions?.global,
|
|
229
|
+
headers: safeHeaders
|
|
230
|
+
},
|
|
231
|
+
auth: {
|
|
232
|
+
...supabaseOptions?.auth,
|
|
233
|
+
persistSession: false,
|
|
234
|
+
autoRefreshToken: false,
|
|
235
|
+
detectSessionInUrl: false
|
|
236
|
+
}
|
|
237
|
+
});
|
|
185
238
|
}
|
|
186
239
|
|
|
187
240
|
//#endregion
|
|
@@ -190,32 +243,44 @@ function createAdminClient(env, keyName) {
|
|
|
190
243
|
* Creates a Supabase client scoped to the caller's context.
|
|
191
244
|
*
|
|
192
245
|
* Configured with a publishable key and (optionally) the caller's JWT,
|
|
193
|
-
* so Row-Level Security policies apply.
|
|
194
|
-
* (stateless, one client per request).
|
|
246
|
+
* so Row-Level Security policies apply. Stateless — one client per request.
|
|
195
247
|
*
|
|
196
|
-
* @param token - The caller's JWT, or `null` for anonymous access.
|
|
197
|
-
* @param env - Optional environment overrides (passed through to {@link resolveEnv}).
|
|
198
|
-
* @param keyName - Name of the publishable key to use. Falls back to `"default"`, then first available.
|
|
199
|
-
* @returns A configured {@link SupabaseClient} with RLS enforced.
|
|
200
248
|
* @throws {@link EnvError} If `SUPABASE_URL` is missing or the specified publishable key is not found.
|
|
201
249
|
*
|
|
202
250
|
* @example
|
|
203
251
|
* ```ts
|
|
204
252
|
* const { data: auth } = await verifyAuth(request, { allow: 'user' })
|
|
205
|
-
* const supabase = createContextClient(
|
|
253
|
+
* const supabase = createContextClient({
|
|
254
|
+
* auth: { token: auth.token, keyName: auth.keyName },
|
|
255
|
+
* })
|
|
206
256
|
* const { data } = await supabase.rpc('get_my_items')
|
|
207
257
|
* ```
|
|
208
258
|
*/
|
|
209
|
-
function createContextClient(
|
|
210
|
-
const { data: resolved, error } = resolveEnv(env);
|
|
259
|
+
function createContextClient(options) {
|
|
260
|
+
const { data: resolved, error } = resolveEnv(options?.env);
|
|
211
261
|
if (error) throw error;
|
|
262
|
+
const token = options?.auth?.token;
|
|
263
|
+
const keyName = options?.auth?.keyName;
|
|
264
|
+
const supabaseOptions = options?.supabaseOptions;
|
|
212
265
|
const name = keyName ?? "default";
|
|
213
266
|
const keys = resolved.publishableKeys;
|
|
214
267
|
const anonKey = keys[name] ?? (keyName == null ? Object.values(keys)[0] : void 0);
|
|
215
|
-
if (!anonKey) throw
|
|
268
|
+
if (!anonKey) throw name === "default" ? Errors[MissingDefaultPublishableKeyError]() : Errors[MissingPublishableKeyError](name);
|
|
269
|
+
const safeHeaders = { ...supabaseOptions?.global?.headers };
|
|
270
|
+
delete safeHeaders.Authorization;
|
|
271
|
+
delete safeHeaders.apikey;
|
|
216
272
|
return (0, _supabase_supabase_js.createClient)(resolved.url, anonKey, {
|
|
217
|
-
|
|
273
|
+
...supabaseOptions,
|
|
274
|
+
accessToken: void 0,
|
|
275
|
+
global: {
|
|
276
|
+
...supabaseOptions?.global,
|
|
277
|
+
headers: {
|
|
278
|
+
...safeHeaders,
|
|
279
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
218
282
|
auth: {
|
|
283
|
+
...supabaseOptions?.auth,
|
|
219
284
|
persistSession: false,
|
|
220
285
|
autoRefreshToken: false,
|
|
221
286
|
detectSessionInUrl: false
|
|
@@ -424,7 +489,7 @@ async function tryMode(mode, credentials, env) {
|
|
|
424
489
|
* allow: ['user', 'public'],
|
|
425
490
|
* })
|
|
426
491
|
* if (error) {
|
|
427
|
-
* return Response.json({
|
|
492
|
+
* return Response.json({ message: error.message }, { status: error.status })
|
|
428
493
|
* }
|
|
429
494
|
* ```
|
|
430
495
|
*/
|
|
@@ -444,7 +509,7 @@ async function verifyCredentials(credentials, options) {
|
|
|
444
509
|
}
|
|
445
510
|
return {
|
|
446
511
|
data: null,
|
|
447
|
-
error:
|
|
512
|
+
error: Errors[InvalidCredentialsError]()
|
|
448
513
|
};
|
|
449
514
|
}
|
|
450
515
|
|
|
@@ -473,7 +538,7 @@ async function verifyCredentials(credentials, options) {
|
|
|
473
538
|
* })
|
|
474
539
|
*
|
|
475
540
|
* if (error) {
|
|
476
|
-
* return Response.json({
|
|
541
|
+
* return Response.json({ message: error.message }, { status: error.status })
|
|
477
542
|
* }
|
|
478
543
|
*
|
|
479
544
|
* console.log(auth.userClaims!.id) // "d0f1a2b3-..."
|
|
@@ -490,12 +555,72 @@ Object.defineProperty(exports, 'AuthError', {
|
|
|
490
555
|
return AuthError;
|
|
491
556
|
}
|
|
492
557
|
});
|
|
558
|
+
Object.defineProperty(exports, 'AuthGenericError', {
|
|
559
|
+
enumerable: true,
|
|
560
|
+
get: function () {
|
|
561
|
+
return AuthGenericError;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
Object.defineProperty(exports, 'CreateSupabaseClientError', {
|
|
565
|
+
enumerable: true,
|
|
566
|
+
get: function () {
|
|
567
|
+
return CreateSupabaseClientError;
|
|
568
|
+
}
|
|
569
|
+
});
|
|
493
570
|
Object.defineProperty(exports, 'EnvError', {
|
|
494
571
|
enumerable: true,
|
|
495
572
|
get: function () {
|
|
496
573
|
return EnvError;
|
|
497
574
|
}
|
|
498
575
|
});
|
|
576
|
+
Object.defineProperty(exports, 'EnvGenericError', {
|
|
577
|
+
enumerable: true,
|
|
578
|
+
get: function () {
|
|
579
|
+
return EnvGenericError;
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
Object.defineProperty(exports, 'Errors', {
|
|
583
|
+
enumerable: true,
|
|
584
|
+
get: function () {
|
|
585
|
+
return Errors;
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
Object.defineProperty(exports, 'InvalidCredentialsError', {
|
|
589
|
+
enumerable: true,
|
|
590
|
+
get: function () {
|
|
591
|
+
return InvalidCredentialsError;
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
Object.defineProperty(exports, 'MissingDefaultPublishableKeyError', {
|
|
595
|
+
enumerable: true,
|
|
596
|
+
get: function () {
|
|
597
|
+
return MissingDefaultPublishableKeyError;
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
Object.defineProperty(exports, 'MissingDefaultSecretKeyError', {
|
|
601
|
+
enumerable: true,
|
|
602
|
+
get: function () {
|
|
603
|
+
return MissingDefaultSecretKeyError;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
Object.defineProperty(exports, 'MissingPublishableKeyError', {
|
|
607
|
+
enumerable: true,
|
|
608
|
+
get: function () {
|
|
609
|
+
return MissingPublishableKeyError;
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
Object.defineProperty(exports, 'MissingSecretKeyError', {
|
|
613
|
+
enumerable: true,
|
|
614
|
+
get: function () {
|
|
615
|
+
return MissingSecretKeyError;
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
Object.defineProperty(exports, 'MissingSupabaseURLError', {
|
|
619
|
+
enumerable: true,
|
|
620
|
+
get: function () {
|
|
621
|
+
return MissingSupabaseURLError;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
499
624
|
Object.defineProperty(exports, 'createAdminClient', {
|
|
500
625
|
enumerable: true,
|
|
501
626
|
get: function () {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supabase/server",
|
|
3
|
-
"version": "0.1.1-rc.
|
|
3
|
+
"version": "0.1.1-rc.28",
|
|
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",
|