change captcha
remove noauthUser, which was just confusing remove rest of api/pixel
This commit is contained in:
parent
2b8b5b5888
commit
ecb27a1ea0
|
@ -19,6 +19,7 @@ import {
|
||||||
receiveChatHistory,
|
receiveChatHistory,
|
||||||
receivePixelReturn,
|
receivePixelReturn,
|
||||||
setMobile,
|
setMobile,
|
||||||
|
tryPlacePixel,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import store from './ui/store';
|
import store from './ui/store';
|
||||||
|
|
||||||
|
@ -116,3 +117,28 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
setInterval(runGC, 300000);
|
setInterval(runGC, 300000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// on captcha received
|
||||||
|
// TODO: this really isn't beautiful
|
||||||
|
window.onCaptcha = async function onCaptcha(token: string) {
|
||||||
|
const body = JSON.stringify({
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
await fetch('/api/captcha', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body,
|
||||||
|
// https://github.com/github/fetch/issues/349
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
i, j, offset, color,
|
||||||
|
} = window.pixel;
|
||||||
|
store.dispatch(tryPlacePixel(i, j, offset, color));
|
||||||
|
|
||||||
|
window.grecaptcha.reset();
|
||||||
|
};
|
||||||
|
|
|
@ -18,7 +18,6 @@ import ChatButton from './ChatButton';
|
||||||
import ChatBox from './ChatBox';
|
import ChatBox from './ChatBox';
|
||||||
import Menu from './Menu';
|
import Menu from './Menu';
|
||||||
import UI from './UI';
|
import UI from './UI';
|
||||||
import ReCaptcha from './ReCaptcha';
|
|
||||||
import ExpandMenuButton from './ExpandMenuButton';
|
import ExpandMenuButton from './ExpandMenuButton';
|
||||||
import MinecraftTPButton from './MinecraftTPButton';
|
import MinecraftTPButton from './MinecraftTPButton';
|
||||||
import ModalRoot from './ModalRoot';
|
import ModalRoot from './ModalRoot';
|
||||||
|
@ -27,7 +26,6 @@ const App = () => (
|
||||||
<div>
|
<div>
|
||||||
<Style />
|
<Style />
|
||||||
<div id="outstreamContainer" />
|
<div id="outstreamContainer" />
|
||||||
<ReCaptcha />
|
|
||||||
<IconContext.Provider value={{ style: { verticalAlign: 'middle' } }}>
|
<IconContext.Provider value={{ style: { verticalAlign: 'middle' } }}>
|
||||||
<CanvasSwitchButton />
|
<CanvasSwitchButton />
|
||||||
<Menu />
|
<Menu />
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { analytics, RECAPTCHA_SITEKEY } from '../core/config';
|
import { CAPTCHA_SITEKEY } from '../core/config';
|
||||||
|
|
||||||
const Html = ({
|
const Html = ({
|
||||||
title,
|
title,
|
||||||
|
@ -48,10 +48,16 @@ const Html = ({
|
||||||
dangerouslySetInnerHTML={{ __html: style.cssText }}
|
dangerouslySetInnerHTML={{ __html: style.cssText }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{RECAPTCHA_SITEKEY && useRecaptcha
|
{CAPTCHA_SITEKEY && useRecaptcha
|
||||||
// eslint-disable-next-line react/no-danger
|
&& (
|
||||||
&& <script dangerouslySetInnerHTML={{ __html: `window.sitekey="${RECAPTCHA_SITEKEY}"` }} />}
|
<div
|
||||||
{RECAPTCHA_SITEKEY && useRecaptcha && <script src="https://www.google.com/recaptcha/api.js" async defer />}
|
className="g-recaptcha"
|
||||||
|
data-sitekey={CAPTCHA_SITEKEY}
|
||||||
|
data-callback="onCaptcha"
|
||||||
|
data-size="invisible"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{CAPTCHA_SITEKEY && useRecaptcha && <script src="https://www.google.com/recaptcha/api.js" async defer />}
|
||||||
{code && (
|
{code && (
|
||||||
<script
|
<script
|
||||||
// eslint-disable-next-line react/no-danger
|
// eslint-disable-next-line react/no-danger
|
||||||
|
@ -67,19 +73,6 @@ const Html = ({
|
||||||
{body}
|
{body}
|
||||||
</div>
|
</div>
|
||||||
{scripts && scripts.map((script) => <script key={script} src={script} />)}
|
{scripts && scripts.map((script) => <script key={script} src={script} />)}
|
||||||
{analytics.google.trackingId
|
|
||||||
&& (
|
|
||||||
<script
|
|
||||||
// eslint-disable-next-line react/no-danger
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html:
|
|
||||||
'window.ga=function(){ga.q.push(arguments)};ga.q=[];ga.l=+new Date;'
|
|
||||||
+ `ga('create','${analytics.google.trackingId}','auto');ga('send','pageview')`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{analytics.google.trackingId
|
|
||||||
&& <script src="https://www.google-analytics.com/analytics.js" async defer />}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
* Implement ReCaptcha
|
|
||||||
* (the recaptcha sitekey gets received in the Html inline script sent by components/Html)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import store from '../ui/store';
|
|
||||||
import { tryPlacePixel } from '../actions';
|
|
||||||
|
|
||||||
|
|
||||||
async function onCaptcha(token: string) {
|
|
||||||
const body = JSON.stringify({
|
|
||||||
token,
|
|
||||||
});
|
|
||||||
await fetch('/api/captcha', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body,
|
|
||||||
// https://github.com/github/fetch/issues/349
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
i, j, offset, color,
|
|
||||||
} = window.pixel;
|
|
||||||
store.dispatch(tryPlacePixel(i, j, offset, color));
|
|
||||||
|
|
||||||
window.grecaptcha.reset();
|
|
||||||
}
|
|
||||||
// https://stackoverflow.com/questions/41717304/recaptcha-google-data-callback-with-angularjs
|
|
||||||
window.onCaptcha = onCaptcha;
|
|
||||||
|
|
||||||
const ReCaptcha = () => (
|
|
||||||
<div
|
|
||||||
className="g-recaptcha"
|
|
||||||
data-sitekey={window.sitekey}
|
|
||||||
data-callback="onCaptcha"
|
|
||||||
data-size="invisible"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ReCaptcha;
|
|
|
@ -89,9 +89,9 @@ export const ads = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RECAPTCHA_SECRET = process.env.RECAPTCHA_SECRET || false;
|
export const CAPTCHA_SECRET = process.env.CAPTCHA_SECRET || false;
|
||||||
export const RECAPTCHA_SITEKEY = process.env.RECAPTCHA_SITEKEY || false;
|
export const CAPTCHA_SITEKEY = process.env.CAPTCHA_SITEKEY || false;
|
||||||
// time on which to display captcha in minutes
|
// time on which to display captcha in minutes
|
||||||
export const RECAPTCHA_TIME = parseInt(process.env.RECAPTCHA_TIME, 10) || 30;
|
export const CAPTCHA_TIME = parseInt(process.env.CAPTCHA_TIME, 10) || 30;
|
||||||
|
|
||||||
export const SESSION_SECRET = process.env.SESSION_SECRET || 'dummy';
|
export const SESSION_SECRET = process.env.SESSION_SECRET || 'dummy';
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { getIPv6Subnet } from '../utils/ip';
|
||||||
import { Blacklist, Whitelist } from '../data/models';
|
import { Blacklist, Whitelist } from '../data/models';
|
||||||
import { proxyLogger } from './logger';
|
import { proxyLogger } from './logger';
|
||||||
|
|
||||||
|
import { USE_PROXYCHECK } from './config';
|
||||||
|
|
||||||
const logger = proxyLogger;
|
const logger = proxyLogger;
|
||||||
|
|
||||||
|
|
||||||
|
@ -204,7 +206,10 @@ async function withCache(f, ip) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cheapDetector(ip: string): Promise<boolean> {
|
export function cheapDetector(ip: string): Promise<boolean> {
|
||||||
return withCache(getProxyCheck, ip);
|
if (USE_PROXYCHECK) {
|
||||||
|
return withCache(getProxyCheck, ip);
|
||||||
|
}
|
||||||
|
return withCache(dummy, ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function strongDetector(ip: string): Promise<boolean> {
|
export function strongDetector(ip: string): Promise<boolean> {
|
||||||
|
@ -214,5 +219,3 @@ export function strongDetector(ip: string): Promise<boolean> {
|
||||||
export function blacklistDetector(ip: string): Promise<boolean> {
|
export function blacklistDetector(ip: string): Promise<boolean> {
|
||||||
return withCache(dummy, ip);
|
return withCache(dummy, ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
// export default cheapDetector;
|
|
||||||
|
|
|
@ -18,17 +18,15 @@ import { sanitizeName } from '../utils/validation';
|
||||||
import { User, RegUser } from '../data/models';
|
import { User, RegUser } from '../data/models';
|
||||||
import { auth } from './config';
|
import { auth } from './config';
|
||||||
import { compareToHash } from '../utils/hash';
|
import { compareToHash } from '../utils/hash';
|
||||||
|
import { getIPFromRequest } from '../utils/ip';
|
||||||
|
|
||||||
|
|
||||||
passport.serializeUser((user, done) => {
|
passport.serializeUser((user, done) => {
|
||||||
done(null, user.id);
|
done(null, user.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
passport.deserializeUser((req, id, done) => {
|
passport.deserializeUser(async (req, id, done) => {
|
||||||
// req.noauthUser already get populated with id and ip in routes/api/index to allow
|
const user = new User(id, getIPFromRequest(req));
|
||||||
// some api requests (pixel) to not require this sql deserlialize
|
|
||||||
// but still know the id
|
|
||||||
const user = (req.noauthUser) ? req.noauthUser : new User(id);
|
|
||||||
if (id) {
|
if (id) {
|
||||||
RegUser.findOne({ where: { id } }).then((reguser) => {
|
RegUser.findOne({ where: { id } }).then((reguser) => {
|
||||||
if (reguser) {
|
if (reguser) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import logger from '../../core/logger';
|
||||||
|
|
||||||
import Model from '../sequelize';
|
import Model from '../sequelize';
|
||||||
import RegUser from './RegUser';
|
import RegUser from './RegUser';
|
||||||
|
import { getIPv6Subnet } from '../../utils/ip';
|
||||||
|
|
||||||
import { ADMIN_IDS } from '../../core/config';
|
import { ADMIN_IDS } from '../../core/config';
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ class User {
|
||||||
// id should stay null if unregistered, and user email if registered
|
// id should stay null if unregistered, and user email if registered
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
|
this.ipSub = getIPv6Subnet(ip);
|
||||||
this.wait = null;
|
this.wait = null;
|
||||||
this.regUser = null;
|
this.regUser = null;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +52,7 @@ class User {
|
||||||
if (!coolDown) return false;
|
if (!coolDown) return false;
|
||||||
this.wait = Date.now() + coolDown;
|
this.wait = Date.now() + coolDown;
|
||||||
// PX is milliseconds expire
|
// PX is milliseconds expire
|
||||||
await redis.setAsync(`cd:${canvasId}:ip:${this.ip}`, '', 'PX', coolDown);
|
await redis.setAsync(`cd:${canvasId}:ip:${this.ipSub}`, '', 'PX', coolDown);
|
||||||
if (this.id != null) {
|
if (this.id != null) {
|
||||||
await redis.setAsync(`cd:${canvasId}:id:${this.id}`, '', 'PX', coolDown);
|
await redis.setAsync(`cd:${canvasId}:id:${this.id}`, '', 'PX', coolDown);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +60,7 @@ class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWait(canvasId: number): Promise<?number> {
|
async getWait(canvasId: number): Promise<?number> {
|
||||||
let ttl: number = await redis.pttlAsync(`cd:${canvasId}:ip:${this.ip}`);
|
let ttl: number = await redis.pttlAsync(`cd:${canvasId}:ip:${this.ipSub}`);
|
||||||
if (this.id != null && ttl < 0) {
|
if (this.id != null && ttl < 0) {
|
||||||
const ttlid: number = await redis.pttlAsync(
|
const ttlid: number = await redis.pttlAsync(
|
||||||
`cd:${canvasId}:id:${this.id}`,
|
`cd:${canvasId}:id:${this.id}`,
|
||||||
|
|
|
@ -65,7 +65,7 @@ router.use(session);
|
||||||
router.use(passport.initialize());
|
router.use(passport.initialize());
|
||||||
router.use(passport.session());
|
router.use(passport.session());
|
||||||
router.use(async (req, res, next) => {
|
router.use(async (req, res, next) => {
|
||||||
const ip = await getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
logger.info(`ADMINTOOLS: ${ip} tried to access admintools without login`);
|
logger.info(`ADMINTOOLS: ${ip} tried to access admintools without login`);
|
||||||
res.status(403).send('You are not logged in');
|
res.status(403).send('You are not logged in');
|
||||||
|
|
|
@ -62,10 +62,10 @@ export default async (req: Request, res: Response) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ip = await getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
logger.info(`Created new user ${name} ${email} ${ip}`);
|
logger.info(`Created new user ${name} ${email} ${ip}`);
|
||||||
|
|
||||||
const user = req.noauthUser;
|
const { user } = req;
|
||||||
user.id = newuser.id;
|
user.id = newuser.id;
|
||||||
user.regUser = newuser;
|
user.regUser = newuser;
|
||||||
const me = await getMe(user);
|
const me = await getMe(user);
|
||||||
|
|
|
@ -8,30 +8,11 @@
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
|
|
||||||
import logger from '../../core/logger';
|
import logger from '../../core/logger';
|
||||||
import redis from '../../data/redis';
|
import { verifyCaptcha } from '../../utils/captcha';
|
||||||
import verifyCaptcha from '../../utils/recaptcha';
|
import { getIPFromRequest } from '../../utils/ip';
|
||||||
|
|
||||||
import {
|
|
||||||
RECAPTCHA_SECRET,
|
|
||||||
RECAPTCHA_TIME,
|
|
||||||
} from '../../core/config';
|
|
||||||
|
|
||||||
const TTL_CACHE = RECAPTCHA_TIME * 60; // seconds
|
|
||||||
|
|
||||||
export default async (req: Request, res: Response) => {
|
export default async (req: Request, res: Response) => {
|
||||||
if (!RECAPTCHA_SECRET) {
|
const ip = getIPFromRequest(req);
|
||||||
res.status(200)
|
|
||||||
.json({
|
|
||||||
errors: [{
|
|
||||||
msg:
|
|
||||||
'No need for a captcha here',
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = req.user || req.noauthUser;
|
|
||||||
const { ip } = user;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { token } = req.body;
|
const { token } = req.body;
|
||||||
|
@ -41,20 +22,6 @@ export default async (req: Request, res: Response) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = `human:${ip}`;
|
|
||||||
|
|
||||||
const ttl: number = await redis.ttlAsync(key);
|
|
||||||
if (ttl > 0) {
|
|
||||||
res.status(400)
|
|
||||||
.json({
|
|
||||||
errors: [{
|
|
||||||
msg:
|
|
||||||
'Why would you even want to solve a captcha?',
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await verifyCaptcha(token, ip)) {
|
if (!await verifyCaptcha(token, ip)) {
|
||||||
logger.info(`CAPTCHA ${ip} failed his captcha`);
|
logger.info(`CAPTCHA ${ip} failed his captcha`);
|
||||||
res.status(422)
|
res.status(422)
|
||||||
|
@ -67,9 +34,6 @@ export default async (req: Request, res: Response) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// save to cache
|
|
||||||
await redis.setAsync(key, 'y', 'EX', TTL_CACHE);
|
|
||||||
|
|
||||||
res.status(200)
|
res.status(200)
|
||||||
.json({ success: true });
|
.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -8,12 +8,11 @@ import bodyParser from 'body-parser';
|
||||||
import session from '../../core/session';
|
import session from '../../core/session';
|
||||||
import passport from '../../core/passport';
|
import passport from '../../core/passport';
|
||||||
import logger from '../../core/logger';
|
import logger from '../../core/logger';
|
||||||
import { User } from '../../data/models';
|
import User from '../../data/models/User';
|
||||||
import { getIPFromRequest, getIPv6Subnet } from '../../utils/ip';
|
import { getIPFromRequest } from '../../utils/ip';
|
||||||
|
|
||||||
import me from './me';
|
import me from './me';
|
||||||
import mctp from './mctp';
|
import mctp from './mctp';
|
||||||
// import pixel from './pixel';
|
|
||||||
import captcha from './captcha';
|
import captcha from './captcha';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import ranking from './ranking';
|
import ranking from './ranking';
|
||||||
|
@ -27,27 +26,6 @@ router.get('/ranking', ranking);
|
||||||
|
|
||||||
router.get('/history', history);
|
router.get('/history', history);
|
||||||
|
|
||||||
/*
|
|
||||||
* get user session
|
|
||||||
*/
|
|
||||||
router.use(session);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* create dummy user that has just ip and id
|
|
||||||
* (cut IPv6 to subnet to prevent abuse)
|
|
||||||
*/
|
|
||||||
router.use(async (req, res, next) => {
|
|
||||||
const { session: sess } = req;
|
|
||||||
const id = (sess.passport && sess.passport.user)
|
|
||||||
? sess.passport.user : null;
|
|
||||||
const ip = await getIPFromRequest(req);
|
|
||||||
const trueIp = ip || '0.0.0.1';
|
|
||||||
req.trueIp = trueIp;
|
|
||||||
const user = new User(id, getIPv6Subnet(trueIp));
|
|
||||||
req.noauthUser = user;
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.use(bodyParser.json());
|
router.use(bodyParser.json());
|
||||||
|
|
||||||
router.use((err, req, res, next) => {
|
router.use((err, req, res, next) => {
|
||||||
|
@ -60,16 +38,21 @@ router.use((err, req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
// captcah doesn't need a user
|
||||||
* rate limiting should occure outside,
|
|
||||||
* with nginx or whatever
|
|
||||||
*/
|
|
||||||
/* api pixel got deactivated in favor of websocket */
|
|
||||||
/* keeping it still here to enable it again if needed */
|
|
||||||
// router.post('/pixel', pixel);
|
|
||||||
|
|
||||||
router.post('/captcha', captcha);
|
router.post('/captcha', captcha);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get user session
|
||||||
|
*/
|
||||||
|
router.use(session);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* at this point we could use the session id to get
|
||||||
|
* stuff without having to verify the whole user,
|
||||||
|
* which would avoid SQL requests and it got used previously
|
||||||
|
* when we set pixels via api/pixel (new removed)
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* passport authenticate
|
* passport authenticate
|
||||||
* and deserlialize
|
* and deserlialize
|
||||||
|
@ -79,6 +62,17 @@ router.post('/captcha', captcha);
|
||||||
router.use(passport.initialize());
|
router.use(passport.initialize());
|
||||||
router.use(passport.session());
|
router.use(passport.session());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* create dummy user with just ip if not
|
||||||
|
* logged in
|
||||||
|
*/
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
if (!req.user) {
|
||||||
|
req.user = new User(null, getIPFromRequest(req));
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/me', me);
|
router.get('/me', me);
|
||||||
|
|
||||||
router.post('/mctp', mctp);
|
router.post('/mctp', mctp);
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { cheapDetector } from '../../core/isProxy';
|
||||||
|
|
||||||
|
|
||||||
export default async (req: Request, res: Response) => {
|
export default async (req: Request, res: Response) => {
|
||||||
const user = req.user || req.noauthUser;
|
const { user } = req;
|
||||||
const userdata = await getMe(user);
|
const userdata = await getMe(user);
|
||||||
user.updateLogInTimestamp();
|
user.updateLogInTimestamp();
|
||||||
|
|
||||||
|
|
|
@ -1,224 +0,0 @@
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Request, Response } from 'express';
|
|
||||||
|
|
||||||
import { drawSafeByCoords } from '../../core/draw';
|
|
||||||
import {
|
|
||||||
blacklistDetector,
|
|
||||||
cheapDetector,
|
|
||||||
strongDetector,
|
|
||||||
} from '../../core/isProxy';
|
|
||||||
import verifyCaptcha from '../../utils/recaptcha';
|
|
||||||
import logger, { pixelLogger } from '../../core/logger';
|
|
||||||
import redis from '../../data/redis';
|
|
||||||
import {
|
|
||||||
USE_PROXYCHECK,
|
|
||||||
RECAPTCHA_SECRET,
|
|
||||||
RECAPTCHA_TIME,
|
|
||||||
} from '../../core/config';
|
|
||||||
|
|
||||||
|
|
||||||
async function validate(req: Request, res: Response, next) {
|
|
||||||
let error = null;
|
|
||||||
const cn = parseInt(req.body.cn, 10);
|
|
||||||
const x = parseInt(req.body.x, 10);
|
|
||||||
const y = parseInt(req.body.y, 10);
|
|
||||||
let z = null;
|
|
||||||
if (typeof req.body.z !== 'undefined') {
|
|
||||||
z = parseInt(req.body.z, 10);
|
|
||||||
}
|
|
||||||
const clr = parseInt(req.body.clr, 10);
|
|
||||||
|
|
||||||
if (Number.isNaN(cn)) {
|
|
||||||
error = 'No valid canvas selected';
|
|
||||||
} else if (Number.isNaN(x)) {
|
|
||||||
error = 'x is not a valid number';
|
|
||||||
} else if (Number.isNaN(y)) {
|
|
||||||
error = 'y is not a valid number';
|
|
||||||
} else if (Number.isNaN(clr)) {
|
|
||||||
error = 'No color selected';
|
|
||||||
} else if (z !== null && Number.isNaN(z)) {
|
|
||||||
error = 'z is not a valid number';
|
|
||||||
}
|
|
||||||
if (error !== null) {
|
|
||||||
res.status(400).json({ errors: [{ msg: error }] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
req.body.cn = cn;
|
|
||||||
req.body.x = x;
|
|
||||||
req.body.y = y;
|
|
||||||
req.body.z = z;
|
|
||||||
req.body.clr = clr;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* make sure that a user is chosen
|
|
||||||
* req.noauthUser: user with just ip and id set
|
|
||||||
* req.user: fully passport authenticated user
|
|
||||||
* api/pixel just requires ip and id, so noauthUser is enough
|
|
||||||
* a fully authenticated user would cause more SQL requests
|
|
||||||
*/
|
|
||||||
let { user } = req;
|
|
||||||
if (!req.user) {
|
|
||||||
req.user = req.noauthUser;
|
|
||||||
user = req.user;
|
|
||||||
}
|
|
||||||
if (!user || !user.ip) {
|
|
||||||
res.status(400).json({ errors: [{ msg: "Couldn't authenticate" }] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const TTL_CACHE = RECAPTCHA_TIME * 60; // seconds
|
|
||||||
async function checkHuman(req: Request, res: Response, next) {
|
|
||||||
if (!RECAPTCHA_SECRET) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { user } = req;
|
|
||||||
const { ip } = user;
|
|
||||||
if (user.isAdmin()) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { token } = req.body;
|
|
||||||
|
|
||||||
const key = `human:${ip}`;
|
|
||||||
|
|
||||||
const ttl: number = await redis.ttlAsync(key);
|
|
||||||
if (ttl > 0) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!token || !await verifyCaptcha(token, ip)) {
|
|
||||||
logger.info(`CAPTCHA ${ip} got a captcha`);
|
|
||||||
res.status(422)
|
|
||||||
.json({ errors: [{ msg: 'Captcha occured' }] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// save to cache
|
|
||||||
await redis.setAsync(key, 'y', 'EX', TTL_CACHE);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('checkHuman', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// cheap check whole canvas for proxies, if USE_PROXYCHECK is one
|
|
||||||
// strongly check selective areas
|
|
||||||
async function checkProxy(req: Request, res: Response, next) {
|
|
||||||
const { trueIp: ip } = req;
|
|
||||||
if (USE_PROXYCHECK && ip !== '0.0.0.1') {
|
|
||||||
/*
|
|
||||||
//one area uses stronger detector
|
|
||||||
const { x, y } = req.body;
|
|
||||||
if ((x > 970 && x < 2380 && y > -11407 && y < -10597) || //nc
|
|
||||||
(x > 4220 && x < 6050 && y > -12955 && y < -11230) || //belarius
|
|
||||||
(x > 14840 && x < 15490 && y > -17380 && y < -16331) || //russian bot
|
|
||||||
(x > 11189 && x < 12003 && y > 3483 && y < 4170) || //random bot
|
|
||||||
(x > -13402 && x < -5617 && y > 1640 && y < 5300)){ //brazil
|
|
||||||
if (!ip || await strongDetector(ip)) {
|
|
||||||
res.status(403)
|
|
||||||
.json({ errors: [{ msg: 'You are using a proxy!' }] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*/
|
|
||||||
if (!ip || await cheapDetector(ip)) {
|
|
||||||
res.status(403)
|
|
||||||
.json({ errors: [{ msg: 'You are using a proxy!' }] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
} else if (await blacklistDetector(ip)) {
|
|
||||||
res.status(403)
|
|
||||||
.json({ errors: [{ msg: 'You are using a proxy or got banned!' }] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// strongly check just specific areas for proxies
|
|
||||||
// do not proxycheck the rest
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
async function checkProxySelective(req: Request, res: Response, next) {
|
|
||||||
const { trueIp: ip } = req;
|
|
||||||
if (USE_PROXYCHECK) {
|
|
||||||
const { x, y } = req.body;
|
|
||||||
if (x > 970 && x < 2380 && y > -11407 && y < -10597) { // nc
|
|
||||||
if (!ip || await strongDetector(ip)) {
|
|
||||||
res.status(403)
|
|
||||||
.json({ errors: [{ msg: 'You are using a proxy!' }] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (await blacklistDetector(ip)) {
|
|
||||||
res.status(403)
|
|
||||||
.json({ errors: [{ msg: 'You are using a proxy or got banned!' }] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// place pixel and return waiting time
|
|
||||||
async function place(req: Request, res: Response) {
|
|
||||||
// https://stackoverflow.com/questions/49547/how-to-control-web-page-caching-across-all-browsers
|
|
||||||
// https://stackoverflow.com/a/7066740
|
|
||||||
res.set({
|
|
||||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
||||||
Pragma: 'no-cache',
|
|
||||||
Expires: '0',
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
cn, x, y, z, clr,
|
|
||||||
} = req.body;
|
|
||||||
const { user, trueIp } = req;
|
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
pixelLogger.info(`${trueIp} ${user.id} ${cn} ${x} ${y} ${z} ${clr}`);
|
|
||||||
|
|
||||||
const {
|
|
||||||
errorTitle, error, success, waitSeconds, coolDownSeconds,
|
|
||||||
} = await drawSafeByCoords(user, cn, clr, x, y, z);
|
|
||||||
logger.log('debug', success);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
res.json({ success, waitSeconds, coolDownSeconds });
|
|
||||||
} else {
|
|
||||||
const errors = [];
|
|
||||||
if (error) {
|
|
||||||
res.status(403);
|
|
||||||
errors.push({ msg: error });
|
|
||||||
}
|
|
||||||
if (errorTitle) {
|
|
||||||
res.json({
|
|
||||||
success, waitSeconds, coolDownSeconds, errorTitle, errors,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.json({
|
|
||||||
success, waitSeconds, coolDownSeconds, errors,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default [validate, checkHuman, checkProxy, place];
|
|
|
@ -27,7 +27,7 @@ function heartbeat() {
|
||||||
async function verifyClient(info, done) {
|
async function verifyClient(info, done) {
|
||||||
const { req } = info;
|
const { req } = info;
|
||||||
const { headers } = req;
|
const { headers } = req;
|
||||||
const ip = await getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
|
|
||||||
if (!headers.authorization
|
if (!headers.authorization
|
||||||
|| headers.authorization !== `Bearer ${APISOCKET_KEY}`) {
|
|| headers.authorization !== `Bearer ${APISOCKET_KEY}`) {
|
||||||
|
|
|
@ -38,7 +38,7 @@ class ProtocolClient extends EventEmitter {
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
this.ws = null;
|
this.ws = null;
|
||||||
this.name = null;
|
this.name = null;
|
||||||
this.canvasId = null;
|
this.canvasId = '0';
|
||||||
this.msgQueue = [];
|
this.msgQueue = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,11 @@ class ProtocolClient extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
setCanvas(canvasId) {
|
setCanvas(canvasId) {
|
||||||
if (this.canvasId === canvasId || canvasId === null) {
|
/* canvasId can be string or integer, thanks to
|
||||||
|
* JSON not allowing integer keys
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
if (this.canvasId == canvasId || canvasId === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Notify websocket server that we changed canvas');
|
console.log('Notify websocket server that we changed canvas');
|
||||||
|
|
|
@ -25,13 +25,8 @@ import authenticateClient from './verifyClient';
|
||||||
import WebSocketEvents from './WebSocketEvents';
|
import WebSocketEvents from './WebSocketEvents';
|
||||||
import webSockets from './websockets';
|
import webSockets from './websockets';
|
||||||
import { drawSafeByOffset } from '../core/draw';
|
import { drawSafeByOffset } from '../core/draw';
|
||||||
|
import { needCaptcha } from '../utils/captcha';
|
||||||
import redis from '../data/redis';
|
import { cheapDetector } from '../core/isProxy';
|
||||||
import { cheapDetector, blacklistDetector } from '../core/isProxy';
|
|
||||||
import {
|
|
||||||
USE_PROXYCHECK,
|
|
||||||
RECAPTCHA_SECRET,
|
|
||||||
} from '../core/config';
|
|
||||||
|
|
||||||
|
|
||||||
const ipCounter: Counter<string> = new Counter();
|
const ipCounter: Counter<string> = new Counter();
|
||||||
|
@ -45,7 +40,7 @@ async function verifyClient(info, done) {
|
||||||
const { headers } = req;
|
const { headers } = req;
|
||||||
|
|
||||||
// Limiting socket connections per ip
|
// Limiting socket connections per ip
|
||||||
const ip = await getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
logger.info(`Got ws request from ${ip} via ${headers.origin}`);
|
logger.info(`Got ws request from ${ip} via ${headers.origin}`);
|
||||||
if (ipCounter.get(ip) > 50) {
|
if (ipCounter.get(ip) > 50) {
|
||||||
logger.info(`Client ${ip} has more than 50 connections open.`);
|
logger.info(`Client ${ip} has more than 50 connections open.`);
|
||||||
|
@ -92,7 +87,7 @@ class SocketServer extends WebSocketEvents {
|
||||||
ws.user = user;
|
ws.user = user;
|
||||||
ws.name = (user.regUser) ? user.regUser.name : null;
|
ws.name = (user.regUser) ? user.regUser.name : null;
|
||||||
ws.rateLimiter = new RateLimiter(20, 15, true);
|
ws.rateLimiter = new RateLimiter(20, 15, true);
|
||||||
SocketServer.checkIfProxy(ws);
|
cheapDetector(user.ip);
|
||||||
|
|
||||||
if (ws.name) {
|
if (ws.name) {
|
||||||
ws.send(`"${ws.name}"`);
|
ws.send(`"${ws.name}"`);
|
||||||
|
@ -102,7 +97,7 @@ class SocketServer extends WebSocketEvents {
|
||||||
online: this.wss.clients.size || 0,
|
online: this.wss.clients.size || 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const ip = await getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
ws.on('error', (e) => {
|
ws.on('error', (e) => {
|
||||||
logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`);
|
logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`);
|
||||||
});
|
});
|
||||||
|
@ -245,16 +240,6 @@ class SocketServer extends WebSocketEvents {
|
||||||
webSockets.broadcastOnlineCounter(online);
|
webSockets.broadcastOnlineCounter(online);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async checkIfProxy(ws) {
|
|
||||||
const { ip } = ws.user;
|
|
||||||
if (USE_PROXYCHECK && ip && await cheapDetector(ip)) {
|
|
||||||
return true;
|
|
||||||
} if (await blacklistDetector(ip)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async onTextMessage(text, ws) {
|
static async onTextMessage(text, ws) {
|
||||||
try {
|
try {
|
||||||
let message;
|
let message;
|
||||||
|
@ -288,7 +273,7 @@ class SocketServer extends WebSocketEvents {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// check proxy
|
// check proxy
|
||||||
if (await SocketServer.checkIfProxy(ws)) {
|
if (await cheapDetector(ws)) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`${ws.name} / ${user.ip} tried to send chat message with proxy`,
|
`${ws.name} / ${user.ip} tried to send chat message with proxy`,
|
||||||
);
|
);
|
||||||
|
@ -346,18 +331,13 @@ class SocketServer extends WebSocketEvents {
|
||||||
}
|
}
|
||||||
const { ip } = user;
|
const { ip } = user;
|
||||||
// check if captcha needed
|
// check if captcha needed
|
||||||
if (RECAPTCHA_SECRET) {
|
if (await needCaptcha(ip)) {
|
||||||
const key = `human:${ip}`;
|
// need captcha
|
||||||
const ttl: number = await redis.ttlAsync(key);
|
ws.send(PixelReturn.dehydrate(10, 0, 0));
|
||||||
if (ttl <= 0) {
|
break;
|
||||||
// need captcha
|
|
||||||
logger.info(`CAPTCHA ${ip} / ${ws.name} got captcha`);
|
|
||||||
ws.send(PixelReturn.dehydrate(10, 0, 0));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// (re)check for Proxy
|
// (re)check for Proxy
|
||||||
if (await SocketServer.checkIfProxy(ws)) {
|
if (await cheapDetector(ip)) {
|
||||||
ws.send(PixelReturn.dehydrate(11, 0, 0));
|
ws.send(PixelReturn.dehydrate(11, 0, 0));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,27 +6,13 @@ import express from 'express';
|
||||||
|
|
||||||
import session from '../core/session';
|
import session from '../core/session';
|
||||||
import passport from '../core/passport';
|
import passport from '../core/passport';
|
||||||
import { User } from '../data/models';
|
import User from '../data/models/User';
|
||||||
import { getIPFromRequest, getIPv6Subnet } from '../utils/ip';
|
import { getIPFromRequest } from '../utils/ip';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(session);
|
router.use(session);
|
||||||
|
|
||||||
/*
|
|
||||||
* create dummy user that has just ip and id
|
|
||||||
* (cut IPv6 to subnet to prevent abuse)
|
|
||||||
*/
|
|
||||||
router.use(async (req, res, next) => {
|
|
||||||
const ip = await getIPFromRequest(req);
|
|
||||||
const trueIp = ip || '0.0.0.1';
|
|
||||||
req.trueIp = trueIp;
|
|
||||||
const user = new User(null, getIPv6Subnet(trueIp));
|
|
||||||
req.noauthUser = user;
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
router.use(passport.initialize());
|
router.use(passport.initialize());
|
||||||
router.use(passport.session());
|
router.use(passport.session());
|
||||||
|
|
||||||
|
@ -34,9 +20,10 @@ router.use(passport.session());
|
||||||
function authenticateClient(req) {
|
function authenticateClient(req) {
|
||||||
return new Promise(
|
return new Promise(
|
||||||
((resolve) => {
|
((resolve) => {
|
||||||
router(req, {}, () => {
|
router(req, {}, async () => {
|
||||||
const user = req.user || req.noauthUser;
|
|
||||||
const country = req.headers['cf-ipcountry'];
|
const country = req.headers['cf-ipcountry'];
|
||||||
|
const user = (req.user) ? req.user
|
||||||
|
: new User(null, getIPFromRequest(req));
|
||||||
user.country = country.toLowerCase();
|
user.country = country.toLowerCase();
|
||||||
resolve(user);
|
resolve(user);
|
||||||
});
|
});
|
||||||
|
|
103
src/utils/captcha.js
Normal file
103
src/utils/captcha.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fetch from 'isomorphic-fetch';
|
||||||
|
import logger from '../core/logger';
|
||||||
|
import redis from '../data/redis';
|
||||||
|
|
||||||
|
import {
|
||||||
|
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}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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> {
|
||||||
|
if (!CAPTCHA_SECRET) {
|
||||||
|
logger.info('Got captcha token but reCaptcha isn\'t configured?!');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const url = `${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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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_SECRET) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const key = `human:${ip}`;
|
||||||
|
|
||||||
|
const ttl: number = await redis.ttlAsync(key);
|
||||||
|
if (ttl > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!await verifyReCaptcha(token, ip)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_SECRET) {
|
||||||
|
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;
|
|
@ -25,10 +25,11 @@ export function getHostFromRequest(req): ?string {
|
||||||
return `${proto}://${host}`;
|
return `${proto}://${host}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getIPFromRequest(req): ?string {
|
export function getIPFromRequest(req): ?string {
|
||||||
const { socket, connection, headers } = req;
|
const { socket, connection, headers } = req;
|
||||||
|
|
||||||
const conip = (connection ? connection.remoteAddress : socket.remoteAddress);
|
let conip = (connection ? connection.remoteAddress : socket.remoteAddress);
|
||||||
|
conip = conip || '0.0.0.1';
|
||||||
|
|
||||||
if (USE_XREALIP) {
|
if (USE_XREALIP) {
|
||||||
const ip = headers['x-real-ip'];
|
const ip = headers['x-real-ip'];
|
||||||
|
@ -48,9 +49,7 @@ export async function getIPFromRequest(req): ?string {
|
||||||
while (isTrustedProxy(ip) && ipList.length) {
|
while (isTrustedProxy(ip) && ipList.length) {
|
||||||
ip = ipList.pop();
|
ip = ipList.pop();
|
||||||
}
|
}
|
||||||
|
return ip || conip;
|
||||||
// logger.info('Proxied Connection allowed', ip, forwardedFor);
|
|
||||||
return ip;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getIPv6Subnet(ip: string): string {
|
export function getIPv6Subnet(ip: string): string {
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fetch from 'isomorphic-fetch';
|
|
||||||
|
|
||||||
import logger from '../core/logger';
|
|
||||||
import { RECAPTCHA_SECRET } from '../core/config';
|
|
||||||
|
|
||||||
|
|
||||||
const BASE_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify';
|
|
||||||
const ENDPOINT = `${BASE_ENDPOINT}?secret=${RECAPTCHA_SECRET}`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 verifyCaptcha(
|
|
||||||
token: string,
|
|
||||||
ip: string,
|
|
||||||
): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
if (!RECAPTCHA_SECRET) {
|
|
||||||
logger.info('Got captcha token but reCaptcha isn\'t configured?!');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const url = `${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`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default verifyCaptcha;
|
|
Loading…
Reference in New Issue
Block a user