mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
feat(experience,core): add recaptcha enterprise to frontend (#7218)
This commit is contained in:
parent
58a5b497bf
commit
d418ad0caa
4 changed files with 94 additions and 1 deletions
|
@ -110,11 +110,23 @@ export default function koaSecurityHeaders<StateT, ContextT, ResponseBodyT>(
|
|||
'https://static.cloudflareinsights.com/',
|
||||
// Cloudflare Turnstile
|
||||
'https://challenges.cloudflare.com/turnstile/v0/api.js',
|
||||
// Google Recaptcha Enterprise
|
||||
'https://www.google.com/recaptcha/enterprise.js',
|
||||
// Google Recaptcha static resources
|
||||
'https://www.gstatic.com/recaptcha/',
|
||||
// Allow "unsafe-eval" for debugging purpose in non-production environment
|
||||
...conditionalArray(!isProduction && "'unsafe-eval'"),
|
||||
],
|
||||
scriptSrcAttr: ["'unsafe-inline'"],
|
||||
connectSrc: ["'self'", gsiOrigin, tenantEndpointOrigin, ...developmentOrigins],
|
||||
connectSrc: [
|
||||
"'self'",
|
||||
gsiOrigin,
|
||||
tenantEndpointOrigin,
|
||||
// Allow reCAPTCHA API calls
|
||||
'https://www.google.com/recaptcha/',
|
||||
'https://www.gstatic.com/recaptcha/',
|
||||
...developmentOrigins,
|
||||
],
|
||||
// WARNING (high risk): Need to allow self-hosted terms of use page loaded in an iframe
|
||||
frameSrc: ["'self'", 'https:', gsiOrigin],
|
||||
// Allow being loaded by console preview iframe
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: _.unit(2);
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
interface Window {
|
||||
grecaptcha?: {
|
||||
enterprise: {
|
||||
ready: (callback: () => void) => void;
|
||||
execute: (sitekey: string, options: { action: string }) => Promise<string>;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
readonly siteKey: string;
|
||||
readonly onVerify: (token: string) => void;
|
||||
};
|
||||
|
||||
const scriptId = 'recaptcha-enterprise-script';
|
||||
|
||||
const ReCaptchaEnterprise = ({ siteKey, onVerify }: Props) => {
|
||||
const captchaRef = useRef<HTMLDivElement>(null);
|
||||
const isRendered = useRef(false);
|
||||
|
||||
/* eslint-disable @silverhand/fp/no-mutation */
|
||||
useEffect(() => {
|
||||
const render = async () => {
|
||||
if (!window.grecaptcha?.enterprise || !captchaRef.current || isRendered.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.grecaptcha.enterprise.ready(async () => {
|
||||
const token = await window.grecaptcha?.enterprise.execute(siteKey, {
|
||||
action: 'interaction',
|
||||
});
|
||||
|
||||
if (token) {
|
||||
onVerify(token);
|
||||
}
|
||||
});
|
||||
isRendered.current = true;
|
||||
};
|
||||
|
||||
// Check if script already exists
|
||||
if (document.querySelector(`#${scriptId}`)) {
|
||||
void render();
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://www.google.com/recaptcha/enterprise.js?render=${siteKey}`;
|
||||
script.id = scriptId;
|
||||
script.async = true;
|
||||
|
||||
script.addEventListener('load', () => {
|
||||
void render();
|
||||
});
|
||||
|
||||
document.body.append(script);
|
||||
}, [siteKey, onVerify]);
|
||||
/* eslint-enable @silverhand/fp/no-mutation */
|
||||
|
||||
return <div ref={captchaRef} className={styles.wrapper} />;
|
||||
};
|
||||
|
||||
export default ReCaptchaEnterprise;
|
|
@ -1,6 +1,7 @@
|
|||
import { CaptchaType } from '@logto/schemas';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import ReCaptchaEnterprise from '@/components/ReCaptchaEnterprise';
|
||||
import Turnstile from '@/components/Turnstile';
|
||||
import { type SignInExperienceResponse } from '@/types';
|
||||
|
||||
|
@ -21,6 +22,10 @@ const CaptchaBox = ({ setCaptchaToken, captchaConfig }: Props) => {
|
|||
return <Turnstile siteKey={captchaConfig.siteKey} onVerify={onVerify} />;
|
||||
}
|
||||
|
||||
if (captchaConfig?.type === CaptchaType.RecaptchaEnterprise) {
|
||||
return <ReCaptchaEnterprise siteKey={captchaConfig.siteKey} onVerify={onVerify} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue