@@ -74,6 +85,8 @@
socialProviders,
saving: false,
+
+ captchaToken: null,
};
},
computed: {
@@ -90,7 +103,10 @@
audience: this.$base,
issuer: this.$base,
});
- }
+ },
+ canInit() {
+ return this.usernameOrEmail && this.captchaToken;
+ },
},
methods: {
async login() {
@@ -98,24 +114,31 @@
return;
}
this.saving = true;
- await this.post(`/user/init`, {
- usernameOrEmail: this.usernameOrEmail
- });
- this.saving = false;
+ try {
+ await this.post(`/user/init`, {
+ usernameOrEmail: this.usernameOrEmail,
+ captchaToken: this.captchaToken,
+ });
+ } finally {
+ this.saving = false;
+ }
},
async validate() {
if (this.saving) {
return;
}
this.saving = true;
- await this.post(`/user/validate`, {
- code: this.code
- }, {
- headers: {
- authorization: 'Bearer ' + this.token,
- },
- });
- this.saving = false;
+ try {
+ await this.post(`/user/validate`, {
+ code: this.code
+ }, {
+ headers: {
+ authorization: 'Bearer ' + this.token,
+ },
+ });
+ } finally {
+ this.saving = false;
+ }
},
async post(url, data, options = {}) {
this.error = '';
diff --git a/locale/de/translations.suml b/locale/de/translations.suml
index 176c89c7..dc182ee2 100644
--- a/locale/de/translations.suml
+++ b/locale/de/translations.suml
@@ -526,6 +526,11 @@ images:
error:
generic: 'Etwas ist schief gelaufen. Bitte versuche es erneut…'
+# TODO
+captcha:
+ reason: 'Please prove you''re not a bot to mitigate spam and DDoS attacks.'
+ invalid: 'Invalid CAPTCHA, please try again'
+
mode:
dark: 'Dunkelmodus'
light: 'Lichtmodus'
diff --git a/locale/en/translations.suml b/locale/en/translations.suml
index c130315e..57648e83 100644
--- a/locale/en/translations.suml
+++ b/locale/en/translations.suml
@@ -611,6 +611,10 @@ images:
error:
generic: 'Something went wrong, please try again…'
+captcha:
+ reason: 'Please prove you''re not a bot to mitigate spam and DDoS attacks.'
+ invalid: 'Invalid CAPTCHA, please try again'
+
mode:
dark: 'Dark mode'
light: 'Light mode'
diff --git a/locale/es/translations.suml b/locale/es/translations.suml
index f41466b5..c79dc61c 100644
--- a/locale/es/translations.suml
+++ b/locale/es/translations.suml
@@ -538,6 +538,11 @@ images:
error:
generic: 'Algo salió mal. Por favor, vuelve a intentarlo…'
+# TODO
+captcha:
+ reason: 'Please prove you''re not a bot to mitigate spam and DDoS attacks.'
+ invalid: 'Invalid CAPTCHA, please try again'
+
mode:
dark: 'Modo oscuro'
light: 'Modo claro'
diff --git a/locale/fr/translations.suml b/locale/fr/translations.suml
index ff3f4b97..4eb56816 100644
--- a/locale/fr/translations.suml
+++ b/locale/fr/translations.suml
@@ -531,6 +531,11 @@ images:
error:
generic: 'Quelque chose s’est mal passé, réessayez s’il vous plaît…'
+# TODO
+captcha:
+ reason: 'Please prove you''re not a bot to mitigate spam and DDoS attacks.'
+ invalid: 'Invalid CAPTCHA, please try again'
+
mode:
dark: 'Mode sombre'
light: 'Mode clair'
diff --git a/locale/nl/translations.suml b/locale/nl/translations.suml
index 3bcd1cf9..7b912dfc 100644
--- a/locale/nl/translations.suml
+++ b/locale/nl/translations.suml
@@ -521,6 +521,11 @@ images:
error:
generic: 'Er is iets misgegaan, probeer het opnieuw…'
+# TODO
+captcha:
+ reason: 'Please prove you''re not a bot to mitigate spam and DDoS attacks.'
+ invalid: 'Invalid CAPTCHA, please try again'
+
mode:
dark: 'Donkere modus'
light: 'Lichte modus'
diff --git a/locale/pl/translations.suml b/locale/pl/translations.suml
index 0c471b36..e0009ea9 100644
--- a/locale/pl/translations.suml
+++ b/locale/pl/translations.suml
@@ -1149,6 +1149,10 @@ images:
error:
generic: 'Coś poszło nie tak, spróbuj ponownie…'
+captcha:
+ reason: 'Prosimy o udowodnienie, że nie jesteś botem, by chronić się przed spamem i DDoS-ami'
+ invalid: 'Nieprawidłowa CAPTCHA, spróbuj ponownie'
+
mode:
dark: 'Tryb nocny'
light: 'Tryb dzienny'
diff --git a/locale/pt/translations.suml b/locale/pt/translations.suml
index 2209e4db..fef7c1fc 100644
--- a/locale/pt/translations.suml
+++ b/locale/pt/translations.suml
@@ -536,6 +536,11 @@ images:
error:
generic: 'Alguma coisa deu errado, tente novamente'
+# TODO
+captcha:
+ reason: 'Please prove you''re not a bot to mitigate spam and DDoS attacks.'
+ invalid: 'Invalid CAPTCHA, please try again'
+
mode:
dark: 'Modo escuro'
light: 'Modo claro'
diff --git a/locale/ru/translations.suml b/locale/ru/translations.suml
index aa8c43c9..b3d828fe 100644
--- a/locale/ru/translations.suml
+++ b/locale/ru/translations.suml
@@ -1095,6 +1095,11 @@ images:
error:
generic: 'Coś poszło nie tak, spróbuj ponownie…'
+# TODO
+captcha:
+ reason: 'Please prove you''re not a bot to mitigate spam and DDoS attacks.'
+ invalid: 'Invalid CAPTCHA, please try again'
+
mode:
dark: 'Dark mode' # TODO
light: 'Light mode'
diff --git a/locale/yi/translations.suml b/locale/yi/translations.suml
index d791e089..2840be55 100644
--- a/locale/yi/translations.suml
+++ b/locale/yi/translations.suml
@@ -539,6 +539,11 @@ images:
error:
generic: 'Something went wrong, please try again…'
+# TODO
+captcha:
+ reason: 'Please prove you''re not a bot to mitigate spam and DDoS attacks.'
+ invalid: 'Invalid CAPTCHA, please try again'
+
mode:
dark: 'טונקלמאָדוס'
light: 'ליכטמאָדוס'
diff --git a/locale/zh/translations.suml b/locale/zh/translations.suml
index 1f73a5c2..64ff5dd6 100644
--- a/locale/zh/translations.suml
+++ b/locale/zh/translations.suml
@@ -506,6 +506,11 @@ images:
error:
generic: '出了點問題,請重試...'
+# TODO
+captcha:
+ reason: 'Please prove you''re not a bot to mitigate spam and DDoS attacks.'
+ invalid: 'Invalid CAPTCHA, please try again'
+
mode:
dark: '黑暗模式'
light: '明亮模式'
diff --git a/nuxt.config.js b/nuxt.config.js
index 4267cc99..38c6af60 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -153,6 +153,7 @@ export default {
FLAGS: buildFlags(),
BUCKET: `https://${process.env.AWS_S3_BUCKET}.s3-${process.env.AWS_REGION}.amazonaws.com`,
STATS_FILE: process.env.STATS_FILE,
+ HCAPTCHA_SITEKEY: process.env.HCAPTCHA_SITEKEY,
},
serverMiddleware: ['~/server/no-ssr.js', '~/server/index.js'],
axios: {
diff --git a/package.json b/package.json
index 6d3f5b44..99815063 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"mailer": "^0.6.7",
"markdown-loader": "^6.0.0",
"multer": "^1.4.2",
+ "node-fetch": "^2.6.1",
"nuxt": "^2.15.2",
"pageres": "^6.2.3",
"rtlcss": "^3.1.2",
diff --git a/server/captcha.js b/server/captcha.js
new file mode 100644
index 00000000..3b172e89
--- /dev/null
+++ b/server/captcha.js
@@ -0,0 +1,13 @@
+import fetch from 'node-fetch';
+
+export const validateCaptcha = async (token) => {
+ const res = await fetch('https://hcaptcha.com/siteverify', {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/x-www-form-urlencoded',
+ },
+ body: `response=${encodeURIComponent(token)}&secret=${encodeURIComponent(process.env.HCAPTCHA_SECRET)}`
+ });
+ const body = await res.json();
+ return body['success'];
+}
diff --git a/server/routes/user.js b/server/routes/user.js
index 6a1ef0a0..03b644b6 100644
--- a/server/routes/user.js
+++ b/server/routes/user.js
@@ -8,6 +8,7 @@ import { loadSuml } from '../loader';
import avatar from '../avatar';
import { config as socialLoginConfig, handlers as socialLoginHandlers } from '../social';
import cookieSettings from "../../src/cookieSettings";
+import {validateCaptcha} from "../captcha";
const config = loadSuml('config');
const translations = loadSuml('translations');
@@ -199,6 +200,10 @@ router.post('/user/init', handleErrorAsync(async (req, res) => {
return;
}
+ if (!await validateCaptcha(req.body.captchaToken)) {
+ return res.json({error: 'captcha.invalid'});
+ }
+
let user = undefined;
let usernameOrEmail = req.body.usernameOrEmail;