diff --git a/.env.example b/.env.example index 44699ff6d0..a01ef6cc4d 100644 --- a/.env.example +++ b/.env.example @@ -212,6 +212,10 @@ E2E_TEST_MAILHOG_ENABLED= # ********************************************************************************************************** +# Cloudflare Turnstile +NEXT_PUBLIC_CLOUDFLARE_SITEKEY= +CLOUDFLARE_TURNSTILE_SECRET= + # Set the following value to true if you wish to enable Team Impersonation NEXT_PUBLIC_TEAM_IMPERSONATION=false diff --git a/apps/web/components/auth/Turnstile.tsx b/apps/web/components/auth/Turnstile.tsx new file mode 100644 index 0000000000..fcc40f28de --- /dev/null +++ b/apps/web/components/auth/Turnstile.tsx @@ -0,0 +1,12 @@ +import type { TurnstileProps } from "react-turnstile"; +import Turnstile from "react-turnstile"; + +import { CLOUDFLARE_SITE_ID } from "@calcom/lib/constants"; + +type Props = Omit; + +export default function TurnstileWidget(props: Props) { + if (!CLOUDFLARE_SITE_ID || process.env.NEXT_PUBLIC_IS_E2E) return null; + + return ; +} diff --git a/apps/web/lib/signup/getServerSideProps.tsx b/apps/web/lib/signup/getServerSideProps.tsx index a763bbc40b..685326625c 100644 --- a/apps/web/lib/signup/getServerSideProps.tsx +++ b/apps/web/lib/signup/getServerSideProps.tsx @@ -24,8 +24,10 @@ const querySchema = z.object({ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { const prisma = await import("@calcom/prisma").then((mod) => mod.default); + const emailVerificationEnabled = await getFeatureFlag(prisma, "email-verification"); + await ssrInit(ctx); const signupDisabled = await getFeatureFlag(prisma, "disable-signup"); - const ssr = await ssrInit(ctx); + const token = z.string().optional().parse(ctx.query.token); const redirectUrlData = z .string() @@ -42,8 +44,8 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { redirectUrl, isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED, isSAMLLoginEnabled, - trpcState: ssr.dehydrate(), prepopulateFormValues: undefined, + emailVerificationEnabled, }; // username + email prepopulated from query params diff --git a/apps/web/package.json b/apps/web/package.json index 0a6f85d041..4343a1f029 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -119,6 +119,7 @@ "react-schemaorg": "^2.0.0", "react-select": "^5.7.0", "react-timezone-select": "^1.4.0", + "react-turnstile": "^1.1.3", "react-use-intercom": "1.5.1", "remove-markdown": "^0.5.0", "rrule": "^2.7.1", diff --git a/apps/web/pages/api/auth/signup.ts b/apps/web/pages/api/auth/signup.ts index 2a35d20a16..7a7c6ac46c 100644 --- a/apps/web/pages/api/auth/signup.ts +++ b/apps/web/pages/api/auth/signup.ts @@ -4,8 +4,10 @@ import calcomSignupHandler from "@calcom/feature-auth/signup/handlers/calcomHand import selfHostedSignupHandler from "@calcom/feature-auth/signup/handlers/selfHostedHandler"; import { type RequestWithUsernameStatus } from "@calcom/features/auth/signup/username"; import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants"; +import getIP from "@calcom/lib/getIP"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; +import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken"; import { signupSchema } from "@calcom/prisma/zod-utils"; function ensureSignupIsEnabled(req: RequestWithUsernameStatus) { @@ -36,8 +38,14 @@ function ensureReqIsPost(req: RequestWithUsernameStatus) { } export default async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) { + const remoteIp = getIP(req); // Use a try catch instead of returning res every time try { + await checkCfTurnstileToken({ + token: req.headers["cf-access-token"] as string, + remoteIp, + }); + ensureReqIsPost(req); ensureSignupIsEnabled(req); diff --git a/apps/web/pages/signup.tsx b/apps/web/pages/signup.tsx index 296689dc3b..9c182c0608 100644 --- a/apps/web/pages/signup.tsx +++ b/apps/web/pages/signup.tsx @@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { signIn } from "next-auth/react"; import { Trans } from "next-i18next"; +import dynamic from "next/dynamic"; import Link from "next/link"; import { useRouter } from "next/navigation"; import Script from "next/script"; @@ -16,9 +17,15 @@ import getStripe from "@calcom/app-store/stripepayment/lib/client"; import { getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils"; import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail"; import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; -import { useFlagMap } from "@calcom/features/flags/context/provider"; import { classNames } from "@calcom/lib"; -import { APP_NAME, URL_PROTOCOL_REGEX, IS_CALCOM, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants"; +import { + APP_NAME, + URL_PROTOCOL_REGEX, + IS_CALCOM, + WEBAPP_URL, + WEBSITE_URL, + CLOUDFLARE_SITE_ID, +} from "@calcom/lib/constants"; import { fetchUsername } from "@calcom/lib/fetchUsername"; import { pushGTMEvent } from "@calcom/lib/gtm"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; @@ -45,8 +52,11 @@ import PageWrapper from "@components/PageWrapper"; const signupSchema = apiSignupSchema.extend({ apiError: z.string().optional(), // Needed to display API errors doesnt get passed to the API + cfToken: z.string().optional(), }); +const TurnstileCaptcha = dynamic(() => import("@components/auth/Turnstile"), { ssr: false }); + type FormValues = z.infer; export type SignupProps = inferSSRProps; @@ -171,6 +181,7 @@ export default function Signup({ isSAMLLoginEnabled, orgAutoAcceptEmail, redirectUrl, + emailVerificationEnabled, }: SignupProps) { const [premiumUsername, setPremiumUsername] = useState(false); const [usernameTaken, setUsernameTaken] = useState(false); @@ -179,7 +190,6 @@ export default function Signup({ const telemetry = useTelemetry(); const { t, i18n } = useLocale(); const router = useRouter(); - const flags = useFlagMap(); const formMethods = useForm({ resolver: zodResolver(signupSchema), defaultValues: prepopulateFormValues satisfies FormValues, @@ -225,7 +235,8 @@ export default function Signup({ const isOrgInviteByLink = orgSlug && !prepopulateFormValues?.username; - const signUp: SubmitHandler = async (data) => { + const signUp: SubmitHandler = async (_data) => { + const { cfToken, ...data } = _data; await fetch("/api/auth/signup", { body: JSON.stringify({ ...data, @@ -234,6 +245,7 @@ export default function Signup({ }), headers: { "Content-Type": "application/json", + "cf-access-token": cfToken ?? "invalid-token", }, method: "POST", }) @@ -243,7 +255,7 @@ export default function Signup({ pushGTMEvent("create_account", { email: data.email, user: data.username, lang: data.language }); telemetry.event(telemetryEventTypes.signup, collectPageParameters()); - const verifyOrGettingStarted = flags["email-verification"] ? "auth/verify-email" : "getting-started"; + const verifyOrGettingStarted = emailVerificationEnabled ? "auth/verify-email" : "getting-started"; const callBackUrl = `${ searchParams?.get("callbackUrl") ? isOrgInviteByLink @@ -362,6 +374,16 @@ export default function Signup({ {...register("password")} hintErrors={["caplow", "min", "num"]} /> + {/* Cloudflare Turnstile Captcha */} + {CLOUDFLARE_SITE_ID ? ( + { + formMethods.setValue("cfToken", token); + }} + /> + ) : null} + handleConsentChange(COOKIE_CONSENT)} description={t("cookie_consent_checkbox")} @@ -375,6 +397,9 @@ export default function Signup({ !!formMethods.formState.errors.email || !formMethods.getValues("email") || !formMethods.getValues("password") || + (CLOUDFLARE_SITE_ID && + !process.env.NEXT_PUBLIC_IS_E2E && + !formMethods.getValues("cfToken")) || isSubmitting || usernameTaken }> @@ -497,14 +522,14 @@ export default function Signup({ className="text-emphasis hover:underline" href={`${WEBSITE_URL}/terms`} target="_blank"> - Terms + Terms {" "} and{" "} - Privacy Policy + Privacy Policy . diff --git a/apps/web/playwright/signup.e2e.ts b/apps/web/playwright/signup.e2e.ts index 4e4c18fd37..dc094df48b 100644 --- a/apps/web/playwright/signup.e2e.ts +++ b/apps/web/playwright/signup.e2e.ts @@ -25,6 +25,7 @@ test.describe("Signup Flow Test", async () => { }); await page.goto("/signup"); + await page.waitForLoadState("networkidle"); const alertMessage = "Username or email is already taken"; @@ -51,6 +52,7 @@ test.describe("Signup Flow Test", async () => { }); await page.goto("/signup"); + await page.waitForLoadState("networkidle"); const alertMessage = "Username or email is already taken"; @@ -81,6 +83,7 @@ test.describe("Signup Flow Test", async () => { // Signup with premium username name await page.goto("/signup"); + await page.waitForLoadState("networkidle"); // Fill form await page.locator('input[name="username"]').fill("rock"); @@ -108,6 +111,7 @@ test.describe("Signup Flow Test", async () => { }); await page.goto("/signup"); + await page.waitForLoadState("networkidle"); // Fill form await page.locator('input[name="username"]').fill(userToCreate.username); @@ -115,10 +119,7 @@ test.describe("Signup Flow Test", async () => { await page.locator('input[name="password"]').fill(userToCreate.password); await page.click('button[type="submit"]'); - await page.waitForLoadState("networkidle"); - // Find the newly created user and add it to the fixture store - const newUser = await users.set(userToCreate.email); - expect(newUser).not.toBeNull(); + await page.waitForURL("/auth/verify-email**"); // Check that the URL matches the expected URL expect(page.url()).toContain("/auth/verify-email"); @@ -177,8 +178,8 @@ test.describe("Signup Flow Test", async () => { }); const signupUrlWithToken = `/signup?token=${token}`; - await page.goto(signupUrlWithToken); + await page.waitForLoadState("networkidle"); const usernameField = page.locator('input[name="username"]'); const emailField = page.locator('input[name="email"]'); diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index 194230896b..e52a052123 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -146,5 +146,6 @@ export const BOOKER_NUMBER_OF_DAYS_TO_LOAD = parseInt( 0 ); +export const CLOUDFLARE_SITE_ID = process.env.NEXT_PUBLIC_CLOUDFLARE_SITEKEY; export const MINIMUM_NUMBER_OF_ORG_SEATS = 30; export const ORG_SELF_SERVE_ENABLED = process.env.NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED === "1"; diff --git a/packages/lib/server/checkCfTurnstileToken.ts b/packages/lib/server/checkCfTurnstileToken.ts new file mode 100644 index 0000000000..7ea6531d75 --- /dev/null +++ b/packages/lib/server/checkCfTurnstileToken.ts @@ -0,0 +1,35 @@ +import { HttpError } from "../http-error"; + +const TURNSTILE_SECRET_ID = process.env.CLOUDFLARE_TURNSTILE_SECRET; + +export async function checkCfTurnstileToken({ token, remoteIp }: { token?: string; remoteIp: string }) { + // This means the instant doesnt have turnstile enabled - we skip the check and just return success. + // OR the instance is running in CI so we skip these checks also + if (!TURNSTILE_SECRET_ID || !!process.env.NEXT_PUBLIC_IS_E2E) { + return { + success: true, + }; + } + + if (!token) { + throw new HttpError({ statusCode: 401, message: "Invalid cloudflare token" }); + } + + const form = new URLSearchParams(); + form.append("secret", TURNSTILE_SECRET_ID); + form.append("response", token); + form.append("remoteip", remoteIp); + + const result = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", { + method: "POST", + body: form, + }); + + const data = await result.json(); + + if (!data["success"]) { + throw new HttpError({ statusCode: 401, message: "Invalid cloudflare token" }); + } + + return data; +} diff --git a/turbo.json b/turbo.json index 2847c649a3..d23948229a 100644 --- a/turbo.json +++ b/turbo.json @@ -251,6 +251,7 @@ "CLOSECOM_API_KEY", "CRON_API_KEY", "CRON_ENABLE_APP_SYNC", + "CLOUDFLARE_TURNSTILE_SECRET", "DAILY_API_KEY", "DAILY_SCALE_PLAN", "DAILY_WEBHOOK_SECRET", @@ -321,6 +322,7 @@ "NEXT_PUBLIC_SWAGGER_DOCS_URL", "NEXT_PUBLIC_TEAM_IMPERSONATION", "NEXT_PUBLIC_VERCEL_URL", + "NEXT_PUBLIC_CLOUDFLARE_SITEKEY", "NEXT_PUBLIC_VERCEL_ENV", "NEXT_PUBLIC_VERCEL_BRANCH_URL", "NEXT_PUBLIC_GTM_ID", diff --git a/yarn.lock b/yarn.lock index d9de978e1c..0f4b596b51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -290,25 +290,6 @@ __metadata: languageName: node linkType: hard -"@auth/core@npm:^0.1.4": - version: 0.1.4 - resolution: "@auth/core@npm:0.1.4" - dependencies: - "@panva/hkdf": 1.0.2 - cookie: 0.5.0 - jose: 4.11.1 - oauth4webapi: 2.0.5 - preact: 10.11.3 - preact-render-to-string: 5.2.3 - peerDependencies: - nodemailer: 6.8.0 - peerDependenciesMeta: - nodemailer: - optional: true - checksum: 64854404ea1883e0deb5535b34bed95cd43fc85094aeaf4f15a79e14045020eb944f844defe857edfc8528a0a024be89cbb2a3069dedef0e9217a74ca6c3eb79 - languageName: node - linkType: hard - "@aw-web-design/x-default-browser@npm:1.4.126": version: 1.4.126 resolution: "@aw-web-design/x-default-browser@npm:1.4.126" @@ -4050,41 +4031,6 @@ __metadata: languageName: unknown linkType: soft -"@calcom/auth@workspace:apps/auth": - version: 0.0.0-use.local - resolution: "@calcom/auth@workspace:apps/auth" - dependencies: - "@auth/core": ^0.1.4 - "@calcom/app-store": "*" - "@calcom/app-store-cli": "*" - "@calcom/config": "*" - "@calcom/core": "*" - "@calcom/dayjs": "*" - "@calcom/embed-core": "workspace:*" - "@calcom/embed-react": "workspace:*" - "@calcom/embed-snippet": "workspace:*" - "@calcom/features": "*" - "@calcom/lib": "*" - "@calcom/prisma": "*" - "@calcom/trpc": "*" - "@calcom/tsconfig": "*" - "@calcom/types": "*" - "@calcom/ui": "*" - "@types/node": 16.9.1 - "@types/react": 18.0.26 - "@types/react-dom": ^18.0.9 - eslint: ^8.34.0 - eslint-config-next: ^13.2.1 - next: ^13.5.4 - next-auth: ^4.22.1 - postcss: ^8.4.18 - react: ^18.2.0 - react-dom: ^18.2.0 - tailwindcss: ^3.3.3 - typescript: ^4.9.4 - languageName: unknown - linkType: soft - "@calcom/base@workspace:packages/platform/examples/base": version: 0.0.0-use.local resolution: "@calcom/base@workspace:packages/platform/examples/base" @@ -4205,7 +4151,7 @@ __metadata: chart.js: ^3.7.1 client-only: ^0.0.1 eslint: ^8.34.0 - next: ^13.5.4 + next: ^13.4.6 next-auth: ^4.22.1 next-i18next: ^13.2.2 postcss: ^8.4.18 @@ -5345,6 +5291,7 @@ __metadata: react-schemaorg: ^2.0.0 react-select: ^5.7.0 react-timezone-select: ^1.4.0 + react-turnstile: ^1.1.3 react-use-intercom: 1.5.1 remove-markdown: ^0.5.0 rrule: ^2.7.1 @@ -9424,6 +9371,13 @@ __metadata: languageName: node linkType: hard +"@next/env@npm:13.5.6": + version: 13.5.6 + resolution: "@next/env@npm:13.5.6" + checksum: 5e8f3f6f987a15dad3cd7b2bcac64a6382c2ec372d95d0ce6ab295eb59c9731222017eebf71ff3005932de2571f7543bce7e5c6a8c90030207fb819404138dc2 + languageName: node + linkType: hard + "@next/env@npm:14.0.4": version: 14.0.4 resolution: "@next/env@npm:14.0.4" @@ -9463,6 +9417,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-arm64@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-darwin-arm64@npm:13.5.6" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-darwin-arm64@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-darwin-arm64@npm:14.0.4" @@ -9484,6 +9445,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-x64@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-darwin-x64@npm:13.5.6" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@next/swc-darwin-x64@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-darwin-x64@npm:14.0.4" @@ -9505,6 +9473,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-gnu@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-linux-arm64-gnu@npm:13.5.6" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-arm64-gnu@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-linux-arm64-gnu@npm:14.0.4" @@ -9526,6 +9501,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-musl@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-linux-arm64-musl@npm:13.5.6" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@next/swc-linux-arm64-musl@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-linux-arm64-musl@npm:14.0.4" @@ -9547,6 +9529,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-gnu@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-linux-x64-gnu@npm:13.5.6" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-x64-gnu@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-linux-x64-gnu@npm:14.0.4" @@ -9568,6 +9557,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-musl@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-linux-x64-musl@npm:13.5.6" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@next/swc-linux-x64-musl@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-linux-x64-musl@npm:14.0.4" @@ -9589,6 +9585,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-arm64-msvc@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-win32-arm64-msvc@npm:13.5.6" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-win32-arm64-msvc@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-win32-arm64-msvc@npm:14.0.4" @@ -9610,6 +9613,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-ia32-msvc@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-win32-ia32-msvc@npm:13.5.6" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@next/swc-win32-ia32-msvc@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-win32-ia32-msvc@npm:14.0.4" @@ -9631,6 +9641,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-x64-msvc@npm:13.5.6": + version: 13.5.6 + resolution: "@next/swc-win32-x64-msvc@npm:13.5.6" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@next/swc-win32-x64-msvc@npm:14.0.4": version: 14.0.4 resolution: "@next/swc-win32-x64-msvc@npm:14.0.4" @@ -10273,13 +10290,6 @@ __metadata: languageName: node linkType: hard -"@panva/hkdf@npm:1.0.2": - version: 1.0.2 - resolution: "@panva/hkdf@npm:1.0.2" - checksum: 75183b4d5ea816ef516dcea70985c610683579a9e2ac540c2d59b9a3ed27eedaff830a43a1c43c1683556a457c92ac66e09109ee995ab173090e4042c4c4bb03 - languageName: node - linkType: hard - "@panva/hkdf@npm:^1.0.2": version: 1.0.4 resolution: "@panva/hkdf@npm:1.0.4" @@ -30855,13 +30865,6 @@ __metadata: languageName: node linkType: hard -"jose@npm:4.11.1": - version: 4.11.1 - resolution: "jose@npm:4.11.1" - checksum: cd15cba258d0fd20f6168631ce2e94fda8442df80e43c1033c523915cecdf390a1cc8efe0eab0c2d65935ca973d791c668aea80724d2aa9c2879d4e70f3081d7 - languageName: node - linkType: hard - "jose@npm:5.2.1": version: 5.2.1 resolution: "jose@npm:5.2.1" @@ -35179,6 +35182,61 @@ __metadata: languageName: node linkType: hard +"next@npm:^13.4.6": + version: 13.5.6 + resolution: "next@npm:13.5.6" + dependencies: + "@next/env": 13.5.6 + "@next/swc-darwin-arm64": 13.5.6 + "@next/swc-darwin-x64": 13.5.6 + "@next/swc-linux-arm64-gnu": 13.5.6 + "@next/swc-linux-arm64-musl": 13.5.6 + "@next/swc-linux-x64-gnu": 13.5.6 + "@next/swc-linux-x64-musl": 13.5.6 + "@next/swc-win32-arm64-msvc": 13.5.6 + "@next/swc-win32-ia32-msvc": 13.5.6 + "@next/swc-win32-x64-msvc": 13.5.6 + "@swc/helpers": 0.5.2 + busboy: 1.6.0 + caniuse-lite: ^1.0.30001406 + postcss: 8.4.31 + styled-jsx: 5.1.1 + watchpack: 2.4.0 + peerDependencies: + "@opentelemetry/api": ^1.1.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + dependenciesMeta: + "@next/swc-darwin-arm64": + optional: true + "@next/swc-darwin-x64": + optional: true + "@next/swc-linux-arm64-gnu": + optional: true + "@next/swc-linux-arm64-musl": + optional: true + "@next/swc-linux-x64-gnu": + optional: true + "@next/swc-linux-x64-musl": + optional: true + "@next/swc-win32-arm64-msvc": + optional: true + "@next/swc-win32-ia32-msvc": + optional: true + "@next/swc-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + sass: + optional: true + bin: + next: dist/bin/next + checksum: c869b0014ae921ada3bf22301985027ec320aebcd6aa9c16e8afbded68bb8def5874cca034c680e8c351a79578f1e514971d02777f6f0a5a1d7290f25970ac0d + languageName: node + linkType: hard + "next@npm:^13.5.4": version: 13.5.5 resolution: "next@npm:13.5.5" @@ -35833,13 +35891,6 @@ __metadata: languageName: node linkType: hard -"oauth4webapi@npm:2.0.5": - version: 2.0.5 - resolution: "oauth4webapi@npm:2.0.5" - checksum: 32d0cb7b1cca42d51dfb88075ca2d69fe33172a807e8ea50e317d17cab3bc80588ab8ebcb7eb4600c371a70af4674595b4b341daf6f3a655f1efa1ab715bb6c9 - languageName: node - linkType: hard - "oauth@npm:^0.9.15": version: 0.9.15 resolution: "oauth@npm:0.9.15" @@ -37970,17 +38021,6 @@ __metadata: languageName: node linkType: hard -"preact-render-to-string@npm:5.2.3": - version: 5.2.3 - resolution: "preact-render-to-string@npm:5.2.3" - dependencies: - pretty-format: ^3.8.0 - peerDependencies: - preact: ">=10" - checksum: 6e46288d8956adde35b9fe3a21aecd9dea29751b40f0f155dea62f3896f27cb8614d457b32f48d33909d2da81135afcca6c55077520feacd7d15164d1371fb44 - languageName: node - linkType: hard - "preact-render-to-string@npm:^5.1.19": version: 5.2.6 resolution: "preact-render-to-string@npm:5.2.6" @@ -37992,7 +38032,7 @@ __metadata: languageName: node linkType: hard -"preact@npm:10.11.3, preact@npm:^10.6.3": +"preact@npm:^10.6.3": version: 10.11.3 resolution: "preact@npm:10.11.3" checksum: 9387115aa0581e8226309e6456e9856f17dfc0e3d3e63f774de80f3d462a882ba7c60914c05942cb51d51e23e120dcfe904b8d392d46f29ad15802941fe7a367 @@ -39772,6 +39812,16 @@ __metadata: languageName: node linkType: hard +"react-turnstile@npm:^1.1.3": + version: 1.1.3 + resolution: "react-turnstile@npm:1.1.3" + peerDependencies: + react: ">= 16.13.1" + react-dom: ">= 16.13.1" + checksum: 1bb040eca9a4b24078dc21ee70947442ad97c7bc6507ba530974c4e5730769af0168e9b221e2835f20ea194282fc7a377eeb102084509f636fab322e95314201 + languageName: node + linkType: hard + "react-twemoji@npm:^0.3.0": version: 0.3.0 resolution: "react-twemoji@npm:0.3.0"