@convex-dev/better-auth 0.12.1 → 0.12.3
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/dist/client/adapter-utils.d.ts.map +1 -1
- package/dist/client/adapter-utils.js +4 -2
- package/dist/client/adapter-utils.js.map +1 -1
- package/dist/client/adapter.d.ts.map +1 -1
- package/dist/client/adapter.js +19 -14
- package/dist/client/adapter.js.map +1 -1
- package/dist/client/create-api.d.ts.map +1 -1
- package/dist/client/create-api.js +17 -7
- package/dist/client/create-api.js.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/testTriggerHandlers.d.ts +10 -0
- package/dist/component/testTriggerHandlers.d.ts.map +1 -0
- package/dist/component/testTriggerHandlers.js +25 -0
- package/dist/component/testTriggerHandlers.js.map +1 -0
- package/dist/nextjs/index.d.ts.map +1 -1
- package/dist/nextjs/index.js +24 -9
- package/dist/nextjs/index.js.map +1 -1
- package/dist/react-start/index.d.ts.map +1 -1
- package/dist/react-start/index.js +5 -0
- package/dist/react-start/index.js.map +1 -1
- package/dist/test/adapter-factory/basic.d.ts +1 -0
- package/dist/test/adapter-factory/basic.d.ts.map +1 -1
- package/dist/test/adapter-factory/convex-custom.d.ts +1 -1
- package/dist/test/adapter-factory/convex-custom.d.ts.map +1 -1
- package/dist/test/adapter-factory/convex-custom.js +3 -3
- package/dist/test/adapter-factory/convex-custom.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +28 -27
- package/src/client/adapter-utils.ts +4 -2
- package/src/client/adapter.test.ts +8 -22
- package/src/client/adapter.ts +22 -17
- package/src/client/create-api.ts +36 -10
- package/src/client/triggers.test.ts +58 -0
- package/src/component/_generated/api.ts +2 -0
- package/src/component/testTriggerHandlers.ts +27 -0
- package/src/nextjs/index.test.ts +109 -0
- package/src/nextjs/index.ts +28 -9
- package/src/react-start/index.test.ts +91 -0
- package/src/react-start/index.ts +5 -0
- package/src/test/adapter-factory/convex-custom.ts +22 -21
- package/src/version.ts +1 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { convexBetterAuthNextJs } from "./index.js";
|
|
3
|
+
|
|
4
|
+
const SITE_URL = "https://test.convex.site";
|
|
5
|
+
const CONVEX_URL = "https://test.convex.cloud";
|
|
6
|
+
|
|
7
|
+
const setup = () => {
|
|
8
|
+
const { handler } = convexBetterAuthNextJs({
|
|
9
|
+
convexUrl: CONVEX_URL,
|
|
10
|
+
convexSiteUrl: SITE_URL,
|
|
11
|
+
});
|
|
12
|
+
const fetchSpy = vi
|
|
13
|
+
.spyOn(globalThis, "fetch")
|
|
14
|
+
.mockResolvedValue(new Response());
|
|
15
|
+
return { handler, fetchSpy };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const initOf = (spy: ReturnType<typeof vi.spyOn>): RequestInit =>
|
|
19
|
+
(spy.mock.calls[0]?.[1] as RequestInit) ?? {};
|
|
20
|
+
|
|
21
|
+
const headersOf = (spy: ReturnType<typeof vi.spyOn>): Headers =>
|
|
22
|
+
new Headers(initOf(spy).headers);
|
|
23
|
+
|
|
24
|
+
describe("convexBetterAuthNextJs handler", () => {
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
vi.restoreAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("strips hop-by-hop headers from the forwarded request", async () => {
|
|
30
|
+
const { handler, fetchSpy } = setup();
|
|
31
|
+
const request = new Request(
|
|
32
|
+
"https://app.example.com/api/auth/sign-in/email",
|
|
33
|
+
{
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
"transfer-encoding": "chunked",
|
|
37
|
+
"content-length": "42",
|
|
38
|
+
connection: "keep-alive",
|
|
39
|
+
"content-type": "application/json",
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({ email: "test@example.com" }),
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
await handler.POST(request);
|
|
45
|
+
const headers = headersOf(fetchSpy);
|
|
46
|
+
expect(headers.get("transfer-encoding")).toBeNull();
|
|
47
|
+
expect(headers.get("content-length")).toBeNull();
|
|
48
|
+
expect(headers.get("connection")).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("forwards to upstream URL preserving path and query", async () => {
|
|
52
|
+
const { handler, fetchSpy } = setup();
|
|
53
|
+
const request = new Request(
|
|
54
|
+
"https://app.example.com/api/auth/sign-in/email?foo=bar",
|
|
55
|
+
{ method: "POST", body: "{}" }
|
|
56
|
+
);
|
|
57
|
+
await handler.POST(request);
|
|
58
|
+
expect(fetchSpy.mock.calls[0]?.[0]).toBe(
|
|
59
|
+
`${SITE_URL}/api/auth/sign-in/email?foo=bar`
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("sets host and forwarding headers", async () => {
|
|
64
|
+
const { handler, fetchSpy } = setup();
|
|
65
|
+
const request = new Request(
|
|
66
|
+
"https://app.example.com/api/auth/sign-in/email",
|
|
67
|
+
{ method: "POST", body: "{}" }
|
|
68
|
+
);
|
|
69
|
+
await handler.POST(request);
|
|
70
|
+
const headers = headersOf(fetchSpy);
|
|
71
|
+
expect(headers.get("host")).toBe(new URL(SITE_URL).host);
|
|
72
|
+
expect(headers.get("x-forwarded-host")).toBe("app.example.com");
|
|
73
|
+
expect(headers.get("x-forwarded-proto")).toBe("https");
|
|
74
|
+
expect(headers.get("x-better-auth-forwarded-host")).toBe("app.example.com");
|
|
75
|
+
expect(headers.get("x-better-auth-forwarded-proto")).toBe("https");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("buffers POST body and forwards as ArrayBuffer", async () => {
|
|
79
|
+
const { handler, fetchSpy } = setup();
|
|
80
|
+
const body = JSON.stringify({ email: "test@example.com" });
|
|
81
|
+
const request = new Request(
|
|
82
|
+
"https://app.example.com/api/auth/sign-in/email",
|
|
83
|
+
{ method: "POST", body }
|
|
84
|
+
);
|
|
85
|
+
await handler.POST(request);
|
|
86
|
+
const init = initOf(fetchSpy);
|
|
87
|
+
expect(init.body).toBeInstanceOf(ArrayBuffer);
|
|
88
|
+
expect(new TextDecoder().decode(init.body as ArrayBuffer)).toBe(body);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("does not set body for GET requests", async () => {
|
|
92
|
+
const { handler, fetchSpy } = setup();
|
|
93
|
+
const request = new Request(
|
|
94
|
+
"https://app.example.com/api/auth/get-session",
|
|
95
|
+
{ method: "GET" }
|
|
96
|
+
);
|
|
97
|
+
await handler.GET(request);
|
|
98
|
+
expect(initOf(fetchSpy).body).toBeUndefined();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("does not set body for empty POST", async () => {
|
|
102
|
+
const { handler, fetchSpy } = setup();
|
|
103
|
+
const request = new Request("https://app.example.com/api/auth/sign-out", {
|
|
104
|
+
method: "POST",
|
|
105
|
+
});
|
|
106
|
+
await handler.POST(request);
|
|
107
|
+
expect(initOf(fetchSpy).body).toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
});
|
package/src/nextjs/index.ts
CHANGED
|
@@ -40,17 +40,36 @@ const parseConvexSiteUrl = (url: string) => {
|
|
|
40
40
|
return url;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
const handler = (request: Request, siteUrl: string) => {
|
|
43
|
+
const handler = async (request: Request, siteUrl: string) => {
|
|
44
44
|
const requestUrl = new URL(request.url);
|
|
45
45
|
const nextUrl = `${siteUrl}${requestUrl.pathname}${requestUrl.search}`;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
|
|
47
|
+
const headers = new Headers(request.headers);
|
|
48
|
+
// Strip hop-by-hop headers; undici rejects outbound `transfer-encoding: chunked`.
|
|
49
|
+
headers.delete("transfer-encoding");
|
|
50
|
+
headers.delete("content-length");
|
|
51
|
+
headers.delete("connection");
|
|
52
|
+
headers.set("accept-encoding", "application/json");
|
|
53
|
+
headers.set("host", new URL(siteUrl).host);
|
|
54
|
+
headers.set("x-forwarded-host", requestUrl.host);
|
|
55
|
+
headers.set("x-forwarded-proto", requestUrl.protocol.replace(/:$/, ""));
|
|
56
|
+
headers.set("x-better-auth-forwarded-host", requestUrl.host);
|
|
57
|
+
headers.set("x-better-auth-forwarded-proto", requestUrl.protocol.replace(/:$/, ""));
|
|
58
|
+
|
|
59
|
+
const init: RequestInit = {
|
|
60
|
+
headers,
|
|
61
|
+
method: request.method,
|
|
62
|
+
redirect: "manual",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
66
|
+
const body = await request.arrayBuffer();
|
|
67
|
+
if (body.byteLength > 0) {
|
|
68
|
+
init.body = body;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return fetch(nextUrl, init);
|
|
54
73
|
};
|
|
55
74
|
|
|
56
75
|
const nextJsHandler = (siteUrl: string) => ({
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { convexBetterAuthReactStart } from "./index.js";
|
|
3
|
+
|
|
4
|
+
const SITE_URL = "https://test.convex.site";
|
|
5
|
+
const CONVEX_URL = "https://test.convex.cloud";
|
|
6
|
+
|
|
7
|
+
const setup = () => {
|
|
8
|
+
const { handler } = convexBetterAuthReactStart({
|
|
9
|
+
convexUrl: CONVEX_URL,
|
|
10
|
+
convexSiteUrl: SITE_URL,
|
|
11
|
+
});
|
|
12
|
+
const fetchSpy = vi
|
|
13
|
+
.spyOn(globalThis, "fetch")
|
|
14
|
+
.mockResolvedValue(new Response());
|
|
15
|
+
return { handler, fetchSpy };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const initOf = (
|
|
19
|
+
spy: ReturnType<typeof vi.spyOn>
|
|
20
|
+
): RequestInit & { duplex?: string } =>
|
|
21
|
+
(spy.mock.calls[0]?.[1] as RequestInit & { duplex?: string }) ?? {};
|
|
22
|
+
|
|
23
|
+
const headersOf = (spy: ReturnType<typeof vi.spyOn>): Headers =>
|
|
24
|
+
new Headers(initOf(spy).headers);
|
|
25
|
+
|
|
26
|
+
describe("convexBetterAuthReactStart handler", () => {
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.restoreAllMocks();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("strips hop-by-hop headers from the forwarded request", async () => {
|
|
32
|
+
const { handler, fetchSpy } = setup();
|
|
33
|
+
const request = new Request(
|
|
34
|
+
"https://app.example.com/api/auth/sign-in/email",
|
|
35
|
+
{
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
"transfer-encoding": "chunked",
|
|
39
|
+
"content-length": "42",
|
|
40
|
+
connection: "keep-alive",
|
|
41
|
+
"content-type": "application/json",
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({ email: "test@example.com" }),
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
await handler(request);
|
|
47
|
+
const headers = headersOf(fetchSpy);
|
|
48
|
+
expect(headers.get("transfer-encoding")).toBeNull();
|
|
49
|
+
expect(headers.get("content-length")).toBeNull();
|
|
50
|
+
expect(headers.get("connection")).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("forwards to upstream URL preserving path and query", async () => {
|
|
54
|
+
const { handler, fetchSpy } = setup();
|
|
55
|
+
const request = new Request(
|
|
56
|
+
"https://app.example.com/api/auth/sign-in/email?foo=bar",
|
|
57
|
+
{ method: "POST", body: "{}" }
|
|
58
|
+
);
|
|
59
|
+
await handler(request);
|
|
60
|
+
expect(fetchSpy.mock.calls[0]?.[0]).toBe(
|
|
61
|
+
`${SITE_URL}/api/auth/sign-in/email?foo=bar`
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("sets host and forwarding headers", async () => {
|
|
66
|
+
const { handler, fetchSpy } = setup();
|
|
67
|
+
const request = new Request(
|
|
68
|
+
"https://app.example.com/api/auth/sign-in/email",
|
|
69
|
+
{ method: "POST", body: "{}" }
|
|
70
|
+
);
|
|
71
|
+
await handler(request);
|
|
72
|
+
const headers = headersOf(fetchSpy);
|
|
73
|
+
expect(headers.get("host")).toBe(new URL(SITE_URL).host);
|
|
74
|
+
expect(headers.get("x-forwarded-host")).toBe("app.example.com");
|
|
75
|
+
expect(headers.get("x-forwarded-proto")).toBe("https");
|
|
76
|
+
expect(headers.get("x-better-auth-forwarded-host")).toBe("app.example.com");
|
|
77
|
+
expect(headers.get("x-better-auth-forwarded-proto")).toBe("https");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("streams the request body with duplex: half", async () => {
|
|
81
|
+
const { handler, fetchSpy } = setup();
|
|
82
|
+
const request = new Request(
|
|
83
|
+
"https://app.example.com/api/auth/sign-in/email",
|
|
84
|
+
{ method: "POST", body: JSON.stringify({ email: "test@example.com" }) }
|
|
85
|
+
);
|
|
86
|
+
await handler(request);
|
|
87
|
+
const init = initOf(fetchSpy);
|
|
88
|
+
expect(init.duplex).toBe("half");
|
|
89
|
+
expect(init.body).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/react-start/index.ts
CHANGED
|
@@ -63,6 +63,11 @@ const handler = (request: Request, opts: { convexSiteUrl: string }) => {
|
|
|
63
63
|
const requestUrl = new URL(request.url);
|
|
64
64
|
const nextUrl = `${opts.convexSiteUrl}${requestUrl.pathname}${requestUrl.search}`;
|
|
65
65
|
const headers = new Headers(request.headers);
|
|
66
|
+
// Strip hop-by-hop headers inherited from the incoming request; undici
|
|
67
|
+
// rejects an outbound `transfer-encoding: chunked` header value.
|
|
68
|
+
headers.delete("transfer-encoding");
|
|
69
|
+
headers.delete("content-length");
|
|
70
|
+
headers.delete("connection");
|
|
66
71
|
headers.set("accept-encoding", "application/json");
|
|
67
72
|
headers.set("host", new URL(opts.convexSiteUrl).host);
|
|
68
73
|
headers.set("x-forwarded-host", requestUrl.host);
|
|
@@ -427,28 +427,29 @@ export const convexCustomTestSuite = createTestSuite(
|
|
|
427
427
|
]);
|
|
428
428
|
},
|
|
429
429
|
|
|
430
|
-
"should
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
data: {
|
|
434
|
-
name: "foo",
|
|
435
|
-
email: "foo@bar.com",
|
|
436
|
-
},
|
|
437
|
-
});
|
|
438
|
-
await expect(
|
|
439
|
-
adapter.update({
|
|
440
|
-
model: "user",
|
|
441
|
-
where: [],
|
|
442
|
-
update: { name: "bar" },
|
|
443
|
-
}),
|
|
444
|
-
).rejects.toThrow("where clause not supported");
|
|
445
|
-
expect(
|
|
446
|
-
await adapter.findOne({
|
|
430
|
+
"should return null and not modify records when update where is empty":
|
|
431
|
+
async () => {
|
|
432
|
+
const user = await adapter.create({
|
|
447
433
|
model: "user",
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
434
|
+
data: {
|
|
435
|
+
name: "foo",
|
|
436
|
+
email: "foo@bar.com",
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
expect(
|
|
440
|
+
await adapter.update({
|
|
441
|
+
model: "user",
|
|
442
|
+
where: [],
|
|
443
|
+
update: { name: "bar" },
|
|
444
|
+
}),
|
|
445
|
+
).toBeNull();
|
|
446
|
+
expect(
|
|
447
|
+
await adapter.findOne({
|
|
448
|
+
model: "user",
|
|
449
|
+
where: [{ field: "id", value: user.id }],
|
|
450
|
+
}),
|
|
451
|
+
).toEqual(user);
|
|
452
|
+
},
|
|
452
453
|
|
|
453
454
|
"should update and count each match only once for overlapping OR clauses":
|
|
454
455
|
async () => {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.12.
|
|
1
|
+
export const VERSION = "0.12.3";
|