@prosopo/better-auth-procaptcha-plugin 0.0.0 → 0.1.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.
Files changed (62) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +32 -0
  2. package/.turbo/turbo-build$colon$esm.log +33 -0
  3. package/.turbo/turbo-build$colon$tsc.log +23 -0
  4. package/.turbo/turbo-build$colon$types.log +1 -54
  5. package/CHANGELOG.md +12 -0
  6. package/dist/esm/_virtual/_rolldown/runtime.js +0 -1
  7. package/dist/tsc/client.d.ts +19 -0
  8. package/dist/tsc/client.d.ts.map +1 -0
  9. package/dist/tsc/client.js +39 -0
  10. package/dist/tsc/client.js.map +1 -0
  11. package/dist/tsc/constants.d.ts +12 -0
  12. package/dist/tsc/constants.d.ts.map +1 -0
  13. package/dist/tsc/constants.js +16 -0
  14. package/dist/tsc/constants.js.map +1 -0
  15. package/dist/tsc/index.d.ts +28 -0
  16. package/dist/tsc/index.d.ts.map +1 -0
  17. package/dist/tsc/index.js +94 -0
  18. package/dist/tsc/index.js.map +1 -0
  19. package/dist/tsc/path.d.ts +4 -0
  20. package/dist/tsc/path.d.ts.map +1 -0
  21. package/dist/tsc/path.js +59 -0
  22. package/dist/tsc/path.js.map +1 -0
  23. package/dist/tsc/siteverify.d.ts +40 -0
  24. package/dist/tsc/siteverify.d.ts.map +1 -0
  25. package/dist/tsc/siteverify.js +80 -0
  26. package/dist/tsc/siteverify.js.map +1 -0
  27. package/dist/tsc/tests/client.unit.test.d.ts +2 -0
  28. package/dist/tsc/tests/client.unit.test.d.ts.map +1 -0
  29. package/dist/tsc/tests/client.unit.test.js +56 -0
  30. package/dist/tsc/tests/client.unit.test.js.map +1 -0
  31. package/dist/tsc/tests/path.unit.test.d.ts +2 -0
  32. package/dist/tsc/tests/path.unit.test.d.ts.map +1 -0
  33. package/dist/tsc/tests/path.unit.test.js +53 -0
  34. package/dist/tsc/tests/path.unit.test.js.map +1 -0
  35. package/dist/tsc/tests/procaptcha.unit.test.d.ts +2 -0
  36. package/dist/tsc/tests/procaptcha.unit.test.d.ts.map +1 -0
  37. package/dist/tsc/tests/procaptcha.unit.test.js +158 -0
  38. package/dist/tsc/tests/procaptcha.unit.test.js.map +1 -0
  39. package/dist/tsc/tests/siteVerifyMock.d.ts +17 -0
  40. package/dist/tsc/tests/siteVerifyMock.d.ts.map +1 -0
  41. package/dist/tsc/tests/siteVerifyMock.js +43 -0
  42. package/dist/tsc/tests/siteVerifyMock.js.map +1 -0
  43. package/dist/tsc/tests/siteverify.unit.test.d.ts +2 -0
  44. package/dist/tsc/tests/siteverify.unit.test.d.ts.map +1 -0
  45. package/dist/tsc/tests/siteverify.unit.test.js +50 -0
  46. package/dist/tsc/tests/siteverify.unit.test.js.map +1 -0
  47. package/package.json +2 -2
  48. package/coverage/base.css +0 -362
  49. package/coverage/block-navigation.js +0 -82
  50. package/coverage/client.ts.html +0 -316
  51. package/coverage/clover.xml +0 -151
  52. package/coverage/constants.ts.html +0 -190
  53. package/coverage/coverage-final.json +0 -6
  54. package/coverage/favicon.png +0 -0
  55. package/coverage/index.html +0 -176
  56. package/coverage/index.ts.html +0 -706
  57. package/coverage/path.ts.html +0 -355
  58. package/coverage/prettify.css +0 -101
  59. package/coverage/prettify.js +0 -937
  60. package/coverage/siteverify.ts.html +0 -559
  61. package/coverage/sort-arrow-sprite.png +0 -0
  62. package/coverage/sorter.js +0 -205
