Use mod_register_json to register with prosody

(untested)
This commit is contained in:
Mathieu Pasquet 2021-04-18 16:22:35 +02:00
parent 9a51821e42
commit 44a56385f8
5 changed files with 113 additions and 24 deletions

View File

@ -2,6 +2,7 @@ from django import forms
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.core.validators import RegexValidator, URLValidator from django.core.validators import RegexValidator, URLValidator
from jabberfr.validators import validate_jid_userpart, validate_jid from jabberfr.validators import validate_jid_userpart, validate_jid
from jabberfr.utils import pick_captcha, validate_captcha
USERNAME_HELP = """ USERNAME_HELP = """
<ul> <ul>
@ -116,6 +117,13 @@ class InscriptionForm(forms.Form):
if choices: if choices:
self.fields['server'].widget.choices = choices self.fields['server'].widget.choices = choices
def clean(self):
cleaned_data = super().clean()
if cleaned_data['password'] != cleaned_data['confirm_password']:
self.add_error(
'confirm_password', 'Les mots de passe ne correspondent pas.'
)
class AdhesionForm(forms.Form): class AdhesionForm(forms.Form):
surname = forms.CharField( surname = forms.CharField(
@ -157,3 +165,9 @@ class AdhesionForm(forms.Form):
self.fields['captcha'].help_text = mark_safe( self.fields['captcha'].help_text = mark_safe(
f'\n<br/>Répondez à cette question: {question}' f'\n<br/>Répondez à cette question: {question}'
) )
def clean(self):
cleaned_data = super().clean()
if not validate_captcha(cleaned_data):
self.add_error('captcha', 'Êtes-vous un robot?')
self.reset_captcha(*pick_captcha())

View File

@ -117,6 +117,9 @@ STATICFILES_DIRS = [
BASE_DIR / 'static' BASE_DIR / 'static'
] ]
# Prosody mod_register_json token
REGISTRATION_TOKEN = 'CHANGEME'
# To override in local settings # To override in local settings
CAPTCHA_QUESTIONS = [ CAPTCHA_QUESTIONS = [
('1 + 1?', '2'), ('1 + 1?', '2'),

View File

@ -0,0 +1,9 @@
{% extends "page_model.html" %}
{% block content %}
<main>
<h2 class="center">Inscription à un serveur Jabber par JabberFR</h2>
<p>Votre compte <code>{{ jid }}</code> a été créé avec succès !</p>
<hr />
</main>
{% endblock %}

View File

@ -2,11 +2,16 @@ import logging
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from asyncio import gather, get_event_loop from asyncio import gather, get_event_loop
from random import randint from random import randint
from uuid import uuid4
from aiodns import DNSResolver from aiodns import DNSResolver
from aiohttp import ClientSession, ClientTimeout from aiohttp import ClientSession, ClientTimeout
from django import forms from aiohttp.web import (
HTTPConflict,
HTTPServiceUnavailable,
)
from django.conf import settings from django.conf import settings
from slixmpp import JID, InvalidJID
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -14,6 +19,9 @@ BASE_MUC_URL = 'http://[::1]:5280/muc_list/?'
DOMAINS_URL = 'https://jabberfr.org/domains.xml' DOMAINS_URL = 'https://jabberfr.org/domains.xml'
REGISTER_URL = 'http://[::1]:5280/register_account/'
VERIFY_URL = REGISTER_URL + 'verify'
async def get_chatrooms(*, limit: int = 25, order: str = 'users') -> str: async def get_chatrooms(*, limit: int = 25, order: str = 'users') -> str:
params = { params = {
@ -108,37 +116,76 @@ async def check_dns_config(data: dict) -> list[str]:
return errors return errors
def check_passwords(form: forms.Form) -> bool:
if form.cleaned_data['password'] != form.cleaned_data['confirm_password']:
form.add_error(
'confirm_password', 'Les mots de passe ne correspondent pas'
)
return False
return True
def pick_captcha() -> tuple[str, str]: def pick_captcha() -> tuple[str, str]:
choice = randint(0, len(settings.CAPTCHA_QUESTIONS) - 1) choice = randint(0, len(settings.CAPTCHA_QUESTIONS) - 1)
question = settings.CAPTCHA_QUESTIONS[choice][0] question = settings.CAPTCHA_QUESTIONS[choice][0]
return str(choice), question return str(choice), question
def validate_captcha(form: forms.Form) -> bool: def validate_captcha(data: dict) -> bool:
data = form.cleaned_data
print(data)
try: try:
captcha_id = data['captcha_id'] captcha_id = data['captcha_id']
if captcha_id < 0 or captcha_id >= len(settings.CAPTCHA_QUESTIONS): if captcha_id < 0 or captcha_id >= len(settings.CAPTCHA_QUESTIONS):
raise ValueError raise ValueError
except ValueError: except ValueError:
form.add_error('captcha', 'Êtes-vous un robot?')
return False return False
question_tup = settings.CAPTCHA_QUESTIONS[captcha_id] question_tup = settings.CAPTCHA_QUESTIONS[captcha_id]
if question_tup is None: if question_tup is None:
form.add_error('captcha', 'Êtes-vous un robot?')
return False return False
if question_tup[1] != data['captcha']: if question_tup[1] != data['captcha']:
form.add_error('captcha', 'Êtes-vous un robot?')
return False return False
return True return True
class RegistrationError(Exception):
def __init__(self, text):
super().__init__()
self.text = text
async def register_account(username: str, host: str, password: str, ip: str):
"""Register an account using prosodys mod_register_json"""
try:
jid = JID(f'{username}@{host}')
except InvalidJID:
return
headers = {'Host': jid.server}
# we dont care about the email feature
email = uuid4().hex + '@example.com'
payload = {
'username': jid.user,
'password': password,
'ip': ip,
'auth_token': settings.REGISTRATION_TOKEN,
'mail': email,
}
tmout = ClientTimeout(total=10)
async with ClientSession(raise_for_status=True, timeout=tmout) as session:
try:
async with session.post(REGISTER_URL, json=payload, headers=headers) as resp:
uid = await resp.text()
async with session.post(VERIFY_URL, data={'uid': uid}, headers=headers):
pass
except HTTPConflict:
raise RegistrationError(
'Ce nom dutilisateur nest pas disponible.'
)
except HTTPServiceUnavailable:
raise RegistrationError(
'Vous avez fait trop de demandes, veuillez revenir plus tard.'
)
except BaseException:
raise RegistrationError(
'Erreur inconnue. (serveur indisponible?)'
)
def get_client_ip(request) -> str:
"""Get a client IP"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip

View File

@ -6,9 +6,11 @@ from jabberfr.utils import (
get_chatrooms, get_chatrooms,
get_custom_domains, get_custom_domains,
check_dns_config, check_dns_config,
check_passwords,
pick_captcha, pick_captcha,
validate_captcha validate_captcha,
register_account,
RegistrationError,
get_client_ip,
) )
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -79,8 +81,24 @@ async def inscription(request, domain=None):
if request.method == 'POST': if request.method == 'POST':
form = InscriptionForm(request.POST, custom_choices=choices) form = InscriptionForm(request.POST, custom_choices=choices)
context['form'] = form context['form'] = form
if form.is_valid() and check_passwords(form): if form.is_valid():
pass try:
data = form.cleaned_data
await register_account(
data['username'],
data['server'],
data['password'],
get_client_ip(request),
)
except RegistrationError as exc:
form.add_error('__all__', exc.text)
else:
context['jid'] = data['username'] + '@' + data['server']
return render(
request,
'inscription_success.html',
context=context
)
else: else:
form = InscriptionForm(custom_choices=choices) form = InscriptionForm(custom_choices=choices)
context['form'] = form context['form'] = form
@ -92,10 +110,8 @@ async def adhesion(request):
if request.method == 'POST': if request.method == 'POST':
form = AdhesionForm(request.POST) form = AdhesionForm(request.POST)
context['form'] = form context['form'] = form
if form.is_valid() and validate_captcha(form): if form.is_valid():
return render(request, 'adhesion_success.html', context=context) return render(request, 'adhesion_success.html', context=context)
else:
form.reset_captcha(*pick_captcha())
else: else:
form = AdhesionForm(captcha=pick_captcha()) form = AdhesionForm(captcha=pick_captcha())
context['form'] = form context['form'] = form