add captcha alert, remove some old captcha stuff
This commit is contained in:
parent
74b4a44224
commit
7dd44811a6
34
README.md
34
README.md
|
@ -79,24 +79,22 @@ Configuration takes place in the environment variables that are defined in ecosy
|
|||
|
||||
#### Optional Configuration
|
||||
|
||||
| Variable | Description | Example |
|
||||
|-------------------|:--------------------------------------|--------------------|
|
||||
| ASSET_SERVER | URL for assets | "http://localhost" |
|
||||
| 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" |
|
||||
| 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 ngins / CDN section | 1 |
|
||||
| BACKUP_URL | url of backup server (see Backup) | "http://localhost" |
|
||||
| BACKUP_DIR | mounted directory of backup server | "/mnt/backup/" |
|
||||
| GMAIL_USER | gmail username if used for mails | "ppfun@gmail.com" |
|
||||
| GMAIL_PW | gmail password if used for mails | "lolrofls" |
|
||||
| HOURLY_EVENT | run hourly void event on main canvas | 1 |
|
||||
| Variable | Description | Example |
|
||||
|-------------------|:--------------------------------------|-------------------------|
|
||||
| ASSET_SERVER | URL for assets | "http://localhost" |
|
||||
| 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" |
|
||||
| CAPTCHA_URL | URL where captcha is served | "http://localhost:8080" |
|
||||
| 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 ngins / CDN section | 1 |
|
||||
| BACKUP_URL | url of backup server (see Backup) | "http://localhost" |
|
||||
| BACKUP_DIR | mounted directory of backup server | "/mnt/backup/" |
|
||||
| GMAIL_USER | gmail username if used for mails | "ppfun@gmail.com" |
|
||||
| GMAIL_PW | gmail password if used for mails | "lolrofls" |
|
||||
| HOURLY_EVENT | run hourly void event on main canvas | 1 |
|
||||
|
||||
Notes:
|
||||
|
||||
|
|
|
@ -3,6 +3,6 @@ apps:
|
|||
name : 'captchas'
|
||||
node_args: --nouse-idle-notification --expose-gc
|
||||
env:
|
||||
PORT: 80
|
||||
PORT: 8080
|
||||
HOST: "localhost"
|
||||
REDIS_URL: 'redis://localhost:6379'
|
||||
|
|
|
@ -8,7 +8,7 @@ import process from 'process';
|
|||
import http from 'http';
|
||||
import ppfunCaptcha from 'ppfun-captcha';
|
||||
|
||||
const PORT = process.env.PORT || 80;
|
||||
const PORT = process.env.PORT || 8080;
|
||||
const HOST = process.env.HOST || 'localhost';
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
|
@ -25,7 +25,7 @@ const server = http.createServer((req, res) => {
|
|||
const ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
|
||||
console.log(`Serving ${captcha.text} to ${ip}`);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html',
|
||||
'Content-Type': 'image/svg+xml',
|
||||
'Cache-Control': 'no-cache',
|
||||
});
|
||||
res.write(captcha.data);
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import Captcha from './Captcha';
|
||||
import { closeAlert } from '../actions';
|
||||
|
||||
const Alert = () => {
|
||||
|
@ -20,9 +21,9 @@ const Alert = () => {
|
|||
} = useSelector((state) => state.alert);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const close = () => {
|
||||
const close = useCallback(() => {
|
||||
dispatch(closeAlert());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const onTransitionEnd = () => {
|
||||
if (!alertOpen) setRender(false);
|
||||
|
@ -54,12 +55,16 @@ const Alert = () => {
|
|||
{alertMessage}
|
||||
</p>
|
||||
<p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={close}
|
||||
>
|
||||
{alertBtn}
|
||||
</button>
|
||||
{(alertType === 'captcha')
|
||||
? <Captcha cancel={close} />
|
||||
: (
|
||||
<button
|
||||
type="button"
|
||||
onClick={close}
|
||||
>
|
||||
{alertBtn}
|
||||
</button>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,10 +2,74 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { t } from 'ttag';
|
||||
|
||||
const Captcha = ({
|
||||
}) => (
|
||||
div />
|
||||
);
|
||||
import { IoReloadCircleSharp } from 'react-icons/io5';
|
||||
|
||||
function getUrl() {
|
||||
return `${window.ssv.captchaurl}/captcha.svg?${new Date().getTime()}`;
|
||||
}
|
||||
|
||||
const Captcha = ({ callback, cancel }) => {
|
||||
const [captchaUrl, setCaptchaUrl] = useState(getUrl());
|
||||
const [text, setText] = useState('');
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="modaltext">
|
||||
{t`Type the characters from the following image:`}
|
||||
<span style={{ fontSize: 11 }}>
|
||||
({t`Tip: Not case-sensitive; I and l are the same`})
|
||||
</span>
|
||||
</p>
|
||||
<img
|
||||
style={{width: '75%'}}
|
||||
src={captchaUrl}
|
||||
onError={() => setError(true)}
|
||||
/>
|
||||
<p className="modaltext">
|
||||
{t`Can't read? Reload:`}
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
title={t`Reload`}
|
||||
onClick={() => setCaptchaUrl(getUrl())}
|
||||
>
|
||||
<IoReloadCircleSharp />
|
||||
</span>
|
||||
</p>
|
||||
<input
|
||||
placeholder={t`I am human`}
|
||||
type="text"
|
||||
value={text}
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
style={{ width: '5em' }}
|
||||
onChange={(evt) => {
|
||||
const txt = evt.target.value;
|
||||
setText(txt);
|
||||
if (callback) callback(txt);
|
||||
}}
|
||||
/>
|
||||
{(!callback) && (
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cancel}
|
||||
>
|
||||
{t`Cancel`}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
{t`Send`}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Captcha);
|
||||
|
|
|
@ -25,7 +25,7 @@ const ChatBox = () => {
|
|||
}, 10);
|
||||
}, [chatOpen]);
|
||||
|
||||
const onTransitionEnd =() => {
|
||||
const onTransitionEnd = () => {
|
||||
if (!chatOpen) setRender(false);
|
||||
};
|
||||
|
||||
|
@ -44,6 +44,6 @@ const ChatBox = () => {
|
|||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default React.memo(ChatBox);
|
||||
|
|
|
@ -90,20 +90,6 @@ can be downloaded from mega.nz here: `}<a href="https://mega.nz/#!JpkBwAbJ!EnSLl
|
|||
{jt`Click ${mouseSymbol} middle mouse button or ${touchSymbol} long-tap to select current hovering color`}<br />
|
||||
</div>
|
||||
<p>{t`Partners:`} <a href="https://www.crazygames.com/c/io" target="_blank" rel="noopener noreferrer">crazygames.com</a></p>
|
||||
{ (typeof window.hcaptcha === 'undefined')
|
||||
? (
|
||||
<p className="modaltext">
|
||||
<small>
|
||||
{jt`This site is protected by reCAPTCHA and the Google ${reCaptchaPP} and ${reCaptchaTOS} apply.`}
|
||||
</small>
|
||||
</p>
|
||||
) : (
|
||||
<p className="modaltext">
|
||||
<small>
|
||||
{jt`This site is protected by hCAPTCHA and its ${hCaptchaPP} and ${hCaptchaTOS} apply.`}
|
||||
</small>
|
||||
</p>
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -121,6 +121,7 @@ class SignUpForm extends React.Component {
|
|||
<input
|
||||
style={inputStyles}
|
||||
value={name}
|
||||
autoComplete="username"
|
||||
onChange={(evt) => this.setState({ name: evt.target.value })}
|
||||
type="text"
|
||||
placeholder={t`Name`}
|
||||
|
@ -128,6 +129,7 @@ class SignUpForm extends React.Component {
|
|||
<input
|
||||
style={inputStyles}
|
||||
value={email}
|
||||
autoComplete="email"
|
||||
onChange={(evt) => this.setState({ email: evt.target.value })}
|
||||
type="text"
|
||||
placeholder={t`Email`}
|
||||
|
@ -135,6 +137,7 @@ class SignUpForm extends React.Component {
|
|||
<input
|
||||
style={inputStyles}
|
||||
value={password}
|
||||
autoComplete="new-password"
|
||||
onChange={(evt) => this.setState({ password: evt.target.value })}
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
|
@ -142,6 +145,7 @@ class SignUpForm extends React.Component {
|
|||
<input
|
||||
style={inputStyles}
|
||||
value={confirmPassword}
|
||||
autoComplete="new-password"
|
||||
onChange={(evt) => this.setState({
|
||||
confirmPassword: evt.target.value,
|
||||
})}
|
||||
|
|
|
@ -20,6 +20,8 @@ export const TILE_FOLDER = path.join(__dirname, `./${TILE_FOLDER_REL}`);
|
|||
|
||||
export const ASSET_SERVER = process.env.ASSET_SERVER || '.';
|
||||
|
||||
export const CAPTCHA_URL = process.env.CAPTCHA_URL || 'http://localhost:8080';
|
||||
|
||||
export const USE_XREALIP = process.env.USE_XREALIP || false;
|
||||
|
||||
export const BACKUP_URL = process.env.BACKUP_URL || null;
|
||||
|
@ -86,12 +88,6 @@ export const auth = {
|
|||
},
|
||||
};
|
||||
|
||||
// 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
|
||||
export const CAPTCHA_TIME = parseInt(process.env.CAPTCHA_TIME, 10) || 30;
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
/* eslint-disable max-len */
|
||||
|
||||
import React from 'react';
|
||||
import { CAPTCHA_METHOD, CAPTCHA_SITEKEY } from '../core/config';
|
||||
|
||||
const Html = ({
|
||||
title,
|
||||
|
@ -48,10 +47,6 @@ const Html = ({
|
|||
dangerouslySetInnerHTML={{ __html: style.cssText }}
|
||||
/>
|
||||
))}
|
||||
{(CAPTCHA_METHOD === 1) && CAPTCHA_SITEKEY && useCaptcha
|
||||
&& <script src="https://www.google.com/recaptcha/api.js" async defer />}
|
||||
{(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
|
||||
|
@ -67,24 +62,6 @@ const Html = ({
|
|||
{body}
|
||||
</div>
|
||||
{scripts && scripts.map((script) => <script key={script} src={script} />)}
|
||||
{(CAPTCHA_METHOD === 2) && CAPTCHA_SITEKEY && useCaptcha
|
||||
&& (
|
||||
<div
|
||||
className="h-captcha"
|
||||
data-sitekey={CAPTCHA_SITEKEY}
|
||||
data-callback="onCaptcha"
|
||||
data-size="invisible"
|
||||
/>
|
||||
)}
|
||||
{(CAPTCHA_METHOD === 1) && CAPTCHA_SITEKEY && useCaptcha
|
||||
&& (
|
||||
<div
|
||||
className="g-recaptcha"
|
||||
data-sitekey={CAPTCHA_SITEKEY}
|
||||
data-callback="onCaptcha"
|
||||
data-size="invisible"
|
||||
/>
|
||||
)}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ import assets from './assets.json';
|
|||
// eslint-disable-next-line import/no-unresolved
|
||||
import styleassets from './styleassets.json';
|
||||
|
||||
import { ASSET_SERVER, BACKUP_URL } from '../core/config';
|
||||
import { CAPTCHA_URL, ASSET_SERVER, BACKUP_URL } from '../core/config';
|
||||
|
||||
/*
|
||||
* generate language list
|
||||
|
@ -31,6 +31,7 @@ const langs = Object.keys(ttags)
|
|||
*/
|
||||
const ssv = {
|
||||
assetserver: ASSET_SERVER,
|
||||
captchaurl: CAPTCHA_URL,
|
||||
availableStyles: styleassets,
|
||||
langs,
|
||||
};
|
||||
|
|
|
@ -239,15 +239,14 @@ export function receivePixelReturn(
|
|||
store.dispatch(pixelWait());
|
||||
break;
|
||||
case 10:
|
||||
// captcha, reCaptcha or hCaptcha
|
||||
if (typeof window.hcaptcha !== 'undefined') {
|
||||
window.hcaptcha.execute();
|
||||
} else {
|
||||
window.grecaptcha.execute();
|
||||
}
|
||||
store.dispatch(sweetAlert(
|
||||
'Captcha',
|
||||
`Please prove that you are human..`,
|
||||
'captcha',
|
||||
t`OK`,
|
||||
));
|
||||
return;
|
||||
case 11:
|
||||
|
||||
errorTitle = t`No Proxies Allowed :(`;
|
||||
msg = t`You are using a Proxy.`;
|
||||
break;
|
||||
|
|
|
@ -3,46 +3,15 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import fetch from 'isomorphic-fetch';
|
||||
import logger from '../core/logger';
|
||||
import redis from '../data/redis';
|
||||
|
||||
import {
|
||||
CAPTCHA_METHOD,
|
||||
CAPTCHA_SECRET,
|
||||
CAPTCHA_URL,
|
||||
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/
|
||||
|
@ -55,24 +24,12 @@ 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()}`);
|
||||
const success = true;
|
||||
if (success) {
|
||||
logger.info(`CAPTCHA ${ip} successfully solved captcha`);
|
||||
return true;
|
||||
}
|
||||
logger.info(`CAPTCHA Token for ${ip} not ok`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -88,24 +45,10 @@ export async function verifyCaptcha(
|
|||
ip: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
if (!CAPTCHA_METHOD) {
|
||||
return true;
|
||||
}
|
||||
const key = `human:${ip}`;
|
||||
|
||||
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
|
||||
if (!await verifyHCaptcha(token, ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await redis.setAsync(key, '', 'EX', TTL_CACHE);
|
||||
|
@ -123,7 +66,7 @@ export async function verifyCaptcha(
|
|||
* @return boolean true if needed
|
||||
*/
|
||||
export async function needCaptcha(ip: string) {
|
||||
if (!CAPTCHA_METHOD) {
|
||||
if (!CAPTCHA_URL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user