@@ -0,0 +1,56 @@
1
+ import { getTestInstance } from "better-auth/test";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { procaptchaClient, procaptchaHeaders, } from "../client.js";
4
+ import { procaptcha } from "../index.js";
5
+ import { mockSiteVerify } from "./siteVerifyMock.js";
6
+ const setup = async (clientOptions) => {
7
+ const siteVerify = mockSiteVerify();
8
+ const { client } = await getTestInstance({
9
+ plugins: [procaptcha({ secretKey: "secret", fetch: siteVerify.fetch })],
10
+ }, { clientOptions: { plugins: [procaptchaClient(clientOptions)] } });
11
+ return { client, siteVerify };
12
+ };
13
+ describe("procaptcha client plugin", () => {
14
+ it("builds the header the server plugin reads", () => {
15
+ expect(procaptchaHeaders("tok")).toEqual({ "x-captcha-response": "tok" });
16
+ expect(procaptchaHeaders("tok", "x-custom")).toEqual({ "x-custom": "tok" });
17
+ });
18
+ it("attaches the token from getToken to protected requests", async () => {
19
+ const getToken = vi.fn(async () => "auto-token");
20
+ const { client, siteVerify } = await setup({ getToken });
21
+ const result = await client.signIn.email({
22
+ email: "test@test.com",
23
+ password: "test123456",
24
+ });
25
+ expect(result.error).toBeNull();
26
+ expect(getToken).toHaveBeenCalledTimes(1);
27
+ expect(siteVerify.calls[0]?.body.token).toBe("auto-token");
28
+ });
29
+ it("does not ask for a token on unprotected requests", async () => {
30
+ const getToken = vi.fn(() => "auto-token");
31
+ const { client } = await setup({ getToken });
32
+ await client.getSession();
33
+ expect(getToken).not.toHaveBeenCalled();
34
+ });
35
+ it("keeps a token passed explicitly in fetchOptions", async () => {
36
+ const getToken = vi.fn(() => "auto-token");
37
+ const { client, siteVerify } = await setup({ getToken });
38
+ await client.signIn.email({
39
+ email: "test@test.com",
40
+ password: "test123456",
41
+ fetchOptions: { headers: procaptchaHeaders("explicit-token") },
42
+ });
43
+ expect(getToken).not.toHaveBeenCalled();
44
+ expect(siteVerify.calls[0]?.body.token).toBe("explicit-token");
45
+ });
46
+ it("sends no header when getToken has no token yet", async () => {
47
+ const { client, siteVerify } = await setup({ getToken: () => undefined });
48
+ const result = await client.signIn.email({
49
+ email: "test@test.com",
50
+ password: "test123456",
51
+ });
52
+ expect(result.error?.status).toBe(400);
53
+ expect(siteVerify.calls).toHaveLength(0);
54
+ });
55
+ });
56
+ //# sourceMappingURL=client.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.unit.test.js","sourceRoot":"","sources":["../../../src/tests/client.unit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=path.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.unit.test.d.ts","sourceRoot":"","sources":["../../../src/tests/path.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createPathMatcher, normalizeAuthPath } from "../path.js";
3
+ describe("normalizeAuthPath", () => {
4
+ it.each([
5
+ ["/api/auth/sign-in/email", "/api/auth", "/sign-in/email"],
6
+ ["/api/auth//sign-in//email/", "/api/auth", "/sign-in/email"],
7
+ ["/api/auth", "/api/auth", "/"],
8
+ ["/other/sign-in/email", "/api/auth", "/other/sign-in/email"],
9
+ ["/sign-in/email", "", "/sign-in/email"],
10
+ ])("%s under %s is %s", (pathname, basePath, expected) => {
11
+ expect(normalizeAuthPath(pathname, basePath)).toBe(expected);
12
+ });
13
+ });
14
+ describe("createPathMatcher", () => {
15
+ it("matches exact paths only when there is no wildcard", () => {
16
+ const matches = createPathMatcher(["/sign-in/email"]);
17
+ expect(matches("/sign-in/email")).toBe(true);
18
+ expect(matches("/sign-in/email/extra")).toBe(false);
19
+ expect(matches("/sign-in/emailx")).toBe(false);
20
+ });
21
+ it("matches one segment with * and any depth with **", () => {
22
+ const single = createPathMatcher(["/sign-in/*"]);
23
+ expect(single("/sign-in/email")).toBe(true);
24
+ expect(single("/sign-in/social/google")).toBe(false);
25
+ const deep = createPathMatcher(["/sign-in/**"]);
26
+ expect(deep("/sign-in")).toBe(true);
27
+ expect(deep("/sign-in/social/google")).toBe(true);
28
+ expect(deep("/sign-up/email")).toBe(false);
29
+ });
30
+ it("matches partial-segment wildcards and ** in the middle", () => {
31
+ const partial = createPathMatcher(["/sign-*/e*l"]);
32
+ expect(partial("/sign-in/email")).toBe(true);
33
+ expect(partial("/sign-up/el")).toBe(true);
34
+ expect(partial("/sign-in/e")).toBe(false);
35
+ expect(partial("/signin/email")).toBe(false);
36
+ const many = createPathMatcher(["/a*b*c"]);
37
+ expect(many("/abc")).toBe(true);
38
+ expect(many("/axxbyyc")).toBe(true);
39
+ expect(many("/abbc")).toBe(true);
40
+ expect(many("/acb")).toBe(false);
41
+ expect(many("/axc")).toBe(false);
42
+ const middle = createPathMatcher(["/**/email"]);
43
+ expect(middle("/email")).toBe(true);
44
+ expect(middle("/sign-in/social/email")).toBe(true);
45
+ expect(middle("/sign-in/email/x")).toBe(false);
46
+ });
47
+ it("treats regex characters in endpoints literally", () => {
48
+ const matches = createPathMatcher(["/a.b/*"]);
49
+ expect(matches("/a.b/c")).toBe(true);
50
+ expect(matches("/axb/c")).toBe(false);
51
+ });
52
+ });
53
+ //# sourceMappingURL=path.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.unit.test.js","sourceRoot":"","sources":["../../../src/tests/path.unit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=procaptcha.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"procaptcha.unit.test.d.ts","sourceRoot":"","sources":["../../../src/tests/procaptcha.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,158 @@
1
+ import { getTestInstance } from "better-auth/test";
2
+ import { describe, expect, it } from "vitest";
3
+ import { PROCAPTCHA_SITEVERIFY_URL, procaptcha, } from "../index.js";
4
+ import { errorEnvelope, mockSiteVerify, rejectedResponse, verifiedResponse, } from "./siteVerifyMock.js";
5
+ const TOKEN = "procaptcha-token";
6
+ const SECRET = "test-secret-key";
7
+ const CLIENT_IP = "203.0.113.7";
8
+ const setup = async (options = {}, respond) => {
9
+ const siteVerify = mockSiteVerify(respond);
10
+ const { auth } = await getTestInstance({
11
+ plugins: [
12
+ procaptcha({ secretKey: SECRET, fetch: siteVerify.fetch, ...options }),
13
+ ],
14
+ });
15
+ const post = (path, body, headers) => auth.handler(new Request(`http://localhost:3000/api/auth${path}`, {
16
+ method: "POST",
17
+ headers: { "content-type": "application/json", ...headers },
18
+ body: JSON.stringify(body),
19
+ }));
20
+ return {
21
+ siteVerify,
22
+ signIn: (headers = {}) => post("/sign-in/email", { email: "test@test.com", password: "test123456" }, headers),
23
+ signUp: (email, headers = {}) => post("/sign-up/email", { email, password: "a-long-password", name: "New User" }, headers),
24
+ };
25
+ };
26
+ const withToken = (extra = {}) => ({
27
+ "x-captcha-response": TOKEN,
28
+ "x-forwarded-for": CLIENT_IP,
29
+ ...extra,
30
+ });
31
+ const readError = async (response) => {
32
+ const body = await response.json();
33
+ if (typeof body !== "object" ||
34
+ body === null ||
35
+ !("code" in body) ||
36
+ typeof body.code !== "string" ||
37
+ !("message" in body) ||
38
+ typeof body.message !== "string") {
39
+ throw new Error(`unexpected error body: ${JSON.stringify(body)}`);
40
+ }
41
+ return { code: body.code, message: body.message };
42
+ };
43
+ describe("procaptcha server plugin", () => {
44
+ it("lets a request with a verified token through and sends secret, token and IP", async () => {
45
+ const { signIn, siteVerify } = await setup();
46
+ const response = await signIn(withToken());
47
+ expect(response.status).toBe(200);
48
+ expect(siteVerify.calls).toHaveLength(1);
49
+ const [call] = siteVerify.calls;
50
+ expect(call?.url).toBe(PROCAPTCHA_SITEVERIFY_URL);
51
+ expect(call?.headers.get("content-type")).toBe("application/json");
52
+ expect(call?.body).toEqual({ secret: SECRET, token: TOKEN, ip: CLIENT_IP });
53
+ });
54
+ it("returns 400 without calling siteverify when the token header is missing", async () => {
55
+ const { signIn, siteVerify } = await setup();
56
+ const response = await signIn({ "x-forwarded-for": CLIENT_IP });
57
+ expect(response.status).toBe(400);
58
+ expect(response.headers.get("content-type")).toContain("application/json");
59
+ expect((await readError(response)).code).toBe("PROCAPTCHA_MISSING_RESPONSE");
60
+ expect(siteVerify.calls).toHaveLength(0);
61
+ });
62
+ it("returns 403 when siteverify answers verified: false", async () => {
63
+ const { signIn } = await setup({}, () => rejectedResponse("API.USER_ALREADY_VERIFIED"));
64
+ const response = await signIn(withToken());
65
+ expect(response.status).toBe(403);
66
+ expect((await readError(response)).code).toBe("PROCAPTCHA_VERIFICATION_FAILED");
67
+ });
68
+ it("leaves endpoints outside the list alone", async () => {
69
+ const { signIn, siteVerify } = await setup({
70
+ endpoints: ["/sign-up/email"],
71
+ });
72
+ const response = await signIn();
73
+ expect(response.status).toBe(200);
74
+ expect(siteVerify.calls).toHaveLength(0);
75
+ });
76
+ it("matches wildcard endpoints", async () => {
77
+ const { signIn, siteVerify } = await setup({ endpoints: ["/sign-in/*"] });
78
+ const response = await signIn();
79
+ expect(response.status).toBe(400);
80
+ expect(siteVerify.calls).toHaveLength(0);
81
+ });
82
+ it("rejects a verified token whose score is above maxScore", async () => {
83
+ const { signIn } = await setup({ maxScore: 0.5 }, () => verifiedResponse({ score: 0.9 }));
84
+ const response = await signIn(withToken());
85
+ expect(response.status).toBe(403);
86
+ });
87
+ it("accepts a verified token at or below maxScore, or with no score", async () => {
88
+ let score = 0.5;
89
+ const { signIn } = await setup({ maxScore: 0.5 }, () => verifiedResponse(score === undefined ? {} : { score }));
90
+ expect((await signIn(withToken())).status).toBe(200);
91
+ score = undefined;
92
+ expect((await signIn(withToken())).status).toBe(200);
93
+ });
94
+ it("omits the IP when sendIP is false", async () => {
95
+ const { signIn, siteVerify } = await setup({ sendIP: false });
96
+ await signIn(withToken());
97
+ expect(siteVerify.calls[0]?.body).toEqual({ secret: SECRET, token: TOKEN });
98
+ });
99
+ it("sends the full IPv6 address rather than Better Auth's rate-limit subnet", async () => {
100
+ const { signIn, siteVerify } = await setup();
101
+ await signIn(withToken({ "x-forwarded-for": "2001:db8::1" }));
102
+ expect(siteVerify.calls[0]?.body.ip).toBe("2001:0db8:0000:0000:0000:0000:0000:0001");
103
+ });
104
+ it("sends only the email domain when sendEmailDomain is on", async () => {
105
+ const { signUp, siteVerify } = await setup({ sendEmailDomain: true });
106
+ const response = await signUp("New.User@Example.COM", withToken());
107
+ expect(response.status).toBe(200);
108
+ expect(siteVerify.calls[0]?.body.email).toBe("example.com");
109
+ });
110
+ it("does not send the email by default", async () => {
111
+ const { signUp, siteVerify } = await setup();
112
+ await signUp("someone@example.com", withToken());
113
+ expect(siteVerify.calls[0]?.body.email).toBeUndefined();
114
+ });
115
+ it("uses a custom header name and siteverify URL", async () => {
116
+ const siteVerifyURL = "https://eu-api.prosopo.io/siteverify";
117
+ const { signIn, siteVerify } = await setup({
118
+ headerName: "x-procaptcha-response",
119
+ siteVerifyURL,
120
+ });
121
+ const response = await signIn({ "x-procaptcha-response": TOKEN });
122
+ expect(response.status).toBe(200);
123
+ expect(siteVerify.calls[0]?.url).toBe(siteVerifyURL);
124
+ });
125
+ it("returns 500 when siteverify rejects the request itself", async () => {
126
+ const { signIn } = await setup({}, () => errorEnvelope(400, "API.INVALID_SITE_KEY", "Invalid site key"));
127
+ const response = await signIn(withToken());
128
+ expect(response.status).toBe(500);
129
+ expect((await readError(response)).code).toBe("PROCAPTCHA_UNKNOWN_ERROR");
130
+ });
131
+ it.each([
132
+ ["a 5xx", () => errorEnvelope(500, "API.BAD_REQUEST", "x")],
133
+ ["a 429", () => errorEnvelope(429, "API.BAD_REQUEST", "x")],
134
+ [
135
+ "a non-JSON 502",
136
+ () => new Response("bad gateway", { status: 502 }),
137
+ ],
138
+ ["an unexpected body", () => Response.json({ ok: true })],
139
+ [
140
+ "a network failure",
141
+ () => {
142
+ throw new TypeError("fetch failed");
143
+ },
144
+ ],
145
+ ])("returns 503 on %s", async (_label, respond) => {
146
+ const { signIn } = await setup({}, respond);
147
+ const response = await signIn(withToken());
148
+ expect(response.status).toBe(503);
149
+ expect((await readError(response)).code).toBe("PROCAPTCHA_SERVICE_UNAVAILABLE");
150
+ });
151
+ it("returns 500 without calling siteverify when no secret key is configured", async () => {
152
+ const { signIn, siteVerify } = await setup({ secretKey: "" });
153
+ const response = await signIn(withToken());
154
+ expect(response.status).toBe(500);
155
+ expect(siteVerify.calls).toHaveLength(0);
156
+ });
157
+ });
158
+ //# sourceMappingURL=procaptcha.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"procaptcha.unit.test.js","sourceRoot":"","sources":["../../../src/tests/procaptcha.unit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type Mock } from "vitest";
2
+ import type { SiteVerifyRequestBody, SiteVerifyResponse } from "../siteverify.js";
3
+ export interface SiteVerifyCall {
4
+ url: string;
5
+ headers: Headers;
6
+ body: SiteVerifyRequestBody;
7
+ }
8
+ export type SiteVerifyResponder = (body: SiteVerifyRequestBody) => Response | Promise<Response>;
9
+ export interface SiteVerifyMock {
10
+ fetch: Mock<typeof fetch>;
11
+ calls: SiteVerifyCall[];
12
+ }
13
+ export declare const verifiedResponse: (overrides?: Partial<SiteVerifyResponse>) => Response;
14
+ export declare const rejectedResponse: (reason?: string) => Response;
15
+ export declare const errorEnvelope: (status: number, key: string, message: string) => Response;
16
+ export declare const mockSiteVerify: (respond?: SiteVerifyResponder) => SiteVerifyMock;
17
+ //# sourceMappingURL=siteVerifyMock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siteVerifyMock.d.ts","sourceRoot":"","sources":["../../../src/tests/siteVerifyMock.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,IAAI,EAAM,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAEX,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,qBAAqB,CAAC;CAC5B;AAED,MAAM,MAAM,mBAAmB,GAAG,CACjC,IAAI,EAAE,qBAAqB,KACvB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElC,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC;IAC1B,KAAK,EAAE,cAAc,EAAE,CAAC;CACxB;AAUD,eAAO,MAAM,gBAAgB,eACjB,OAAO,CAAC,kBAAkB,CAAC,KACpC,QAOF,CAAC;AAEF,eAAO,MAAM,gBAAgB,YAAa,MAAM,KAAG,QAOlD,CAAC;AAEF,eAAO,MAAM,aAAa,WACjB,MAAM,OACT,MAAM,WACF,MAAM,KACb,QAKF,CAAC;AAEF,eAAO,MAAM,cAAc,aACjB,mBAAmB,KAC1B,cAeF,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { vi } from "vitest";
2
+ const isRequestBody = (value) => typeof value === "object" &&
3
+ value !== null &&
4
+ "secret" in value &&
5
+ typeof value.secret === "string" &&
6
+ "token" in value &&
7
+ typeof value.token === "string";
8
+ export const verifiedResponse = (overrides = {}) => {
9
+ const body = {
10
+ status: "ok",
11
+ verified: true,
12
+ ...overrides,
13
+ };
14
+ return Response.json(body);
15
+ };
16
+ export const rejectedResponse = (reason) => {
17
+ const body = {
18
+ status: "ok",
19
+ verified: false,
20
+ ...(reason ? { reason } : {}),
21
+ };
22
+ return Response.json(body);
23
+ };
24
+ export const errorEnvelope = (status, key, message) => {
25
+ const body = {
26
+ error: { code: status, key, message },
27
+ };
28
+ return Response.json(body, { status });
29
+ };
30
+ export const mockSiteVerify = (respond = () => verifiedResponse()) => {
31
+ const calls = [];
32
+ const fetchMock = vi.fn(async (input, init) => {
33
+ const parsed = JSON.parse(String(init?.body));
34
+ if (!isRequestBody(parsed)) {
35
+ throw new Error("siteverify was called without secret and token");
36
+ }
37
+ const url = input instanceof Request ? input.url : new URL(input).toString();
38
+ calls.push({ url, headers: new Headers(init?.headers), body: parsed });
39
+ return respond(parsed);
40
+ });
41
+ return { fetch: fetchMock, calls };
42
+ };
43
+ //# sourceMappingURL=siteVerifyMock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siteVerifyMock.js","sourceRoot":"","sources":["../../../src/tests/siteVerifyMock.ts"],"names":[],"mappings":"AAaA,OAAO,EAAa,EAAE,EAAE,MAAM,QAAQ,CAAC;AAsBvC,MAAM,aAAa,GAAG,CAAC,KAAc,EAAkC,EAAE,CACxE,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,KAAK,IAAI;IACd,QAAQ,IAAI,KAAK;IACjB,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;IAChC,OAAO,IAAI,KAAK;IAChB,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC;AAEjC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC/B,YAAyC,EAAE,EAChC,EAAE;IACb,MAAM,IAAI,GAAuB;QAChC,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,IAAI;QACd,GAAG,SAAS;KACZ,CAAC;IACF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAe,EAAY,EAAE;IAC7D,MAAM,IAAI,GAAuB;QAChC,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAK;QACf,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7B,CAAC;IACF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAC5B,MAAc,EACd,GAAW,EACX,OAAe,EACJ,EAAE;IACb,MAAM,IAAI,GAA4B;QACrC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE;KACrC,CAAC;IACF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AACxC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAC7B,UAA+B,GAAG,EAAE,CAAC,gBAAgB,EAAE,EACtC,EAAE;IACnB,MAAM,KAAK,GAAqB,EAAE,CAAC;IACnC,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CACtB,KAAK,EAAE,KAAwB,EAAE,IAAkB,EAAqB,EAAE;QACzE,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,GAAG,GACR,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;QAClE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACvE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC,CACD,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=siteverify.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siteverify.unit.test.d.ts","sourceRoot":"","sources":["../../../src/tests/siteverify.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { verifyProcaptchaToken } from "../siteverify.js";
3
+ import { errorEnvelope, mockSiteVerify, rejectedResponse, verifiedResponse, } from "./siteVerifyMock.js";
4
+ describe("verifyProcaptchaToken", () => {
5
+ it("reports a verified token with its score", async () => {
6
+ const { fetch } = mockSiteVerify(() => verifiedResponse({ score: 0.2 }));
7
+ await expect(verifyProcaptchaToken({ secretKey: "s", token: "t", fetch })).resolves.toEqual({ outcome: "verified", score: 0.2 });
8
+ });
9
+ it("reports a rejected token with its reason", async () => {
10
+ const { fetch } = mockSiteVerify(() => rejectedResponse("API.USER_ALREADY_VERIFIED"));
11
+ await expect(verifyProcaptchaToken({ secretKey: "s", token: "t", fetch })).resolves.toEqual({
12
+ outcome: "rejected",
13
+ reason: "API.USER_ALREADY_VERIFIED",
14
+ });
15
+ });
16
+ it("passes through the error key of a 4xx envelope as a permanent error", async () => {
17
+ const { fetch } = mockSiteVerify(() => errorEnvelope(400, "GENERAL.MISSING_SECRET_KEY", "Missing secret key"));
18
+ await expect(verifyProcaptchaToken({ secretKey: "s", token: "t", fetch })).resolves.toEqual({
19
+ outcome: "error",
20
+ transient: false,
21
+ status: 400,
22
+ key: "GENERAL.MISSING_SECRET_KEY",
23
+ message: "Missing secret key",
24
+ });
25
+ });
26
+ it("does not send empty optional fields", async () => {
27
+ const { fetch, calls } = mockSiteVerify();
28
+ await verifyProcaptchaToken({
29
+ secretKey: "s",
30
+ token: "t",
31
+ ip: "",
32
+ email: "",
33
+ fetch,
34
+ });
35
+ expect(calls[0]?.body).toEqual({ secret: "s", token: "t" });
36
+ });
37
+ it("aborts a slow siteverify call after timeoutMs", async () => {
38
+ const slowFetch = vi.fn((_input, init) => new Promise((_resolve, reject) => {
39
+ init?.signal?.addEventListener("abort", () => reject(init.signal?.reason));
40
+ }));
41
+ const result = await verifyProcaptchaToken({
42
+ secretKey: "s",
43
+ token: "t",
44
+ timeoutMs: 20,
45
+ fetch: slowFetch,
46
+ });
47
+ expect(result).toMatchObject({ outcome: "error", transient: true });
48
+ });
49
+ });
50
+ //# sourceMappingURL=siteverify.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siteverify.unit.test.js","sourceRoot":"","sources":["../../../src/tests/siteverify.unit.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prosopo/better-auth-procaptcha-plugin",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "Prosopo Procaptcha bot protection for Better Auth. Verifies a Procaptcha token before sign-up, sign-in and other auth endpoints run.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -28,7 +28,7 @@
28
28
  "bot-protection"
29
29
  ],
30
30
  "dependencies": {
31
- "@prosopo/types": "5.30.0"
31
+ "@prosopo/types": "5.31.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "better-auth": "^1.7.0"