diff --git a/packages/toolpad-app/src/server/config.ts b/packages/toolpad-app/src/server/config.ts index aeac419816e..cc6ec4798b0 100644 --- a/packages/toolpad-app/src/server/config.ts +++ b/packages/toolpad-app/src/server/config.ts @@ -18,6 +18,7 @@ export type ServerConfig = { encryptionKeys: string[]; basicAuthUser?: string; basicAuthPassword?: string; + recaptchaSecretKey?: string; } & BasicAuthConfig; function readConfig(): ServerConfig & typeof sharedConfig { @@ -52,6 +53,7 @@ function readConfig(): ServerConfig & typeof sharedConfig { googleSheetsClientId: process.env.TOOLPAD_DATASOURCE_GOOGLESHEETS_CLIENT_ID, googleSheetsClientSecret: process.env.TOOLPAD_DATASOURCE_GOOGLESHEETS_CLIENT_SECRET, externalUrl: process.env.TOOLPAD_EXTERNAL_URL || `http://localhost:${process.env.PORT || 3000}`, + recaptchaSecretKey: process.env.TOOLPAD_RECAPTCHA_SECRET_KEY, encryptionKeys, }; } diff --git a/packages/toolpad-app/src/server/data.ts b/packages/toolpad-app/src/server/data.ts index a1c740ac9e6..7e5860518e0 100644 --- a/packages/toolpad-app/src/server/data.ts +++ b/packages/toolpad-app/src/server/data.ts @@ -11,7 +11,8 @@ import applyTransform from './applyTransform'; import { excludeFields } from '../utils/prisma'; import { latestVersion, latestMigration } from '../appDomMigrations'; import { getAppTemplateDom } from './appTemplateDoms/doms'; -import { validateRecaptchaToken } from '../utils/recaptcha'; +import { validateRecaptchaToken } from './validateRecaptchaToken'; +import config from './config'; const SELECT_RELEASE_META = excludeFields(prisma.Prisma.ReleaseScalarFieldEnum, ['snapshot']); const SELECT_APP_META = excludeFields(prisma.Prisma.AppScalarFieldEnum, ['dom']); @@ -212,7 +213,7 @@ export type CreateAppOptions = { export async function createApp(name: string, opts: CreateAppOptions = {}): Promise { const { from } = opts; - const recaptchaSecretKey = process.env.TOOLPAD_RECAPTCHA_SECRET_KEY; + const recaptchaSecretKey = config.recaptchaSecretKey; if (recaptchaSecretKey) { const isRecaptchaTokenValid = await validateRecaptchaToken( recaptchaSecretKey, diff --git a/packages/toolpad-app/src/server/validateRecaptchaToken.ts b/packages/toolpad-app/src/server/validateRecaptchaToken.ts new file mode 100644 index 00000000000..b4218278aa5 --- /dev/null +++ b/packages/toolpad-app/src/server/validateRecaptchaToken.ts @@ -0,0 +1,22 @@ +const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'; +const SCORE_THRESHOLD = 0.5; + +export const validateRecaptchaToken = async ( + secretKey: string, + token: string, +): Promise => { + const siteVerifyUrl = new URL(SITE_VERIFY_URL); + siteVerifyUrl.searchParams.set('secret', secretKey); + siteVerifyUrl.searchParams.set('response', token); + + const recaptchaResponse = await fetch(siteVerifyUrl, { + method: 'POST', + }); + + const { success, score } = await recaptchaResponse.json(); + if (!success || score < SCORE_THRESHOLD) { + return false; + } + + return true; +}; diff --git a/packages/toolpad-app/src/toolpad/Home/index.tsx b/packages/toolpad-app/src/toolpad/Home/index.tsx index 0ae5fa7763b..e2472a17501 100644 --- a/packages/toolpad-app/src/toolpad/Home/index.tsx +++ b/packages/toolpad-app/src/toolpad/Home/index.tsx @@ -61,7 +61,6 @@ import { ConfirmDialog } from '../../components/SystemDialogs'; import config from '../../config'; import { AppTemplateId } from '../../types'; import { errorFrom } from '../../utils/errors'; -import { getRecaptchaToken } from '../../utils/recaptcha'; export const APP_TEMPLATE_OPTIONS = { blank: { @@ -118,7 +117,10 @@ function CreateAppDialog({ onClose, ...props }: CreateAppDialogProps) { event.preventDefault(); let recaptchaToken; if (config.recaptchaSiteKey) { - recaptchaToken = await getRecaptchaToken(config.recaptchaSiteKey); + await new Promise((resolve) => grecaptcha.ready(resolve)); + recaptchaToken = await grecaptcha.execute(config.recaptchaSiteKey, { + action: 'submit', + }); } const appDom = dom.trim() ? JSON.parse(dom) : null; diff --git a/packages/toolpad-app/src/utils/recaptcha.ts b/packages/toolpad-app/src/utils/recaptcha.ts deleted file mode 100644 index 8c8660a4c0a..00000000000 --- a/packages/toolpad-app/src/utils/recaptcha.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const getRecaptchaToken = async (siteKey: string): Promise => { - await new Promise((resolve) => grecaptcha.ready(resolve)); - return grecaptcha.execute(siteKey, { - action: 'submit', - }); -}; - -const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'; -const SCORE_THRESHOLD = 0.5; - -export const validateRecaptchaToken = async ( - secretKey: string, - token: string, -): Promise => { - const recaptchaParams = new URLSearchParams({ - secret: secretKey, - response: token, - }); - const siteVerifyUrlWithParams = `${SITE_VERIFY_URL}?${recaptchaParams}`; - - const recaptchaResponse = await fetch(siteVerifyUrlWithParams, { - method: 'POST', - }); - - const { success, score } = await recaptchaResponse.json(); - if (!success || score < SCORE_THRESHOLD) { - return false; - } - - return true; -};