@supabase/server 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,270 @@
1
+ import { createClient } from "@supabase/supabase-js";
2
+ import { createLocalJWKSet, jwtVerify } from "jose";
3
+
4
+ //#region src/errors.ts
5
+ var EnvError = class extends Error {
6
+ constructor(message, code = "ENV_ERROR") {
7
+ super(message);
8
+ this.status = 500;
9
+ this.name = "EnvError";
10
+ this.code = code;
11
+ }
12
+ };
13
+ var AuthError = class extends Error {
14
+ constructor(message, code = "AUTH_ERROR", status = 401) {
15
+ super(message);
16
+ this.name = "AuthError";
17
+ this.code = code;
18
+ this.status = status;
19
+ }
20
+ };
21
+
22
+ //#endregion
23
+ //#region src/core/resolve-env.ts
24
+ function getEnvVar(name) {
25
+ if (typeof Deno !== "undefined" && Deno.env?.get) return Deno.env.get(name);
26
+ if (typeof process !== "undefined" && process.env) return process.env[name];
27
+ }
28
+ function parseKeys(raw) {
29
+ if (!raw) return {};
30
+ try {
31
+ const parsed = JSON.parse(raw);
32
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
33
+ return parsed;
34
+ } catch {
35
+ return {};
36
+ }
37
+ }
38
+ function resolveKeys(singularVar, pluralVar) {
39
+ const plural = getEnvVar(pluralVar);
40
+ if (plural) return parseKeys(plural);
41
+ const singular = getEnvVar(singularVar);
42
+ if (singular) return { default: singular };
43
+ return {};
44
+ }
45
+ function parseJwks(raw) {
46
+ if (!raw) return null;
47
+ try {
48
+ const parsed = JSON.parse(raw);
49
+ if (Array.isArray(parsed)) return { keys: parsed };
50
+ if (parsed?.keys && Array.isArray(parsed.keys)) return parsed;
51
+ return null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+ function resolveEnv(overrides) {
57
+ const url = overrides?.url ?? getEnvVar("SUPABASE_URL");
58
+ if (!url) return {
59
+ data: null,
60
+ error: new EnvError("SUPABASE_URL is required but not set", "MISSING_SUPABASE_URL")
61
+ };
62
+ return {
63
+ data: {
64
+ url,
65
+ publishableKeys: overrides?.publishableKeys ?? resolveKeys("SUPABASE_PUBLISHABLE_KEY", "SUPABASE_PUBLISHABLE_KEYS"),
66
+ secretKeys: overrides?.secretKeys ?? resolveKeys("SUPABASE_SECRET_KEY", "SUPABASE_SECRET_KEYS"),
67
+ jwks: overrides?.jwks ?? parseJwks(getEnvVar("SUPABASE_JWKS"))
68
+ },
69
+ error: null
70
+ };
71
+ }
72
+
73
+ //#endregion
74
+ //#region src/core/create-admin-client.ts
75
+ function createAdminClient(env, keyName) {
76
+ const { data: resolved, error } = resolveEnv(env);
77
+ if (error) throw error;
78
+ const name = keyName ?? "default";
79
+ const keys = resolved.secretKeys;
80
+ const secretKey = keys[name] ?? (keyName == null ? Object.values(keys)[0] : void 0);
81
+ if (!secretKey) throw new EnvError(name === "default" ? "No default secret key found. Set SUPABASE_SECRET_KEY or include a \"default\" entry in SUPABASE_SECRET_KEYS." : `No "${name}" secret key found. Include a "${name}" entry in SUPABASE_SECRET_KEYS.`, "MISSING_SECRET_KEY");
82
+ return createClient(resolved.url, secretKey, { auth: {
83
+ persistSession: false,
84
+ autoRefreshToken: false,
85
+ detectSessionInUrl: false
86
+ } });
87
+ }
88
+
89
+ //#endregion
90
+ //#region src/core/create-context-client.ts
91
+ function createContextClient(token, env, keyName) {
92
+ const { data: resolved, error } = resolveEnv(env);
93
+ if (error) throw error;
94
+ const name = keyName ?? "default";
95
+ const keys = resolved.publishableKeys;
96
+ const anonKey = keys[name] ?? (keyName == null ? Object.values(keys)[0] : void 0);
97
+ if (!anonKey) throw new EnvError(name === "default" ? "No default publishable key found. Set SUPABASE_PUBLISHABLE_KEY or include a \"default\" entry in SUPABASE_PUBLISHABLE_KEYS." : `No "${name}" publishable key found. Include a "${name}" entry in SUPABASE_PUBLISHABLE_KEYS.`, "MISSING_PUBLISHABLE_KEY");
98
+ return createClient(resolved.url, anonKey, {
99
+ global: { headers: token ? { Authorization: `Bearer ${token}` } : {} },
100
+ auth: {
101
+ persistSession: false,
102
+ autoRefreshToken: false,
103
+ detectSessionInUrl: false
104
+ }
105
+ });
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/core/extract-credentials.ts
110
+ function extractCredentials(request) {
111
+ const authHeader = request.headers.get("authorization");
112
+ return {
113
+ token: authHeader?.startsWith("Bearer ") ? authHeader.slice(7) || null : null,
114
+ apikey: request.headers.get("apikey")
115
+ };
116
+ }
117
+
118
+ //#endregion
119
+ //#region src/core/utils/timing-safe-equal.ts
120
+ const encoder = new TextEncoder();
121
+ async function timingSafeEqual(a, b) {
122
+ const key = crypto.getRandomValues(new Uint8Array(32));
123
+ const cryptoKey = await crypto.subtle.importKey("raw", key, {
124
+ name: "HMAC",
125
+ hash: "SHA-256"
126
+ }, false, ["sign"]);
127
+ const [sigA, sigB] = await Promise.all([crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(a)), crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(b))]);
128
+ if (sigA.byteLength !== sigB.byteLength) return false;
129
+ const viewA = new Uint8Array(sigA);
130
+ const viewB = new Uint8Array(sigB);
131
+ let result = 0;
132
+ for (let i = 0; i < viewA.length; i++) result |= viewA[i] ^ viewB[i];
133
+ return result === 0;
134
+ }
135
+
136
+ //#endregion
137
+ //#region src/core/verify-credentials.ts
138
+ function parseAllowMode(mode) {
139
+ if (mode === "always" || mode === "public" || mode === "secret" || mode === "user") return {
140
+ base: mode,
141
+ keyName: null
142
+ };
143
+ const colonIndex = mode.indexOf(":");
144
+ const base = mode.slice(0, colonIndex);
145
+ const keyName = mode.slice(colonIndex + 1);
146
+ if (!keyName) return {
147
+ base,
148
+ keyName: null
149
+ };
150
+ return {
151
+ base,
152
+ keyName
153
+ };
154
+ }
155
+ function claimsToUserClaims(claims) {
156
+ return {
157
+ id: claims.sub,
158
+ role: claims.role,
159
+ email: claims.email,
160
+ appMetadata: claims.app_metadata,
161
+ userMetadata: claims.user_metadata
162
+ };
163
+ }
164
+ async function tryMode(mode, credentials, env) {
165
+ const { base, keyName } = parseAllowMode(mode);
166
+ switch (base) {
167
+ case "always": return {
168
+ authType: "always",
169
+ token: null,
170
+ userClaims: null,
171
+ claims: null,
172
+ keyName: null
173
+ };
174
+ case "public": {
175
+ if (!credentials.apikey) return null;
176
+ const keys = env.publishableKeys;
177
+ if (keyName === "*") {
178
+ for (const [name, value] of Object.entries(keys)) if (await timingSafeEqual(credentials.apikey, value)) return {
179
+ authType: "public",
180
+ token: null,
181
+ userClaims: null,
182
+ claims: null,
183
+ keyName: name
184
+ };
185
+ } else {
186
+ const name = keyName ?? "default";
187
+ const value = keys[name];
188
+ if (value && await timingSafeEqual(credentials.apikey, value)) return {
189
+ authType: "public",
190
+ token: null,
191
+ userClaims: null,
192
+ claims: null,
193
+ keyName: name
194
+ };
195
+ }
196
+ return null;
197
+ }
198
+ case "secret": {
199
+ if (!credentials.apikey) return null;
200
+ const keys = env.secretKeys;
201
+ if (keyName === "*") {
202
+ for (const [name, value] of Object.entries(keys)) if (await timingSafeEqual(credentials.apikey, value)) return {
203
+ authType: "secret",
204
+ token: null,
205
+ userClaims: null,
206
+ claims: null,
207
+ keyName: name
208
+ };
209
+ } else {
210
+ const name = keyName ?? "default";
211
+ const value = keys[name];
212
+ if (value && await timingSafeEqual(credentials.apikey, value)) return {
213
+ authType: "secret",
214
+ token: null,
215
+ userClaims: null,
216
+ claims: null,
217
+ keyName: name
218
+ };
219
+ }
220
+ return null;
221
+ }
222
+ case "user":
223
+ if (!credentials.token) return null;
224
+ if (!env.jwks) return null;
225
+ try {
226
+ const jwkSet = createLocalJWKSet(env.jwks);
227
+ const { payload } = await jwtVerify(credentials.token, jwkSet);
228
+ if (typeof payload.sub !== "string") return null;
229
+ const claims = payload;
230
+ return {
231
+ authType: "user",
232
+ token: credentials.token,
233
+ userClaims: claimsToUserClaims(claims),
234
+ claims,
235
+ keyName: null
236
+ };
237
+ } catch {
238
+ return null;
239
+ }
240
+ default: return null;
241
+ }
242
+ }
243
+ async function verifyCredentials(credentials, options) {
244
+ const { data: env, error: envError } = resolveEnv(options.env);
245
+ if (envError) return {
246
+ data: null,
247
+ error: new AuthError(envError.message, envError.code, 500)
248
+ };
249
+ const modes = Array.isArray(options.allow) ? options.allow : [options.allow];
250
+ for (const mode of modes) {
251
+ const result = await tryMode(mode, credentials, env);
252
+ if (result) return {
253
+ data: result,
254
+ error: null
255
+ };
256
+ }
257
+ return {
258
+ data: null,
259
+ error: new AuthError("Invalid credentials", "INVALID_CREDENTIALS", 401)
260
+ };
261
+ }
262
+
263
+ //#endregion
264
+ //#region src/core/verify-auth.ts
265
+ async function verifyAuth(request, options) {
266
+ return verifyCredentials(extractCredentials(request), options);
267
+ }
268
+
269
+ //#endregion
270
+ export { createAdminClient as a, EnvError as c, createContextClient as i, verifyCredentials as n, resolveEnv as o, extractCredentials as r, AuthError as s, verifyAuth as t };
@@ -0,0 +1,26 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ //#region src/wrappers/webhook.ts
4
+ const encoder = new TextEncoder();
5
+ async function verifyWebhookSignature(payload, signature, secret) {
6
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
7
+ name: "HMAC",
8
+ hash: "SHA-256"
9
+ }, false, ["sign"]);
10
+ const expected = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
11
+ const expectedHex = Array.from(new Uint8Array(expected)).map((b) => b.toString(16).padStart(2, "0")).join("");
12
+ const compareKey = await crypto.subtle.importKey("raw", crypto.getRandomValues(new Uint8Array(32)), {
13
+ name: "HMAC",
14
+ hash: "SHA-256"
15
+ }, false, ["sign"]);
16
+ const [sigA, sigB] = await Promise.all([crypto.subtle.sign("HMAC", compareKey, encoder.encode(expectedHex)), crypto.subtle.sign("HMAC", compareKey, encoder.encode(signature))]);
17
+ const viewA = new Uint8Array(sigA);
18
+ const viewB = new Uint8Array(sigB);
19
+ if (viewA.length !== viewB.length) return false;
20
+ let result = 0;
21
+ for (let i = 0; i < viewA.length; i++) result |= viewA[i] ^ viewB[i];
22
+ return result === 0;
23
+ }
24
+
25
+ //#endregion
26
+ exports.verifyWebhookSignature = verifyWebhookSignature;
@@ -0,0 +1,4 @@
1
+ //#region src/wrappers/webhook.d.ts
2
+ declare function verifyWebhookSignature(payload: string, signature: string, secret: string): Promise<boolean>;
3
+ //#endregion
4
+ export { verifyWebhookSignature };
@@ -0,0 +1,4 @@
1
+ //#region src/wrappers/webhook.d.ts
2
+ declare function verifyWebhookSignature(payload: string, signature: string, secret: string): Promise<boolean>;
3
+ //#endregion
4
+ export { verifyWebhookSignature };
@@ -0,0 +1,24 @@
1
+ //#region src/wrappers/webhook.ts
2
+ const encoder = new TextEncoder();
3
+ async function verifyWebhookSignature(payload, signature, secret) {
4
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
5
+ name: "HMAC",
6
+ hash: "SHA-256"
7
+ }, false, ["sign"]);
8
+ const expected = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
9
+ const expectedHex = Array.from(new Uint8Array(expected)).map((b) => b.toString(16).padStart(2, "0")).join("");
10
+ const compareKey = await crypto.subtle.importKey("raw", crypto.getRandomValues(new Uint8Array(32)), {
11
+ name: "HMAC",
12
+ hash: "SHA-256"
13
+ }, false, ["sign"]);
14
+ const [sigA, sigB] = await Promise.all([crypto.subtle.sign("HMAC", compareKey, encoder.encode(expectedHex)), crypto.subtle.sign("HMAC", compareKey, encoder.encode(signature))]);
15
+ const viewA = new Uint8Array(sigA);
16
+ const viewB = new Uint8Array(sigB);
17
+ if (viewA.length !== viewB.length) return false;
18
+ let result = 0;
19
+ for (let i = 0; i < viewA.length; i++) result |= viewA[i] ^ viewB[i];
20
+ return result === 0;
21
+ }
22
+
23
+ //#endregion
24
+ export { verifyWebhookSignature };
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "@supabase/server",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Server-side utilities for Supabase. Handles auth, client creation, and context injection so you write business logic, not boilerplate.",
5
+ "keywords": [
6
+ "edge",
7
+ "functions",
8
+ "supabase"
9
+ ],
10
+ "homepage": "https://github.com/supabase/edge-functions#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/supabase/edge-functions/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/supabase/edge-functions.git"
17
+ },
18
+ "license": "MIT",
19
+ "author": "supabase",
20
+ "type": "module",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.mts",
24
+ "import": "./dist/index.mjs",
25
+ "require": "./dist/index.cjs"
26
+ },
27
+ "./core": {
28
+ "types": "./dist/core/index.d.mts",
29
+ "import": "./dist/core/index.mjs",
30
+ "require": "./dist/core/index.cjs"
31
+ },
32
+ "./wrappers": {
33
+ "types": "./dist/wrappers/index.d.mts",
34
+ "import": "./dist/wrappers/index.mjs",
35
+ "require": "./dist/wrappers/index.cjs"
36
+ },
37
+ "./adapters/hono": {
38
+ "types": "./dist/adapters/hono/index.d.mts",
39
+ "import": "./dist/adapters/hono/index.mjs",
40
+ "require": "./dist/adapters/hono/index.cjs"
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
44
+ "main": "./dist/index.cjs",
45
+ "types": "./dist/index.d.cts",
46
+ "sideEffects": false,
47
+ "files": [
48
+ "dist"
49
+ ],
50
+ "engines": {
51
+ "node": ">=20"
52
+ },
53
+ "scripts": {
54
+ "build": "tsdown",
55
+ "dev": "tsdown --watch",
56
+ "format": "prettier --write .",
57
+ "lint": "eslint src",
58
+ "lint:fix": "eslint src --fix",
59
+ "prepare": "simple-git-hooks",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "typecheck": "tsc --noEmit"
63
+ },
64
+ "simple-git-hooks": {
65
+ "pre-commit": "pnpm pretty-quick --staged",
66
+ "commit-msg": "pnpm commitlint --edit \"$1\""
67
+ },
68
+ "pnpm": {
69
+ "onlyBuiltDependencies": [
70
+ "simple-git-hooks"
71
+ ]
72
+ },
73
+ "peerDependencies": {
74
+ "@supabase/supabase-js": "^2.0.0",
75
+ "hono": "^4.0.0"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "hono": {
79
+ "optional": true
80
+ }
81
+ },
82
+ "devDependencies": {
83
+ "@commitlint/cli": "^20.4.2",
84
+ "@commitlint/config-conventional": "^20.4.2",
85
+ "@supabase/supabase-js": "^2.98.0",
86
+ "eslint": "^10.0.2",
87
+ "hono": "^4.12.5",
88
+ "prettier": "3.8.1",
89
+ "pretty-quick": "^4.2.2",
90
+ "simple-git-hooks": "^2.13.1",
91
+ "tsdown": "^0.20.3",
92
+ "typescript": "^5.9.3",
93
+ "typescript-eslint": "^8.56.1",
94
+ "vitest": "^4.0.18"
95
+ },
96
+ "dependencies": {
97
+ "jose": "^6.2.0"
98
+ }
99
+ }