Merge 33c7752cfa
into 74c97efdef
This commit is contained in:
commit
6b065d1f3e
11 changed files with 73 additions and 8 deletions
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
@ -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"):
|
||||||
|
|
|
@ -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="")
|
||||||
|
|
Reference in a new issue