Skip to content
Snippets Groups Projects

Registrace

Merged Martin Mareš requested to merge mj/registrace into devel
1 file
+ 57
49
Compare changes
  • Side-by-side
  • Inline
+ 57
49
@@ -183,11 +183,14 @@ def reset():
class RegStatus(Enum):
new = auto()
ok = auto()
expired = auto()
already_exists = auto()
# Jen v 1. kroku:
rate_limited = auto()
ok = auto()
wrong_captcha = auto()
# Jen v 2. kroku:
already_spent = auto()
already_exists = auto()
class Reg1:
@@ -195,21 +198,21 @@ class Reg1:
seed: str
status: RegStatus
email_token: str
x: int
y: int
def __init__(self, from_token: Optional[str] = None):
self.status = self._parse_token(from_token)
if self.status != RegStatus.ok:
if self.status == RegStatus.ok:
self._gen_captcha()
else:
self._reset()
def _reset(self):
self.create_time = mo.now
self.seed = secrets.token_hex(16)
app.logger.debug(f'Reg1: Nový token: {self.create_time} {self.seed}')
def _init_rng(self) -> random.Random:
rng = random.Random()
rng.seed(mo.tokens.hash('rng-init', self.seed))
return rng
app.logger.debug(f'Reg1: Nový token: seed={self.seed}')
self._gen_captcha()
def as_token(self) -> str:
return mo.tokens.sign_token([str(int(self.create_time.timestamp())), self.seed], 'reg1')
@@ -234,27 +237,29 @@ class Reg1:
return RegStatus.ok
def _captcha(self) -> Tuple[int, int]:
def _init_rng(self) -> random.Random:
rng = random.Random()
rng.seed(mo.tokens.hash('rng-init', self.seed))
return rng
def _gen_captcha(self):
rng = self._init_rng()
x = rng.randrange(1, 10)
y = rng.randrange(1, 10)
return x, y
self.x = rng.randrange(1, 10)
self.y = rng.randrange(1, 20)
def captcha_task(self) -> str:
x, y = self._captcha()
cisla = ['nula', 'jedna', 'dva', 'tři', 'čtyři', 'pět', 'šest', 'sedm', 'osm', 'devět',
'deset', 'jedenáct', 'dvanáct', 'třináct', 'čtrnáct', 'patnáct', 'šestnáct', 'sedmnáct', 'osmnáct', 'devatenáct']
app.logger.debug(f'Reg1: Captcha: {x}*{y}')
return f'Napiš číslem, kolik je {cisla[x]} krát {cisla[y]}.'
app.logger.debug(f'Reg1: Captcha: {self.x}*{self.y}')
return f'Napiš číslem, kolik je {cisla[self.x]} krát {cisla[self.y]}.'
def captcha_check_answer(self, answer: str) -> bool:
x, y = self._captcha()
correct = x * y
correct = self.x * self.y
if answer == str(correct):
app.logger.debug(f'Reg1: Captcha: {x}*{y}={answer} správně')
app.logger.debug(f'Reg1: Captcha: {self.x}*{self.y}={answer} správně')
return True
else:
app.logger.debug(f'Reg1: Captcha: {x}*{y}={answer} špatně')
app.logger.debug(f'Reg1: Captcha: {self.x}*{self.y}={answer} špatně')
return False
def create_reg_request(self, email: str) -> bool:
@@ -279,17 +284,24 @@ class Reg1:
rr.captcha_token = self.seed
sess.add(rr)
sess.commit()
app.logger.debug(f'Reg1: E-mailový token {self.email_token}')
return True
def check_email_free(self, email: str) -> bool:
if not mo.users.user_by_email(email):
return True
self._reset()
self.status = RegStatus.already_exists
app.logger.info(f'Reg1: Účet s e-mailem {email} už existuje')
return False
def process(self, email: str, captcha: str):
# XXX: Nejdříve zapisujeme registraci do DB, a teprve pak ověřujeme captchu.
# Tímto způsobem je těžší captchu obejít (protože je rate-limitovaná), ale
# zase je snazší páchat DoS útok na celou registraci (protože je rate-limitovaná).
if not self.create_reg_request(email):
return
elif not self.captcha_check_answer(captcha):
self._reset()
self.status = RegStatus.wrong_captcha
elif mo.users.user_by_email(email):
self._reset()
self.status = RegStatus.already_exists
app.logger.info(f'Reg1: Účet s e-mailem {email} už existuje')
class Reg1Form(FlaskForm):
@@ -298,33 +310,24 @@ class Reg1Form(FlaskForm):
captcha = wtforms.StringField('Kontrola', validators=[validators.DataRequired()])
submit = wtforms.SubmitField('Vytvořit účet')
_reg1: Reg1
def validate_email(form, field):
try:
field.data = mo.users.normalize_email(field.data)
except mo.CheckError as e:
raise wtforms.ValidationError(str(e))
def validate_captcha(self, field):
c = self._reg1
if not c.captcha_check_answer(field.data):
raise ValidationError('Chybný výsledek: ' + c.captcha_task())
@app.route('/auth/create', methods=('GET', 'POST'))
def create_acct():
form = Reg1Form()
reg1 = Reg1(form.token.data)
form._reg1 = reg1
if reg1.status == RegStatus.ok and form.validate_on_submit():
# XXX: Existenci e-mailu chceme kontrolovat až po spotřebování captchi na reg_request
if reg1.create_reg_request(form.email.data) and reg1.check_email_free(form.email.data):
flash('Odeslán e-mail s odkazem na založení účtu', 'success')
user = db.User(email=form.email.data, first_name='Nový', last_name='Uživatel')
mo.util.send_confirm_create_email(user, reg1.email_token)
return redirect(url_for('confirm_email'))
if reg1.status == RegStatus.ok and form.validate_on_submit() and reg1.process(form.email.data, form.captcha.data):
app.logger.debug(f'Reg1: E-mailový token {reg1.email_token}')
flash('Odeslán e-mail s odkazem na založení účtu', 'success')
user = db.User(email=form.email.data, first_name='Nový', last_name='Uživatel')
mo.util.send_confirm_create_email(user, reg1.email_token)
return redirect(url_for('confirm_email'))
form.captcha.description = reg1.captcha_task()
if reg1.status != RegStatus.ok:
@@ -336,7 +339,9 @@ def create_acct():
elif reg1.status == RegStatus.rate_limited:
flash('Přichází příliš mnoho registrací najednou, zkuste to prosím za chvíli znovu.', 'danger')
elif reg1.status == RegStatus.already_exists:
form.email.errors.append('Účet s touto adresou už existuje')
form.email.errors.append('Účet s touto adresou už existuje.')
elif reg1.status == RegStatus.wrong_captcha:
form.captcha.errors.append('Chybný výsledek. Zkuste to znovu: ' + reg1.captcha_task())
return render_template('acct_reg1.html', form=form)
@@ -447,11 +452,14 @@ def confirm_email():
return render_template('acct_reg2.html', form=None)
reg2 = Reg2(token)
if reg2.status == RegStatus.new:
flash('Chybný registrační kód. Zkontrolujte, že jste odkaz z e-mailu zkopírovali správně.', 'danger')
return render_template('acct_reg2.html', form=None)
if reg2.status != RegStatus.ok:
if reg2.status == RegStatus.expired:
flash('Vypršela platnost registrace, vyplňte ji prosím znovu.', 'danger')
elif reg2.status == RegStatus.already_spent:
flash('Tento odkaz na potvrzení registrace byl již využit', 'danger')
flash('Tento odkaz na potvrzení registrace byl již využit.', 'danger')
return redirect(url_for('create_acct'))
if reg2.rr.type != db.RegReqType.register:
@@ -460,12 +468,12 @@ def confirm_email():
form = Reg2Form()
if form.validate_on_submit():
if reg2.create(form.first_name.data, form.last_name.data, form.passwd.data):
flash('Založení účtu a přihlášení do systému proběhlo úspěšně', 'success')
flash('Založení účtu a přihlášení do systému proběhlo úspěšně.', 'success')
app.logger.info(f'Login: Přihlásil se uživatel <{reg2.user.email}> po založení účtu')
return login_and_redirect(reg2.user, flash_msg='Účet úspěšně založen')
return login_and_redirect(reg2.user, flash_msg='Účet úspěšně založen.')
if reg2.status == RegStatus.already_exists:
flash('Účet s touto adresou už existuje')
flash('Účet s touto adresou už existuje.', 'danger')
form.email.data = reg2.rr.email
Loading