diff --git a/app/__init__.py b/app/__init__.py index 8c6ef2a..dc71a95 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,7 +3,7 @@ from config import Config from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate - +from flask_login import LoginManager # Construct core Flask application with embedded Dash app. app = Flask(__name__, instance_relative_config=False) @@ -11,6 +11,10 @@ app.config.from_object(Config) db = SQLAlchemy(app) migrate = Migrate(app, db) +login = LoginManager(app) +login.login_view='login' + +from app import routes,models with app.app_context(): diff --git a/app/forms.py b/app/forms.py index 0657146..04391a9 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,6 +1,7 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField -from wtforms.validators import DataRequired +from wtforms.validators import DataRequired, ValidationError, Email, EqualTo +from app.models import User class LoginForm(FlaskForm): @@ -8,3 +9,20 @@ class LoginForm(FlaskForm): password = PasswordField("Password", validators=[DataRequired()]) remember_me = BooleanField("Remember Me") submit = SubmitField("Log In") + +class RegistrationForm(FlaskForm): + username = StringField('Username', validators=[DataRequired()]) + email = StringField('Email',validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired()]) + password2 = PasswordField('Repeat Password', validators=[DataRequired(), EqualTo('password')]) + submit = SubmitField('Register') + + def validate_usernames(self, username): + user = User.query.filter_by(username=username.data).first() + if user is not None: + raise ValidationError('This username is already in use.') + + def validate_email(self, email): + user = User.query.filter_by(email=email.data).first() + if user is not None: + raise ValidationError('This email address is already active.') diff --git a/app/models.py b/app/models.py index 3c7be1a..e88f90d 100644 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,8 @@ from datetime import datetime -from app import db +from app import db, login +from werkzeug.security import generate_password_hash, check_password_hash +from flask_login import UserMixin +from hashlib import md5 user_coins = db.Table( @@ -13,7 +16,7 @@ ) -class User(db.Model): +class User(db.Model, UserMixin): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) @@ -26,7 +29,15 @@ class User(db.Model): def __repr__(self): return "".format(self.username) + def set_password(self, password): + self.password_hash = generate_password_hash(password) + def check_password(self, password): + return check_password_hash(self.password_hash, password) + +@login.user_loader +def load_user(id): + return User.query.get(int(id)) class Coin(db.Model): __tablename__ = "coin" id = db.Column(db.Integer, primary_key=True) diff --git a/app/routes.py b/app/routes.py index cab1df0..d933a24 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,7 +1,9 @@ -from flask import render_template, flash, redirect, url_for -from app import app -from app.forms import LoginForm - +from flask import render_template, flash, redirect, url_for, request +from app import app, db +from app.forms import LoginForm, RegistrationForm +from flask_login import current_user, login_user, logout_user, login_required +from app.models import User +from werkzeug.urls import url_parse @app.route("/") @app.route("/index") @@ -11,22 +13,44 @@ def index(): {"author": {"username": "Sal"}, "body": "Beautiful day in Portland!"}, {"author": {"username": "Michael"}, "body": "Pyrates are so cool!"}, {"author": {"username": "Scott"}, "body": "Ooh, Bootstrap!"}, + {"author": {"username": "Allyson"}, "body": "Hello, World!"}, ] return render_template("index.html", title="Home", user=user, posts=posts) @app.route("/login", methods=["GET", "POST"]) def login(): + if current_user.is_authenticated: + return redirect(url_for('index')) form = LoginForm() if form.validate_on_submit(): - flash( - f"Login requested for user {form.username.data}, " - f"remember_me={form.remember_me.data}" - ) - return redirect(url_for("index")) + user = User.query.filter_by(username=form.username.data).first() + if user is None or not user.check_password(form.password.data): + flash('Invalid username or password') + return redirect(url_for('login')) + login_user(user, remember=form.remember_me.data) + next_page = request.args.get('next') + if not next_page or url_parse(next_page).netloc !='': + next_page = url_for('index') + return redirect(next_page) return render_template("login.html", title="Log In", form=form) +@app.route("/logout") +def logout(): + logout_user + return redirect(url_for('index')) + -@app.route("/register") +@app.route("/register", methods=['GET', 'POST']) def register(): - return render_template("wip.html") + if current_user.is_authenticated: + return redirect(url_for('index')) + form = RegistrationForm() + if form.validate_on_submit(): + user = User(username=form.username.data, email=form.email.data) + user.set_password(form.password.data) + db.session.add(user) + db.session.commit() + flash('CONGRATS! Welcome to the Crypto Sea! X marks the spot!') + return redirect(url_for('login')) + return render_template('register.html', title='Register', form=form) diff --git a/app/templates/404.html b/app/templates/404.html index ce8a7c1..5d55d3f 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block content %}
-

