@every-app/sdk 0.0.3 → 0.0.5
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/package.json +7 -3
- package/src/client/_internal/useEveryAppRouter.tsx +3 -3
- package/src/client/_internal/useEveryAppSession.tsx +1 -6
- package/src/client/session-manager.test.ts +796 -0
- package/src/client/session-manager.ts +92 -188
- package/src/server/auth-config.ts +0 -1
- package/src/server/authenticateRequest.test.ts +416 -0
- package/src/server/authenticateRequest.ts +11 -6
- package/src/server/getLocalD1Url.ts +10 -12
- package/src/server/types.ts +0 -4
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { SignJWT, generateKeyPair, exportJWK } from "jose";
|
|
3
|
+
|
|
4
|
+
// Mock the cloudflare:workers module
|
|
5
|
+
vi.mock("cloudflare:workers", () => ({
|
|
6
|
+
env: {
|
|
7
|
+
GATEWAY_URL: "https://gateway.example.com",
|
|
8
|
+
EVERY_APP_GATEWAY: null,
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
// Mock @tanstack/react-start/server
|
|
13
|
+
vi.mock("@tanstack/react-start/server", () => ({
|
|
14
|
+
getRequest: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import { authenticateRequest } from "./authenticateRequest";
|
|
18
|
+
import type { AuthConfig } from "./types";
|
|
19
|
+
|
|
20
|
+
describe("authenticateRequest", () => {
|
|
21
|
+
let keyPair: Awaited<ReturnType<typeof generateKeyPair>>;
|
|
22
|
+
let jwks: { keys: object[] };
|
|
23
|
+
|
|
24
|
+
const authConfig: AuthConfig = {
|
|
25
|
+
issuer: "https://gateway.example.com",
|
|
26
|
+
audience: "test-app",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
// Generate a fresh key pair for each test
|
|
31
|
+
keyPair = await generateKeyPair("RS256");
|
|
32
|
+
const publicJwk = await exportJWK(keyPair.publicKey);
|
|
33
|
+
|
|
34
|
+
jwks = {
|
|
35
|
+
keys: [
|
|
36
|
+
{
|
|
37
|
+
...publicJwk,
|
|
38
|
+
kid: "test-key-1",
|
|
39
|
+
use: "sig",
|
|
40
|
+
alg: "RS256",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Mock global fetch for JWKS endpoint
|
|
46
|
+
global.fetch = vi.fn().mockImplementation(async (url: string) => {
|
|
47
|
+
if (url.includes("/api/embedded/jwks")) {
|
|
48
|
+
return new Response(JSON.stringify(jwks), {
|
|
49
|
+
status: 200,
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return new Response("Not Found", { status: 404 });
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
vi.restoreAllMocks();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
async function createValidToken(overrides: Record<string, unknown> = {}) {
|
|
62
|
+
const jwt = await new SignJWT({
|
|
63
|
+
email: "user@example.com",
|
|
64
|
+
appId: "test-app",
|
|
65
|
+
permissions: ["read", "write"],
|
|
66
|
+
...overrides,
|
|
67
|
+
})
|
|
68
|
+
.setProtectedHeader({ alg: "RS256" })
|
|
69
|
+
.setSubject("user-123")
|
|
70
|
+
.setIssuer(authConfig.issuer)
|
|
71
|
+
.setAudience(authConfig.audience)
|
|
72
|
+
.setExpirationTime("1h")
|
|
73
|
+
.setIssuedAt()
|
|
74
|
+
.sign(keyPair.privateKey);
|
|
75
|
+
|
|
76
|
+
return jwt;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createRequest(authHeader?: string): Request {
|
|
80
|
+
const headers = new Headers();
|
|
81
|
+
if (authHeader) {
|
|
82
|
+
headers.set("authorization", authHeader);
|
|
83
|
+
}
|
|
84
|
+
return new Request("https://app.example.com/api/test", { headers });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe("missing or invalid authorization header", () => {
|
|
88
|
+
it("returns null when no authorization header is present", async () => {
|
|
89
|
+
const request = createRequest();
|
|
90
|
+
const result = await authenticateRequest(authConfig, request);
|
|
91
|
+
expect(result).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns null for empty authorization header", async () => {
|
|
95
|
+
const request = createRequest("");
|
|
96
|
+
const result = await authenticateRequest(authConfig, request);
|
|
97
|
+
expect(result).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns null for non-Bearer authorization", async () => {
|
|
101
|
+
const request = createRequest("Basic dXNlcjpwYXNz");
|
|
102
|
+
const result = await authenticateRequest(authConfig, request);
|
|
103
|
+
expect(result).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("returns null for malformed Bearer token (no space)", async () => {
|
|
107
|
+
const request = createRequest("BearereyJhbGciOiJSUzI1NiJ9");
|
|
108
|
+
const result = await authenticateRequest(authConfig, request);
|
|
109
|
+
expect(result).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("returns null for lowercase bearer prefix", async () => {
|
|
113
|
+
const token = await createValidToken();
|
|
114
|
+
const request = createRequest(`bearer ${token}`);
|
|
115
|
+
const result = await authenticateRequest(authConfig, request);
|
|
116
|
+
expect(result).toBeNull();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("valid token verification", () => {
|
|
121
|
+
it("returns payload for valid token with correct issuer and audience", async () => {
|
|
122
|
+
const token = await createValidToken();
|
|
123
|
+
const request = createRequest(`Bearer ${token}`);
|
|
124
|
+
|
|
125
|
+
const result = await authenticateRequest(authConfig, request);
|
|
126
|
+
|
|
127
|
+
expect(result).not.toBeNull();
|
|
128
|
+
expect(result!.sub).toBe("user-123");
|
|
129
|
+
expect(result!.email).toBe("user@example.com");
|
|
130
|
+
expect(result!.appId).toBe("test-app");
|
|
131
|
+
expect(result!.permissions).toEqual(["read", "write"]);
|
|
132
|
+
expect(result!.iss).toBe(authConfig.issuer);
|
|
133
|
+
expect(result!.aud).toBe(authConfig.audience);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("includes iat and exp claims in returned payload", async () => {
|
|
137
|
+
const token = await createValidToken();
|
|
138
|
+
const request = createRequest(`Bearer ${token}`);
|
|
139
|
+
|
|
140
|
+
const result = await authenticateRequest(authConfig, request);
|
|
141
|
+
|
|
142
|
+
expect(result).not.toBeNull();
|
|
143
|
+
expect(typeof result!.iat).toBe("number");
|
|
144
|
+
expect(typeof result!.exp).toBe("number");
|
|
145
|
+
expect(result!.exp).toBeGreaterThan(result!.iat);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("token expiration", () => {
|
|
150
|
+
it("returns null for expired token", async () => {
|
|
151
|
+
const jwt = await new SignJWT({ email: "user@example.com" })
|
|
152
|
+
.setProtectedHeader({ alg: "RS256" })
|
|
153
|
+
.setSubject("user-123")
|
|
154
|
+
.setIssuer(authConfig.issuer)
|
|
155
|
+
.setAudience(authConfig.audience)
|
|
156
|
+
.setExpirationTime("-1h") // Expired 1 hour ago
|
|
157
|
+
.setIssuedAt(Math.floor(Date.now() / 1000) - 7200) // Issued 2 hours ago
|
|
158
|
+
.sign(keyPair.privateKey);
|
|
159
|
+
|
|
160
|
+
const request = createRequest(`Bearer ${jwt}`);
|
|
161
|
+
const result = await authenticateRequest(authConfig, request);
|
|
162
|
+
|
|
163
|
+
expect(result).toBeNull();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("issuer validation", () => {
|
|
168
|
+
it("returns null for token with wrong issuer", async () => {
|
|
169
|
+
const jwt = await new SignJWT({ email: "user@example.com" })
|
|
170
|
+
.setProtectedHeader({ alg: "RS256" })
|
|
171
|
+
.setSubject("user-123")
|
|
172
|
+
.setIssuer("https://malicious.example.com") // Wrong issuer
|
|
173
|
+
.setAudience(authConfig.audience)
|
|
174
|
+
.setExpirationTime("1h")
|
|
175
|
+
.setIssuedAt()
|
|
176
|
+
.sign(keyPair.privateKey);
|
|
177
|
+
|
|
178
|
+
const request = createRequest(`Bearer ${jwt}`);
|
|
179
|
+
const result = await authenticateRequest(authConfig, request);
|
|
180
|
+
|
|
181
|
+
expect(result).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("returns null for token with missing issuer", async () => {
|
|
185
|
+
const jwt = await new SignJWT({ email: "user@example.com" })
|
|
186
|
+
.setProtectedHeader({ alg: "RS256" })
|
|
187
|
+
.setSubject("user-123")
|
|
188
|
+
// No issuer set
|
|
189
|
+
.setAudience(authConfig.audience)
|
|
190
|
+
.setExpirationTime("1h")
|
|
191
|
+
.setIssuedAt()
|
|
192
|
+
.sign(keyPair.privateKey);
|
|
193
|
+
|
|
194
|
+
const request = createRequest(`Bearer ${jwt}`);
|
|
195
|
+
const result = await authenticateRequest(authConfig, request);
|
|
196
|
+
|
|
197
|
+
expect(result).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("audience validation", () => {
|
|
202
|
+
it("returns null for token with wrong audience", async () => {
|
|
203
|
+
const jwt = await new SignJWT({ email: "user@example.com" })
|
|
204
|
+
.setProtectedHeader({ alg: "RS256" })
|
|
205
|
+
.setSubject("user-123")
|
|
206
|
+
.setIssuer(authConfig.issuer)
|
|
207
|
+
.setAudience("wrong-app") // Wrong audience
|
|
208
|
+
.setExpirationTime("1h")
|
|
209
|
+
.setIssuedAt()
|
|
210
|
+
.sign(keyPair.privateKey);
|
|
211
|
+
|
|
212
|
+
const request = createRequest(`Bearer ${jwt}`);
|
|
213
|
+
const result = await authenticateRequest(authConfig, request);
|
|
214
|
+
|
|
215
|
+
expect(result).toBeNull();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("returns null for token with missing audience", async () => {
|
|
219
|
+
const jwt = await new SignJWT({ email: "user@example.com" })
|
|
220
|
+
.setProtectedHeader({ alg: "RS256" })
|
|
221
|
+
.setSubject("user-123")
|
|
222
|
+
.setIssuer(authConfig.issuer)
|
|
223
|
+
// No audience set
|
|
224
|
+
.setExpirationTime("1h")
|
|
225
|
+
.setIssuedAt()
|
|
226
|
+
.sign(keyPair.privateKey);
|
|
227
|
+
|
|
228
|
+
const request = createRequest(`Bearer ${jwt}`);
|
|
229
|
+
const result = await authenticateRequest(authConfig, request);
|
|
230
|
+
|
|
231
|
+
expect(result).toBeNull();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe("signature validation", () => {
|
|
236
|
+
it("returns null for token signed with different key", async () => {
|
|
237
|
+
// Generate a different key pair
|
|
238
|
+
const differentKeyPair = await generateKeyPair("RS256");
|
|
239
|
+
|
|
240
|
+
const jwt = await new SignJWT({ email: "user@example.com" })
|
|
241
|
+
.setProtectedHeader({ alg: "RS256" })
|
|
242
|
+
.setSubject("user-123")
|
|
243
|
+
.setIssuer(authConfig.issuer)
|
|
244
|
+
.setAudience(authConfig.audience)
|
|
245
|
+
.setExpirationTime("1h")
|
|
246
|
+
.setIssuedAt()
|
|
247
|
+
.sign(differentKeyPair.privateKey); // Different key!
|
|
248
|
+
|
|
249
|
+
const request = createRequest(`Bearer ${jwt}`);
|
|
250
|
+
const result = await authenticateRequest(authConfig, request);
|
|
251
|
+
|
|
252
|
+
expect(result).toBeNull();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("returns null for tampered token payload", async () => {
|
|
256
|
+
const token = await createValidToken();
|
|
257
|
+
// Tamper with the payload by modifying the base64
|
|
258
|
+
const [header, _payload, signature] = token.split(".");
|
|
259
|
+
const tamperedPayload = btoa(
|
|
260
|
+
JSON.stringify({ sub: "attacker-id", email: "attacker@evil.com" }),
|
|
261
|
+
)
|
|
262
|
+
.replace(/=/g, "")
|
|
263
|
+
.replace(/\+/g, "-")
|
|
264
|
+
.replace(/\//g, "_");
|
|
265
|
+
const tamperedToken = `${header}.${tamperedPayload}.${signature}`;
|
|
266
|
+
|
|
267
|
+
const request = createRequest(`Bearer ${tamperedToken}`);
|
|
268
|
+
const result = await authenticateRequest(authConfig, request);
|
|
269
|
+
|
|
270
|
+
expect(result).toBeNull();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("returns null for malformed JWT structure", async () => {
|
|
274
|
+
const malformedTokens = [
|
|
275
|
+
"not.a.valid.jwt.structure",
|
|
276
|
+
"only-one-part",
|
|
277
|
+
"two.parts",
|
|
278
|
+
"",
|
|
279
|
+
"header.payload.", // Missing signature
|
|
280
|
+
".payload.signature", // Missing header
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
for (const token of malformedTokens) {
|
|
284
|
+
const request = createRequest(`Bearer ${token}`);
|
|
285
|
+
const result = await authenticateRequest(authConfig, request);
|
|
286
|
+
expect(result).toBeNull();
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("algorithm restrictions", () => {
|
|
292
|
+
it("returns null for HS256-signed token against RSA JWKS", async () => {
|
|
293
|
+
// Create HS256 token using a symmetric secret
|
|
294
|
+
const secret = new TextEncoder().encode("super-secret-key");
|
|
295
|
+
const jwt = await new SignJWT({ email: "user@example.com" })
|
|
296
|
+
.setProtectedHeader({ alg: "HS256" })
|
|
297
|
+
.setSubject("user-123")
|
|
298
|
+
.setIssuer(authConfig.issuer)
|
|
299
|
+
.setAudience(authConfig.audience)
|
|
300
|
+
.setExpirationTime("1h")
|
|
301
|
+
.setIssuedAt()
|
|
302
|
+
.sign(secret);
|
|
303
|
+
|
|
304
|
+
const request = createRequest(`Bearer ${jwt}`);
|
|
305
|
+
const result = await authenticateRequest(authConfig, request);
|
|
306
|
+
|
|
307
|
+
expect(result).toBeNull();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("returns null for unsecured 'none' algorithm token", async () => {
|
|
311
|
+
// Craft a JWT with alg: none and no signature
|
|
312
|
+
const header = Buffer.from(JSON.stringify({ alg: "none", typ: "JWT" }))
|
|
313
|
+
.toString("base64")
|
|
314
|
+
.replace(/=/g, "")
|
|
315
|
+
.replace(/\+/g, "-")
|
|
316
|
+
.replace(/\//g, "_");
|
|
317
|
+
|
|
318
|
+
const payload = Buffer.from(
|
|
319
|
+
JSON.stringify({
|
|
320
|
+
sub: "user-123",
|
|
321
|
+
iss: authConfig.issuer,
|
|
322
|
+
aud: authConfig.audience,
|
|
323
|
+
iat: Math.floor(Date.now() / 1000),
|
|
324
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
325
|
+
email: "user@example.com",
|
|
326
|
+
}),
|
|
327
|
+
)
|
|
328
|
+
.toString("base64")
|
|
329
|
+
.replace(/=/g, "")
|
|
330
|
+
.replace(/\+/g, "-")
|
|
331
|
+
.replace(/\//g, "_");
|
|
332
|
+
|
|
333
|
+
// Note the trailing dot for empty signature
|
|
334
|
+
const unsecuredToken = `${header}.${payload}.`;
|
|
335
|
+
|
|
336
|
+
const request = createRequest(`Bearer ${unsecuredToken}`);
|
|
337
|
+
const result = await authenticateRequest(authConfig, request);
|
|
338
|
+
|
|
339
|
+
expect(result).toBeNull();
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe("JWKS fetch failures", () => {
|
|
344
|
+
it("returns null when JWKS endpoint returns 404", async () => {
|
|
345
|
+
global.fetch = vi
|
|
346
|
+
.fn()
|
|
347
|
+
.mockResolvedValue(new Response("Not Found", { status: 404 }));
|
|
348
|
+
|
|
349
|
+
const token = await createValidToken();
|
|
350
|
+
const request = createRequest(`Bearer ${token}`);
|
|
351
|
+
const result = await authenticateRequest(authConfig, request);
|
|
352
|
+
|
|
353
|
+
expect(result).toBeNull();
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("returns null when JWKS endpoint returns 500", async () => {
|
|
357
|
+
global.fetch = vi
|
|
358
|
+
.fn()
|
|
359
|
+
.mockResolvedValue(
|
|
360
|
+
new Response("Internal Server Error", { status: 500 }),
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const token = await createValidToken();
|
|
364
|
+
const request = createRequest(`Bearer ${token}`);
|
|
365
|
+
const result = await authenticateRequest(authConfig, request);
|
|
366
|
+
|
|
367
|
+
expect(result).toBeNull();
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("returns null when JWKS fetch throws network error", async () => {
|
|
371
|
+
global.fetch = vi.fn().mockRejectedValue(new Error("Network error"));
|
|
372
|
+
|
|
373
|
+
const token = await createValidToken();
|
|
374
|
+
const request = createRequest(`Bearer ${token}`);
|
|
375
|
+
const result = await authenticateRequest(authConfig, request);
|
|
376
|
+
|
|
377
|
+
expect(result).toBeNull();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("returns null when JWKS returns invalid JSON", async () => {
|
|
381
|
+
global.fetch = vi.fn().mockResolvedValue(
|
|
382
|
+
new Response("not valid json", {
|
|
383
|
+
status: 200,
|
|
384
|
+
headers: { "Content-Type": "application/json" },
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const token = await createValidToken();
|
|
389
|
+
const request = createRequest(`Bearer ${token}`);
|
|
390
|
+
const result = await authenticateRequest(authConfig, request);
|
|
391
|
+
|
|
392
|
+
expect(result).toBeNull();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("returns null when JWKS has no matching key", async () => {
|
|
396
|
+
// Return JWKS with a different key
|
|
397
|
+
const differentKeyPair = await generateKeyPair("RS256");
|
|
398
|
+
const differentJwk = await exportJWK(differentKeyPair.publicKey);
|
|
399
|
+
|
|
400
|
+
global.fetch = vi.fn().mockResolvedValue(
|
|
401
|
+
new Response(
|
|
402
|
+
JSON.stringify({
|
|
403
|
+
keys: [{ ...differentJwk, kid: "different-key", alg: "RS256" }],
|
|
404
|
+
}),
|
|
405
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
406
|
+
),
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const token = await createValidToken();
|
|
410
|
+
const request = createRequest(`Bearer ${token}`);
|
|
411
|
+
const result = await authenticateRequest(authConfig, request);
|
|
412
|
+
|
|
413
|
+
expect(result).toBeNull();
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
});
|
|
@@ -41,7 +41,6 @@ export async function authenticateRequest(
|
|
|
41
41
|
try {
|
|
42
42
|
const session = await verifySessionToken(token, authConfig);
|
|
43
43
|
return session;
|
|
44
|
-
// TODO Is there a way to handle this more gracefully?
|
|
45
44
|
} catch (error) {
|
|
46
45
|
console.error(
|
|
47
46
|
JSON.stringify({
|
|
@@ -49,7 +48,8 @@ export async function authenticateRequest(
|
|
|
49
48
|
error: error instanceof Error ? error.message : String(error),
|
|
50
49
|
stack: error instanceof Error ? error.stack : undefined,
|
|
51
50
|
errorType: error instanceof Error ? error.constructor.name : "Unknown",
|
|
52
|
-
authConfig,
|
|
51
|
+
issuer: authConfig.issuer,
|
|
52
|
+
audience: authConfig.audience,
|
|
53
53
|
}),
|
|
54
54
|
);
|
|
55
55
|
return null;
|
|
@@ -70,9 +70,7 @@ async function verifySessionToken(
|
|
|
70
70
|
throw new Error("Audience must be provided for token verification");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
//
|
|
74
|
-
// But, the limitation of these services not being able to talk to each other will be frustrating.
|
|
75
|
-
// I wonder if there is a better abstraction to wrap this dynamic fetching and link all the services together.
|
|
73
|
+
// Fetch JWKS - use service binding in production, direct fetch in development
|
|
76
74
|
const jwksResponse =
|
|
77
75
|
import.meta.env.PROD && env.EVERY_APP_GATEWAY
|
|
78
76
|
? await env.EVERY_APP_GATEWAY.fetch("http://localhost/api/embedded/jwks")
|
|
@@ -90,13 +88,20 @@ async function verifySessionToken(
|
|
|
90
88
|
const options: JWTVerifyOptions = {
|
|
91
89
|
issuer,
|
|
92
90
|
audience,
|
|
91
|
+
algorithms: ["RS256"],
|
|
93
92
|
};
|
|
94
93
|
|
|
95
94
|
const { payload } = await jwtVerify(token, localJWKS, options);
|
|
96
95
|
return payload as SessionTokenPayload;
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Extracts the bearer token from an Authorization header.
|
|
100
|
+
*
|
|
101
|
+
* @param authHeader - The Authorization header value (e.g., "Bearer eyJ...")
|
|
102
|
+
* @returns The token string if valid, null otherwise
|
|
103
|
+
*/
|
|
104
|
+
export function extractBearerToken(authHeader: string | null): string | null {
|
|
100
105
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
101
106
|
return null;
|
|
102
107
|
}
|
|
@@ -3,6 +3,12 @@ import path from "path";
|
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import { parse } from "jsonc-parser";
|
|
5
5
|
|
|
6
|
+
function findSqliteFile(basePath: string): string | undefined {
|
|
7
|
+
return fs
|
|
8
|
+
.readdirSync(basePath, { encoding: "utf-8", recursive: true })
|
|
9
|
+
.find((f) => f.endsWith(".sqlite"));
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
export function getLocalD1Url() {
|
|
7
13
|
const basePath = path.resolve(".wrangler");
|
|
8
14
|
|
|
@@ -22,9 +28,7 @@ export function getLocalD1Url() {
|
|
|
22
28
|
return null;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
.readdirSync(basePath, { encoding: "utf-8", recursive: true })
|
|
27
|
-
.find((f) => f.endsWith(".sqlite"));
|
|
31
|
+
let dbFile = findSqliteFile(basePath);
|
|
28
32
|
|
|
29
33
|
if (!dbFile) {
|
|
30
34
|
// Read wrangler.jsonc to get the database name
|
|
@@ -47,20 +51,14 @@ export function getLocalD1Url() {
|
|
|
47
51
|
);
|
|
48
52
|
|
|
49
53
|
// Try to find the db file again after initialization
|
|
50
|
-
|
|
51
|
-
.readdirSync(basePath, { encoding: "utf-8", recursive: true })
|
|
52
|
-
.find((f) => f.endsWith(".sqlite"));
|
|
54
|
+
dbFile = findSqliteFile(basePath);
|
|
53
55
|
|
|
54
|
-
if (!
|
|
56
|
+
if (!dbFile) {
|
|
55
57
|
throw new Error(
|
|
56
58
|
`Failed to initialize local D1 database. The sqlite file was not created.`,
|
|
57
59
|
);
|
|
58
60
|
}
|
|
59
|
-
|
|
60
|
-
const url = path.resolve(basePath, dbFileAfterInit);
|
|
61
|
-
return url;
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
return url;
|
|
63
|
+
return path.resolve(basePath, dbFile);
|
|
66
64
|
}
|