mirror of
https://git.anonymousland.org/anonymousland/synapse-captcha.git
synced 2024-10-01 08:25:43 -04:00
195 lines
6.6 KiB
Python
195 lines
6.6 KiB
Python
# Standard library imports...
|
|
import logging
|
|
from requests import exceptions
|
|
import re
|
|
from urllib.parse import urlparse
|
|
|
|
# Third-party imports...
|
|
from flask import (Blueprint, abort, jsonify, request, make_response,
|
|
render_template)
|
|
from wtforms import (Form, StringField, PasswordField, validators)
|
|
from wtforms.fields.simple import HiddenField
|
|
|
|
# Local imports...
|
|
from .matrix_api import create_account
|
|
from . import config
|
|
from . import captcha
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
api = Blueprint("api", __name__)
|
|
|
|
re_mxid = re.compile(r'^@?[a-zA-Z_\-=\.\/0-9]+(:[a-zA-Z\-\.:\/0-9]+)?$')
|
|
|
|
|
|
def validate_captcha(form, captcha_answer):
|
|
"""
|
|
validates captcha
|
|
Parameters
|
|
----------
|
|
arg1 : Form object
|
|
arg2 : str
|
|
captcha answer, e.g. '4tg'
|
|
Raises
|
|
-------
|
|
ValidationError
|
|
captcha is invalid
|
|
"""
|
|
if not captcha.captcha.validate(captcha_answer.data,
|
|
form.captcha_token.data):
|
|
raise validators.ValidationError("captcha is invalid")
|
|
|
|
|
|
def validate_username(form, username):
|
|
"""
|
|
validates username
|
|
|
|
Parameters
|
|
----------
|
|
arg1 : Form object
|
|
arg2 : str
|
|
username name, e.g: '@user:matrix.org' or 'user'
|
|
https://github.com/matrix-org/matrix-doc/blob/master/specification/appendices/identifier_grammar.rst#user-identifiers
|
|
Raises
|
|
-------
|
|
ValidationError
|
|
Username doesn't follow mxid requirements
|
|
"""
|
|
domain = urlparse(config.config.server_location).hostname
|
|
re_mxid = r'^@?[a-zA-Z_\-=\.\/0-9]+(:' + \
|
|
re.escape(domain) + \
|
|
r')?$'
|
|
err = "Username doesn't follow pattern: '%s'" % re_mxid
|
|
if not re.search(re_mxid, username.data):
|
|
raise validators.ValidationError(err)
|
|
|
|
|
|
def validate_password(form, password):
|
|
"""
|
|
validates username
|
|
|
|
Parameters
|
|
----------
|
|
arg1 : Form object
|
|
arg2 : str
|
|
password
|
|
Raises
|
|
-------
|
|
ValidationError
|
|
Password doesn't follow length requirements
|
|
"""
|
|
min_length = config.config.password['min_length']
|
|
err = 'Password should be between %s and 255 chars long' % min_length
|
|
if len(password.data) < min_length or len(password.data) > 255:
|
|
raise validators.ValidationError(err)
|
|
|
|
|
|
class RegistrationForm(Form):
|
|
"""
|
|
Registration Form
|
|
|
|
validates user account registration requests
|
|
"""
|
|
username = StringField(
|
|
'Username',
|
|
[
|
|
validators.Length(min=1, max=200),
|
|
# validators.Regexp(re_mxid)
|
|
validate_username
|
|
])
|
|
password = PasswordField(
|
|
'New Password',
|
|
[
|
|
# validators.Length(min=8),
|
|
validate_password,
|
|
validators.DataRequired(),
|
|
validators.EqualTo('confirm', message='Passwords must match')
|
|
])
|
|
confirm = PasswordField('Repeat Password')
|
|
captcha_answer = StringField("Captcha answer", [validate_captcha])
|
|
captcha_token = HiddenField("Captcha token")
|
|
|
|
|
|
@api.route('/register', methods=['GET', 'POST'])
|
|
def register():
|
|
"""
|
|
main user account registration endpoint
|
|
to register an account you need to send a
|
|
application/x-www-form-urlencoded request with
|
|
- username
|
|
- password
|
|
- confirm
|
|
- captcha_answer
|
|
- captcha_token
|
|
as described in the RegistrationForm
|
|
"""
|
|
if request.method == 'POST':
|
|
logger.debug('an account registration started...')
|
|
form = RegistrationForm(request.form)
|
|
logger.debug('validating request data...')
|
|
if form.validate():
|
|
logger.debug('request valid')
|
|
# remove sigil and the domain from the username
|
|
username = form.username.data.rsplit(':')[0].split('@')[-1]
|
|
logger.debug('creating account %s...' % username)
|
|
# send account creation request to the hs
|
|
try:
|
|
account_data = create_account(form.username.data,
|
|
form.password.data,
|
|
config.config.server_location,
|
|
config.config.shared_secret)
|
|
except exceptions.ConnectionError:
|
|
logger.error('can not connect to %s' %
|
|
config.config.server_location,
|
|
exc_info=True)
|
|
abort(500)
|
|
except exceptions.HTTPError as e:
|
|
resp = e.response
|
|
error = resp.json()
|
|
status_code = resp.status_code
|
|
if status_code == 404:
|
|
logger.error('no HS found at %s' %
|
|
config.config.server_location)
|
|
elif status_code == 403:
|
|
logger.error(
|
|
'wrong shared registration secret or not enabled')
|
|
elif status_code == 400:
|
|
# most likely this should only be triggered if a userid
|
|
# is already in use
|
|
return make_response(jsonify(error), 400)
|
|
else:
|
|
logger.error('failure communicating with HS',
|
|
exc_info=True)
|
|
abort(500)
|
|
logger.debug('account creation succeded!')
|
|
return jsonify(access_token=account_data['access_token'],
|
|
home_server=account_data['home_server'],
|
|
user_id=account_data['user_id'],
|
|
status='success',
|
|
status_code=200)
|
|
else:
|
|
logger.debug('account creation failed!')
|
|
captcha_data = captcha.captcha.generate()
|
|
resp = {
|
|
'errcode': 'MR_BAD_USER_REQUEST',
|
|
'error': form.errors,
|
|
"captcha_image": captcha_data["captcha_image"].decode(),
|
|
"captcha_token": captcha_data["captcha_token"]
|
|
}
|
|
return make_response(jsonify(resp), 400)
|
|
# for fieldName, errorMessages in form.errors.items():
|
|
# for err in errorMessages:
|
|
# # return error to user
|
|
else:
|
|
server_name = config.config.server_name
|
|
pw_length = config.config.password['min_length']
|
|
captcha_data = captcha.captcha.generate()
|
|
return render_template(
|
|
'register.html',
|
|
server_name=server_name,
|
|
pw_length=pw_length,
|
|
riot_instance=config.config.riot_instance,
|
|
base_url=config.config.base_url,
|
|
captcha_token=captcha_data["captcha_token"],
|
|
captcha_image=captcha_data["captcha_image"].decode())
|