diff --git a/.oxlintrc.json b/.oxlintrc.json index beeadf02c34..9c88d4e9c6a 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -30,7 +30,9 @@ "rules": { "react/jsx-max-depth": "off", "typescript/no-floating-promises": "off", - "typescript/unbound-method": "off" + "typescript/unbound-method": "off", + "typescript/no-base-to-string": "off", + "restrict-template-expressions": "off" } }, { diff --git a/packages/admin/package.json b/packages/admin/package.json index a8cbf6b1ae8..0372e87ea41 100644 --- a/packages/admin/package.json +++ b/packages/admin/package.json @@ -36,6 +36,7 @@ "prepublishOnly": "pnpm build" }, "devDependencies": { + "@better-captcha/react": "^0.5.6", "@rematch/core": "2.2.0", "base-icon": "2.3.2", "classnames": "2.5.1", diff --git a/packages/admin/src/components/Captcha.js b/packages/admin/src/components/Captcha.js new file mode 100644 index 00000000000..8d49c9142db --- /dev/null +++ b/packages/admin/src/components/Captcha.js @@ -0,0 +1,7 @@ +import { ReCaptchaV3 } from '@better-captcha/react/provider/recaptcha-v3'; +import { Turnstile } from '@better-captcha/react/provider/turnstile'; + +export const CaptchaProviders = { + recaptchaV3: ReCaptchaV3, + turnstile: Turnstile, +}; diff --git a/packages/admin/src/components/useCaptcha.js b/packages/admin/src/components/useCaptcha.js deleted file mode 100644 index de9283f78dc..00000000000 --- a/packages/admin/src/components/useCaptcha.js +++ /dev/null @@ -1,18 +0,0 @@ -import { useRecaptcha } from './useRecaptchaV3.js'; -import { useTurnstile } from './useTurnstile.js'; - -export const useCaptcha = (config) => { - const recaptchaV3 = useRecaptcha(config); - const turnstile = useTurnstile(config); - - if (window.turnstileKey) { - return turnstile; - } - if (window.recaptchaV3Key) { - return recaptchaV3; - } - - return () => { - // do nothing - }; -}; diff --git a/packages/admin/src/components/useRecaptchaV3.js b/packages/admin/src/components/useRecaptchaV3.js deleted file mode 100644 index a494ee8a628..00000000000 --- a/packages/admin/src/components/useRecaptchaV3.js +++ /dev/null @@ -1,53 +0,0 @@ -import { useEffect, useState } from 'react'; - -import useScript from './useScript.js'; - -export const useRecaptcha = ({ sitekey, hideDefaultBadge = false, checkForExisting = true }) => { - const [recaptcha, setRecaptcha] = useState(); - - useEffect(() => { - if (isBrowser && hideDefaultBadge) { - injectStyle('.grecaptcha-badge { visibility: hidden; }'); - } - }, [hideDefaultBadge]); - - useScript({ - src: window.recaptchaV3Key - ? `https://recaptcha.net/recaptcha/api.js?render=${sitekey}` - : undefined, - onload: () => - window.grecaptcha.ready(() => { - setRecaptcha(window.grecaptcha); - }), - checkForExisting, - }); - - useEffect(() => { - if (window.grecaptcha && window.recaptchaV3Key) { - window.grecaptcha.ready(() => { - setRecaptcha(window.grecaptcha); - }); - } - }, []); - - return (action) => - new Promise((resolve, reject) => { - if (recaptcha) { - resolve(recaptcha.execute(sitekey, { action })); - } else { - reject(new Error('Recaptcha script not available')); - } - }); -}; - -const isBrowser = typeof window !== 'undefined' && Boolean(window.document); - -const injectStyle = (rule) => { - const styleEl = document.createElement('style'); - - document.head.append(styleEl); - - const styleSheet = styleEl.sheet; - - if (styleSheet) styleSheet.insertRule(rule, styleSheet.cssRules.length); -}; diff --git a/packages/admin/src/components/useScript.js b/packages/admin/src/components/useScript.js deleted file mode 100644 index 2a1fcb816fd..00000000000 --- a/packages/admin/src/components/useScript.js +++ /dev/null @@ -1,104 +0,0 @@ -// The hooks original author is @hupe1980 fork from https://github.com/hupe1980/react-script-hook/blob/master/src/use-script.tsx - -import { useEffect, useState } from 'react'; - -// Previously loading/loaded scripts and their current status -export const scripts = {}; - -// Check for existing diff --git a/packages/server/src/logic/base.js b/packages/server/src/logic/base.js index a5439a047bc..aebc3a8e4e9 100644 --- a/packages/server/src/logic/base.js +++ b/packages/server/src/logic/base.js @@ -157,12 +157,12 @@ module.exports = class BaseLogic extends think.Logic { async useCaptchaCheck() { const { RECAPTCHA_V3_SECRET, TURNSTILE_SECRET } = process.env; - const { turnstile, recaptchaV3 } = this.post(); + const { turnstile, recaptchaV3, captcha } = this.post(); if (TURNSTILE_SECRET) { return this.useRecaptchaOrTurnstileCheck({ secret: TURNSTILE_SECRET, - token: turnstile, + token: captcha || turnstile, api: 'https://challenges.cloudflare.com/turnstile/v0/siteverify', method: 'POST', }); @@ -171,7 +171,7 @@ module.exports = class BaseLogic extends think.Logic { if (RECAPTCHA_V3_SECRET) { return this.useRecaptchaOrTurnstileCheck({ secret: RECAPTCHA_V3_SECRET, - token: recaptchaV3, + token: captcha || recaptchaV3, api: 'https://recaptcha.net/recaptcha/api/siteverify', method: 'GET', }); diff --git a/packages/server/src/middleware/dashboard.js b/packages/server/src/middleware/dashboard.js index 8f145dcef78..479d1aab827 100644 --- a/packages/server/src/middleware/dashboard.js +++ b/packages/server/src/middleware/dashboard.js @@ -15,6 +15,7 @@ module.exports = function () { window.SITE_NAME = ${JSON.stringify(process.env.SITE_NAME)}; window.recaptchaV3Key = ${JSON.stringify(process.env.RECAPTCHA_V3_KEY)}; window.turnstileKey = ${JSON.stringify(process.env.TURNSTILE_KEY)}; + window.captcha = ${JSON.stringify(process.env.CAPTCHA)}; window.oauthServices = ${JSON.stringify(ctx.state.oauthServices || [])}; window.serverURL = '${ctx.serverURL}/api/'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b276fe6ef78..1ae5eeae2e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,6 +168,9 @@ importers: packages/admin: devDependencies: + '@better-captcha/react': + specifier: ^0.5.6 + version: 0.5.6(react@19.2.4)(typescript@5.9.3) '@rematch/core': specifier: 2.2.0 version: 2.2.0(redux@5.0.1) @@ -218,6 +221,9 @@ importers: packages/client: dependencies: + '@better-captcha/vue': + specifier: ^0.5.6 + version: 0.5.6(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)) '@vueuse/core': specifier: ^14.2.1 version: 14.2.1(vue@3.5.29(typescript@5.9.3)) @@ -233,9 +239,6 @@ importers: marked-highlight: specifier: ^2.2.3 version: 2.2.3(marked@17.0.4) - recaptcha-v3: - specifier: ^1.11.3 - version: 1.11.3 vue: specifier: ^3.5.29 version: 3.5.29(typescript@5.9.3) @@ -956,6 +959,23 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@better-captcha/core@0.5.6': + resolution: {integrity: sha512-t+VDA+cDh3dy1d2WxrNS/7wD60R7ERV5UJM2CyHZNkizvrK4ymW93VXgQfHBpT4YgpSri6i/zN16vO0RKKqKgA==} + peerDependencies: + typescript: ^5.0.0 + + '@better-captcha/react@0.5.6': + resolution: {integrity: sha512-/NsTEx3y1LhYFLtgAOLkXEPKw88m5fFLp903rIoJZuKmZFtzrGr7i/T0ua0wK0flPPxe3dRNl8Fje3Sz9/HV7A==} + peerDependencies: + react: ^18 || ^19 + typescript: ^5.0.0 + + '@better-captcha/vue@0.5.6': + resolution: {integrity: sha512-n1eIHLhs3DBHrepnUAK0Bq8BW/1gjYy2QjyTMVk0v7tyfkd1qYaELyjyP/gvamVZrmy2A1+th01bAr15lESkIQ==} + peerDependencies: + typescript: ^5.0.0 + vue: ^3.3.0 + '@bufbuild/protobuf@2.11.0': resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} @@ -7338,9 +7358,6 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - recaptcha-v3@1.11.3: - resolution: {integrity: sha512-sEE6J0RzUkS+sKEBpgCD/AqCU0ffrAVOADGjvAx9vcttN+VLK42SWMkj/J/I6vHu3Kew+xcfbBqDVb65N0QGDw==} - rechoir@0.7.1: resolution: {integrity: sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==} engines: {node: '>= 0.10'} @@ -9944,6 +9961,22 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@better-captcha/core@0.5.6(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@better-captcha/react@0.5.6(react@19.2.4)(typescript@5.9.3)': + dependencies: + '@better-captcha/core': 0.5.6(typescript@5.9.3) + react: 19.2.4 + typescript: 5.9.3 + + '@better-captcha/vue@0.5.6(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3))': + dependencies: + '@better-captcha/core': 0.5.6(typescript@5.9.3) + typescript: 5.9.3 + vue: 3.5.29(typescript@5.9.3) + '@bufbuild/protobuf@2.11.0': {} '@bytecodealliance/preview2-shim@0.17.6': {} @@ -16668,8 +16701,6 @@ snapshots: readdirp@5.0.0: {} - recaptcha-v3@1.11.3: {} - rechoir@0.7.1: dependencies: resolve: 1.22.11