@supabase/server 0.1.0-alpha.1 → 0.1.1-rc.25

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.
@@ -2,6 +2,26 @@ import { createClient } from "@supabase/supabase-js";
2
2
  import { createLocalJWKSet, jwtVerify } from "jose";
3
3
 
4
4
  //#region src/errors.ts
5
+ /**
6
+ * Thrown when a required environment variable is missing or malformed.
7
+ *
8
+ * Has a fixed `status` of `500` since environment errors are server-side
9
+ * configuration issues, not client errors.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { EnvError } from '@supabase/server'
14
+ *
15
+ * try {
16
+ * const client = createAdminClient()
17
+ * } catch (e) {
18
+ * if (e instanceof EnvError) {
19
+ * console.error(`Config issue [${e.code}]: ${e.message}`)
20
+ * // → "Config issue [MISSING_SUPABASE_URL]: SUPABASE_URL is required but not set"
21
+ * }
22
+ * }
23
+ * ```
24
+ */
5
25
  var EnvError = class extends Error {
6
26
  constructor(message, code = "ENV_ERROR") {
7
27
  super(message);
@@ -10,6 +30,26 @@ var EnvError = class extends Error {
10
30
  this.code = code;
11
31
  }
12
32
  };
33
+ /**
34
+ * Thrown when authentication or authorization fails.
35
+ *
36
+ * Carries an HTTP `status` code suitable for returning directly in a response
37
+ * (typically `401` for invalid credentials, `500` for server-side auth failures).
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import { AuthError, createSupabaseContext } from '@supabase/server'
42
+ *
43
+ * const { data: ctx, error } = await createSupabaseContext(request, { allow: 'user' })
44
+ * if (error) {
45
+ * // error is an AuthError
46
+ * return Response.json(
47
+ * { error: error.message, code: error.code },
48
+ * { status: error.status },
49
+ * )
50
+ * }
51
+ * ```
52
+ */
13
53
  var AuthError = class extends Error {
14
54
  constructor(message, code = "AUTH_ERROR", status = 401) {
15
55
  super(message);
@@ -21,10 +61,20 @@ var AuthError = class extends Error {
21
61
 
22
62
  //#endregion
23
63
  //#region src/core/resolve-env.ts
64
+ /**
65
+ * Reads an environment variable from the current runtime (Deno, Node.js, or Bun).
66
+ * Cloudflare Workers require node-compat or passing values via `overrides`.
67
+ * @internal
68
+ */
24
69
  function getEnvVar(name) {
25
70
  if (typeof Deno !== "undefined" && Deno.env?.get) return Deno.env.get(name);
26
71
  if (typeof process !== "undefined" && process.env) return process.env[name];
27
72
  }
73
+ /**
74
+ * Parses a JSON string into a `Record<string, string>` key map.
75
+ * Returns an empty object if the input is missing, malformed, or not a plain object.
76
+ * @internal
77
+ */
28
78
  function parseKeys(raw) {
29
79
  if (!raw) return {};
30
80
  try {
@@ -35,6 +85,12 @@ function parseKeys(raw) {
35
85
  return {};
36
86
  }
37
87
  }
88
+ /**
89
+ * Resolves API keys from environment variables. Checks the plural form first
90
+ * (`SUPABASE_PUBLISHABLE_KEYS` as JSON), then falls back to the singular form
91
+ * (`SUPABASE_PUBLISHABLE_KEY` stored as `{ default: "<value>" }`).
92
+ * @internal
93
+ */
38
94
  function resolveKeys(singularVar, pluralVar) {
39
95
  const plural = getEnvVar(pluralVar);
40
96
  if (plural) return parseKeys(plural);
@@ -42,6 +98,12 @@ function resolveKeys(singularVar, pluralVar) {
42
98
  if (singular) return { default: singular };
43
99
  return {};
44
100
  }
101
+ /**
102
+ * Parses a JWKS JSON string into a {@link JsonWebKeySet}.
103
+ * Accepts both `{ keys: [...] }` and bare `[...]` array formats.
104
+ * Returns `null` if the input is missing or malformed.
105
+ * @internal
106
+ */
45
107
  function parseJwks(raw) {
46
108
  if (!raw) return null;
47
109
  try {
@@ -53,6 +115,25 @@ function parseJwks(raw) {
53
115
  return null;
54
116
  }
55
117
  }
118
+ /**
119
+ * Resolves Supabase environment configuration from runtime environment variables.
120
+ *
121
+ * Reads `SUPABASE_URL`, keys (`SUPABASE_PUBLISHABLE_KEYS` / `SUPABASE_SECRET_KEYS`),
122
+ * and `SUPABASE_JWKS`. Works across Deno, Node.js, and Bun. For Cloudflare Workers,
123
+ * use `overrides` or enable node-compat.
124
+ *
125
+ * @param overrides - Partial values that take precedence over env vars.
126
+ * @returns `{ data: SupabaseEnv, error: null }` on success, `{ data: null, error: EnvError }` on failure.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * const { data: env, error } = resolveEnv()
131
+ * if (error) throw error
132
+ *
133
+ * // Override for tests
134
+ * const { data: env } = resolveEnv({ url: 'http://localhost:54321' })
135
+ * ```
136
+ */
56
137
  function resolveEnv(overrides) {
57
138
  const url = overrides?.url ?? getEnvVar("SUPABASE_URL");
58
139
  if (!url) return {
@@ -72,6 +153,23 @@ function resolveEnv(overrides) {
72
153
 
73
154
  //#endregion
74
155
  //#region src/core/create-admin-client.ts
156
+ /**
157
+ * Creates an admin Supabase client that bypasses Row-Level Security.
158
+ *
159
+ * Uses a secret key for authentication, giving full access to all data.
160
+ * Session persistence is disabled (stateless, one client per request).
161
+ *
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
+ * @throws {@link EnvError} If `SUPABASE_URL` is missing or the specified secret key is not found.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * const supabaseAdmin = createAdminClient()
170
+ * const { data } = await supabaseAdmin.from('audit_log').insert({ action: 'user_login' })
171
+ * ```
172
+ */
75
173
  function createAdminClient(env, keyName) {
76
174
  const { data: resolved, error } = resolveEnv(env);
77
175
  if (error) throw error;
@@ -88,6 +186,26 @@ function createAdminClient(env, keyName) {
88
186
 
89
187
  //#endregion
90
188
  //#region src/core/create-context-client.ts
189
+ /**
190
+ * Creates a Supabase client scoped to the caller's context.
191
+ *
192
+ * Configured with a publishable key and (optionally) the caller's JWT,
193
+ * so Row-Level Security policies apply. Session persistence is disabled
194
+ * (stateless, one client per request).
195
+ *
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
+ * @throws {@link EnvError} If `SUPABASE_URL` is missing or the specified publishable key is not found.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const { data: auth } = await verifyAuth(request, { allow: 'user' })
205
+ * const supabase = createContextClient(auth.token)
206
+ * const { data } = await supabase.rpc('get_my_items')
207
+ * ```
208
+ */
91
209
  function createContextClient(token, env, keyName) {
92
210
  const { data: resolved, error } = resolveEnv(env);
93
211
  if (error) throw error;
@@ -107,6 +225,28 @@ function createContextClient(token, env, keyName) {
107
225
 
108
226
  //#endregion
109
227
  //#region src/core/extract-credentials.ts
228
+ /**
229
+ * Extracts authentication credentials from an incoming HTTP request.
230
+ *
231
+ * Reads two headers:
232
+ * - `Authorization: Bearer <token>` → extracted as `token`
233
+ * - `apikey: <key>` → extracted as `apikey`
234
+ *
235
+ * This is a pure extraction step — no validation or verification is performed.
236
+ * Pass the result to {@link verifyCredentials} to validate against allowed auth modes.
237
+ *
238
+ * @param request - The incoming HTTP request.
239
+ * @returns The extracted {@link Credentials}. Fields are `null` when the corresponding header is absent.
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * import { extractCredentials } from '@supabase/server/core'
244
+ *
245
+ * const creds = extractCredentials(request)
246
+ * console.log(creds.token) // "eyJhbGci..." or null
247
+ * console.log(creds.apikey) // "sb-abc123-publishable-..." or null
248
+ * ```
249
+ */
110
250
  function extractCredentials(request) {
111
251
  const authHeader = request.headers.get("authorization");
112
252
  return {
@@ -118,6 +258,12 @@ function extractCredentials(request) {
118
258
  //#endregion
119
259
  //#region src/core/utils/timing-safe-equal.ts
120
260
  const encoder = new TextEncoder();
261
+ /**
262
+ * Compares two strings in constant time to prevent timing attacks.
263
+ * Uses the double-HMAC technique with a random ephemeral key.
264
+ *
265
+ * @internal
266
+ */
121
267
  async function timingSafeEqual(a, b) {
122
268
  const key = crypto.getRandomValues(new Uint8Array(32));
123
269
  const cryptoKey = await crypto.subtle.importKey("raw", key, {
@@ -135,6 +281,18 @@ async function timingSafeEqual(a, b) {
135
281
 
136
282
  //#endregion
137
283
  //#region src/core/verify-credentials.ts
284
+ /**
285
+ * Parses an {@link AllowWithKey} string into its base mode and optional key name.
286
+ *
287
+ * @example
288
+ * ```
289
+ * parseAllowMode('user') → { base: 'user', keyName: null }
290
+ * parseAllowMode('public:web') → { base: 'public', keyName: 'web' }
291
+ * parseAllowMode('secret:*') → { base: 'secret', keyName: '*' }
292
+ * ```
293
+ *
294
+ * @internal
295
+ */
138
296
  function parseAllowMode(mode) {
139
297
  if (mode === "always" || mode === "public" || mode === "secret" || mode === "user") return {
140
298
  base: mode,
@@ -152,6 +310,10 @@ function parseAllowMode(mode) {
152
310
  keyName
153
311
  };
154
312
  }
313
+ /**
314
+ * Converts raw {@link JWTClaims} (snake_case) to a normalized {@link UserClaims} (camelCase).
315
+ * @internal
316
+ */
155
317
  function claimsToUserClaims(claims) {
156
318
  return {
157
319
  id: claims.sub,
@@ -161,6 +323,11 @@ function claimsToUserClaims(claims) {
161
323
  userMetadata: claims.user_metadata
162
324
  };
163
325
  }
326
+ /**
327
+ * Attempts to authenticate credentials against a single auth mode.
328
+ * Returns the {@link AuthResult} on success, or `null` if the mode doesn't match.
329
+ * @internal
330
+ */
164
331
  async function tryMode(mode, credentials, env) {
165
332
  const { base, keyName } = parseAllowMode(mode);
166
333
  switch (base) {
@@ -240,6 +407,27 @@ async function tryMode(mode, credentials, env) {
240
407
  default: return null;
241
408
  }
242
409
  }
410
+ /**
411
+ * Verifies pre-extracted credentials against one or more allowed auth modes.
412
+ *
413
+ * Tries each mode in order — first match wins. Use {@link verifyAuth} to extract
414
+ * and verify in a single call.
415
+ *
416
+ * @param credentials - The credentials to verify (from {@link extractCredentials}).
417
+ * @param options - Allowed auth modes and optional env overrides.
418
+ * @returns `{ data: AuthResult, error: null }` on success, `{ data: null, error: AuthError }` on failure.
419
+ *
420
+ * @example
421
+ * ```ts
422
+ * const credentials = extractCredentials(request)
423
+ * const { data: auth, error } = await verifyCredentials(credentials, {
424
+ * allow: ['user', 'public'],
425
+ * })
426
+ * if (error) {
427
+ * return Response.json({ error: error.message }, { status: error.status })
428
+ * }
429
+ * ```
430
+ */
243
431
  async function verifyCredentials(credentials, options) {
244
432
  const { data: env, error: envError } = resolveEnv(options.env);
245
433
  if (envError) return {
@@ -262,6 +450,35 @@ async function verifyCredentials(credentials, options) {
262
450
 
263
451
  //#endregion
264
452
  //#region src/core/verify-auth.ts
453
+ /**
454
+ * Extracts credentials from a request and verifies them in a single step.
455
+ *
456
+ * This is a convenience function that combines {@link extractCredentials} and
457
+ * {@link verifyCredentials}. Use it when you want the full auth flow without
458
+ * needing to inspect the raw credentials.
459
+ *
460
+ * @param request - The incoming HTTP request.
461
+ * @param options - Auth modes to accept and optional environment overrides.
462
+ *
463
+ * @returns A result tuple: `{ data, error }`.
464
+ * - On success: `{ data: AuthResult, error: null }`
465
+ * - On failure: `{ data: null, error: AuthError }`
466
+ *
467
+ * @example
468
+ * ```ts
469
+ * import { verifyAuth } from '@supabase/server/core'
470
+ *
471
+ * const { data: auth, error } = await verifyAuth(request, {
472
+ * allow: 'user',
473
+ * })
474
+ *
475
+ * if (error) {
476
+ * return Response.json({ error: error.message }, { status: error.status })
477
+ * }
478
+ *
479
+ * console.log(auth.userClaims!.id) // "d0f1a2b3-..."
480
+ * ```
481
+ */
265
482
  async function verifyAuth(request, options) {
266
483
  return verifyCredentials(extractCredentials(request), options);
267
484
  }
@@ -2,6 +2,26 @@ let _supabase_supabase_js = require("@supabase/supabase-js");
2
2
  let jose = require("jose");
3
3
 
4
4
  //#region src/errors.ts
5
+ /**
6
+ * Thrown when a required environment variable is missing or malformed.
7
+ *
8
+ * Has a fixed `status` of `500` since environment errors are server-side
9
+ * configuration issues, not client errors.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { EnvError } from '@supabase/server'
14
+ *
15
+ * try {
16
+ * const client = createAdminClient()
17
+ * } catch (e) {
18
+ * if (e instanceof EnvError) {
19
+ * console.error(`Config issue [${e.code}]: ${e.message}`)
20
+ * // → "Config issue [MISSING_SUPABASE_URL]: SUPABASE_URL is required but not set"
21
+ * }
22
+ * }
23
+ * ```
24
+ */
5
25
  var EnvError = class extends Error {
6
26
  constructor(message, code = "ENV_ERROR") {
7
27
  super(message);
@@ -10,6 +30,26 @@ var EnvError = class extends Error {
10
30
  this.code = code;
11
31
  }
12
32
  };
33
+ /**
34
+ * Thrown when authentication or authorization fails.
35
+ *
36
+ * Carries an HTTP `status` code suitable for returning directly in a response
37
+ * (typically `401` for invalid credentials, `500` for server-side auth failures).
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import { AuthError, createSupabaseContext } from '@supabase/server'
42
+ *
43
+ * const { data: ctx, error } = await createSupabaseContext(request, { allow: 'user' })
44
+ * if (error) {
45
+ * // error is an AuthError
46
+ * return Response.json(
47
+ * { error: error.message, code: error.code },
48
+ * { status: error.status },
49
+ * )
50
+ * }
51
+ * ```
52
+ */
13
53
  var AuthError = class extends Error {
14
54
  constructor(message, code = "AUTH_ERROR", status = 401) {
15
55
  super(message);
@@ -21,10 +61,20 @@ var AuthError = class extends Error {
21
61
 
22
62
  //#endregion
23
63
  //#region src/core/resolve-env.ts
64
+ /**
65
+ * Reads an environment variable from the current runtime (Deno, Node.js, or Bun).
66
+ * Cloudflare Workers require node-compat or passing values via `overrides`.
67
+ * @internal
68
+ */
24
69
  function getEnvVar(name) {
25
70
  if (typeof Deno !== "undefined" && Deno.env?.get) return Deno.env.get(name);
26
71
  if (typeof process !== "undefined" && process.env) return process.env[name];
27
72
  }
73
+ /**
74
+ * Parses a JSON string into a `Record<string, string>` key map.
75
+ * Returns an empty object if the input is missing, malformed, or not a plain object.
76
+ * @internal
77
+ */
28
78
  function parseKeys(raw) {
29
79
  if (!raw) return {};
30
80
  try {
@@ -35,6 +85,12 @@ function parseKeys(raw) {
35
85
  return {};
36
86
  }
37
87
  }
88
+ /**
89
+ * Resolves API keys from environment variables. Checks the plural form first
90
+ * (`SUPABASE_PUBLISHABLE_KEYS` as JSON), then falls back to the singular form
91
+ * (`SUPABASE_PUBLISHABLE_KEY` stored as `{ default: "<value>" }`).
92
+ * @internal
93
+ */
38
94
  function resolveKeys(singularVar, pluralVar) {
39
95
  const plural = getEnvVar(pluralVar);
40
96
  if (plural) return parseKeys(plural);
@@ -42,6 +98,12 @@ function resolveKeys(singularVar, pluralVar) {
42
98
  if (singular) return { default: singular };
43
99
  return {};
44
100
  }
101
+ /**
102
+ * Parses a JWKS JSON string into a {@link JsonWebKeySet}.
103
+ * Accepts both `{ keys: [...] }` and bare `[...]` array formats.
104
+ * Returns `null` if the input is missing or malformed.
105
+ * @internal
106
+ */
45
107
  function parseJwks(raw) {
46
108
  if (!raw) return null;
47
109
  try {
@@ -53,6 +115,25 @@ function parseJwks(raw) {
53
115
  return null;
54
116
  }
55
117
  }
118
+ /**
119
+ * Resolves Supabase environment configuration from runtime environment variables.
120
+ *
121
+ * Reads `SUPABASE_URL`, keys (`SUPABASE_PUBLISHABLE_KEYS` / `SUPABASE_SECRET_KEYS`),
122
+ * and `SUPABASE_JWKS`. Works across Deno, Node.js, and Bun. For Cloudflare Workers,
123
+ * use `overrides` or enable node-compat.
124
+ *
125
+ * @param overrides - Partial values that take precedence over env vars.
126
+ * @returns `{ data: SupabaseEnv, error: null }` on success, `{ data: null, error: EnvError }` on failure.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * const { data: env, error } = resolveEnv()
131
+ * if (error) throw error
132
+ *
133
+ * // Override for tests
134
+ * const { data: env } = resolveEnv({ url: 'http://localhost:54321' })
135
+ * ```
136
+ */
56
137
  function resolveEnv(overrides) {
57
138
  const url = overrides?.url ?? getEnvVar("SUPABASE_URL");
58
139
  if (!url) return {
@@ -72,6 +153,23 @@ function resolveEnv(overrides) {
72
153
 
73
154
  //#endregion
74
155
  //#region src/core/create-admin-client.ts
156
+ /**
157
+ * Creates an admin Supabase client that bypasses Row-Level Security.
158
+ *
159
+ * Uses a secret key for authentication, giving full access to all data.
160
+ * Session persistence is disabled (stateless, one client per request).
161
+ *
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
+ * @throws {@link EnvError} If `SUPABASE_URL` is missing or the specified secret key is not found.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * const supabaseAdmin = createAdminClient()
170
+ * const { data } = await supabaseAdmin.from('audit_log').insert({ action: 'user_login' })
171
+ * ```
172
+ */
75
173
  function createAdminClient(env, keyName) {
76
174
  const { data: resolved, error } = resolveEnv(env);
77
175
  if (error) throw error;
@@ -88,6 +186,26 @@ function createAdminClient(env, keyName) {
88
186
 
89
187
  //#endregion
90
188
  //#region src/core/create-context-client.ts
189
+ /**
190
+ * Creates a Supabase client scoped to the caller's context.
191
+ *
192
+ * Configured with a publishable key and (optionally) the caller's JWT,
193
+ * so Row-Level Security policies apply. Session persistence is disabled
194
+ * (stateless, one client per request).
195
+ *
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
+ * @throws {@link EnvError} If `SUPABASE_URL` is missing or the specified publishable key is not found.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const { data: auth } = await verifyAuth(request, { allow: 'user' })
205
+ * const supabase = createContextClient(auth.token)
206
+ * const { data } = await supabase.rpc('get_my_items')
207
+ * ```
208
+ */
91
209
  function createContextClient(token, env, keyName) {
92
210
  const { data: resolved, error } = resolveEnv(env);
93
211
  if (error) throw error;
@@ -107,6 +225,28 @@ function createContextClient(token, env, keyName) {
107
225
 
108
226
  //#endregion
109
227
  //#region src/core/extract-credentials.ts
228
+ /**
229
+ * Extracts authentication credentials from an incoming HTTP request.
230
+ *
231
+ * Reads two headers:
232
+ * - `Authorization: Bearer <token>` → extracted as `token`
233
+ * - `apikey: <key>` → extracted as `apikey`
234
+ *
235
+ * This is a pure extraction step — no validation or verification is performed.
236
+ * Pass the result to {@link verifyCredentials} to validate against allowed auth modes.
237
+ *
238
+ * @param request - The incoming HTTP request.
239
+ * @returns The extracted {@link Credentials}. Fields are `null` when the corresponding header is absent.
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * import { extractCredentials } from '@supabase/server/core'
244
+ *
245
+ * const creds = extractCredentials(request)
246
+ * console.log(creds.token) // "eyJhbGci..." or null
247
+ * console.log(creds.apikey) // "sb-abc123-publishable-..." or null
248
+ * ```
249
+ */
110
250
  function extractCredentials(request) {
111
251
  const authHeader = request.headers.get("authorization");
112
252
  return {
@@ -118,6 +258,12 @@ function extractCredentials(request) {
118
258
  //#endregion
119
259
  //#region src/core/utils/timing-safe-equal.ts
120
260
  const encoder = new TextEncoder();
261
+ /**
262
+ * Compares two strings in constant time to prevent timing attacks.
263
+ * Uses the double-HMAC technique with a random ephemeral key.
264
+ *
265
+ * @internal
266
+ */
121
267
  async function timingSafeEqual(a, b) {
122
268
  const key = crypto.getRandomValues(new Uint8Array(32));
123
269
  const cryptoKey = await crypto.subtle.importKey("raw", key, {
@@ -135,6 +281,18 @@ async function timingSafeEqual(a, b) {
135
281
 
136
282
  //#endregion
137
283
  //#region src/core/verify-credentials.ts
284
+ /**
285
+ * Parses an {@link AllowWithKey} string into its base mode and optional key name.
286
+ *
287
+ * @example
288
+ * ```
289
+ * parseAllowMode('user') → { base: 'user', keyName: null }
290
+ * parseAllowMode('public:web') → { base: 'public', keyName: 'web' }
291
+ * parseAllowMode('secret:*') → { base: 'secret', keyName: '*' }
292
+ * ```
293
+ *
294
+ * @internal
295
+ */
138
296
  function parseAllowMode(mode) {
139
297
  if (mode === "always" || mode === "public" || mode === "secret" || mode === "user") return {
140
298
  base: mode,
@@ -152,6 +310,10 @@ function parseAllowMode(mode) {
152
310
  keyName
153
311
  };
154
312
  }
313
+ /**
314
+ * Converts raw {@link JWTClaims} (snake_case) to a normalized {@link UserClaims} (camelCase).
315
+ * @internal
316
+ */
155
317
  function claimsToUserClaims(claims) {
156
318
  return {
157
319
  id: claims.sub,
@@ -161,6 +323,11 @@ function claimsToUserClaims(claims) {
161
323
  userMetadata: claims.user_metadata
162
324
  };
163
325
  }
326
+ /**
327
+ * Attempts to authenticate credentials against a single auth mode.
328
+ * Returns the {@link AuthResult} on success, or `null` if the mode doesn't match.
329
+ * @internal
330
+ */
164
331
  async function tryMode(mode, credentials, env) {
165
332
  const { base, keyName } = parseAllowMode(mode);
166
333
  switch (base) {
@@ -240,6 +407,27 @@ async function tryMode(mode, credentials, env) {
240
407
  default: return null;
241
408
  }
242
409
  }
410
+ /**
411
+ * Verifies pre-extracted credentials against one or more allowed auth modes.
412
+ *
413
+ * Tries each mode in order — first match wins. Use {@link verifyAuth} to extract
414
+ * and verify in a single call.
415
+ *
416
+ * @param credentials - The credentials to verify (from {@link extractCredentials}).
417
+ * @param options - Allowed auth modes and optional env overrides.
418
+ * @returns `{ data: AuthResult, error: null }` on success, `{ data: null, error: AuthError }` on failure.
419
+ *
420
+ * @example
421
+ * ```ts
422
+ * const credentials = extractCredentials(request)
423
+ * const { data: auth, error } = await verifyCredentials(credentials, {
424
+ * allow: ['user', 'public'],
425
+ * })
426
+ * if (error) {
427
+ * return Response.json({ error: error.message }, { status: error.status })
428
+ * }
429
+ * ```
430
+ */
243
431
  async function verifyCredentials(credentials, options) {
244
432
  const { data: env, error: envError } = resolveEnv(options.env);
245
433
  if (envError) return {
@@ -262,6 +450,35 @@ async function verifyCredentials(credentials, options) {
262
450
 
263
451
  //#endregion
264
452
  //#region src/core/verify-auth.ts
453
+ /**
454
+ * Extracts credentials from a request and verifies them in a single step.
455
+ *
456
+ * This is a convenience function that combines {@link extractCredentials} and
457
+ * {@link verifyCredentials}. Use it when you want the full auth flow without
458
+ * needing to inspect the raw credentials.
459
+ *
460
+ * @param request - The incoming HTTP request.
461
+ * @param options - Auth modes to accept and optional environment overrides.
462
+ *
463
+ * @returns A result tuple: `{ data, error }`.
464
+ * - On success: `{ data: AuthResult, error: null }`
465
+ * - On failure: `{ data: null, error: AuthError }`
466
+ *
467
+ * @example
468
+ * ```ts
469
+ * import { verifyAuth } from '@supabase/server/core'
470
+ *
471
+ * const { data: auth, error } = await verifyAuth(request, {
472
+ * allow: 'user',
473
+ * })
474
+ *
475
+ * if (error) {
476
+ * return Response.json({ error: error.message }, { status: error.status })
477
+ * }
478
+ *
479
+ * console.log(auth.userClaims!.id) // "d0f1a2b3-..."
480
+ * ```
481
+ */
265
482
  async function verifyAuth(request, options) {
266
483
  return verifyCredentials(extractCredentials(request), options);
267
484
  }
@@ -2,6 +2,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  //#region src/wrappers/webhook.ts
4
4
  const encoder = new TextEncoder();
5
+ /**
6
+ * Verifies a webhook signature using HMAC-SHA256 with timing-safe comparison.
7
+ *
8
+ * @param payload - The raw request body as a string.
9
+ * @param signature - The hex-encoded signature from the webhook header.
10
+ * @param secret - The shared secret used to sign webhooks.
11
+ * @returns `true` if the signature is valid, `false` otherwise.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const payload = await req.text()
16
+ * const signature = req.headers.get('x-webhook-signature') ?? ''
17
+ *
18
+ * const isValid = await verifyWebhookSignature(payload, signature, secret)
19
+ * if (!isValid) {
20
+ * return Response.json({ error: 'Invalid signature' }, { status: 401 })
21
+ * }
22
+ * ```
23
+ */
5
24
  async function verifyWebhookSignature(payload, signature, secret) {
6
25
  const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
7
26
  name: "HMAC",
@@ -1,4 +1,23 @@
1
1
  //#region src/wrappers/webhook.d.ts
2
+ /**
3
+ * Verifies a webhook signature using HMAC-SHA256 with timing-safe comparison.
4
+ *
5
+ * @param payload - The raw request body as a string.
6
+ * @param signature - The hex-encoded signature from the webhook header.
7
+ * @param secret - The shared secret used to sign webhooks.
8
+ * @returns `true` if the signature is valid, `false` otherwise.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const payload = await req.text()
13
+ * const signature = req.headers.get('x-webhook-signature') ?? ''
14
+ *
15
+ * const isValid = await verifyWebhookSignature(payload, signature, secret)
16
+ * if (!isValid) {
17
+ * return Response.json({ error: 'Invalid signature' }, { status: 401 })
18
+ * }
19
+ * ```
20
+ */
2
21
  declare function verifyWebhookSignature(payload: string, signature: string, secret: string): Promise<boolean>;
3
22
  //#endregion
4
23
  export { verifyWebhookSignature };