fix table width
more captcha changes
This commit is contained in:
parent
7dd44811a6
commit
1c0b1101b0
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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',
|
|
||||||
}],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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`,
|
||||||
));
|
));
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user