Skip to content

Commit e1871f5

Browse files
committed
Implemented ReCaptcha
1 parent ab05e74 commit e1871f5

File tree

11 files changed

+479
-37
lines changed

11 files changed

+479
-37
lines changed

package-lock.json

+319-21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@types/execa": "^0.9.0",
1616
"@types/got": "^9.4.1",
1717
"@types/helmet": "0.0.42",
18+
"@types/recaptcha2": "^1.3.0",
1819
"@types/socket.io": "^2.1.2",
1920
"adjective-adjective-animal": "^1.3.3",
2021
"async-file": "^2.0.2",
@@ -27,6 +28,7 @@
2728
"got": "^9.6.0",
2829
"helmet": "^3.15.1",
2930
"pino": "^5.8.1",
31+
"recaptcha2": "^1.3.3",
3032
"socket.io": "^2.2.0",
3133
"uuid-by-string": "^2.1.0"
3234
},

server/common/recaptcha/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import reCAPTCHA from 'recaptcha2';
2+
3+
export default new reCAPTCHA({
4+
siteKey: 'notNeeded',
5+
secretKey: process.env.RECAPTCHA_SECRET_KEY
6+
});

server/common/socket/index.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { IBuildResponse, IDependency } from '../fiddle/interfaces';
88
import server from '../express';
99
import StdMessages from './StdMessages';
1010
import Fiddle from '../fiddle';
11+
import recaptcha from '../recaptcha';
1112

1213
const aaaRegex = /^(?=(.*[A-Z]){3,})(?=(.*[a-z]){3,})[^\W|_|\d]+$/;
1314

@@ -129,14 +130,23 @@ export default class SocketServer {
129130
socket.content = content;
130131
}
131132