Page Not Found

+

Page Has Been Eaten by a Gru

Back to home

{% endblock %} \ No newline at end of file diff --git a/app/templates/500.html b/app/templates/500.html index 7c99306..c79acf4 100644 --- a/app/templates/500.html +++ b/app/templates/500.html @@ -2,7 +2,7 @@ {% block content %}

An unexpected error has occurred

-

The administrator has been notified. Sorry for the inconvenience!

+

An administrator has been bribed with coffee and will fix it soon.

Back

{% endblock %} \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 5e9f315..626efe8 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -16,9 +16,11 @@
  • Home
  • diff --git a/app/templates/index.html b/app/templates/index.html index 42f9a6f..e029015 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,12 +1,12 @@ {% extends "base.html" %} {% block content %} -
    -

    Hi, {{ user.username }}!

    -
    - {% for post in posts %} -
    -

    {{ post.author.username }} says: {{ post.body }}

    -
    - {% endfor %} +
    +

    Hi, {{ user.username }}!

    +
    + {% for post in posts %} +
    +

    {{ post.author.username }} says: {{ post.body }}

    +
    + {% endfor %} {% endblock %} \ No newline at end of file diff --git a/app/templates/login.html b/app/templates/login.html index 5de1d3c..a552b4b 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block content %} -
    +

    Log In

    @@ -24,6 +24,6 @@

    Log In

    {{ form.remember_me() }} {{ form.remember_me.label }}

    {{ form.submit() }}

    - -
    +

    New User? Click to Register!

    +
    {% endblock %} \ No newline at end of file diff --git a/app/templates/register.html b/app/templates/register.html new file mode 100644 index 0000000..0916827 --- /dev/null +++ b/app/templates/register.html @@ -0,0 +1,39 @@ +{% extends 'base.html' %} + +{% block content %} +
    +

    Register

    +
    + {{ form.hidden_tag() }} +

    + {{ form.username.label }}
    + {{ form.username(size=32) }}
    + {% for error in form.username.errors %} + [{{ error }}] + {% endfor %} +

    +

    + {{ form.email.label }}
    + {{ form.email(size=32) }}
    + {% for error in form.email.errors %} + [{{ error }}] + {% endfor %} +

    +

    + {{ form.password.label }}
    + {{ form.password(size=32) }}
    + {% for error in form.password.errors %} + [{{ error }}] + {% endfor %} +

    +

    + {{ form.password2.label }}
    + {{ form.password2(size=32) }}
    + {% for error in form.password2.errors %} + [{{ error }}] + {% endfor %} +

    +

    {{ form.submit() }}

    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/app/templates/user.html b/app/templates/user.html new file mode 100644 index 0000000..dbac00a --- /dev/null +++ b/app/templates/user.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% block content %} + + + + + +

    User: {{ user.username }}

    +
    +
    + {% for post in posts %} + + + + + +
    {{ post.author.username }} says:
    {{ post.body }}
    + {%endfor %} +
    +{% endblock %} diff --git a/docker-compose.yml b/docker-compose.yml index a0f359c..1688e13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,8 +37,8 @@ services: MYSQL_ROOT_HOST: '%' ports: # : < MySQL Port running inside container> + # Where our data will be persisted - '33060:3306' - # Where our data will be persisted volumes: # pass volume named mysql-data to mysql container - mysql-data:/var/lib/mysql diff --git a/poetry.lock b/poetry.lock index 3c68c8c..69bd08c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -198,6 +198,22 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "dnspython" +version = "2.2.1" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +dnssec = ["cryptography (>=2.6,<37.0)"] +curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] +doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.20)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] + [[package]] name = "dominate" version = "2.6.0" @@ -206,6 +222,18 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "email-validator" +version = "1.2.1" +description = "A robust email syntax and deliverability validation library." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +dnspython = ">=1.15.0" +idna = ">=2.0.0" + [[package]] name = "filelock" version = "3.6.0" @@ -274,6 +302,18 @@ python-versions = "*" brotli = "*" flask = "*" +[[package]] +name = "flask-login" +version = "0.6.1" +description = "User authentication and session management for Flask." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +Flask = ">=1.0.4" +Werkzeug = ">=1.0.1" + [[package]] name = "flask-migrate" version = "3.1.0" @@ -850,7 +890,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "==3.8.10" -content-hash = "7f4ddf244726fd42a10a6b6dd0556a2d93a6dc27573e0babda64266245157fc8" +content-hash = "48bf6f5fa1282b265e7181ca749dec8653a2113dfe476b5d5983dc8af4cad287" [metadata.files] alembic = [ @@ -1055,22 +1095,33 @@ dash = [ {file = "dash-2.2.0.tar.gz", hash = "sha256:e88e6615695e83b7a8339301a92700461435fef734b40eca864cff6d853a3b9c"}, ] dash-core-components = [ + {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, ] dash-html-components = [ + {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, ] dash-table = [ + {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, ] distlib = [ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] +dnspython = [ + {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, + {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, +] dominate = [ {file = "dominate-2.6.0-py2.py3-none-any.whl", hash = "sha256:84b5f71ed30021193cb0faa45d7776e1083f392cfe67a49f44e98cb2ed76c036"}, {file = "dominate-2.6.0.tar.gz", hash = "sha256:76ec2cde23700a6fc4fee098168b9dee43b99c2f1dd0ca6a711f683e8eb7e1e4"}, ] +email-validator = [ + {file = "email_validator-1.2.1-py2.py3-none-any.whl", hash = "sha256:c8589e691cf73eb99eed8d10ce0e9cbb05a0886ba920c8bcb7c82873f4c5789c"}, + {file = "email_validator-1.2.1.tar.gz", hash = "sha256:6757aea012d40516357c0ac2b1a4c31219ab2f899d26831334c5d069e8b6c3d8"}, +] filelock = [ {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, @@ -1090,6 +1141,10 @@ flask-compress = [ {file = "Flask-Compress-1.12.tar.gz", hash = "sha256:e2159499f39d618a4d56ba0484e7b58b57956b9a2c6d3510f095f5bb14b7afc5"}, {file = "Flask_Compress-1.12-py3-none-any.whl", hash = "sha256:9f4e40211755e86f85e5eb7d414856ef1e8751912caa78d62853169400335f0c"}, ] +flask-login = [ + {file = "Flask-Login-0.6.1.tar.gz", hash = "sha256:1306d474a270a036d6fd14f45640c4d77355e4f1c67ca4331b372d3448997b8c"}, + {file = "Flask_Login-0.6.1-py3-none-any.whl", hash = "sha256:b9a4287a2d0067a7a482a23e40075e0d670f371974633fe890222dece4e02a74"}, +] flask-migrate = [ {file = "Flask-Migrate-3.1.0.tar.gz", hash = "sha256:57d6060839e3a7f150eaab6fe4e726d9e3e7cffe2150fb223d73f92421c6d1d9"}, {file = "Flask_Migrate-3.1.0-py3-none-any.whl", hash = "sha256:a6498706241aba6be7a251078de9cf166d74307bca41a4ca3e403c9d39e2f897"}, diff --git a/pyproject.toml b/pyproject.toml index 459d977..3b14262 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,8 @@ pycoingecko = "==2.2.0" Werkzeug = "==2.0.0" # required pin https://github.com/plotly/dash/issues/1992 PyMySQL = "==1.0.2" cryptography = "==37.0.2" +Flask-Login = "==0.6.1" +email-validator = "==1.2.1" [tool.poetry.dev-dependencies] black = "==22.3.0"