This commit is contained in:
Jessie 2020-08-22 17:43:39 -04:00 committed by GitHub
commit 6b065d1f3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 73 additions and 8 deletions

View file

@ -22,6 +22,7 @@
<link rel='apple-touch-startup-image' href='img/apple-touch-startup-image-1668x2224.png' media='(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)'/> <link rel='apple-touch-startup-image' href='img/apple-touch-startup-image-1668x2224.png' media='(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)'/>
<link rel='apple-touch-startup-image' href='img/apple-touch-startup-image-2048x2732.png' media='(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)'/> <link rel='apple-touch-startup-image' href='img/apple-touch-startup-image-2048x2732.png' media='(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)'/>
<link rel='manifest' href='manifest.json'/> <link rel='manifest' href='manifest.json'/>
<script src="https://www.google.com/recaptcha/api.js"></script>
</head> </head>
<body> <body>
<div id='top-navigation-holder'></div> <div id='top-navigation-holder'></div>

View file

@ -38,6 +38,7 @@
<div class='messages'></div> <div class='messages'></div>
<div class='buttons'> <div class='buttons'>
<% if(ctx.enableRecaptcha) print(`<div id="recaptcha"></div><br>`); %>
<input type='submit' value='Create an account'/> <input type='submit' value='Create an account'/>
</div> </div>
</form> </form>

View file

@ -100,6 +100,10 @@ class Api extends events.EventTarget {
return remoteConfig.contactEmail; return remoteConfig.contactEmail;
} }
getRecaptchaSiteKey() {
return remoteConfig.recaptchaSiteKey;
}
canSendMails() { canSendMails() {
return !!remoteConfig.canSendMails; return !!remoteConfig.canSendMails;
} }
@ -108,6 +112,10 @@ class Api extends events.EventTarget {
return !!remoteConfig.enableSafety; return !!remoteConfig.enableSafety;
} }
recaptchaEnabled() {
return !this.isLoggedIn() && !!remoteConfig.enableRecaptcha;
}
hasPrivilege(lookup) { hasPrivilege(lookup) {
let minViableRank = null; let minViableRank = null;
for (let p of Object.keys(remoteConfig.privileges)) { for (let p of Object.keys(remoteConfig.privileges)) {

View file

@ -30,7 +30,7 @@ class UserRegistrationController {
user.email = e.detail.email; user.email = e.detail.email;
user.password = e.detail.password; user.password = e.detail.password;
const isLoggedIn = api.isLoggedIn(); const isLoggedIn = api.isLoggedIn();
user.save() user.save(e.detail.recaptchaToken)
.then(() => { .then(() => {
if (isLoggedIn) { if (isLoggedIn) {
return Promise.resolve(); return Promise.resolve();

View file

@ -107,7 +107,7 @@ class User extends events.EventTarget {
}); });
} }
save() { save(recaptchaToken) {
const files = []; const files = [];
const detail = { version: this._version }; const detail = { version: this._version };
const transient = this._orig._name; const transient = this._orig._name;
@ -131,13 +131,16 @@ class User extends events.EventTarget {
if (this._password) { if (this._password) {
detail.password = this._password; detail.password = this._password;
} }
if (api.recaptchaEnabled) {
detail.recaptchaToken = recaptchaToken;
}
let promise = this._orig._name let promise = this._orig._name
? api.put( ? api.put(
uri.formatApiLink("user", this._orig._name), uri.formatApiLink("user", this._orig._name),
detail, detail,
files files
) )
: api.post(uri.formatApiLink("users"), detail, files); : api.post(uri.formatApiLink("users"), detail, files);
return promise.then((response) => { return promise.then((response) => {

View file

@ -15,11 +15,27 @@ class RegistrationView extends events.EventTarget {
template({ template({
userNamePattern: api.getUserNameRegex(), userNamePattern: api.getUserNameRegex(),
passwordPattern: api.getPasswordRegex(), passwordPattern: api.getPasswordRegex(),
enableRecaptcha: api.recaptchaEnabled(),
}) })
); );
views.syncScrollPosition(); views.syncScrollPosition();
views.decorateValidator(this._formNode); views.decorateValidator(this._formNode);
this._formNode.addEventListener("submit", (e) => this._evtSubmit(e)); this._formNode.addEventListener("submit", (e) => this._evtSubmit(e));
this.setRecaptchaToken = this.setRecaptchaToken.bind(this);
if (api.recaptchaEnabled())
this.renderRecaptcha();
}
renderRecaptcha() {
grecaptcha.render(this._recaptchaNode, {
"callback": this.setRecaptchaToken,
"sitekey": api.getRecaptchaSiteKey(),
});
}
setRecaptchaToken(token) {
this.recaptchaToken = token;
} }
clearMessages() { clearMessages() {
@ -46,6 +62,7 @@ class RegistrationView extends events.EventTarget {
name: this._userNameFieldNode.value, name: this._userNameFieldNode.value,
password: this._passwordFieldNode.value, password: this._passwordFieldNode.value,
email: this._emailFieldNode.value, email: this._emailFieldNode.value,
recaptchaToken: this.recaptchaToken,
}, },
}) })
); );
@ -66,6 +83,10 @@ class RegistrationView extends events.EventTarget {
get _emailFieldNode() { get _emailFieldNode() {
return this._formNode.querySelector("[name=email]"); return this._formNode.querySelector("[name=email]");
} }
get _recaptchaNode() {
return this._formNode.querySelector("#recaptcha");
}
} }
module.exports = RegistrationView; module.exports = RegistrationView;

View file

@ -24,7 +24,7 @@ RUN \
alembic \ alembic \
"coloredlogs==5.0" \ "coloredlogs==5.0" \
youtube-dl \ youtube-dl \
&& apk --no-cache del py3-pip requests
ARG PUID=1000 ARG PUID=1000
ARG PGID=1000 ARG PGID=1000

View file

@ -8,6 +8,15 @@ domain: # example: http://example.com
# used to salt the users' password hashes and generate filenames for static content # used to salt the users' password hashes and generate filenames for static content
secret: change 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
recaptcha_secret: change
# Delete thumbnails and source files on post delete # Delete thumbnails and source files on post delete
# Original functionality is no, to mitigate the impacts of admins going # Original functionality is no, to mitigate the impacts of admins going
# on unchecked post purges. # on unchecked post purges.

View file

@ -10,3 +10,4 @@ pynacl>=1.2.1
pytz>=2018.3 pytz>=2018.3
pyRFC3339>=1.0 pyRFC3339>=1.0
youtube-dl youtube-dl
requests

View file

@ -49,6 +49,8 @@ def get_info(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response:
"privileges": util.snake_case_to_lower_camel_case_keys( "privileges": util.snake_case_to_lower_camel_case_keys(
config.config["privileges"] config.config["privileges"]
), ),
"enableRecaptcha": config.config["enable_recaptcha"],
"recaptchaSiteKey": config.config["recaptcha_site_key"],
}, },
} }
if auth.has_privilege(ctx.user, "posts:view:featured"): if auth.has_privilege(ctx.user, "posts:view:featured"):

View file

@ -1,6 +1,8 @@
from typing import Any, Dict 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 from szurubooru.func import auth, serialization, users, versions
_search_executor = search.Executor(search.configs.UserSearchConfig()) _search_executor = search.Executor(search.configs.UserSearchConfig())
@ -31,11 +33,28 @@ def get_users(
def create_user( def create_user(
ctx: rest.Context, _params: Dict[str, str] = {} ctx: rest.Context, _params: Dict[str, str] = {}
) -> rest.Response: ) -> rest.Response:
expect_recaptcha = False
if ctx.user.user_id is None: if ctx.user.user_id is None:
expect_recaptcha = True
auth.verify_privilege(ctx.user, "users:create:self") auth.verify_privilege(ctx.user, "users:create:self")
else: else:
auth.verify_privilege(ctx.user, "users:create:any") auth.verify_privilege(ctx.user, "users:create:any")
# Verify if the recaptcha was correct.
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=""),
})
# 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") name = ctx.get_param_as_string("name")
password = ctx.get_param_as_string("password") password = ctx.get_param_as_string("password")
email = ctx.get_param_as_string("email", default="") email = ctx.get_param_as_string("email", default="")