Guard invalid Turnstile site key
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 46s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 46s
This commit is contained in:
@@ -1,17 +1,19 @@
|
||||
require('dotenv').config();
|
||||
|
||||
const TURNSTILE_SITE_KEY =
|
||||
process.env.TURNSTILE_SITE_KEY ||
|
||||
process.env.TURNSTILE_SITEKEY ||
|
||||
process.env.VITE_TURNSTILE_SITE_KEY ||
|
||||
process.env.CLOUDFLARE_TURNSTILE_SITE_KEY ||
|
||||
'';
|
||||
const firstEnvValue = (...values) => values.find((value) => typeof value === 'string' && value.trim())?.trim() || '';
|
||||
|
||||
const TURNSTILE_SECRET =
|
||||
process.env.TURNSTILE_SECRET ||
|
||||
process.env.TURNSTILE_SECRET_KEY ||
|
||||
process.env.CLOUDFLARE_TURNSTILE_SECRET ||
|
||||
'';
|
||||
const TURNSTILE_SITE_KEY = firstEnvValue(
|
||||
process.env.TURNSTILE_SITE_KEY,
|
||||
process.env.TURNSTILE_SITEKEY,
|
||||
process.env.VITE_TURNSTILE_SITE_KEY,
|
||||
process.env.CLOUDFLARE_TURNSTILE_SITE_KEY
|
||||
);
|
||||
|
||||
const TURNSTILE_SECRET = firstEnvValue(
|
||||
process.env.TURNSTILE_SECRET,
|
||||
process.env.TURNSTILE_SECRET_KEY,
|
||||
process.env.CLOUDFLARE_TURNSTILE_SECRET
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
PORT: process.env.PORT || 3004,
|
||||
|
||||
@@ -4,6 +4,8 @@ const { TURNSTILE_SECRET, TURNSTILE_SITE_KEY } = require('../config');
|
||||
|
||||
const router = express.Router();
|
||||
const TURNSTILE_VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
|
||||
const TURNSTILE_SITE_KEY_PATTERN = /^[0-9]x[0-9A-Za-z_-]{20,}$/;
|
||||
const hasValidTurnstileSiteKey = TURNSTILE_SITE_KEY_PATTERN.test(TURNSTILE_SITE_KEY);
|
||||
|
||||
const verifyCaptcha = async (captchaToken, remoteIp) => {
|
||||
if (!TURNSTILE_SECRET) return true;
|
||||
@@ -37,7 +39,8 @@ const verifyCaptcha = async (captchaToken, remoteIp) => {
|
||||
router.get('/login/config', (req, res) => {
|
||||
res.json({
|
||||
captchaRequired: Boolean(TURNSTILE_SECRET),
|
||||
turnstileSiteKey: TURNSTILE_SITE_KEY
|
||||
turnstileSiteKey: hasValidTurnstileSiteKey ? TURNSTILE_SITE_KEY : '',
|
||||
captchaConfigured: Boolean(TURNSTILE_SECRET && hasValidTurnstileSiteKey)
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ const buildDateRangeParams = (dateRange: DateRange) => new URLSearchParams({
|
||||
|
||||
export type LoginConfig = {
|
||||
captchaRequired: boolean;
|
||||
captchaConfigured: boolean;
|
||||
turnstileSiteKey: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ const securityUnavailableMessage = 'Não foi possível carregar a verificação
|
||||
const securityConfigurationMessage = 'Verificação de segurança indisponível. Entre em contato com o administrador.';
|
||||
const securityRequiredMessage = 'Conclua a verificação de segurança para continuar.';
|
||||
const securityFailedMessage = 'Não foi possível validar a verificação de segurança. Atualize a página e tente novamente.';
|
||||
const turnstileSiteKeyPattern = /^[0-9]x[0-9A-Za-z_-]{20,}$/;
|
||||
|
||||
const Login = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
@@ -41,8 +42,9 @@ const Login = () => {
|
||||
const captchaContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const captchaWidgetIdRef = useRef<string | null>(null);
|
||||
const navigate = useNavigate();
|
||||
const captchaEnabled = Boolean(captchaRequired && turnstileSiteKey);
|
||||
const securityMisconfigured = captchaRequired && !turnstileSiteKey;
|
||||
const hasValidSiteKey = turnstileSiteKeyPattern.test(turnstileSiteKey);
|
||||
const captchaEnabled = Boolean(captchaRequired && hasValidSiteKey);
|
||||
const securityMisconfigured = captchaRequired && !hasValidSiteKey;
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
@@ -53,9 +55,9 @@ const Login = () => {
|
||||
if (!isMounted) return;
|
||||
|
||||
setCaptchaRequired(config.captchaRequired);
|
||||
setTurnstileSiteKey(config.turnstileSiteKey);
|
||||
setTurnstileSiteKey(config.turnstileSiteKey.trim());
|
||||
setCaptchaReady(!config.captchaRequired);
|
||||
if (config.captchaRequired && !config.turnstileSiteKey) {
|
||||
if (config.captchaRequired && !config.captchaConfigured) {
|
||||
setError(securityConfigurationMessage);
|
||||
}
|
||||
} catch {
|
||||
@@ -77,13 +79,14 @@ const Login = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!turnstileSiteKey || !captchaContainerRef.current || captchaWidgetIdRef.current) return;
|
||||
if (!hasValidSiteKey || !captchaContainerRef.current || captchaWidgetIdRef.current) return;
|
||||
|
||||
const siteKey = turnstileSiteKey;
|
||||
|
||||
const renderCaptcha = () => {
|
||||
if (!window.turnstile || !captchaContainerRef.current || captchaWidgetIdRef.current) return;
|
||||
|
||||
try {
|
||||
captchaWidgetIdRef.current = window.turnstile.render(captchaContainerRef.current, {
|
||||
sitekey: siteKey,
|
||||
theme: 'dark',
|
||||
@@ -102,6 +105,11 @@ const Login = () => {
|
||||
},
|
||||
});
|
||||
setCaptchaReady(true);
|
||||
} catch {
|
||||
setCaptchaToken('');
|
||||
setCaptchaReady(false);
|
||||
setError(securityConfigurationMessage);
|
||||
}
|
||||
};
|
||||
|
||||
if (window.turnstile) {
|
||||
@@ -128,7 +136,7 @@ const Login = () => {
|
||||
document.head.appendChild(script);
|
||||
|
||||
return () => script.removeEventListener('load', renderCaptcha);
|
||||
}, [turnstileSiteKey]);
|
||||
}, [hasValidSiteKey, turnstileSiteKey]);
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user