Skip to content

Commit 7550878

Browse files
Added Errors Pages and Authentication Module
1 parent e27117e commit 7550878

File tree

22 files changed

+745
-61
lines changed

22 files changed

+745
-61
lines changed

app/__init__.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
from celery import Celery
66
from flask_caching import Cache
77
from depot.manager import DepotManager
8+
from flask_login import LoginManager
89

910
mail = Mail()
1011
db = Orator()
1112
celery = Celery(__name__, broker=DevelopmentConfig.CELERY_BROKER_URL, result_backend=DevelopmentConfig.RESULT_BACKEND)
1213
cache = Cache()
14+
login_manager = LoginManager()
1315

1416

1517
def factory(config=DevelopmentConfig) -> Flask:
@@ -35,6 +37,9 @@ def factory(config=DevelopmentConfig) -> Flask:
3537
db.init_app(app)
3638
db.app = app
3739

40+
# initialize login_manager
41+
register_login_manager(app)
42+
3843
# configure depotmanager
3944
DepotManager.configure(name='default', config={
4045
'depot.storage_path': 'storage/public'
@@ -46,14 +51,33 @@ def factory(config=DevelopmentConfig) -> Flask:
4651

4752
register_blueprints(app)
4853
register_logging(app)
54+
register_error_handlers(app)
4955

5056
return app
5157

5258

59+
def register_login_manager(app):
60+
login_manager.login_view = 'auth.login_page'
61+
login_manager.init_app(app)
62+
login_manager.session_protection = 'strong'
63+
login_manager.login_message = 'Please login to access this page.'
64+
login_manager.login_message_category = 'info'
65+
login_manager.needs_refresh_message = 'To protect your account, please reauthenticate to access this page.'
66+
login_manager.needs_refresh_message_category = 'info'
67+
login_manager.refresh_view = 'login'
68+
69+
@login_manager.user_loader
70+
def load_user(user_id):
71+
from app.models.User import User
72+
return User.find(user_id)
73+
74+
5375
def register_blueprints(app):
76+
from routes.auth import router as auth_router
5477
from routes.web import router as web_router
5578
from routes.api import router as api_router
5679

80+
app.register_blueprint(auth_router)
5781
app.register_blueprint(web_router)
5882
app.register_blueprint(api_router)
5983

@@ -86,24 +110,39 @@ def register_error_handlers(app) -> None:
86110
# 400 - Bad Request
87111
@app.errorhandler(400)
88112
def bad_request(e):
89-
return render_template('errors/400.html'), 400
113+
return render_template('errors/400.html', context={
114+
"title": 400,
115+
"message": "Bad Request"
116+
}), 400
90117

91118
# 403 - Forbidden
92119
@app.errorhandler(403)
93120
def forbidden(e):
94-
return render_template('errors/403.html'), 403
121+
return render_template('errors/403.html', context={
122+
"title": 403,
123+
"message": "Forbidden"
124+
}), 403
95125

96126
# 404 - Page Not Found
97127
@app.errorhandler(404)
98128
def page_not_found(e):
99-
return render_template('errors/404.html'), 404
129+
return render_template('errors/404.html', context={
130+
"title": 404,
131+
"message": "Page not found"
132+
}), 404
100133

101134
# 405 - Method Not Allowed
102135
@app.errorhandler(405)
103136
def method_not_allowed(e):
104-
return render_template('errors/405.html'), 405
137+
return render_template('errors/405.html', context={
138+
"title": 405,
139+
"message": "Method Not Allowed"
140+
}), 405
105141

106142
# 500 - Internal Server Error
107143
@app.errorhandler(500)
108144
def server_error(e):
109-
return render_template('errors/500.html'), 500
145+
return render_template('errors/500.html', context={
146+
"title": 500,
147+
"message": "Internal Server Error"
148+
}), 500

app/controllers/AuthController.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from app.models.User import User
2+
from app.forms import ForgotPasswordForm, LoginForm
3+
from flask import render_template, redirect, request
4+
from flask_login import login_user, logout_user, login_required, current_user
5+
6+
7+
class AuthController():
8+
9+
@staticmethod
10+
def login():
11+
form = LoginForm()
12+
if request.method == 'GET':
13+
return render_template('pages/auth/login.html', title='Login', form=form)
14+
else:
15+
if form.validate_on_submit():
16+
email = form.data['email']
17+
password = form.data['password']
18+
19+
user = User.where("email", email).first()
20+
if user is None:
21+
return render_template('pages/auth/login.html', title='Login', form=form, message="User not found!")
22+
23+
if user.check_password(password):
24+
login_user(user)
25+
return redirect('/home')
26+
27+
return render_template('pages/auth/login.html', title='Login', form=form, message="User not found!")
28+
29+
def register():
30+
"""
31+
Register a user
32+
"""
33+
return redirect('/')
34+
35+
def forgot_password():
36+
"""
37+
Forgot password
38+
"""
39+
form = ForgotPasswordForm()
40+
if request.method == 'GET':
41+
return render_template('pages/auth/forgot_password.html', title='Forgot Password', form=form)
42+
else:
43+
if form.validate_on_submit():
44+
email = form.data['email']
45+
user = User.where("email", email).first()
46+
if user is None:
47+
return render_template('pages/auth/forgot_password.html', title="Forgot Password", form=form, message="User not found!")
48+
user.send_password_reset_email()
49+
return render_template('pages/auth/forgot_password.html', title="Forgot Password", form=form, message="User not found!")
50+
return render_template('pages/auth/forgot_password.html', title="Forgot Password", form=form, message="Invalid parameters!")
51+
52+
@login_required
53+
def logout():
54+
logout_user()
55+
return redirect('/')
56+
57+
@login_required
58+
def profile():
59+
return render_template('pages/auth/profile.html', title='Profile', user=current_user)
60+
61+
@login_required
62+
def home():
63+
return render_template('pages/auth/home.html', title='Home', user=current_user)

app/controllers/WelcomeController.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
1-
from app.services import sign
21
from flask import render_template, request
3-
from app import cache
42
from app.services.Storage import Storage
5-
import json
63

74

85
class WelcomeController():
96

107
@staticmethod
11-
@cache.memoize(timeout=60)
128
def index():
13-
# cache.set('name', 'Yubaraj Shrestha')
14-
# print(cache.get('name'))
15-
169
return render_template("pages/index.html")
1710

1811
@staticmethod
1912
def about():
20-
encrypted = sign().sign(json.dumps({
21-
"name": "Yubaraj Shrestha"
22-
}))
23-
print(encrypted)
2413
return render_template("pages/about.html")
2514

2615
@staticmethod

app/forms/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from flask_wtf import FlaskForm
2+
from wtforms import StringField, PasswordField, SubmitField, BooleanField, TextAreaField
3+
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
4+
from app.models.User import User
5+
6+
7+
class RegistrationForm(FlaskForm):
8+
9+
username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
10+
email = StringField('Email', validators=[DataRequired(), Email()])
11+
password = PasswordField('Password', validators=[DataRequired()])
12+
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
13+
submit = SubmitField('Sign Up')
14+
15+
def validate_username(self, username):
16+
user = User.where("username", username.data).first()
17+
if user:
18+
raise ValidationError('That username is taken. Please choose a different one.')
19+
20+
def validate_email(self, email):
21+
user = User.where("email", email.data).first()
22+
if user:
23+
raise ValidationError('That email is taken. Please choose a different one.')
24+
25+
26+
class LoginForm(FlaskForm):
27+
email = StringField('Email', validators=[DataRequired(), Email()])
28+
password = PasswordField('Password', validators=[DataRequired()])
29+
remember = BooleanField('Remember Me')
30+
submit = SubmitField('Login')
31+
32+
33+
class ForgotPasswordForm(FlaskForm):
34+
email = StringField('Email', validators=[DataRequired(), Email()])
35+
submit = SubmitField('Send Reset Link')

app/jobs/MailJob.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from flask_mail import Message
2+
from app import mail, celery
3+
from flask import current_app
4+
5+
6+
@celery.task(name='app.jobs.MailJob.send_mail_job')
7+
def send_mail_job(message_data):
8+
"""
9+
This function is used to send a test email to the user.
10+
"""
11+
app = current_app._get_current_object()
12+
13+
message = Message(subject=message_data['subject'],
14+
recipients=[message_data['recipients']],
15+
body=message_data['body'],
16+
sender=app.config['MAIL_DEFAULT_SENDER'])
17+
18+
mail.send(message)

app/models/User.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
from app import db
2+
from flask_login import UserMixin
23

34

4-
class User(db.Model):
5+
class User(db.Model, UserMixin):
56

67
__fillable__ = ['name', 'email']
78

9+
def check_password(self, password):
10+
return True
11+
12+
def send_password_reset_email(self):
13+
from app.jobs.MailJob import send_mail_job
14+
message_data = {
15+
'subject': 'Reset Password',
16+
'body': 'This email was sent asynchronously using Celery.',
17+
'recipients': self.email,
18+
19+
}
20+
send_mail_job.apply_async(args=[message_data])
21+
822
def __repr__(self) -> str:
923
return '<User %r>' % self.name

app/views/errors/400.html

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,68 @@
1-
400 Error
1+
{% extends 'layouts/base.html' %}
2+
3+
{% block content %}
4+
5+
<!-- component -->
6+
<style>
7+
.emoji-404 {
8+
9+
position: relative;
10+
animation: mymove 2.5s infinite;
11+
}
12+
13+
@keyframes mymove {
14+
33% {
15+
top: 0px;
16+
}
17+
18+
66% {
19+
top: 20px;
20+
}
21+
22+
100% {
23+
top: 0px
24+
}
25+
26+
27+
28+
}
29+
</style>
30+
<div class="bg-gray-100 h-screen flex justify-center items-center">
31+
<div>
32+
<center class="m-auto">
33+
<svg class="emoji-404 " enable-background="new 0 0 226 249.135" height="249.135" id="Layer_1"
34+
overflow="visible" version="1.1" viewBox="0 0 226 249.135" width="226" xml:space="preserve">
35+
<circle cx="113" cy="113" fill="#FFE585" r="109" />
36+
<line enable-background="new " fill="none" opacity="0.29" stroke="#6E6E96" stroke-linecap="round"
37+
stroke-linejoin="round" stroke-width="8" x1="88.866" x2="136.866" y1="245.135" y2="245.135" />
38+
<line enable-background="new " fill="none" opacity="0.17" stroke="#6E6E96" stroke-linecap="round"
39+
stroke-linejoin="round" stroke-width="8" x1="154.732" x2="168.732" y1="245.135" y2="245.135" />
40+
<line enable-background="new " fill="none" opacity="0.17" stroke="#6E6E96" stroke-linecap="round"
41+
stroke-linejoin="round" stroke-width="8" x1="69.732" x2="58.732" y1="245.135" y2="245.135" />
42+
<circle cx="68.732" cy="93" fill="#6E6E96" r="9" />
43+
<path
44+
d="M115.568,5.947c-1.026,0-2.049,0.017-3.069,0.045 c54.425,1.551,98.069,46.155,98.069,100.955c0,55.781-45.219,101-101,101c-55.781,0-101-45.219-101-101 c0-8.786,1.124-17.309,3.232-25.436c-3.393,10.536-5.232,21.771-5.232,33.436c0,60.199,48.801,109,109,109s109-48.801,109-109 S175.768,5.947,115.568,5.947z"
45+
enable-background="new " fill="#FF9900" opacity="0.24" />
46+
<circle cx="156.398" cy="93" fill="#6E6E96" r="9" />
47+
<ellipse cx="67.732" cy="140.894" enable-background="new " fill="#FF0000" opacity="0.18" rx="17.372"
48+
ry="8.106" />
49+
<ellipse cx="154.88" cy="140.894" enable-background="new " fill="#FF0000" opacity="0.18" rx="17.371"
50+
ry="8.106" />
51+
<path
52+
d="M13,118.5C13,61.338,59.338,15,116.5,15c55.922,0,101.477,44.353,103.427,99.797 c0.044-1.261,0.073-2.525,0.073-3.797C220,50.802,171.199,2,111,2S2,50.802,2,111c0,50.111,33.818,92.318,79.876,105.06 C41.743,201.814,13,163.518,13,118.5z"
53+
fill="#FFEFB5" />
54+
<circle cx="113" cy="113" fill="none" r="109" stroke="#6E6E96" stroke-width="8" />
55+
</svg>
56+
<div class=" tracking-widest mt-4">
57+
<span class="text-gray-500 text-6xl block"><span>{{ title }}</span></span>
58+
<span class="text-gray-500 text-xl">{{ message }}</span>
59+
60+
</div>
61+
</center>
62+
<center class="mt-6">
63+
<a href="/" class="text-gray-500 font-mono text-xl bg-gray-200 p-3 rounded-md hover:shadow-md">Go back </a>
64+
</center>
65+
</div>
66+
</div>
67+
68+
{% endblock %}

0 commit comments

Comments
 (0)