132-
async onRunScript(socket: IExtendedSocket): Promise<any> {
133+
async onRunScript(socket: IExtendedSocket, captchaToken: string): Promise<any> {
133134
if (socket.isRunning || socket.isProcessing)
134135
return StdMessages.sendErrorMessage(socket, 'Invalid request. (Your script is already running)');
135136

136137
socket.isProcessing = true;
137138
socket.isRunning = false;
138139
StdMessages.sendScriptExecutionState(socket);
139140

141+
try {
142+
await recaptcha.validate(captchaToken);
143+
} catch (ex) {
144+
socket.isProcessing = false;
145+
socket.isRunning = false;
146+
StdMessages.sendScriptExecutionState(socket);
147+
return StdMessages.sendErrorMessage(socket, 'Invalid request. (Captcha challenge failed)');
148+
}
149+
140150
socket.emit('clearConsole');
141151

142152
if (!socket.fiddleInstance)
@@ -197,7 +207,13 @@ export default class SocketServer {
197207
socket.fiddleInstance.terminate(); // We just hope that the client was subscribed to the error / close event
198208
}
199209

200-
async onShare(socket: IExtendedSocket): Promise<any> {
210+
async onShare(socket: IExtendedSocket, captchaToken: string): Promise<any> {
211+
try {
212+
await recaptcha.validate(captchaToken);
213+
} catch (ex) {
214+
return StdMessages.sendErrorMessage(socket, 'Invalid request. (Captcha challenge failed)');
215+
}
216+
201217
if (!socket.fiddleInstance)
202218
socket.fiddleInstance = new Fiddle();
203219

ui/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"@types/react": "16.8.4",
1212
"@types/react-custom-scrollbars": "^4.0.5",
1313
"@types/react-dom": "16.8.2",
14+
"@types/react-google-recaptcha": "^1.0.0",
1415
"@types/react-router-dom": "^4.3.1",
1516
"@types/react-select": "^2.0.13",
1617
"@types/react-splitter-layout": "^3.0.0",
@@ -25,6 +26,7 @@
2526
"react-container-dimensions": "^1.4.1",
2627
"react-custom-scrollbars": "^4.2.1",
2728
"react-dom": "^16.8.2",
29+
"react-google-recaptcha": "^1.0.5",
2830
"react-monaco-editor": "^0.24.0",
2931
"react-router-dom": "^4.3.1",
3032
"react-scripts": "2.1.5",
@@ -33,7 +35,8 @@
3335
"sanitize-html-react": "^1.13.0",
3436
"socket.io-client": "^2.2.0",
3537
"tabler-react": "^1.28.0",
36-
"typescript": "3.3.3"
38+
"typescript": "3.3.3",
39+
"wait-for-cond": "^1.6.0"
3740
},
3841
"scripts": {
3942
"start": "react-scripts start",

ui/src/components/Footer/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class Footer extends Component<{}, IState> {
6464
<div className={'row footer bp3-navbar'}>
6565
PAWN Fiddle <Button className={'bp3-minimal'} onClick={this.handleDialogOpen} icon={'help'}></Button> |
6666
Made with <Icon icon={'heart'} intent={Intent.DANGER} /> by <a href={'https://sa-mp.dev'}>sa-mp.dev</a> | Powered by <a href={'http://sampctl.com'}>sampctl</a>
67+
<div className={'recaptcha-footer'}>
68+
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.
69+
</div>
6770
</div>
6871
</>
6972
);

ui/src/components/Footer/style.scss

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1+
@import "~@blueprintjs/core/lib/scss/variables";
2+
13
.footer {
2-
line-height: 40px;
3-
text-align: center;
4-
}
4+
line-height: 40px;
5+
text-align: center;
6+
}
7+
8+
.recaptcha-footer {
9+
background-color: $dark-gray2;
10+
border-radius: 5px;
11+
position: absolute;
12+
top: 5px;
13+
right: 5px;
14+
height: 30px;
15+
width: 215px;
16+
font-size: 9px;
17+
line-height: 9px;
18+
padding: 6px;
19+
}

ui/src/components/NavBar/index.tsx

+60-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import React, { Component } from 'react';
1+
import React, { Component, RefObject } from 'react';
22
import { Classes, Navbar, Alignment, EditableText, Button, Popover, Spinner, H5, Intent } from '@blueprintjs/core';
3+
import ReCAPTCHA from 'react-google-recaptcha';
4+
import waitFor from 'wait-for-cond';
35

46
import socketClient from '../../socketClient';
57
import Toast from '../../toast';
@@ -9,6 +11,8 @@ import './style.scss';
911
import logoPath from '../../assets/images/pawnlogo.png';
1012

1113
interface IState extends IExecutionState {
14+
recaptcha: RefObject<ReCAPTCHA>,
15+
captchaToken: string | null,
1216
locked: boolean,
1317
title: string,
1418
isSharing: boolean,
@@ -23,6 +27,8 @@ interface IExecutionState {
2327

2428
class NavBar extends Component {
2529
state: IState = {
30+
recaptcha: React.createRef(),
31+
captchaToken: null,
2632
locked: false,
2733
title: '',
2834
isProcessing: false,
@@ -93,10 +99,20 @@ class NavBar extends Component {
9399
Toast.show({ intent: Intent.SUCCESS, icon: 'tick', message: `Forked ${previousTitle} successfully.` });
94100
}
95101

96-
private runScript(): void {
97-
if (!this.state.isProcessing || !this.state.isRunning) {
98-
socketClient.socket.emit('runScript');
102+
private async runScript(): Promise<any> {
103+
if (this.state.isProcessing || this.state.isRunning)
104+
return;
105+
106+
if (!await this.checkCaptcha()) {
107+
return Toast.show({
108+
intent: Intent.DANGER,
109+
icon: 'error',
110+
message: 'You are solving that damn captcha for over an hour now... Hit the button again to retry solving the captcha.'
111+
});
99112
}
113+
114+
socketClient.socket.emit('runScript', this.state.captchaToken);
115+
this.invalidateCaptcha();
100116
}
101117

102118
private stopScript(): void {
@@ -144,12 +160,43 @@ class NavBar extends Component {
144160
}
145161
}
146162

147-
private shareFiddle(): void {
163+
private async checkCaptcha(): Promise<boolean> {
164+
if (!this.state.recaptcha.current)
165+
return false;
166+
167+
this.state.recaptcha.current.execute();
168+
169+
try {
170+
await waitFor(() => this.state.captchaToken, 1 * 60 * 60 * 1000); // Shouldn't take longer than an hour to solve some captchas...
171+
return true;
172+
} catch (ex) {
173+
return false; // he actually took longer than 1 hour to solve captchas... smh my head
174+
}
175+
}
176+
177+
private invalidateCaptcha(): void {
178+
if (!this.state.recaptcha.current)
179+
return;
180+
181+
this.state.recaptcha.current.reset();
182+
this.setState({ captchaToken: null });
183+
}
184+
185+
private async shareFiddle(): Promise<any> {
148186
if (this.state.isSharing || this.state.locked)
149187
return;
150188

189+
if (!await this.checkCaptcha()) {
190+
return Toast.show({
191+
intent: Intent.DANGER,
192+
icon: 'error',
193+
message: 'You are solving that damn captcha for over an hour now... Hit the button again to retry solving the captcha.'
194+
});
195+
}
196+
151197
this.setState({ isSharing: true });
152-
socketClient.socket.emit('share');
198+
socketClient.socket.emit('share', this.state.captchaToken);
199+
this.invalidateCaptcha();
153200
}
154201

155202
private forkFiddle(): void {
@@ -180,6 +227,13 @@ class NavBar extends Component {
180227
</Navbar.Heading>
181228
</Navbar.Group>
182229
<Navbar.Group align={Alignment.RIGHT}>
230+
<ReCAPTCHA
231+
ref={this.state.recaptcha}
232+
size={'invisible'}
233+
theme={'dark'}
234+
onChange={captchaToken => this.setState({ captchaToken })}
235+
sitekey={process.env.REACT_APP_RECAPTCHA_KEY || '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'}
236+
/>
183237
<Popover>
184238
<Button className={'bp3-minimal'} disabled={this.state.locked} icon={'share'} text={'Share'} large />
185239
<div className={'sharePopover'}>

ui/src/components/NavBar/style.scss

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
.sharePopover {
2-
padding: 20px;
2+
padding: 20px;
33
}
44

55
.shareURL {
6-
user-select: all;
7-
}
6+
user-select: all;
7+
}
8+
9+
.grecaptcha-badge {
10+
visibility: collapse !important;
11+
}

ui/src/react-app-env.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// <reference types="react-scripts" />
22
declare module '@uiw/react-monacoeditor';
33
declare module 'sanitize-html-react';
4+
declare module 'wait-for-cond';
45
declare module '*.scss' {
56
const content: {[className: string]: string};
67
export default content;

ui/yarn.lock

+41-1
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,13 @@
10351035
dependencies:
10361036
"@types/react" "*"
10371037

1038+
"@types/react-google-recaptcha@^1.0.0":
1039+
version "1.0.0"
1040+
resolved "https://registry.yarnpkg.com/@types/react-google-recaptcha/-/react-google-recaptcha-1.0.0.tgz#f0831066d082b7dcb3613c2ce361ff96fbc1b567"
1041+
integrity sha512-NhHWdRzRt766Zw+e5qvEAP0dVPHX9RDcv9BCrQ3GWXbN5qUjUe7ZSSpwa1lftVzjt4vD9755uBsSJ668rJpOHQ==
1042+
dependencies:
1043+
"@types/react" "*"
1044+
10381045
"@types/react-router-dom@^4.3.1":
10391046
version "4.3.1"
10401047
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04"
@@ -4973,6 +4980,13 @@ hoist-non-react-statics@^2.5.0:
49734980
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
49744981
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
49754982

4983+
hoist-non-react-statics@^3.0.1:
4984+
version "3.3.0"
4985+
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b"
4986+
integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==
4987+
dependencies:
4988+
react-is "^16.7.0"
4989+
49764990
home-or-tmp@^2.0.0:
49774991
version "2.0.0"
49784992
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -8625,7 +8639,7 @@ prompts@^0.1.9:
86258639
kleur "^2.0.1"
86268640
sisteransi "^0.1.1"
86278641

8628-
prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1:
8642+
prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1:
86298643
version "15.7.2"
86308644
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
86318645
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -8832,6 +8846,14 @@ react-app-polyfill@^0.2.1:
88328846
raf "3.4.1"
88338847
whatwg-fetch "3.0.0"
88348848

8849+
react-async-script@^1.0.0:
8850+
version "1.0.0"
8851+
resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.0.0.tgz#3578153247bc3f9654a5878c4142539ffbf65c2d"
8852+
integrity sha512-KNbqPgaOrb7sxEr3qLuyxswJfveCGSGsxj/jYbUT0esTD2p5u5kmnt6huOOEcL5UwU4Zmbw561gUC45xPjB+MA==
8853+
dependencies:
8854+
hoist-non-react-statics "^3.0.1"
8855+
prop-types "^15.5.0"
8856+
88358857
react-container-dimensions@^1.4.1:
88368858
version "1.4.1"
88378859
resolved "https://registry.yarnpkg.com/react-container-dimensions/-/react-container-dimensions-1.4.1.tgz#73a8b497f09c6b55a18a79b18a57a8327138f343"
@@ -8912,13 +8934,26 @@ react-error-overlay@^5.1.3:
89128934
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.3.tgz#16fcbde75ed4dc6161dc6dc959b48e92c6ffa9ad"
89138935
integrity sha512-GoqeM3Xadie7XUApXOjkY3Qhs8RkwB/Za4WMedBGrOKH1eTuKGyoAECff7jiVonJchOx6KZ9i8ILO5XIoHB+Tg==
89148936

8937+
react-google-recaptcha@^1.0.5:
8938+
version "1.0.5"
8939+
resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-1.0.5.tgz#fc5a1c5c9fd678ccea11c9a47b22f38a8b9d3c88"
8940+
integrity sha512-IUIIQVUIKgsG7Ok1AiqBdJZVpFRpIWOM3H/36fAJHMd52l+X0pn4sTxvm2YJEN01QXWR1jg79+93J8mQef7hfw==
8941+
dependencies:
8942+
prop-types "^15.5.0"
8943+
react-async-script "^1.0.0"
8944+
89158945
react-input-autosize@^2.2.1:
89168946
version "2.2.1"
89178947
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8"
89188948
integrity sha512-3+K4CD13iE4lQQ2WlF8PuV5htfmTRLH6MDnfndHM6LuBRszuXnuyIfE7nhSKt8AzRBZ50bu0sAhkNMeS5pxQQA==
89198949
dependencies:
89208950
prop-types "^15.5.8"
89218951

8952+
react-is@^16.7.0:
8953+
version "16.8.4"
8954+
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2"
8955+
integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==
8956+
89228957
react-is@^16.8.1:
89238958
version "16.8.1"
89248959
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.1.tgz#a80141e246eb894824fb4f2901c0c50ef31d4cdb"
@@ -10993,6 +11028,11 @@ w3c-hr-time@^1.0.1:
1099311028
dependencies:
1099411029
browser-process-hrtime "^0.1.2"
1099511030

11031+
wait-for-cond@^1.6.0:
11032+
version "1.6.0"
11033+
resolved "https://registry.yarnpkg.com/wait-for-cond/-/wait-for-cond-1.6.0.tgz#7ec38c4ccd99eceefae63d62be2498100f9f7ac5"
11034+
integrity sha512-hC+xGlFfspoTPpie43JjFi4onzVNAxr6UtvttAT+QigVbge7hJPH1oR0LNvLcsNOEt5K4FoR6Q2Ez3a9jWNmig==
11035+
1099611036
walker@~1.0.5:
1099711037
version "1.0.7"
1099811038
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"

0 commit comments

Comments
 (0)