fix table width

more captcha changes
This commit is contained in:
HF 2021-03-08 23:47:44 +01:00
parent 7dd44811a6
commit 1c0b1101b0
10 changed files with 90 additions and 67 deletions

View File

@ -6,3 +6,4 @@ apps:
PORT: 8080 PORT: 8080
HOST: "localhost" HOST: "localhost"
REDIS_URL: 'redis://localhost:6379' REDIS_URL: 'redis://localhost:6379'
CAPTCHA_TIMEOUT: 120

View File

@ -8,6 +8,9 @@ import process from 'process';
import http from 'http'; import http from 'http';
import ppfunCaptcha from 'ppfun-captcha'; import ppfunCaptcha from 'ppfun-captcha';
import { getIPFromRequest } from './utils/ip';
import { setCaptchaSolution } from './utils/captcha';
const PORT = process.env.PORT || 8080; const PORT = process.env.PORT || 8080;
const HOST = process.env.HOST || 'localhost'; const HOST = process.env.HOST || 'localhost';
@ -22,8 +25,12 @@ const server = http.createServer((req, res) => {
nodeDeviation: 0.5, nodeDeviation: 0.5,
connectionPathDeviation: 0.3, connectionPathDeviation: 0.3,
}); });
const ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
const ip = getIPFromRequest(req);
setCaptchaSolution(captcha.text, ip);
console.log(`Serving ${captcha.text} to ${ip}`); console.log(`Serving ${captcha.text} to ${ip}`);
res.writeHead(200, { res.writeHead(200, {
'Content-Type': 'image/svg+xml', 'Content-Type': 'image/svg+xml',
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',

View File

@ -1,4 +1,8 @@
/* /*
* Form to ask for captcha.
* If callback is provided, it sets the captcha text to it.
* If callback is not provided, it provides a button to send the
* captcha itself
* @flow * @flow
*/ */
@ -6,6 +10,7 @@ import React, { useState } from 'react';
import { t } from 'ttag'; import { t } from 'ttag';
import { IoReloadCircleSharp } from 'react-icons/io5'; import { IoReloadCircleSharp } from 'react-icons/io5';
import { requestSolveCaptcha } from '../actions/fetch';
function getUrl() { function getUrl() {
return `${window.ssv.captchaurl}/captcha.svg?${new Date().getTime()}`; return `${window.ssv.captchaurl}/captcha.svg?${new Date().getTime()}`;
@ -25,7 +30,7 @@ const Captcha = ({ callback, cancel }) => {
</span> </span>
</p> </p>
<img <img
style={{width: '75%'}} style={{ width: '75%' }}
src={captchaUrl} src={captchaUrl}
onError={() => setError(true)} onError={() => setError(true)}
/> />

View File

@ -90,5 +90,7 @@ export const auth = {
// time on which to display captcha in minutes // time on which to display captcha in minutes
export const CAPTCHA_TIME = parseInt(process.env.CAPTCHA_TIME, 10) || 30; export const CAPTCHA_TIME = parseInt(process.env.CAPTCHA_TIME, 10) || 30;
// time during which the user can solve a captcha in seconds
export const CAPTCHA_TIMEOUT = parseInt(process.env.CAPTCHA_TIMEOUT, 10) || 120;
export const SESSION_SECRET = process.env.SESSION_SECRET || 'dummy'; export const SESSION_SECRET = process.env.SESSION_SECRET || 'dummy';

View File

@ -8,42 +8,51 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import logger from '../../core/logger'; import logger from '../../core/logger';
import { verifyCaptcha } from '../../utils/captcha'; import { checkCaptchaSolution } from '../../utils/captcha';
import { getIPFromRequest } from '../../utils/ip'; import { getIPFromRequest } from '../../utils/ip';
export default async (req: Request, res: Response) => { export default async (req: Request, res: Response) => {
const ip = getIPFromRequest(req); const ip = getIPFromRequest(req);
const { t } = req.ttag;
try { try {
const { token } = req.body; const { text } = req.body;
if (!token) { if (!text) {
res.status(400) res.status(400)
.json({ errors: [{ msg: 'No token given' }] }); .json({ errors: [t`No captcha text given`] });
return; return;
} }
if (!await verifyCaptcha(token, ip)) { const ret = await checkCaptchaSolution(text, ip);
logger.info(`CAPTCHA ${ip} failed his captcha`);
res.status(422)
.json({
errors: [{
msg:
'You failed your captcha',
}],
});
return;
}
res.status(200) switch (ret) {
.json({ success: true }); case 0:
res.status(200)
.json({ success: true });
break;
case 1:
res.status(422)
.json({
errors: [t`You took too long, try again.`],
});
break;
case 2:
res.status(422)
.json({
errors: [t`You failed your captcha`],
});
break;
default:
res.status(422)
.json({
errors: [t`Unknown Captcha Error`],
});
}
} catch (error) { } catch (error) {
logger.error('checkHuman', error); logger.error('CAPTCHA', error);
res.status(500) res.status(500)
.json({ .json({
errors: [{ errors: [t`Server error occured`],
msg:
'Server error occured',
}],
}); });
} }
}; };

View File

@ -44,6 +44,11 @@ router.use((err, req, res, next) => {
} }
}); });
/*
* make localisations available
*/
router.use(expressTTag);
// captcah doesn't need a user // captcah doesn't need a user
router.post('/captcha', captcha); router.post('/captcha', captcha);
@ -89,11 +94,6 @@ router.post('/block', block);
router.post('/blockdm', blockdm); router.post('/blockdm', blockdm);
/*
* make localisations available
*/
router.use(expressTTag);
router.get('/chathistory', chatHistory); router.get('/chathistory', chatHistory);
router.get('/me', me); router.get('/me', me);

View File

@ -24,8 +24,6 @@ const Html = ({
styles, styles,
// code as string // code as string
code, code,
// if recaptcha should get loaded
useCaptcha,
}) => ( }) => (
<html className="no-js" lang="en"> <html className="no-js" lang="en">
<head> <head>

View File

@ -112,6 +112,8 @@ td, th {
border: 1px solid #dddddd; border: 1px solid #dddddd;
text-align: left; text-align: left;
padding: 8px; padding: 8px;
max-width: 18em;
overflow-x: hidden;
} }
tr:nth-child(even) { tr:nth-child(even) {

View File

@ -241,7 +241,7 @@ export function receivePixelReturn(
case 10: case 10:
store.dispatch(sweetAlert( store.dispatch(sweetAlert(
'Captcha', 'Captcha',
`Please prove that you are human..`, t`Please prove that you are human`,
'captcha', 'captcha',
t`OK`, t`OK`,
)); ));

View File

@ -5,58 +5,60 @@
import logger from '../core/logger'; import logger from '../core/logger';
import redis from '../data/redis'; import redis from '../data/redis';
import { getIPv6Subnet } from './ip';
import { import {
CAPTCHA_URL, CAPTCHA_URL,
CAPTCHA_TIME, CAPTCHA_TIME,
CAPTCHA_TIMEOUT,
} from '../core/config'; } from '../core/config';
const TTL_CACHE = CAPTCHA_TIME * 60; // seconds const TTL_CACHE = CAPTCHA_TIME * 60; // seconds
/* /*
* https://docs.hcaptcha.com/ * set captcha solution
* *
* @param token * @param text Solution of captcha
* @param ip * @param ip
* @return boolean, true if successful, false on error or fail * @param ttl time to be valid in seconds
*/ */
async function verifyHCaptcha( export function setCaptchaSolution(
token: string, text: string,
ip: string, ip: string,
): Promise<boolean> { ) {
const success = true; const key = `capt:${getIPv6Subnet(ip)}`;
if (success) { return redis.setAsync(key, text, 'EX', CAPTCHA_TIMEOUT);
logger.info(`CAPTCHA ${ip} successfully solved captcha`);
return true;
}
logger.info(`CAPTCHA Token for ${ip} not ok`);
return false;
} }
/* /*
* verify captcha token from client * check captcha solution
* *
* @param token token of solved captcha from client * @param text Solution of captcha
* @param ip * @param ip
* @returns Boolean if successful * @return 0 if solution right
* 1 if timed out
* 2 if wrong
*/ */
export async function verifyCaptcha( export async function checkCaptchaSolution(
token: string, text: string,
ip: string, ip: string,
): Promise<boolean> { ) {
try { const ipn = getIPv6Subnet(ip);
const key = `human:${ip}`; const key = `capt:${ipn}`;
const solution = await redis.getAsync(key);
if (!await verifyHCaptcha(token, ip)) { if (solution) {
return false; if (solution.toString('utf8') === text) {
const solvkey = `human:${ipn}`;
await redis.setAsync(solvkey, '', 'EX', TTL_CACHE);
logger.info(`CAPTCHA ${ip} successfully solved captcha`);
return 0;
} }
logger.info(
await redis.setAsync(key, '', 'EX', TTL_CACHE); `CAPTCHA ${ip} got captcha wrong (${text} instead of ${solution})`,
return true; );
} catch (error) { return 2;
logger.error(error);
} }
return false; logger.info(`CAPTCHA ${ip} timed out`);
return 1;
} }
/* /*
@ -70,7 +72,7 @@ export async function needCaptcha(ip: string) {
return false; return false;
} }
const key = `human:${ip}`; const key = `human:${getIPv6Subnet(ip)}`;
const ttl: number = await redis.ttlAsync(key); const ttl: number = await redis.ttlAsync(key);
if (ttl > 0) { if (ttl > 0) {
return false; return false;
@ -78,6 +80,3 @@ export async function needCaptcha(ip: string) {
logger.info(`CAPTCHA ${ip} got captcha`); logger.info(`CAPTCHA ${ip} got captcha`);
return true; return true;
} }
export default verifyCaptcha;