From 3c6a3f1d30c860a5e93c7c891026b2c22c3665f1 Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 03:20:43 -0400 Subject: [PATCH 01/10] Render recaptcha on registration page --- client/html/index.htm | 1 + client/html/user_registration.tpl | 2 ++ client/js/views/registration_view.js | 12 ++++++++++++ 3 files changed, 15 insertions(+) diff --git a/client/html/index.htm b/client/html/index.htm index 00728903..34895d1e 100644 --- a/client/html/index.htm +++ b/client/html/index.htm @@ -22,6 +22,7 @@ +
diff --git a/client/html/user_registration.tpl b/client/html/user_registration.tpl index a6d291f4..fa0a5627 100644 --- a/client/html/user_registration.tpl +++ b/client/html/user_registration.tpl @@ -38,6 +38,8 @@
+
+
diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index 0a08de23..11b02e62 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -5,6 +5,7 @@ const api = require("../api.js"); const views = require("../util/views.js"); const template = views.getTemplate("user-registration"); +const RECAPTCHA_SITE_KEY = "site key"; class RegistrationView extends events.EventTarget { constructor() { @@ -20,6 +21,13 @@ class RegistrationView extends events.EventTarget { views.syncScrollPosition(); views.decorateValidator(this._formNode); this._formNode.addEventListener("submit", (e) => this._evtSubmit(e)); + this.renderRecaptcha(); + } + + renderRecaptcha() { + grecaptcha.render(this._recaptchaNode, { + "sitekey": RECAPTCHA_SITE_KEY + }); } clearMessages() { @@ -66,6 +74,10 @@ class RegistrationView extends events.EventTarget { get _emailFieldNode() { return this._formNode.querySelector("[name=email]"); } + + get _recaptchaNode() { + return this._formNode.querySelector("#recaptcha"); + } } module.exports = RegistrationView; From 82596e8ee91aa1a3ea3d258a512df011d5a7296c Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 03:29:23 -0400 Subject: [PATCH 02/10] Get recaptcha token via callback --- client/js/views/registration_view.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index 11b02e62..c14a13fe 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -21,15 +21,22 @@ class RegistrationView extends events.EventTarget { views.syncScrollPosition(); views.decorateValidator(this._formNode); this._formNode.addEventListener("submit", (e) => this._evtSubmit(e)); + this.setRecaptchaToken = this.setRecaptchaToken.bind(this); this.renderRecaptcha(); } renderRecaptcha() { grecaptcha.render(this._recaptchaNode, { - "sitekey": RECAPTCHA_SITE_KEY + "callback": this.setRecaptchaToken, + "sitekey": RECAPTCHA_SITE_KEY, }); } + setRecaptchaToken(token) { + console.log("Recaptcha token:", token); + this.recaptchaToken = token; + } + clearMessages() { views.clearMessages(this._hostNode); } @@ -54,6 +61,7 @@ class RegistrationView extends events.EventTarget { name: this._userNameFieldNode.value, password: this._passwordFieldNode.value, email: this._emailFieldNode.value, + recaptchaToken: this.recaptchaToken, }, }) ); From 58d2c273bb1c497408042c17c7de80dd260ca1ef Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 03:40:31 -0400 Subject: [PATCH 03/10] Only show recaptcha for anonymous users, send token to API --- .../js/controllers/user_registration_controller.js | 2 +- client/js/models/user.js | 13 ++++++++----- client/js/views/registration_view.js | 5 ++++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/client/js/controllers/user_registration_controller.js b/client/js/controllers/user_registration_controller.js index 89cfd8cd..72a52b5e 100644 --- a/client/js/controllers/user_registration_controller.js +++ b/client/js/controllers/user_registration_controller.js @@ -30,7 +30,7 @@ class UserRegistrationController { user.email = e.detail.email; user.password = e.detail.password; const isLoggedIn = api.isLoggedIn(); - user.save() + user.save(e.detail.recaptchaToken) .then(() => { if (isLoggedIn) { return Promise.resolve(); diff --git a/client/js/models/user.js b/client/js/models/user.js index 28dc3efe..e682f4b4 100644 --- a/client/js/models/user.js +++ b/client/js/models/user.js @@ -107,7 +107,7 @@ class User extends events.EventTarget { }); } - save() { + save(recaptchaToken) { const files = []; const detail = { version: this._version }; const transient = this._orig._name; @@ -131,13 +131,16 @@ class User extends events.EventTarget { if (this._password) { detail.password = this._password; } + if (!api.isLoggedIn()) { + detail.recaptchaToken = recaptchaToken; + } let promise = this._orig._name ? api.put( - uri.formatApiLink("user", this._orig._name), - detail, - files - ) + uri.formatApiLink("user", this._orig._name), + detail, + files + ) : api.post(uri.formatApiLink("users"), detail, files); return promise.then((response) => { diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index c14a13fe..f2bbcb7d 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -22,7 +22,10 @@ class RegistrationView extends events.EventTarget { views.decorateValidator(this._formNode); this._formNode.addEventListener("submit", (e) => this._evtSubmit(e)); this.setRecaptchaToken = this.setRecaptchaToken.bind(this); - this.renderRecaptcha(); + + // Show the recaptcha for anonymous users. + if (!api.isLoggedIn()) + this.renderRecaptcha(); } renderRecaptcha() { From 882cb91ae00b311e673b253db584e8f9772897d7 Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 04:03:41 -0400 Subject: [PATCH 04/10] Check recaptcha response from google API --- server/szurubooru/api/user_api.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/server/szurubooru/api/user_api.py b/server/szurubooru/api/user_api.py index a6196cb8..ee555117 100644 --- a/server/szurubooru/api/user_api.py +++ b/server/szurubooru/api/user_api.py @@ -1,6 +1,8 @@ from typing import Any, Dict -from szurubooru import model, rest, search +import requests + +from szurubooru import config, model, rest, search from szurubooru.func import auth, serialization, users, versions _search_executor = search.Executor(search.configs.UserSearchConfig()) @@ -31,11 +33,28 @@ def get_users( def create_user( ctx: rest.Context, _params: Dict[str, str] = {} ) -> rest.Response: + expect_recaptcha = False + if ctx.user.user_id is None: + expect_recaptcha = True auth.verify_privilege(ctx.user, "users:create:self") else: auth.verify_privilege(ctx.user, "users:create:any") + # Verify if the recaptcha was correct. + if expect_recaptcha: + resp = requests.post("https://www.google.com/recaptcha/api/siteverify", data={ + "secret": config.config["recaptcha_secret"], + "response": ctx.get_param_as_string("recaptchaToken", default=""), + }) + + # Raise a 400 error if the recaptcha wasn't OK. + if not resp.json()["success"]: + raise rest.errors.HttpBadRequest( + "ValidationError", + "Recaptcha response was invalid." + ) + name = ctx.get_param_as_string("name") password = ctx.get_param_as_string("password") email = ctx.get_param_as_string("email", default="") From 3ecb61909fefe2f27f7329d8b8df86dba79f0477 Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 04:03:57 -0400 Subject: [PATCH 05/10] Add recaptcha_secret to config --- server/config.yaml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/server/config.yaml.dist b/server/config.yaml.dist index 937c1387..eec7d7fe 100644 --- a/server/config.yaml.dist +++ b/server/config.yaml.dist @@ -7,6 +7,7 @@ name: szurubooru domain: # example: http://example.com # used to salt the users' password hashes and generate filenames for static content secret: change +recaptcha_secret: change # Delete thumbnails and source files on post delete # Original functionality is no, to mitigate the impacts of admins going From a36e228fc3ec9c04fb024ce7ce5a85939f71b5d0 Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 04:12:57 -0400 Subject: [PATCH 06/10] Install requests library --- server/Dockerfile | 2 +- server/requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/server/Dockerfile b/server/Dockerfile index 50cd700c..dfdb8176 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -24,7 +24,7 @@ RUN \ alembic \ "coloredlogs==5.0" \ youtube-dl \ - && apk --no-cache del py3-pip + requests ARG PUID=1000 ARG PGID=1000 diff --git a/server/requirements.txt b/server/requirements.txt index d80ec060..f176d659 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -10,3 +10,4 @@ pynacl>=1.2.1 pytz>=2018.3 pyRFC3339>=1.0 youtube-dl +requests \ No newline at end of file From b771dd6791cab4a2bd00ac400d00663c54310b7c Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 04:38:47 -0400 Subject: [PATCH 07/10] Add config for enabling/disabling recaptcha --- client/html/user_registration.tpl | 3 +-- client/js/api.js | 4 ++++ client/js/views/registration_view.js | 9 ++++++--- server/config.yaml.dist | 7 +++++++ server/szurubooru/api/info_api.py | 1 + server/szurubooru/api/user_api.py | 2 +- 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/client/html/user_registration.tpl b/client/html/user_registration.tpl index fa0a5627..d2b008f2 100644 --- a/client/html/user_registration.tpl +++ b/client/html/user_registration.tpl @@ -38,8 +38,7 @@
-
-
+ <% if(ctx.enableRecaptcha) print(`

`); %>
diff --git a/client/js/api.js b/client/js/api.js index 5bde6d81..a6bec97f 100644 --- a/client/js/api.js +++ b/client/js/api.js @@ -108,6 +108,10 @@ class Api extends events.EventTarget { return !!remoteConfig.enableSafety; } + recaptchaEnabled() { + return !!remoteConfig.enableRecaptcha; + } + hasPrivilege(lookup) { let minViableRank = null; for (let p of Object.keys(remoteConfig.privileges)) { diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index f2bbcb7d..4e29a07d 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -10,12 +10,17 @@ const RECAPTCHA_SITE_KEY = "site key"; class RegistrationView extends events.EventTarget { constructor() { super(); + + // Show the recaptcha only for anonymous users. + const showRecaptcha = (!api.isLoggedIn() && api.recaptchaEnabled()); + this._hostNode = document.getElementById("content-holder"); views.replaceContent( this._hostNode, template({ userNamePattern: api.getUserNameRegex(), passwordPattern: api.getPasswordRegex(), + enableRecaptcha: showRecaptcha, }) ); views.syncScrollPosition(); @@ -23,8 +28,7 @@ class RegistrationView extends events.EventTarget { this._formNode.addEventListener("submit", (e) => this._evtSubmit(e)); this.setRecaptchaToken = this.setRecaptchaToken.bind(this); - // Show the recaptcha for anonymous users. - if (!api.isLoggedIn()) + if (showRecaptcha) this.renderRecaptcha(); } @@ -36,7 +40,6 @@ class RegistrationView extends events.EventTarget { } setRecaptchaToken(token) { - console.log("Recaptcha token:", token); this.recaptchaToken = token; } diff --git a/server/config.yaml.dist b/server/config.yaml.dist index eec7d7fe..5758922e 100644 --- a/server/config.yaml.dist +++ b/server/config.yaml.dist @@ -7,6 +7,13 @@ name: szurubooru domain: # example: http://example.com # used to salt the users' password hashes and generate filenames for static content secret: change + +# Whether solving a captcha is required for registration for anonymous users. +enable_recaptcha: no + +# A reCAPTCHA v2 secret token. +# https://developers.google.com/recaptcha/intro +# https://developers.google.com/recaptcha/docs/display recaptcha_secret: change # Delete thumbnails and source files on post delete diff --git a/server/szurubooru/api/info_api.py b/server/szurubooru/api/info_api.py index 757b09cf..5574270c 100644 --- a/server/szurubooru/api/info_api.py +++ b/server/szurubooru/api/info_api.py @@ -49,6 +49,7 @@ def get_info(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response: "privileges": util.snake_case_to_lower_camel_case_keys( config.config["privileges"] ), + "enableRecaptcha": config.config["enable_recaptcha"], }, } if auth.has_privilege(ctx.user, "posts:view:featured"): diff --git a/server/szurubooru/api/user_api.py b/server/szurubooru/api/user_api.py index ee555117..588bcf6c 100644 --- a/server/szurubooru/api/user_api.py +++ b/server/szurubooru/api/user_api.py @@ -42,7 +42,7 @@ def create_user( auth.verify_privilege(ctx.user, "users:create:any") # Verify if the recaptcha was correct. - if expect_recaptcha: + if expect_recaptcha and config.config["enable_recaptcha"]: resp = requests.post("https://www.google.com/recaptcha/api/siteverify", data={ "secret": config.config["recaptcha_secret"], "response": ctx.get_param_as_string("recaptchaToken", default=""), From 293c42113c3fa64ade813f7d2a6815b7564be124 Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 04:56:23 -0400 Subject: [PATCH 08/10] Add recaptcha site key config --- client/js/api.js | 4 ++++ client/js/views/registration_view.js | 3 +-- server/config.yaml.dist | 3 ++- server/szurubooru/api/info_api.py | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client/js/api.js b/client/js/api.js index a6bec97f..21ec235d 100644 --- a/client/js/api.js +++ b/client/js/api.js @@ -100,6 +100,10 @@ class Api extends events.EventTarget { return remoteConfig.contactEmail; } + getRecaptchaSiteKey() { + return remoteConfig.recaptchaSiteKey; + } + canSendMails() { return !!remoteConfig.canSendMails; } diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index 4e29a07d..4870c871 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -5,7 +5,6 @@ const api = require("../api.js"); const views = require("../util/views.js"); const template = views.getTemplate("user-registration"); -const RECAPTCHA_SITE_KEY = "site key"; class RegistrationView extends events.EventTarget { constructor() { @@ -35,7 +34,7 @@ class RegistrationView extends events.EventTarget { renderRecaptcha() { grecaptcha.render(this._recaptchaNode, { "callback": this.setRecaptchaToken, - "sitekey": RECAPTCHA_SITE_KEY, + "sitekey": api.getRecaptchaSiteKey(), }); } diff --git a/server/config.yaml.dist b/server/config.yaml.dist index 5758922e..b28710ec 100644 --- a/server/config.yaml.dist +++ b/server/config.yaml.dist @@ -10,7 +10,8 @@ secret: change # Whether solving a captcha is required for registration for anonymous users. enable_recaptcha: no - +# The reCAPTCHA site key. +recaptcha_site_key: change # A reCAPTCHA v2 secret token. # https://developers.google.com/recaptcha/intro # https://developers.google.com/recaptcha/docs/display diff --git a/server/szurubooru/api/info_api.py b/server/szurubooru/api/info_api.py index 5574270c..667b2c5d 100644 --- a/server/szurubooru/api/info_api.py +++ b/server/szurubooru/api/info_api.py @@ -50,6 +50,7 @@ def get_info(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response: config.config["privileges"] ), "enableRecaptcha": config.config["enable_recaptcha"], + "recaptchaSiteKey": config.config["recaptcha_site_key"], }, } if auth.has_privilege(ctx.user, "posts:view:featured"): From 6e178e2eed061e6048b2a104616d4ab35c499b47 Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 05:08:54 -0400 Subject: [PATCH 09/10] Refactor recaptchaEnabled --- client/js/api.js | 2 +- client/js/models/user.js | 2 +- client/js/views/registration_view.js | 6 +----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/client/js/api.js b/client/js/api.js index 21ec235d..2df1fdf1 100644 --- a/client/js/api.js +++ b/client/js/api.js @@ -113,7 +113,7 @@ class Api extends events.EventTarget { } recaptchaEnabled() { - return !!remoteConfig.enableRecaptcha; + return !this.isLoggedIn() && !!remoteConfig.enableRecaptcha; } hasPrivilege(lookup) { diff --git a/client/js/models/user.js b/client/js/models/user.js index e682f4b4..5efbf3ec 100644 --- a/client/js/models/user.js +++ b/client/js/models/user.js @@ -131,7 +131,7 @@ class User extends events.EventTarget { if (this._password) { detail.password = this._password; } - if (!api.isLoggedIn()) { + if (api.recaptchaEnabled) { detail.recaptchaToken = recaptchaToken; } diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index 4870c871..898d606c 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -9,17 +9,13 @@ const template = views.getTemplate("user-registration"); class RegistrationView extends events.EventTarget { constructor() { super(); - - // Show the recaptcha only for anonymous users. - const showRecaptcha = (!api.isLoggedIn() && api.recaptchaEnabled()); - this._hostNode = document.getElementById("content-holder"); views.replaceContent( this._hostNode, template({ userNamePattern: api.getUserNameRegex(), passwordPattern: api.getPasswordRegex(), - enableRecaptcha: showRecaptcha, + enableRecaptcha: api.recaptchaEnabled(), }) ); views.syncScrollPosition(); From 33c7752cfac9743143d856999208b214445a18da Mon Sep 17 00:00:00 2001 From: Jesse <2302541+Kangaroux@users.noreply.github.com> Date: Wed, 8 Jul 2020 13:46:50 -0400 Subject: [PATCH 10/10] Fix recaptcha check --- client/js/views/registration_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index 898d606c..5c495b30 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -23,7 +23,7 @@ class RegistrationView extends events.EventTarget { this._formNode.addEventListener("submit", (e) => this._evtSubmit(e)); this.setRecaptchaToken = this.setRecaptchaToken.bind(this); - if (showRecaptcha) + if (api.recaptchaEnabled()) this.renderRecaptcha(); }