@businessflow/reviews 2.1.4 → 2.1.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.
@@ -0,0 +1,92 @@
1
+ // src/server/recaptcha.ts
2
+ async function verifyRecaptcha(token, config) {
3
+ if (!config.secretKey) {
4
+ console.warn("RECAPTCHA_SECRET_KEY not configured, skipping verification");
5
+ return { success: true };
6
+ }
7
+ if (!token || typeof token !== "string") {
8
+ return {
9
+ success: false,
10
+ errorCodes: ["missing-input-response"]
11
+ };
12
+ }
13
+ const timeout = config.timeoutMs || 1e4;
14
+ try {
15
+ const controller = new AbortController();
16
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
17
+ const response = await fetch("https://www.google.com/recaptcha/api/siteverify", {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/x-www-form-urlencoded"
21
+ },
22
+ body: new URLSearchParams({
23
+ secret: config.secretKey,
24
+ response: token
25
+ }),
26
+ signal: controller.signal
27
+ });
28
+ clearTimeout(timeoutId);
29
+ if (!response.ok) {
30
+ console.error("reCAPTCHA API returned non-OK status:", response.status);
31
+ return {
32
+ success: false,
33
+ errorCodes: ["recaptcha-api-error"]
34
+ };
35
+ }
36
+ const data = await response.json();
37
+ if (config.minimumScore !== void 0 && data.score !== void 0) {
38
+ if (data.score < config.minimumScore) {
39
+ return {
40
+ success: false,
41
+ score: data.score,
42
+ errorCodes: ["score-threshold-not-met"]
43
+ };
44
+ }
45
+ }
46
+ return {
47
+ success: data.success === true,
48
+ score: data.score,
49
+ action: data.action,
50
+ challengeTimestamp: data.challenge_ts,
51
+ hostname: data.hostname,
52
+ errorCodes: data["error-codes"] || []
53
+ };
54
+ } catch (error) {
55
+ if (error instanceof Error && error.name === "AbortError") {
56
+ console.error("reCAPTCHA verification timeout");
57
+ return {
58
+ success: false,
59
+ errorCodes: ["timeout-or-duplicate"]
60
+ };
61
+ }
62
+ console.error("reCAPTCHA verification error:", error);
63
+ return {
64
+ success: false,
65
+ errorCodes: ["network-error"]
66
+ };
67
+ }
68
+ }
69
+ function getRecaptchaErrorMessage(errorCodes) {
70
+ const errorMessages = {
71
+ "missing-input-secret": "reCAPTCHA secret key is missing",
72
+ "invalid-input-secret": "reCAPTCHA secret key is invalid",
73
+ "missing-input-response": "reCAPTCHA token is missing",
74
+ "invalid-input-response": "reCAPTCHA token is invalid or malformed",
75
+ "bad-request": "The request is invalid or malformed",
76
+ "timeout-or-duplicate": "reCAPTCHA verification timed out or token was already used",
77
+ "score-threshold-not-met": "reCAPTCHA score is below the required threshold",
78
+ "recaptcha-api-error": "reCAPTCHA service is unavailable",
79
+ "network-error": "Network error during reCAPTCHA verification"
80
+ };
81
+ if (!errorCodes || errorCodes.length === 0) {
82
+ return "reCAPTCHA verification failed";
83
+ }
84
+ const knownErrors = errorCodes.filter((code) => errorMessages[code]).map((code) => errorMessages[code]);
85
+ return knownErrors.length > 0 ? knownErrors.join(", ") : "reCAPTCHA verification failed";
86
+ }
87
+
88
+ export {
89
+ verifyRecaptcha,
90
+ getRecaptchaErrorMessage
91
+ };
92
+ //# sourceMappingURL=chunk-FQOS7677.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/recaptcha.ts"],"sourcesContent":["/**\n * Server-side reCAPTCHA verification utility\n */\n\nexport interface RecaptchaConfig {\n secretKey: string;\n minimumScore?: number; // For reCAPTCHA v3, minimum score (0.0 to 1.0)\n timeoutMs?: number; // Request timeout in milliseconds\n}\n\nexport interface RecaptchaVerificationResult {\n success: boolean;\n score?: number; // reCAPTCHA v3 score\n action?: string; // reCAPTCHA v3 action\n challengeTimestamp?: string;\n hostname?: string;\n errorCodes?: string[];\n}\n\n/**\n * Verify reCAPTCHA token with Google's API\n */\nexport async function verifyRecaptcha(\n token: string,\n config: RecaptchaConfig\n): Promise<RecaptchaVerificationResult> {\n if (!config.secretKey) {\n console.warn('RECAPTCHA_SECRET_KEY not configured, skipping verification');\n return { success: true };\n }\n\n if (!token || typeof token !== 'string') {\n return {\n success: false,\n errorCodes: ['missing-input-response']\n };\n }\n\n const timeout = config.timeoutMs || 10000; // 10 second default timeout\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch('https://www.google.com/recaptcha/api/siteverify', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n secret: config.secretKey,\n response: token\n }),\n signal: controller.signal\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.error('reCAPTCHA API returned non-OK status:', response.status);\n return {\n success: false,\n errorCodes: ['recaptcha-api-error']\n };\n }\n\n const data = await response.json();\n \n // Check minimum score for v3 (if configured)\n if (config.minimumScore !== undefined && data.score !== undefined) {\n if (data.score < config.minimumScore) {\n return {\n success: false,\n score: data.score,\n errorCodes: ['score-threshold-not-met']\n };\n }\n }\n\n return {\n success: data.success === true,\n score: data.score,\n action: data.action,\n challengeTimestamp: data.challenge_ts,\n hostname: data.hostname,\n errorCodes: data['error-codes'] || []\n };\n\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n console.error('reCAPTCHA verification timeout');\n return {\n success: false,\n errorCodes: ['timeout-or-duplicate']\n };\n }\n\n console.error('reCAPTCHA verification error:', error);\n return {\n success: false,\n errorCodes: ['network-error']\n };\n }\n}\n\n/**\n * Get human-readable error message for reCAPTCHA error codes\n */\nexport function getRecaptchaErrorMessage(errorCodes: string[]): string {\n const errorMessages: { [key: string]: string } = {\n 'missing-input-secret': 'reCAPTCHA secret key is missing',\n 'invalid-input-secret': 'reCAPTCHA secret key is invalid',\n 'missing-input-response': 'reCAPTCHA token is missing',\n 'invalid-input-response': 'reCAPTCHA token is invalid or malformed',\n 'bad-request': 'The request is invalid or malformed',\n 'timeout-or-duplicate': 'reCAPTCHA verification timed out or token was already used',\n 'score-threshold-not-met': 'reCAPTCHA score is below the required threshold',\n 'recaptcha-api-error': 'reCAPTCHA service is unavailable',\n 'network-error': 'Network error during reCAPTCHA verification'\n };\n\n if (!errorCodes || errorCodes.length === 0) {\n return 'reCAPTCHA verification failed';\n }\n\n const knownErrors = errorCodes\n .filter(code => errorMessages[code])\n .map(code => errorMessages[code]);\n\n return knownErrors.length > 0 \n ? knownErrors.join(', ')\n : 'reCAPTCHA verification failed';\n}"],"mappings":";AAsBA,eAAsB,gBACpB,OACA,QACsC;AACtC,MAAI,CAAC,OAAO,WAAW;AACrB,YAAQ,KAAK,4DAA4D;AACzE,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAEA,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,WAAO;AAAA,MACL,SAAS;AAAA,MACT,YAAY,CAAC,wBAAwB;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,UAAU,OAAO,aAAa;AAEpC,MAAI;AACF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAE9D,UAAM,WAAW,MAAM,MAAM,mDAAmD;AAAA,MAC9E,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB;AAAA,QACxB,QAAQ,OAAO;AAAA,QACf,UAAU;AAAA,MACZ,CAAC;AAAA,MACD,QAAQ,WAAW;AAAA,IACrB,CAAC;AAED,iBAAa,SAAS;AAEtB,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ,MAAM,yCAAyC,SAAS,MAAM;AACtE,aAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY,CAAC,qBAAqB;AAAA,MACpC;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AAGjC,QAAI,OAAO,iBAAiB,UAAa,KAAK,UAAU,QAAW;AACjE,UAAI,KAAK,QAAQ,OAAO,cAAc;AACpC,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO,KAAK;AAAA,UACZ,YAAY,CAAC,yBAAyB;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS,KAAK,YAAY;AAAA,MAC1B,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,oBAAoB,KAAK;AAAA,MACzB,UAAU,KAAK;AAAA,MACf,YAAY,KAAK,aAAa,KAAK,CAAC;AAAA,IACtC;AAAA,EAEF,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAS,MAAM,SAAS,cAAc;AACzD,cAAQ,MAAM,gCAAgC;AAC9C,aAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY,CAAC,sBAAsB;AAAA,MACrC;AAAA,IACF;AAEA,YAAQ,MAAM,iCAAiC,KAAK;AACpD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,YAAY,CAAC,eAAe;AAAA,IAC9B;AAAA,EACF;AACF;AAKO,SAAS,yBAAyB,YAA8B;AACrE,QAAM,gBAA2C;AAAA,IAC/C,wBAAwB;AAAA,IACxB,wBAAwB;AAAA,IACxB,0BAA0B;AAAA,IAC1B,0BAA0B;AAAA,IAC1B,eAAe;AAAA,IACf,wBAAwB;AAAA,IACxB,2BAA2B;AAAA,IAC3B,uBAAuB;AAAA,IACvB,iBAAiB;AAAA,EACnB;AAEA,MAAI,CAAC,cAAc,WAAW,WAAW,GAAG;AAC1C,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,WACjB,OAAO,UAAQ,cAAc,IAAI,CAAC,EAClC,IAAI,UAAQ,cAAc,IAAI,CAAC;AAElC,SAAO,YAAY,SAAS,IACxB,YAAY,KAAK,IAAI,IACrB;AACN;","names":[]}
package/dist/index.js CHANGED
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
6
9
  var __export = (target, all) => {
7
10
  for (var name in all)
8
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,6 +20,103 @@ var __copyProps = (to, from, except, desc) => {
17
20
  };
18
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
22
 
23
+ // src/server/recaptcha.ts
24
+ var recaptcha_exports = {};
25
+ __export(recaptcha_exports, {
26
+ getRecaptchaErrorMessage: () => getRecaptchaErrorMessage,
27
+ verifyRecaptcha: () => verifyRecaptcha
28
+ });
29
+ async function verifyRecaptcha(token, config) {
30
+ if (!config.secretKey) {
31
+ console.warn("RECAPTCHA_SECRET_KEY not configured, skipping verification");
32
+ return { success: true };
33
+ }
34
+ if (!token || typeof token !== "string") {
35
+ return {
36
+ success: false,
37
+ errorCodes: ["missing-input-response"]
38
+ };
39
+ }
40
+ const timeout = config.timeoutMs || 1e4;
41
+ try {
42
+ const controller = new AbortController();
43
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
44
+ const response = await fetch("https://www.google.com/recaptcha/api/siteverify", {
45
+ method: "POST",
46
+ headers: {
47
+ "Content-Type": "application/x-www-form-urlencoded"
48
+ },
49
+ body: new URLSearchParams({
50
+ secret: config.secretKey,
51
+ response: token
52
+ }),
53
+ signal: controller.signal
54
+ });
55
+ clearTimeout(timeoutId);
56
+ if (!response.ok) {
57
+ console.error("reCAPTCHA API returned non-OK status:", response.status);
58
+ return {
59
+ success: false,
60
+ errorCodes: ["recaptcha-api-error"]
61
+ };
62
+ }
63
+ const data = await response.json();
64
+ if (config.minimumScore !== void 0 && data.score !== void 0) {
65
+ if (data.score < config.minimumScore) {
66
+ return {
67
+ success: false,
68
+ score: data.score,
69
+ errorCodes: ["score-threshold-not-met"]
70
+ };
71
+ }
72
+ }
73
+ return {
74
+ success: data.success === true,
75
+ score: data.score,
76
+ action: data.action,
77
+ challengeTimestamp: data.challenge_ts,
78
+ hostname: data.hostname,
79
+ errorCodes: data["error-codes"] || []
80
+ };
81
+ } catch (error) {
82
+ if (error instanceof Error && error.name === "AbortError") {
83
+ console.error("reCAPTCHA verification timeout");
84
+ return {
85
+ success: false,
86
+ errorCodes: ["timeout-or-duplicate"]
87
+ };
88
+ }
89
+ console.error("reCAPTCHA verification error:", error);
90
+ return {
91
+ success: false,
92
+ errorCodes: ["network-error"]
93
+ };
94
+ }
95
+ }
96
+ function getRecaptchaErrorMessage(errorCodes) {
97
+ const errorMessages = {
98
+ "missing-input-secret": "reCAPTCHA secret key is missing",
99
+ "invalid-input-secret": "reCAPTCHA secret key is invalid",
100
+ "missing-input-response": "reCAPTCHA token is missing",
101
+ "invalid-input-response": "reCAPTCHA token is invalid or malformed",
102
+ "bad-request": "The request is invalid or malformed",
103
+ "timeout-or-duplicate": "reCAPTCHA verification timed out or token was already used",
104
+ "score-threshold-not-met": "reCAPTCHA score is below the required threshold",
105
+ "recaptcha-api-error": "reCAPTCHA service is unavailable",
106
+ "network-error": "Network error during reCAPTCHA verification"
107
+ };
108
+ if (!errorCodes || errorCodes.length === 0) {
109
+ return "reCAPTCHA verification failed";
110
+ }
111
+ const knownErrors = errorCodes.filter((code) => errorMessages[code]).map((code) => errorMessages[code]);
112
+ return knownErrors.length > 0 ? knownErrors.join(", ") : "reCAPTCHA verification failed";
113
+ }
114
+ var init_recaptcha = __esm({
115
+ "src/server/recaptcha.ts"() {
116
+ "use strict";
117
+ }
118
+ });
119
+
20
120
  // src/index.ts
21
121
  var index_exports = {};
22
122
  __export(index_exports, {
@@ -508,6 +608,24 @@ function createReviewSubmitHandler(config) {
508
608
  }
509
609
  }
510
610
  }
611
+ if (config.recaptcha) {
612
+ const token = body.token;
613
+ if (!token) {
614
+ return import_server.NextResponse.json(
615
+ { error: "reCAPTCHA verification is required" },
616
+ { status: 400 }
617
+ );
618
+ }
619
+ const { verifyRecaptcha: verifyRecaptcha2, getRecaptchaErrorMessage: getRecaptchaErrorMessage2 } = await Promise.resolve().then(() => (init_recaptcha(), recaptcha_exports));
620
+ const recaptchaResult = await verifyRecaptcha2(token, config.recaptcha);
621
+ if (!recaptchaResult.success) {
622
+ const errorMessage = getRecaptchaErrorMessage2(recaptchaResult.errorCodes || []);
623
+ return import_server.NextResponse.json(
624
+ { error: `reCAPTCHA verification failed: ${errorMessage}` },
625
+ { status: 400 }
626
+ );
627
+ }
628
+ }
511
629
  let response;
512
630
  try {
513
631
  if (!config.onSubmit) {
@@ -642,6 +760,9 @@ function createReviewHandler(config) {
642
760
  };
643
761
  }
644
762
 
763
+ // src/server/index.ts
764
+ init_recaptcha();
765
+
645
766
  // src/react/components/StarRating.tsx
646
767
  var import_jsx_runtime = require("react/jsx-runtime");
647
768
  var StarRating = ({