Browse Source
Add a database schema, admin page, db commands, code creation form
Add a database schema, admin page, db commands, code creation form
* Added a simple database schema that is created at start * Added database methods to CRUD registration codes and read registered users * Added a table to the admin page with registration codes * Added a form to create registration codes * Added embedded forms to expire and delete registration codes * Added endpoints to facilitate expiring and deleting registration codes * Added models and helpers to facilitate storing and retrieving data * Added a password safety validator on registrationmaster
Drew Short
6 years ago
9 changed files with 315 additions and 26 deletions
-
4.gitignore
-
90app.py
-
112db.py
-
34forms.py
-
43models.py
-
3requirements.txt
-
12schema.sql
-
42templates/admin.html
-
1templates/register.html
@ -1 +1,3 @@ |
|||||
.idea |
|
||||
|
.idea |
||||
|
|
||||
|
*.db |
@ -1,14 +1,40 @@ |
|||||
from flask_wtf import FlaskForm |
from flask_wtf import FlaskForm |
||||
from wtforms import StringField, PasswordField |
|
||||
from wtforms.validators import DataRequired |
|
||||
|
from wtforms import StringField, PasswordField, DateField, IntegerField, SubmitField |
||||
|
from wtforms.validators import DataRequired, Length, EqualTo, InputRequired, \ |
||||
|
ValidationError, NumberRange, Optional |
||||
|
import safe |
||||
|
|
||||
|
|
||||
|
def safe_password_validator(form: FlaskForm, field): |
||||
|
strength = safe.check(field.data, level=safe.MEDIUM) |
||||
|
if not strength.valid: |
||||
|
raise ValidationError("Password is not secure enough: %s" % strength.message) |
||||
|
|
||||
|
|
||||
class RegistrationForm(FlaskForm): |
class RegistrationForm(FlaskForm): |
||||
username = StringField('Username', validators=[DataRequired()]) |
|
||||
password = PasswordField('Password', validators=[DataRequired()]) |
|
||||
|
username = StringField('Username', validators=[DataRequired(), Length(min=3, max=30)]) |
||||
|
password = PasswordField( |
||||
|
'Password', |
||||
|
validators=[ |
||||
|
InputRequired(), |
||||
|
EqualTo('confirm', message='Passwords must match'), |
||||
|
safe_password_validator |
||||
|
]) |
||||
|
confirm = PasswordField('Repeat Password') |
||||
registration_code = StringField('Registration Code', validators=[DataRequired()]) |
registration_code = StringField('Registration Code', validators=[DataRequired()]) |
||||
|
|
||||
|
|
||||
class LoginForm(FlaskForm): |
class LoginForm(FlaskForm): |
||||
username = StringField('Username', validators=[DataRequired()]) |
username = StringField('Username', validators=[DataRequired()]) |
||||
token = PasswordField('Token', validators=[DataRequired()]) |
token = PasswordField('Token', validators=[DataRequired()]) |
||||
|
|
||||
|
|
||||
|
class RegistrationCodeForm(FlaskForm): |
||||
|
expiration_time = DateField('Expiration Time', validators=[Optional()]) |
||||
|
max_usages = IntegerField('Max Usages', validators=[NumberRange(min=1)]) |
||||
|
|
||||
|
|
||||
|
class ExpireRegistrationCodeForm(FlaskForm): |
||||
|
registration_code = StringField('Registration Code') |
||||
|
expire = SubmitField(label='Expire') |
||||
|
delete = SubmitField(label='Delete') |
@ -0,0 +1,43 @@ |
|||||
|
from datetime import datetime |
||||
|
from typing import Tuple |
||||
|
|
||||
|
|
||||
|
class RegistrationCode: |
||||
|
def __init__(self, |
||||
|
code: str, |
||||
|
creation_time: datetime = datetime.now(), |
||||
|
expiration_time: datetime = None, |
||||
|
usages: int = 0, |
||||
|
max_usages: int = 1): |
||||
|
self.code = code |
||||
|
self.creation_time = creation_time |
||||
|
self.expiration_time = expiration_time |
||||
|
self.usages = usages |
||||
|
self.max_usages = max_usages |
||||
|
|
||||
|
@staticmethod |
||||
|
def from_db(db_registration_code: Tuple) -> 'RegistrationCode': |
||||
|
expiration_time = None if db_registration_code[2] is None else datetime.fromisoformat(db_registration_code[2]) |
||||
|
return RegistrationCode( |
||||
|
db_registration_code[0], |
||||
|
datetime.fromisoformat(db_registration_code[1]), |
||||
|
expiration_time, |
||||
|
db_registration_code[3], |
||||
|
db_registration_code[4] |
||||
|
) |
||||
|
|
||||
|
def is_expired(self): |
||||
|
return self.expiration_time is not None and self.expiration_time < datetime.now() |
||||
|
|
||||
|
|
||||
|
class RegisteredUser: |
||||
|
def __init__(self, username: str, registered_time: datetime = datetime.now()): |
||||
|
self.username = username |
||||
|
self.registered_time = registered_time |
||||
|
|
||||
|
@staticmethod |
||||
|
def from_db(db_registered_user: Tuple) -> 'RegisteredUser': |
||||
|
return RegisteredUser( |
||||
|
db_registered_user[0], |
||||
|
datetime.fromisoformat(db_registered_user[1]) |
||||
|
) |
@ -1,4 +1,5 @@ |
|||||
flask==1.0.2 |
flask==1.0.2 |
||||
flask-wtf==0.14 |
flask-wtf==0.14 |
||||
flask-login==0.4.1 |
flask-login==0.4.1 |
||||
requests==2.21.0 |
|
||||
|
requests==2.21.0 |
||||
|
safe |
@ -0,0 +1,12 @@ |
|||||
|
CREATE TABLE IF NOT EXISTS registration_codes ( |
||||
|
code VARCHAR(60) NOT NULL UNIQUE, |
||||
|
creationTime TIMESTAMP NOT NULL, |
||||
|
expirationTime TIMESTAMP, |
||||
|
usages INTEGER NOT NULL DEFAULT 0, |
||||
|
maxUsages Integer NOT NULL DEFAULT 1 |
||||
|
); |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS registered_users ( |
||||
|
username VARCHAR(30) NOT NULL UNIQUE , |
||||
|
registeredTime TIMESTAMP NOT NULL |
||||
|
); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue