forked from ppfun/pixelplanet
Add hCaptcha support
This commit is contained in:
parent
ecb27a1ea0
commit
76d2a041b5
|
@ -81,10 +81,11 @@ Configuration takes place in the environment variables that are defined in ecosy
|
|||
| USE_PROXYCHECK | Check users for Proxies | 0 |
|
||||
| APISOCKET_KEY | Key for API Socket for SpecialAccess™ | "SDfasife3" |
|
||||
| ADMIN_IDS | Ids of users with Admin rights | "1,12,3" |
|
||||
| RECAPTCHA_SECRET | reCaptcha secret key | "asdieewff" |
|
||||
| RECAPTCHA_SITEKEY | reCaptcha site key | "23ksdfssd" |
|
||||
| RECAPTCHA_TIME | time in minutes between captchas | 30 |
|
||||
| SESSION_SECRET | random sting for expression sessions | "ayylmao" |
|
||||
| CAPTCHA_METHOD | 0: none, 1: reCaptcha, 2: hCaptcha | 2 |
|
||||
| CAPTCHA_SECRET | re/hCaptcha secret key | "asdieewff" |
|
||||
| CAPTCHA_SITEKEY | re/hCaptcha site key | "23ksdfssd" |
|
||||
| CAPTCHA_TIME | time in minutes between captchas | 30 |
|
||||
| SESSION_SECRET | random sting for express sessions | "ayylmao" |
|
||||
| LOG_MYSQL | if sql queries should get logged | 0 |
|
||||
| USE_XREALIP | see cloudflare section | 1 |
|
||||
| BACKUP_URL | url of backup server (see Backup) | "http://localhost" |
|
||||
|
|
145
src/:wq
Normal file
145
src/:wq
Normal file
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import fetch from 'isomorphic-fetch';
|
||||
import logger from '../core/logger';
|
||||
import redis from '../data/redis';
|
||||
|
||||
import {
|
||||
CAPTCHA_METHOD,
|
||||
CAPTCHA_SECRET,
|
||||
CAPTCHA_TIME,
|
||||
} from '../core/config';
|
||||
|
||||
const TTL_CACHE = CAPTCHA_TIME * 60; // seconds
|
||||
// eslint-disable-next-line max-len
|
||||
const RECAPTCHA_ENDPOINT = `https://www.google.com/recaptcha/api/siteverify?secret=${CAPTCHA_SECRET}`;
|
||||
const HCAPTCHA_ENDPOINT = 'https://hcaptcha.com/siteverify';
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/questions/27297067/google-recaptcha-how-to-get-user-response-and-validate-in-the-server-side
|
||||
*
|
||||
* @param token
|
||||
* @param ip
|
||||
* @returns {Promise.<boolean>}
|
||||
*/
|
||||
async function verifyReCaptcha(
|
||||
token: string,
|
||||
ip: string,
|
||||
): Promise<boolean> {
|
||||
const url = `${RECAPTCHA_ENDPOINT}&response=${token}&remoteip=${ip}`;
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
const { success } = await response.json();
|
||||
if (success) {
|
||||
logger.info(`CAPTCHA ${ip} successfully solved captcha`);
|
||||
return true;
|
||||
}
|
||||
logger.info(`CAPTCHA Token for ${ip} not ok`);
|
||||
} else {
|
||||
logger.warn(`CAPTCHA Recapcha answer for ${ip} not ok`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* https://docs.hcaptcha.com/
|
||||
*
|
||||
* @param token
|
||||
* @param ip
|
||||
* @return boolean, true if successful, false on error or fail
|
||||
*/
|
||||
async function verifyHCaptcha(
|
||||
token: string,
|
||||
ip: string,
|
||||
): Promise<boolean> {
|
||||
const response = await fetch(HCAPTCHA_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `response=${token}&secret=${CAPTCHA_SECRET}&remoteip=${ip}`,
|
||||
});
|
||||
if (response.ok) {
|
||||
const { success } = await response.json();
|
||||
if (success) {
|
||||
logger.info(`CAPTCHA ${ip} successfully solved captcha`);
|
||||
return true;
|
||||
}
|
||||
logger.info(`CAPTCHA Token for ${ip} not ok`);
|
||||
} else {
|
||||
// eslint-disable-next-line max-len
|
||||
logger.warn(`CAPTCHA hCapcha answer for ${ip} not ok ${await response.text()}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* verify captcha token from client
|
||||
*
|
||||
* @param token token of solved captcha from client
|
||||
* @param ip
|
||||
* @returns Boolean if successful
|
||||
*/
|
||||
export async function verifyCaptcha(
|
||||
token: string,
|
||||
ip: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
if (!CAPTCHA_METHOD) {
|
||||
return true;
|
||||
}
|
||||
const key = `human:${ip}`;
|
||||
|
||||
const ttl: number = await redis.ttlAsync(key);
|
||||
if (ttl > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (CAPTCHA_METHOD) {
|
||||
case 1:
|
||||
if (!await verifyReCaptcha(token, ip)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (!await verifyHCaptcha(token, ip)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// nothing
|
||||
}
|
||||
|
||||
await redis.setAsync(key, '', 'EX', TTL_CACHE);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* check if captcha is needed
|
||||
*
|
||||
* @param ip
|
||||
* @return boolean true if needed
|
||||
*/
|
||||
export async function needCaptcha(ip: string) {
|
||||
if (!CAPTCHA_METHOD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const key = `human:${ip}`;
|
||||
const ttl: number = await redis.ttlAsync(key);
|
||||
if (ttl > 0) {
|
||||
return false;
|
||||
}
|
||||
logger.info(`CAPTCHA ${ip} got captcha`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
export default verifyCaptcha;
|
|
@ -316,8 +316,12 @@ export function receivePixelReturn(
|
|||
dispatch(pixelWait());
|
||||
break;
|
||||
case 10:
|
||||
// captcha
|
||||
window.grecaptcha.execute();
|
||||
// captcha, reCaptcha or hCaptcha
|
||||
if (typeof window.hcaptcha !== 'undefined') {
|
||||
window.hcaptcha.execute();
|
||||
} else {
|
||||
window.grecaptcha.execute();
|
||||
}
|
||||
break;
|
||||
case 11:
|
||||
errorTitle = 'No Proxies Allowed :(';
|
||||
|
|
|
@ -140,5 +140,9 @@ window.onCaptcha = async function onCaptcha(token: string) {
|
|||
} = window.pixel;
|
||||
store.dispatch(tryPlacePixel(i, j, offset, color));
|
||||
|
||||
window.grecaptcha.reset();
|
||||
if (typeof window.hcaptcha !== 'undefined') {
|
||||
window.hcaptcha.reset();
|
||||
} else {
|
||||
window.grecaptcha.reset();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -304,7 +304,7 @@ function Converter({
|
|||
>
|
||||
Download
|
||||
</button>
|
||||
<p>Credit for the Palette of the Moon goes to
|
||||
<p>Credit for the Palette of the Moon goes to
|
||||
<a href="https://twitter.com/starhousedev">starhouse</a>.</p>
|
||||
</p>
|
||||
<h3 className="modaltitle">Image Converter</h3>
|
||||
|
|
|
@ -52,12 +52,22 @@ const HelpModal = () => (
|
|||
<p className="modaltext">Left Click or tap to place a pixel</p>
|
||||
<p className="modaltext">Right Click of double tap to remove a pixel</p>
|
||||
<p>Partners: <a href="https://www.crazygames.com/c/io" target="_blank" rel="noopener noreferrer">crazygames.com</a></p>
|
||||
<p className="modaltext">
|
||||
<small>This site is protected by reCAPTCHA and the Google
|
||||
<a href="https://policies.google.com/privacy">Privacy Policy</a> and
|
||||
<a href="https://policies.google.com/terms">Terms of Service</a> apply.
|
||||
</small>
|
||||
</p>
|
||||
{ (typeof window.hcaptcha === 'undefined')
|
||||
? (
|
||||
<p className="modaltext">
|
||||
<small>This site is protected by reCAPTCHA and the Google
|
||||
<a href="https://policies.google.com/privacy">Privacy Policy</a> and
|
||||
<a href="https://policies.google.com/terms">Terms of Service</a> apply.
|
||||
</small>
|
||||
</p>
|
||||
) : (
|
||||
<p className="modaltext">
|
||||
<small>This site is protected by hCAPTCHA and its
|
||||
<a href="https://hcaptcha.com/privacy">Privacy Policy</a>and
|
||||
<a href="https://hcaptcha.com/terms">Terms of Service</a>apply.
|
||||
</small>
|
||||
</p>
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
/* eslint-disable max-len */
|
||||
|
||||
import React from 'react';
|
||||
import { CAPTCHA_SITEKEY } from '../core/config';
|
||||
import { CAPTCHA_METHOD, CAPTCHA_SITEKEY } from '../core/config';
|
||||
|
||||
const Html = ({
|
||||
title,
|
||||
|
@ -26,7 +26,7 @@ const Html = ({
|
|||
// code as string
|
||||
code,
|
||||
// if recaptcha should get loaded
|
||||
useRecaptcha,
|
||||
useCaptcha,
|
||||
}) => (
|
||||
<html className="no-js" lang="en">
|
||||
<head>
|
||||
|
@ -48,7 +48,7 @@ const Html = ({
|
|||
dangerouslySetInnerHTML={{ __html: style.cssText }}
|
||||
/>
|
||||
))}
|
||||
{CAPTCHA_SITEKEY && useRecaptcha
|
||||
{(CAPTCHA_METHOD === 1) && CAPTCHA_SITEKEY && useCaptcha
|
||||
&& (
|
||||
<div
|
||||
className="g-recaptcha"
|
||||
|
@ -57,7 +57,19 @@ const Html = ({
|
|||
data-size="invisible"
|
||||
/>
|
||||
)}
|
||||
{CAPTCHA_SITEKEY && useRecaptcha && <script src="https://www.google.com/recaptcha/api.js" async defer />}
|
||||
{(CAPTCHA_METHOD === 1) && CAPTCHA_SITEKEY && useCaptcha
|
||||
&& <script src="https://www.google.com/recaptcha/api.js" async defer />}
|
||||
{(CAPTCHA_METHOD === 2) && CAPTCHA_SITEKEY && useCaptcha
|
||||
&& (
|
||||
<div
|
||||
className="h-captcha"
|
||||
data-sitekey={CAPTCHA_SITEKEY}
|
||||
data-callback="onCaptcha"
|
||||
data-size="invisible"
|
||||
/>
|
||||
)}
|
||||
{(CAPTCHA_METHOD === 2) && CAPTCHA_SITEKEY && useCaptcha
|
||||
&& <script src="https://hcaptcha.com/1/api.js" async defer />}
|
||||
{code && (
|
||||
<script
|
||||
// eslint-disable-next-line react/no-danger
|
||||
|
|
|
@ -51,7 +51,7 @@ function generateMainPage(countryCoords: Cell): string {
|
|||
scripts={scripts}
|
||||
css={css}
|
||||
code={`${code}window.coordx=${x};window.coordy=${y};`}
|
||||
useRecaptcha
|
||||
useCaptcha
|
||||
/>,
|
||||
);
|
||||
|
||||
|
|
|
@ -82,13 +82,10 @@ export const auth = {
|
|||
},
|
||||
};
|
||||
|
||||
|
||||
export const ads = {
|
||||
adsense: {
|
||||
id: 'ca-pub-41116611299745444',
|
||||
},
|
||||
};
|
||||
|
||||
// o: none
|
||||
// 1: reCaptcha
|
||||
// 2: hCaptcha
|
||||
export const CAPTCHA_METHOD = Number(process.env.CAPTCHA_METHOD || 0);
|
||||
export const CAPTCHA_SECRET = process.env.CAPTCHA_SECRET || false;
|
||||
export const CAPTCHA_SITEKEY = process.env.CAPTCHA_SITEKEY || false;
|
||||
// time on which to display captcha in minutes
|
||||
|
|
|
@ -164,6 +164,7 @@ class ProtocolClient extends EventEmitter {
|
|||
} catch (err) {
|
||||
console.log(
|
||||
`An error occured while parsing websocket message ${message}`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,15 @@ import logger from '../core/logger';
|
|||
import redis from '../data/redis';
|
||||
|
||||
import {
|
||||
CAPTCHA_METHOD,
|
||||
CAPTCHA_SECRET,
|
||||
CAPTCHA_TIME,
|
||||
} from '../core/config';
|
||||
|
||||
const TTL_CACHE = CAPTCHA_TIME * 60; // seconds
|
||||
const BASE_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify';
|
||||
const ENDPOINT = `${BASE_ENDPOINT}?secret=${CAPTCHA_SECRET}`;
|
||||
// eslint-disable-next-line max-len
|
||||
const RECAPTCHA_ENDPOINT = `https://www.google.com/recaptcha/api/siteverify?secret=${CAPTCHA_SECRET}`;
|
||||
const HCAPTCHA_ENDPOINT = 'https://hcaptcha.com/siteverify';
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/questions/27297067/google-recaptcha-how-to-get-user-response-and-validate-in-the-server-side
|
||||
|
@ -27,11 +29,7 @@ async function verifyReCaptcha(
|
|||
token: string,
|
||||
ip: string,
|
||||
): Promise<boolean> {
|
||||
if (!CAPTCHA_SECRET) {
|
||||
logger.info('Got captcha token but reCaptcha isn\'t configured?!');
|
||||
return true;
|
||||
}
|
||||
const url = `${ENDPOINT}&response=${token}&remoteip=${ip}`;
|
||||
const url = `${RECAPTCHA_ENDPOINT}&response=${token}&remoteip=${ip}`;
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
const { success } = await response.json();
|
||||
|
@ -46,6 +44,38 @@ async function verifyReCaptcha(
|
|||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* https://docs.hcaptcha.com/
|
||||
*
|
||||
* @param token
|
||||
* @param ip
|
||||
* @return boolean, true if successful, false on error or fail
|
||||
*/
|
||||
async function verifyHCaptcha(
|
||||
token: string,
|
||||
ip: string,
|
||||
): Promise<boolean> {
|
||||
const response = await fetch(HCAPTCHA_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `response=${token}&secret=${CAPTCHA_SECRET}&remoteip=${ip}`,
|
||||
});
|
||||
if (response.ok) {
|
||||
const { success } = await response.json();
|
||||
if (success) {
|
||||
logger.info(`CAPTCHA ${ip} successfully solved captcha`);
|
||||
return true;
|
||||
}
|
||||
logger.info(`CAPTCHA Token for ${ip} not ok`);
|
||||
} else {
|
||||
// eslint-disable-next-line max-len
|
||||
logger.warn(`CAPTCHA hCapcha answer for ${ip} not ok ${await response.text()}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* verify captcha token from client
|
||||
*
|
||||
|
@ -58,7 +88,7 @@ export async function verifyCaptcha(
|
|||
ip: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
if (!CAPTCHA_SECRET) {
|
||||
if (!CAPTCHA_METHOD) {
|
||||
return true;
|
||||
}
|
||||
const key = `human:${ip}`;
|
||||
|
@ -67,8 +97,20 @@ export async function verifyCaptcha(
|
|||
if (ttl > 0) {
|
||||
return true;
|
||||
}
|
||||
if (!await verifyReCaptcha(token, ip)) {
|
||||
return false;
|
||||
|
||||
switch (CAPTCHA_METHOD) {
|
||||
case 1:
|
||||
if (!await verifyReCaptcha(token, ip)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (!await verifyHCaptcha(token, ip)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// nothing
|
||||
}
|
||||
|
||||
await redis.setAsync(key, '', 'EX', TTL_CACHE);
|
||||
|
@ -86,7 +128,7 @@ export async function verifyCaptcha(
|
|||
* @return boolean true if needed
|
||||
*/
|
||||
export async function needCaptcha(ip: string) {
|
||||
if (!CAPTCHA_SECRET) {
|
||||
if (!CAPTCHA_METHOD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user