-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
253 lines (208 loc) · 10.5 KB
/
app.py
File metadata and controls
253 lines (208 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length
from werkzeug.security import generate_password_hash, check_password_hash
from flask_wtf.csrf import CSRFProtect
from sqlalchemy.exc import IntegrityError
# Creating a web application instance
app = Flask(__name__)
# Set the secret key for securely signing the session cookies and CSRF tokens
app.config['SECRET_KEY'] = 'SECRET_KEY'
# This URI tells Flask-SQLAlchemy where to find or create the database 'notes.db'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///notes.db'
# Making session cookies be able to only be sent over secure HTTPS connections
app.config['SESSION_COOKIE_SECURE'] = True
# Prevent client-side JavaScript from accessing the session cookie
# This helps avoid XSS (Cross-Site Scripting) attacks by making the cookie HTTP-only
app.config['SESSION_COOKIE_HTTPONLY'] = True
# Set the SameSite policy to 'Lax' to help avoid CSRF attacks
# This ensures the session cookie is not sent with most cross-site requests, except top-level navigations
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
db = SQLAlchemy(app)
login_manager = LoginManager(app)
csrf = CSRFProtect(app)
login_manager.login_view = 'login'
# A login manager used for loading/reloading a user for the userID
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
### Flask-WTF is used for validation on all the below classes
# A form class for creating and editing notes, contains fields for: title, note content, and a submit button
class NoteForm(FlaskForm):
title = StringField('Title', validators=[DataRequired(), Length(max=100)])
content = TextAreaField('Content', validators=[DataRequired()])
submit = SubmitField('Submit')
# A form class for user login, contains fields for: username, password, and a submit button
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Login')
# A form class for user registration, contains fields for: username, password, and a register button
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, ValidationError
import re
class RegisterForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Register')
# Custom validator for password strength
def validate_password(self, field):
password = field.data
if len(password) < 8 or len(password) > 12:
raise ValidationError('Password must be between 8 and 12 characters long.')
if not re.search(r'[A-Z]', password):
raise ValidationError('Password must contain at least one uppercase letter.')
if not re.search(r'[a-z]', password):
raise ValidationError('Password must contain at least one lowercase letter.')
if not re.search(r'[0-9]', password):
raise ValidationError('Password must contain at least one number.')
if not re.search(r'[!@#\$%\^&\*]', password):
raise ValidationError('Password must contain at least one special character: !, @, #, $, %, etc.')
# Defines a User model for managing user data in the application, including username, password, and
# a relationship to associated notes. (Uses SQLAlchemy for ORM and Flask-Login for user authentication)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(150), unique=True, nullable=False)
password = db.Column(db.String(150), nullable=False)
notes = db.relationship('Note', backref='author', lazy=True)
# Defines a Note model for managing note data, including title, content, and a foreign key linking to the user who created the note.
# (Uses SQLAlchemy for ORM and maintains a relationship with the User model)
class Note(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# The index page route
@app.route('/', methods=['GET', 'POST'])
@login_required
def index():
# Initialize the NoteForm
form = NoteForm()
# Handle form submission for note deletion
if request.method == 'POST' and 'delete' in request.form:
note_id = request.form.get('note_id') # Retrieve the note ID from the form
note = Note.query.get(note_id) # Query the note by its ID
# Check if the note exists and if the current user is the author
if note and note.user_id == current_user.id:
# Delete the note from the database and commit the changes to the database
db.session.delete(note)
db.session.commit()
# Notify the user
flash('Note deleted successfully!', 'success')
# If the user is not authorized to delete the note notify them
else:
flash('You are not authorized to delete this note.', 'danger')
# Redirect to the index page
return redirect(url_for('index'))
# Query all notes associated with the current user and refresh the index page with their list of notes
notes = Note.query.filter_by(user_id=current_user.id).all()
return render_template('index.html', notes=notes, form=form)
# The add_note route
@app.route('/add_note', methods=['GET', 'POST'])
# User need to be logged in
@login_required
def add_note():
form = NoteForm()
# When the user submits the note validate its data
if form.validate_on_submit():
# Retrieve the entered note title and content
title = form.title.data
content = form.content.data
new_note = Note(title=title, content=content, user_id=current_user.id) # Creating a note object with the data entered by the user
# Adding the new note to the database
db.session.add(new_note)
# Commiting the note to the db
db.session.commit()
# Redirecting the user to the index page if all goes to plan
return redirect(url_for('index'))
# If an issue occurs return the user to the add note page
return render_template('add_note.html', form=form)
# The app route used for editing a note
@app.route('/edit_note/<int:note_id>', methods=['GET', 'POST'])
# User need to be logged in
@login_required
def edit_note(note_id):
# Trying to retrieve the note or return a 404 error
note = Note.query.get_or_404(note_id)
# Checking for user authorization to edit the note
if note.user_id != current_user.id:
flash('You are not authorized to edit this note.')
return redirect(url_for('index'))
# Creating a noteform object
form = NoteForm(obj=note)
# When the user submits the note validate its data
if form.validate_on_submit():
# Retrieve the entered note title and content
note.title = form.title.data
note.content = form.content.data
# Commiting the changes to the db
db.session.commit()
# Notifying the user and returning them to the index page
flash('Note updated successfully!')
return redirect(url_for('index'))
# If an issue occurs return the user to the edit note page
return render_template('edit_note.html', form=form, note=note)
# The app route for user login
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# Retrieve the entered username and password
username = form.username.data
password = form.password.data
# Query the db for the user
user = User.query.filter_by(username=username).first()
if not user:
# If the username doesn't exist, pass an error message to the template
return render_template('login.html', form=form, username_error="Username doesn't exist.")
if not check_password_hash(user.password, password):
# If the password is incorrect, pass an error message to the template
return render_template('login.html', form=form, password_error="Incorrect password.")
# If both the username and password are correct, log in the user and redirect to the index page
login_user(user)
return redirect(url_for('index'))
# If form validation fails, re-render the login form
return render_template('login.html', form=form)
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
username = form.username.data
password = form.password.data
# Check if the username already exists
existing_user = User.query.filter_by(username=username).first()
if existing_user:
return render_template('register.html', form=form, username_error="Username already exists.")
# Hash the password and create a new user
hashed_password = generate_password_hash(password, method='pbkdf2:sha256')
new_user = User(username=username, password=hashed_password)
db.session.add(new_user)
db.session.commit()
flash('Registration successful! You can now log in.')
return redirect(url_for('login'))
return render_template('register.html', form=form)
# Used for user logout. When the user logs out they are redirected to the login page
@app.route('/logout')
# User need to be logged in
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
# Basic error handling
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(e):
return render_template('500.html'), 500
if __name__ == '__main__':
# Create an application context to access and interact with the database
with app.app_context():
# Create all the database tables defined by the models if they don't already exist
db.create_all()
# Running the app
app.run(host='0.0.0.0', port=443, ssl_context=('Certificates/server.crt', 'Certificates/server.key'))