@prosopo/better-auth-procaptcha-plugin 0.0.0-stage → 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 (83) 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 -0
  5. package/CHANGELOG.md +12 -0
  6. package/LICENSE +201 -0
  7. package/dist/cjs/client.cjs +32 -0
  8. package/dist/cjs/constants.cjs +22 -0
  9. package/dist/cjs/index.cjs +80 -0
  10. package/dist/cjs/path.cjs +41 -0
  11. package/dist/cjs/siteverify.cjs +68 -0
  12. package/dist/esm/_virtual/_rolldown/runtime.js +2 -0
  13. package/dist/esm/client.js +31 -0
  14. package/dist/esm/constants.js +18 -0
  15. package/dist/esm/index.js +75 -0
  16. package/dist/esm/path.js +40 -0
  17. package/dist/esm/siteverify.js +68 -0
  18. package/dist/tsc/client.d.ts +19 -0
  19. package/dist/tsc/client.d.ts.map +1 -0
  20. package/dist/tsc/client.js +39 -0
  21. package/dist/tsc/client.js.map +1 -0
  22. package/dist/tsc/constants.d.ts +12 -0
  23. package/dist/tsc/constants.d.ts.map +1 -0
  24. package/dist/tsc/constants.js +16 -0
  25. package/dist/tsc/constants.js.map +1 -0
  26. package/dist/tsc/index.d.ts +28 -0
  27. package/dist/tsc/index.d.ts.map +1 -0
  28. package/dist/tsc/index.js +94 -0
  29. package/dist/tsc/index.js.map +1 -0
  30. package/dist/tsc/path.d.ts +4 -0
  31. package/dist/tsc/path.d.ts.map +1 -0
  32. package/dist/tsc/path.js +59 -0
  33. package/dist/tsc/path.js.map +1 -0
  34. package/dist/tsc/siteverify.d.ts +40 -0
  35. package/dist/tsc/siteverify.d.ts.map +1 -0
  36. package/dist/tsc/siteverify.js +80 -0
  37. package/dist/tsc/siteverify.js.map +1 -0
  38. package/dist/tsc/tests/client.unit.test.d.ts +2 -0
  39. package/dist/tsc/tests/client.unit.test.d.ts.map +1 -0
  40. package/dist/tsc/tests/client.unit.test.js +56 -0
  41. package/dist/tsc/tests/client.unit.test.js.map +1 -0
  42. package/dist/tsc/tests/path.unit.test.d.ts +2 -0
  43. package/dist/tsc/tests/path.unit.test.d.ts.map +1 -0
  44. package/dist/tsc/tests/path.unit.test.js +53 -0
  45. package/dist/tsc/tests/path.unit.test.js.map +1 -0
  46. package/dist/tsc/tests/procaptcha.unit.test.d.ts +2 -0
  47. package/dist/tsc/tests/procaptcha.unit.test.d.ts.map +1 -0
  48. package/dist/tsc/tests/procaptcha.unit.test.js +158 -0
  49. package/dist/tsc/tests/procaptcha.unit.test.js.map +1 -0
  50. package/dist/tsc/tests/siteVerifyMock.d.ts +17 -0
  51. package/dist/tsc/tests/siteVerifyMock.d.ts.map +1 -0
  52. package/dist/tsc/tests/siteVerifyMock.js +43 -0
  53. package/dist/tsc/tests/siteVerifyMock.js.map +1 -0
  54. package/dist/tsc/tests/siteverify.unit.test.d.ts +2 -0
  55. package/dist/tsc/tests/siteverify.unit.test.d.ts.map +1 -0
  56. package/dist/tsc/tests/siteverify.unit.test.js +50 -0
  57. package/dist/tsc/tests/siteverify.unit.test.js.map +1 -0
  58. package/dist/types/client.d.ts +19 -0
  59. package/dist/types/client.d.ts.map +1 -0
  60. package/dist/types/constants.d.ts +12 -0
  61. package/dist/types/constants.d.ts.map +1 -0
  62. package/dist/types/index.d.ts +28 -0
  63. package/dist/types/index.d.ts.map +1 -0
  64. package/dist/types/path.d.ts +4 -0
  65. package/dist/types/path.d.ts.map +1 -0
  66. package/dist/types/siteverify.d.ts +40 -0
  67. package/dist/types/siteverify.d.ts.map +1 -0
  68. package/dist/types/tests/client.unit.test.d.ts +2 -0
  69. package/dist/types/tests/client.unit.test.d.ts.map +1 -0
  70. package/dist/types/tests/path.unit.test.d.ts +2 -0
  71. package/dist/types/tests/path.unit.test.d.ts.map +1 -0
  72. package/dist/types/tests/procaptcha.unit.test.d.ts +2 -0
  73. package/dist/types/tests/procaptcha.unit.test.d.ts.map +1 -0
  74. package/dist/types/tests/siteVerifyMock.d.ts +17 -0
  75. package/dist/types/tests/siteVerifyMock.d.ts.map +1 -0
  76. package/dist/types/tests/siteverify.unit.test.d.ts +2 -0
  77. package/dist/types/tests/siteverify.unit.test.d.ts.map +1 -0
  78. package/package.json +68 -3
  79. package/readme.md +145 -0
  80. package/vite.cjs.config.ts +24 -0
  81. package/vite.esm.config.ts +23 -0
  82. package/vite.test.config.ts +19 -0
  83. package/README.md +0 -3
@@ -0,0 +1,75 @@
1
+ import "./_virtual/_rolldown/runtime.js";
2
+ import { DEFAULT_ENDPOINTS, PROCAPTCHA_ERROR_CODES, PROCAPTCHA_HEADER, PROCAPTCHA_SITEVERIFY_URL } from "./constants.js";
3
+ import { createPathMatcher, normalizeAuthPath } from "./path.js";
4
+ import { verifyProcaptchaToken } from "./siteverify.js";
5
+ import { getIP } from "better-auth/api";
6
+ //#region src/index.ts
7
+ var errorResponse = (code, status) => ({ response: Response.json({
8
+ code,
9
+ message: PROCAPTCHA_ERROR_CODES[code].message
10
+ }, { status }) });
11
+ var resolveClientIP = (request, options) => getIP(request, {
12
+ ...options,
13
+ advanced: {
14
+ ...options.advanced,
15
+ ipAddress: {
16
+ ...options.advanced?.ipAddress,
17
+ ipv6Subnet: 128
18
+ }
19
+ }
20
+ }) ?? void 0;
21
+ var readEmailDomain = async (request) => {
22
+ if (!request.headers.get("content-type")?.includes("application/json")) return;
23
+ try {
24
+ const body = await request.clone().json();
25
+ if (typeof body !== "object" || body === null || !("email" in body)) return;
26
+ const { email } = body;
27
+ if (typeof email !== "string") return;
28
+ const domain = email.slice(email.lastIndexOf("@") + 1).trim();
29
+ return email.includes("@") && domain ? domain.toLowerCase() : void 0;
30
+ } catch {
31
+ return;
32
+ }
33
+ };
34
+ var procaptcha = (options) => {
35
+ const isProtected = createPathMatcher(options.endpoints?.length ? options.endpoints : DEFAULT_ENDPOINTS);
36
+ const headerName = options.headerName ?? "x-captcha-response";
37
+ return {
38
+ id: "procaptcha",
39
+ $ERROR_CODES: PROCAPTCHA_ERROR_CODES,
40
+ onRequest: async (request, ctx) => {
41
+ const path = normalizeAuthPath(new URL(request.url).pathname, ctx.options.basePath ?? "/api/auth");
42
+ if (!isProtected(path)) return;
43
+ if (!options.secretKey) {
44
+ ctx.logger.error("Procaptcha: secretKey is not configured");
45
+ return errorResponse("PROCAPTCHA_UNKNOWN_ERROR", 500);
46
+ }
47
+ const token = request.headers.get(headerName);
48
+ if (!token) return errorResponse("PROCAPTCHA_MISSING_RESPONSE", 400);
49
+ const ip = options.sendIP === false ? void 0 : resolveClientIP(request, ctx.options);
50
+ const email = options.sendEmailDomain ? await readEmailDomain(request) : void 0;
51
+ const result = await verifyProcaptchaToken({
52
+ secretKey: options.secretKey,
53
+ token,
54
+ ...ip ? { ip } : {},
55
+ ...email ? { email } : {},
56
+ siteVerifyURL: options.siteVerifyURL ?? "https://api.prosopo.io/siteverify",
57
+ timeoutMs: options.timeoutMs ?? 1e4,
58
+ ...options.fetch ? { fetch: options.fetch } : {}
59
+ });
60
+ if (result.outcome === "error") {
61
+ ctx.logger.error("Procaptcha siteverify failed", {
62
+ status: result.status,
63
+ key: result.key,
64
+ message: result.message
65
+ });
66
+ return result.transient ? errorResponse("PROCAPTCHA_SERVICE_UNAVAILABLE", 503) : errorResponse("PROCAPTCHA_UNKNOWN_ERROR", 500);
67
+ }
68
+ if (result.outcome === "rejected") return errorResponse("PROCAPTCHA_VERIFICATION_FAILED", 403);
69
+ if (options.maxScore !== void 0 && result.score !== void 0 && result.score > options.maxScore) return errorResponse("PROCAPTCHA_VERIFICATION_FAILED", 403);
70
+ },
71
+ options
72
+ };
73
+ };
74
+ //#endregion
75
+ export { DEFAULT_ENDPOINTS, PROCAPTCHA_ERROR_CODES, PROCAPTCHA_HEADER, PROCAPTCHA_SITEVERIFY_URL, procaptcha, verifyProcaptchaToken };
@@ -0,0 +1,40 @@
1
+ //#region src/path.ts
2
+ var normalizeAuthPath = (pathname, basePath) => {
3
+ const collapsed = `/${basePath && pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname}`.replace(/\/{2,}/g, "/");
4
+ return collapsed.length > 1 ? collapsed.replace(/\/$/, "") : collapsed;
5
+ };
6
+ var matchesSegment = (pattern, segment) => {
7
+ const parts = pattern.split("*");
8
+ if (parts.length === 1) return pattern === segment;
9
+ const first = parts[0] ?? "";
10
+ const last = parts[parts.length - 1] ?? "";
11
+ if (segment.length < first.length + last.length || !segment.startsWith(first) || !segment.endsWith(last)) return false;
12
+ let cursor = first.length;
13
+ const end = segment.length - last.length;
14
+ for (const part of parts.slice(1, -1)) {
15
+ const found = segment.indexOf(part, cursor);
16
+ if (found === -1 || found + part.length > end) return false;
17
+ cursor = found + part.length;
18
+ }
19
+ return true;
20
+ };
21
+ var matchesSegments = (patterns, segments) => {
22
+ const [pattern, ...restPatterns] = patterns;
23
+ if (pattern === void 0) return segments.length === 0;
24
+ if (pattern === "**") {
25
+ for (let skip = 0; skip <= segments.length; skip++) if (matchesSegments(restPatterns, segments.slice(skip))) return true;
26
+ return false;
27
+ }
28
+ const [segment, ...restSegments] = segments;
29
+ return segment !== void 0 && matchesSegment(pattern, segment) && matchesSegments(restPatterns, restSegments);
30
+ };
31
+ var createPathMatcher = (endpoints) => {
32
+ const matchers = endpoints.map((endpoint) => {
33
+ if (!endpoint.includes("*")) return (path) => path === endpoint;
34
+ const patterns = endpoint.split("/");
35
+ return (path) => matchesSegments(patterns, path.split("/"));
36
+ });
37
+ return (path) => matchers.some((matches) => matches(path));
38
+ };
39
+ //#endregion
40
+ export { createPathMatcher, normalizeAuthPath };
@@ -0,0 +1,68 @@
1
+ import { DEFAULT_TIMEOUT_MS, PROCAPTCHA_SITEVERIFY_URL } from "./constants.js";
2
+ //#region src/siteverify.ts
3
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
+ var isSiteVerifyResponse = (value) => isRecord(value) && typeof value.status === "string" && typeof value.verified === "boolean" && (value.score === void 0 || typeof value.score === "number") && (value.reason === void 0 || typeof value.reason === "string");
5
+ var isSiteVerifyErrorResponse = (value) => isRecord(value) && isRecord(value.error) && typeof value.error.key === "string";
6
+ var readJson = async (response) => {
7
+ try {
8
+ return await response.json();
9
+ } catch {
10
+ return;
11
+ }
12
+ };
13
+ var isTransientStatus = (status) => status === 429 || status >= 500;
14
+ var verifyProcaptchaToken = async ({ secretKey, token, ip, email, siteVerifyURL = PROCAPTCHA_SITEVERIFY_URL, timeoutMs = DEFAULT_TIMEOUT_MS, fetch: fetchImpl = fetch }) => {
15
+ const body = {
16
+ secret: secretKey,
17
+ token,
18
+ ...ip ? { ip } : {},
19
+ ...email ? { email } : {}
20
+ };
21
+ let response;
22
+ try {
23
+ response = await fetchImpl(siteVerifyURL, {
24
+ method: "POST",
25
+ headers: {
26
+ "Content-Type": "application/json",
27
+ Accept: "application/json"
28
+ },
29
+ body: JSON.stringify(body),
30
+ signal: AbortSignal.timeout(timeoutMs)
31
+ });
32
+ } catch (error) {
33
+ return {
34
+ outcome: "error",
35
+ transient: true,
36
+ message: error instanceof Error ? error.message : String(error)
37
+ };
38
+ }
39
+ const payload = await readJson(response);
40
+ if (!response.ok) {
41
+ const errorBody = isSiteVerifyErrorResponse(payload) ? payload.error : void 0;
42
+ return {
43
+ outcome: "error",
44
+ transient: isTransientStatus(response.status),
45
+ status: response.status,
46
+ ...errorBody ? { key: errorBody.key } : {},
47
+ message: typeof errorBody?.message === "string" ? errorBody.message : `siteverify responded with HTTP ${response.status}`
48
+ };
49
+ }
50
+ if (!isSiteVerifyResponse(payload)) return {
51
+ outcome: "error",
52
+ transient: true,
53
+ status: response.status,
54
+ message: "siteverify returned an unexpected response body"
55
+ };
56
+ const score = payload.score === void 0 ? {} : { score: payload.score };
57
+ if (!payload.verified) return {
58
+ outcome: "rejected",
59
+ ...payload.reason ? { reason: payload.reason } : {},
60
+ ...score
61
+ };
62
+ return {
63
+ outcome: "verified",
64
+ ...score
65
+ };
66
+ };
67
+ //#endregion
68
+ export { verifyProcaptchaToken };
@@ -0,0 +1,19 @@
1
+ import type { procaptcha } from "./index.js";
2
+ export interface ProcaptchaClientOptions {
3
+ getToken?: () => string | undefined | Promise<string | undefined>;
4
+ endpoints?: readonly string[];
5
+ headerName?: string;
6
+ }
7
+ export declare const procaptchaHeaders: (token: string, headerName?: string) => Record<string, string>;
8
+ export declare const procaptchaClient: (options?: ProcaptchaClientOptions) => {
9
+ id: "procaptcha";
10
+ $InferServerPlugin: ReturnType<typeof procaptcha>;
11
+ fetchPlugins: {
12
+ id: string;
13
+ name: string;
14
+ hooks: {
15
+ onRequest: <T extends Record<string, any>>(context: import("better-auth/client").RequestContext<T>) => Promise<import("better-auth/client").RequestContext<T>>;
16
+ };
17
+ }[];
18
+ };
19
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,uBAAuB;IAMvC,QAAQ,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAElE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAE9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,iBAAiB,UACtB,MAAM,eACD,MAAM,KAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAA8B,CAAC;AAQvD,eAAO,MAAM,gBAAgB,aAAa,uBAAuB;;wBASrC,UAAU,CAAC,OAAO,UAAU,CAAC;;;;;;;;CAyBxD,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { DEFAULT_ENDPOINTS, PROCAPTCHA_HEADER } from "./constants.js";
2
+ import { createPathMatcher, normalizeAuthPath } from "./path.js";
3
+ export const procaptchaHeaders = (token, headerName = PROCAPTCHA_HEADER) => ({ [headerName]: token });
4
+ const authPathOf = (url, baseURL) => {
5
+ const requestURL = new URL(url, "http://localhost");
6
+ const basePath = baseURL ? new URL(baseURL, requestURL).pathname : "";
7
+ return normalizeAuthPath(requestURL.pathname, basePath);
8
+ };
9
+ export const procaptchaClient = (options = {}) => {
10
+ const { getToken } = options;
11
+ const headerName = options.headerName ?? PROCAPTCHA_HEADER;
12
+ const isProtected = createPathMatcher(options.endpoints?.length ? options.endpoints : DEFAULT_ENDPOINTS);
13
+ return {
14
+ id: "procaptcha",
15
+ $InferServerPlugin: {},
16
+ fetchPlugins: getToken
17
+ ? [
18
+ {
19
+ id: "procaptcha",
20
+ name: "procaptcha",
21
+ hooks: {
22
+ onRequest: async (context) => {
23
+ if (context.headers.has(headerName) ||
24
+ !isProtected(authPathOf(context.url, context.baseURL))) {
25
+ return context;
26
+ }
27
+ const token = await getToken();
28
+ if (token) {
29
+ context.headers.set(headerName, token);
30
+ }
31
+ return context;
32
+ },
33
+ },
34
+ },
35
+ ]
36
+ : [],
37
+ };
38
+ };
39
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEtE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAejE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAChC,KAAa,EACb,aAAqB,iBAAiB,EACb,EAAE,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAEvD,MAAM,UAAU,GAAG,CAAC,GAAiB,EAAE,OAA2B,EAAU,EAAE;IAC7E,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,OAAO,iBAAiB,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACzD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,UAAmC,EAAE,EAAE,EAAE;IACzE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC7B,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,iBAAiB,CAAC;IAC3D,MAAM,WAAW,GAAG,iBAAiB,CACpC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CACjE,CAAC;IAEF,OAAO;QACN,EAAE,EAAE,YAAY;QAChB,kBAAkB,EAAE,EAAmC;QACvD,YAAY,EAAE,QAAQ;YACrB,CAAC,CAAC;gBACA;oBACC,EAAE,EAAE,YAAY;oBAChB,IAAI,EAAE,YAAY;oBAClB,KAAK,EAAE;wBACN,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;4BAC5B,IACC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;gCAC/B,CAAC,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,EACrD,CAAC;gCACF,OAAO,OAAO,CAAC;4BAChB,CAAC;4BACD,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;4BAC/B,IAAI,KAAK,EAAE,CAAC;gCACX,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;4BACxC,CAAC;4BACD,OAAO,OAAO,CAAC;wBAChB,CAAC;qBACD;iBACD;aACD;YACF,CAAC,CAAC,EAAE;KAC4B,CAAC;AACpC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ export declare const PROCAPTCHA_SITEVERIFY_URL = "https://api.prosopo.io/siteverify";
2
+ export declare const PROCAPTCHA_HEADER = "x-captcha-response";
3
+ export declare const DEFAULT_ENDPOINTS: readonly string[];
4
+ export declare const DEFAULT_TIMEOUT_MS = 10000;
5
+ export declare const PROCAPTCHA_ERROR_CODES: {
6
+ PROCAPTCHA_MISSING_RESPONSE: import("better-auth").RawError<"PROCAPTCHA_MISSING_RESPONSE">;
7
+ PROCAPTCHA_VERIFICATION_FAILED: import("better-auth").RawError<"PROCAPTCHA_VERIFICATION_FAILED">;
8
+ PROCAPTCHA_SERVICE_UNAVAILABLE: import("better-auth").RawError<"PROCAPTCHA_SERVICE_UNAVAILABLE">;
9
+ PROCAPTCHA_UNKNOWN_ERROR: import("better-auth").RawError<"PROCAPTCHA_UNKNOWN_ERROR">;
10
+ };
11
+ export type ProcaptchaErrorCode = keyof typeof PROCAPTCHA_ERROR_CODES;
12
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,yBAAyB,sCAAsC,CAAC;AAE7E,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AAEtD,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAI9C,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC,eAAO,MAAM,sBAAsB;;;;;CAKjC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,MAAM,OAAO,sBAAsB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { defineErrorCodes } from "better-auth";
2
+ export const PROCAPTCHA_SITEVERIFY_URL = "https://api.prosopo.io/siteverify";
3
+ export const PROCAPTCHA_HEADER = "x-captcha-response";
4
+ export const DEFAULT_ENDPOINTS = [
5
+ "/sign-up/email",
6
+ "/sign-in/email",
7
+ "/request-password-reset",
8
+ ];
9
+ export const DEFAULT_TIMEOUT_MS = 10_000;
10
+ export const PROCAPTCHA_ERROR_CODES = defineErrorCodes({
11
+ PROCAPTCHA_MISSING_RESPONSE: "Missing Procaptcha response",
12
+ PROCAPTCHA_VERIFICATION_FAILED: "Procaptcha verification failed",
13
+ PROCAPTCHA_SERVICE_UNAVAILABLE: "Procaptcha service unavailable",
14
+ PROCAPTCHA_UNKNOWN_ERROR: "Something went wrong",
15
+ });
16
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,CAAC,MAAM,yBAAyB,GAAG,mCAAmC,CAAC;AAE7E,MAAM,CAAC,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AAEtD,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IACnD,gBAAgB;IAChB,gBAAgB;IAChB,yBAAyB;CACzB,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAEzC,MAAM,CAAC,MAAM,sBAAsB,GAAG,gBAAgB,CAAC;IACtD,2BAA2B,EAAE,6BAA6B;IAC1D,8BAA8B,EAAE,gCAAgC;IAChE,8BAA8B,EAAE,gCAAgC;IAChE,wBAAwB,EAAE,sBAAsB;CAChD,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { AuthContext } from "better-auth";
2
+ export { DEFAULT_ENDPOINTS, PROCAPTCHA_ERROR_CODES, PROCAPTCHA_HEADER, PROCAPTCHA_SITEVERIFY_URL, type ProcaptchaErrorCode, } from "./constants.js";
3
+ export { verifyProcaptchaToken, type SiteVerifyErrorResponse, type SiteVerifyRequestBody, type SiteVerifyResponse, type VerifyResult, type VerifyTokenParams, } from "./siteverify.js";
4
+ export interface ProcaptchaOptions {
5
+ secretKey: string;
6
+ endpoints?: readonly string[];
7
+ siteVerifyURL?: string;
8
+ headerName?: string;
9
+ sendIP?: boolean;
10
+ sendEmailDomain?: boolean;
11
+ maxScore?: number;
12
+ timeoutMs?: number;
13
+ fetch?: typeof fetch;
14
+ }
15
+ export declare const procaptcha: (options: ProcaptchaOptions) => {
16
+ id: "procaptcha";
17
+ $ERROR_CODES: {
18
+ PROCAPTCHA_MISSING_RESPONSE: import("better-auth").RawError<"PROCAPTCHA_MISSING_RESPONSE">;
19
+ PROCAPTCHA_VERIFICATION_FAILED: import("better-auth").RawError<"PROCAPTCHA_VERIFICATION_FAILED">;
20
+ PROCAPTCHA_SERVICE_UNAVAILABLE: import("better-auth").RawError<"PROCAPTCHA_SERVICE_UNAVAILABLE">;
21
+ PROCAPTCHA_UNKNOWN_ERROR: import("better-auth").RawError<"PROCAPTCHA_UNKNOWN_ERROR">;
22
+ };
23
+ onRequest: (request: Request, ctx: AuthContext) => Promise<{
24
+ response: Response;
25
+ } | undefined>;
26
+ options: ProcaptchaOptions;
27
+ };
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACX,WAAW,EAGX,MAAM,aAAa,CAAC;AAarB,OAAO,EACN,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,yBAAyB,EACzB,KAAK,mBAAmB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACtB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,iBAAiB;IAEjC,SAAS,EAAE,MAAM,CAAC;IAOlB,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAE9B,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,UAAU,CAAC,EAAE,MAAM,CAAC;IAOpB,MAAM,CAAC,EAAE,OAAO,CAAC;IAOjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAM1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACrB;AA8CD,eAAO,MAAM,UAAU,YAAa,iBAAiB;;;;;;;;yBAUzC,OAAO,OACX,WAAW,KACd,OAAO,CAAC;QAAE,QAAQ,EAAE,QAAQ,CAAA;KAAE,GAAG,SAAS,CAAC;;CAgE/C,CAAC"}
@@ -0,0 +1,94 @@
1
+ import { getIP } from "better-auth/api";
2
+ import { DEFAULT_ENDPOINTS, DEFAULT_TIMEOUT_MS, PROCAPTCHA_ERROR_CODES, PROCAPTCHA_HEADER, PROCAPTCHA_SITEVERIFY_URL, } from "./constants.js";
3
+ import { createPathMatcher, normalizeAuthPath } from "./path.js";
4
+ import { verifyProcaptchaToken } from "./siteverify.js";
5
+ export { DEFAULT_ENDPOINTS, PROCAPTCHA_ERROR_CODES, PROCAPTCHA_HEADER, PROCAPTCHA_SITEVERIFY_URL, } from "./constants.js";
6
+ export { verifyProcaptchaToken, } from "./siteverify.js";
7
+ const errorResponse = (code, status) => ({
8
+ response: Response.json({ code, message: PROCAPTCHA_ERROR_CODES[code].message }, { status }),
9
+ });
10
+ const resolveClientIP = (request, options) => getIP(request, {
11
+ ...options,
12
+ advanced: {
13
+ ...options.advanced,
14
+ ipAddress: { ...options.advanced?.ipAddress, ipv6Subnet: 128 },
15
+ },
16
+ }) ?? undefined;
17
+ const readEmailDomain = async (request) => {
18
+ if (!request.headers.get("content-type")?.includes("application/json")) {
19
+ return undefined;
20
+ }
21
+ try {
22
+ const body = await request.clone().json();
23
+ if (typeof body !== "object" || body === null || !("email" in body)) {
24
+ return undefined;
25
+ }
26
+ const { email } = body;
27
+ if (typeof email !== "string") {
28
+ return undefined;
29
+ }
30
+ const domain = email.slice(email.lastIndexOf("@") + 1).trim();
31
+ return email.includes("@") && domain ? domain.toLowerCase() : undefined;
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ };
37
+ export const procaptcha = (options) => {
38
+ const isProtected = createPathMatcher(options.endpoints?.length ? options.endpoints : DEFAULT_ENDPOINTS);
39
+ const headerName = options.headerName ?? PROCAPTCHA_HEADER;
40
+ return {
41
+ id: "procaptcha",
42
+ $ERROR_CODES: PROCAPTCHA_ERROR_CODES,
43
+ onRequest: async (request, ctx) => {
44
+ const path = normalizeAuthPath(new URL(request.url).pathname, ctx.options.basePath ?? "/api/auth");
45
+ if (!isProtected(path)) {
46
+ return undefined;
47
+ }
48
+ if (!options.secretKey) {
49
+ ctx.logger.error("Procaptcha: secretKey is not configured");
50
+ return errorResponse("PROCAPTCHA_UNKNOWN_ERROR", 500);
51
+ }
52
+ const token = request.headers.get(headerName);
53
+ if (!token) {
54
+ return errorResponse("PROCAPTCHA_MISSING_RESPONSE", 400);
55
+ }
56
+ const ip = options.sendIP === false
57
+ ? undefined
58
+ : resolveClientIP(request, ctx.options);
59
+ const email = options.sendEmailDomain
60
+ ? await readEmailDomain(request)
61
+ : undefined;
62
+ const result = await verifyProcaptchaToken({
63
+ secretKey: options.secretKey,
64
+ token,
65
+ ...(ip ? { ip } : {}),
66
+ ...(email ? { email } : {}),
67
+ siteVerifyURL: options.siteVerifyURL ?? PROCAPTCHA_SITEVERIFY_URL,
68
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
69
+ ...(options.fetch ? { fetch: options.fetch } : {}),
70
+ });
71
+ if (result.outcome === "error") {
72
+ ctx.logger.error("Procaptcha siteverify failed", {
73
+ status: result.status,
74
+ key: result.key,
75
+ message: result.message,
76
+ });
77
+ return result.transient
78
+ ? errorResponse("PROCAPTCHA_SERVICE_UNAVAILABLE", 503)
79
+ : errorResponse("PROCAPTCHA_UNKNOWN_ERROR", 500);
80
+ }
81
+ if (result.outcome === "rejected") {
82
+ return errorResponse("PROCAPTCHA_VERIFICATION_FAILED", 403);
83
+ }
84
+ if (options.maxScore !== undefined &&
85
+ result.score !== undefined &&
86
+ result.score > options.maxScore) {
87
+ return errorResponse("PROCAPTCHA_VERIFICATION_FAILED", 403);
88
+ }
89
+ return undefined;
90
+ },
91
+ options,
92
+ };
93
+ };
94
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare const normalizeAuthPath: (pathname: string, basePath: string) => string;
2
+ export type PathMatcher = (path: string) => boolean;
3
+ export declare const createPathMatcher: (endpoints: readonly string[]) => PathMatcher;
4
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/path.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,iBAAiB,aACnB,MAAM,YACN,MAAM,KACd,MAOF,CAAC;AAoDF,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;AAEpD,eAAO,MAAM,iBAAiB,cAClB,SAAS,MAAM,EAAE,KAC1B,WAUF,CAAC"}
@@ -0,0 +1,59 @@
1
+ export const normalizeAuthPath = (pathname, basePath) => {
2
+ const relative = basePath && pathname.startsWith(basePath)
3
+ ? pathname.slice(basePath.length)
4
+ : pathname;
5
+ const collapsed = `/${relative}`.replace(/\/{2,}/g, "/");
6
+ return collapsed.length > 1 ? collapsed.replace(/\/$/, "") : collapsed;
7
+ };
8
+ const matchesSegment = (pattern, segment) => {
9
+ const parts = pattern.split("*");
10
+ if (parts.length === 1) {
11
+ return pattern === segment;
12
+ }
13
+ const first = parts[0] ?? "";
14
+ const last = parts[parts.length - 1] ?? "";
15
+ if (segment.length < first.length + last.length ||
16
+ !segment.startsWith(first) ||
17
+ !segment.endsWith(last)) {
18
+ return false;
19
+ }
20
+ let cursor = first.length;
21
+ const end = segment.length - last.length;
22
+ for (const part of parts.slice(1, -1)) {
23
+ const found = segment.indexOf(part, cursor);
24
+ if (found === -1 || found + part.length > end) {
25
+ return false;
26
+ }
27
+ cursor = found + part.length;
28
+ }
29
+ return true;
30
+ };
31
+ const matchesSegments = (patterns, segments) => {
32
+ const [pattern, ...restPatterns] = patterns;
33
+ if (pattern === undefined) {
34
+ return segments.length === 0;
35
+ }
36
+ if (pattern === "**") {
37
+ for (let skip = 0; skip <= segments.length; skip++) {
38
+ if (matchesSegments(restPatterns, segments.slice(skip))) {
39
+ return true;
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ const [segment, ...restSegments] = segments;
45
+ return (segment !== undefined &&
46
+ matchesSegment(pattern, segment) &&
47
+ matchesSegments(restPatterns, restSegments));
48
+ };
49
+ export const createPathMatcher = (endpoints) => {
50
+ const matchers = endpoints.map((endpoint) => {
51
+ if (!endpoint.includes("*")) {
52
+ return (path) => path === endpoint;
53
+ }
54
+ const patterns = endpoint.split("/");
55
+ return (path) => matchesSegments(patterns, path.split("/"));
56
+ });
57
+ return (path) => matchers.some((matches) => matches(path));
58
+ };
59
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/path.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { VerificationResponse } from "@prosopo/types";
2
+ export interface SiteVerifyRequestBody {
3
+ secret: string;
4
+ token: string;
5
+ ip?: string;
6
+ email?: string;
7
+ }
8
+ export type SiteVerifyResponse = Omit<VerificationResponse, "error" | "sessionId">;
9
+ export interface SiteVerifyErrorResponse {
10
+ error: {
11
+ code: number;
12
+ key: string;
13
+ message: string | unknown[];
14
+ };
15
+ }
16
+ export type VerifyResult = {
17
+ outcome: "verified";
18
+ score?: number;
19
+ } | {
20
+ outcome: "rejected";
21
+ reason?: string;
22
+ score?: number;
23
+ } | {
24
+ outcome: "error";
25
+ transient: boolean;
26
+ status?: number;
27
+ key?: string;
28
+ message: string;
29
+ };
30
+ export interface VerifyTokenParams {
31
+ secretKey: string;
32
+ token: string;
33
+ ip?: string;
34
+ email?: string;
35
+ siteVerifyURL?: string;
36
+ timeoutMs?: number;
37
+ fetch?: typeof fetch;
38
+ }
39
+ export declare const verifyProcaptchaToken: ({ secretKey, token, ip, email, siteVerifyURL, timeoutMs, fetch: fetchImpl, }: VerifyTokenParams) => Promise<VerifyResult>;
40
+ //# sourceMappingURL=siteverify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siteverify.d.ts","sourceRoot":"","sources":["../../src/siteverify.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACpC,oBAAoB,EACpB,OAAO,GAAG,WAAW,CACrB,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;KAC5B,CAAC;CACF;AAED,MAAM,MAAM,YAAY,GACrB;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD;IACA,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACf,CAAC;AAEL,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACrB;AA+BD,eAAO,MAAM,qBAAqB,iFAQ/B,iBAAiB,KAAG,OAAO,CAAC,YAAY,CA+D1C,CAAC"}
@@ -0,0 +1,80 @@
1
+ import { DEFAULT_TIMEOUT_MS, PROCAPTCHA_SITEVERIFY_URL } from "./constants.js";
2
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3
+ const isSiteVerifyResponse = (value) => isRecord(value) &&
4
+ typeof value.status === "string" &&
5
+ typeof value.verified === "boolean" &&
6
+ (value.score === undefined || typeof value.score === "number") &&
7
+ (value.reason === undefined || typeof value.reason === "string");
8
+ const isSiteVerifyErrorResponse = (value) => isRecord(value) &&
9
+ isRecord(value.error) &&
10
+ typeof value.error.key === "string";
11
+ const readJson = async (response) => {
12
+ try {
13
+ const body = await response.json();
14
+ return body;
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ };
20
+ const isTransientStatus = (status) => status === 429 || status >= 500;
21
+ export const verifyProcaptchaToken = async ({ secretKey, token, ip, email, siteVerifyURL = PROCAPTCHA_SITEVERIFY_URL, timeoutMs = DEFAULT_TIMEOUT_MS, fetch: fetchImpl = fetch, }) => {
22
+ const body = {
23
+ secret: secretKey,
24
+ token,
25
+ ...(ip ? { ip } : {}),
26
+ ...(email ? { email } : {}),
27
+ };
28
+ let response;
29
+ try {
30
+ response = await fetchImpl(siteVerifyURL, {
31
+ method: "POST",
32
+ headers: {
33
+ "Content-Type": "application/json",
34
+ Accept: "application/json",
35
+ },
36
+ body: JSON.stringify(body),
37
+ signal: AbortSignal.timeout(timeoutMs),
38
+ });
39
+ }
40
+ catch (error) {
41
+ return {
42
+ outcome: "error",
43
+ transient: true,
44
+ message: error instanceof Error ? error.message : String(error),
45
+ };
46
+ }
47
+ const payload = await readJson(response);
48
+ if (!response.ok) {
49
+ const errorBody = isSiteVerifyErrorResponse(payload)
50
+ ? payload.error
51
+ : undefined;
52
+ return {
53
+ outcome: "error",
54
+ transient: isTransientStatus(response.status),
55
+ status: response.status,
56
+ ...(errorBody ? { key: errorBody.key } : {}),
57
+ message: typeof errorBody?.message === "string"
58
+ ? errorBody.message
59
+ : `siteverify responded with HTTP ${response.status}`,
60
+ };
61
+ }
62
+ if (!isSiteVerifyResponse(payload)) {
63
+ return {
64
+ outcome: "error",
65
+ transient: true,
66
+ status: response.status,
67
+ message: "siteverify returned an unexpected response body",
68
+ };
69
+ }
70
+ const score = payload.score === undefined ? {} : { score: payload.score };
71
+ if (!payload.verified) {
72
+ return {
73
+ outcome: "rejected",
74
+ ...(payload.reason ? { reason: payload.reason } : {}),
75
+ ...score,
76
+ };
77
+ }
78
+ return { outcome: "verified", ...score };
79
+ };
80
+ //# sourceMappingURL=siteverify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siteverify.js","sourceRoot":"","sources":["../../src/siteverify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.unit.test.d.ts","sourceRoot":"","sources":["../../../src/tests/client.unit.test.ts"],"names":[],"mappings":""}