Skip to content

Commit

Permalink
Fixed various authorization issues, limitations listed in TODO
Browse files Browse the repository at this point in the history
+ Fixed @login_required issues with /search URL
+ Modified session handling to allow at most one logged user
+ Added Auth.authorized function to check route-specific authorization
(the @login_required decorator right now just checks that someone is logged in)
  • Loading branch information
etrian-dev committed Sep 10, 2022
1 parent e0e7208 commit c46405b
Show file tree
Hide file tree
Showing 20 changed files with 175 additions and 40 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FLASK_APP=chatroom
FLASK_ENV=development
4 changes: 4 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
- Left header for navigation
- index page on route '/'
- User detail page
- randomize user id at user registration
- Add header with login info in all user views
- Encryption/decryption of message data with RSA
- logout function and proper authentication (maybe sessions)
+ issue in @login_required decorator with args from variable routes
+ repetitiveness of checking for authorization with Auth.authorized(uid)
- Fix timestamp for message viewing order in messages.html
+ Needs revision on Chat.py:display_chat
- In msg.py all function that modify a chat (adding a message, deleting one) should update the
Expand Down
10 changes: 9 additions & 1 deletion chatroom/Admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ def show_users():
SELECT * FROM Users''')
all_users = dict()
for user in cursor.fetchall():
cur = conn.execute('''
SELECT * FROM Sessions WHERE userref=?''',
[user['user_id']])
user_session = cur.fetchone()
status = "Not logged"
if user_session is not None:
status = "Logged in"
all_users[user['user_id']] = {
"username": user['username'],
"password": user['password'],
"status": status,
"pub_key": [user['pk_e'].hex()[:20] + "...", user['pk_n'].hex()[:20] + "..."],
"priv_key": user['pk_d'].hex()[:20] + "..."}
return render_template('admin_console.html', all_users=all_users)
except DatabaseError:
return "DB error"
return "DB error"
16 changes: 13 additions & 3 deletions chatroom/Auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@

blueprint = Blueprint('auth', __name__,url_prefix='/auth')

@login_required
def authorized(uid):
print(f"uid id {uid}, and logged user is {session['user_id']}")
return session['user_id'] == uid

@blueprint.route('/register', methods=['GET', 'POST'])
def register():
'''Register a new user.
Expand Down Expand Up @@ -69,8 +74,11 @@ def login():
while row is not None:
if row['password'] == pwd:
user_id = row['user_id']

# create a session for this user
# wipe any other session
cur.execute('''
DELETE FROM Sessions
''')
# and create a session for this user
cur.execute('''
INSERT INTO Sessions(userref, login_tm)
VALUES (?,?)''', [user_id, time_ns()])
Expand All @@ -80,6 +88,7 @@ def login():
cur.close()
# User found: redirect to its own chats page
if user_id is not None:
session['user_id'] = user_id
return redirect(url_for('chat.home_user', user_id=user_id))
error = 'Password incorrect or user inexistent'
if error is not None:
Expand All @@ -98,4 +107,5 @@ def logout(user_id: int):
cur.execute('''
DELETE FROM Sessions WHERE userref = ?;''', [user_id])
conn.commit()
return redirect(url_for('auth.login'))
del session['user_id']
return redirect(url_for('auth.login'))
9 changes: 9 additions & 0 deletions chatroom/Chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from flask import (Blueprint, request, render_template)
from . import db
from . import Msg
from . import Auth
from chatroom.decorators.auth import login_required
from flask import (
Blueprint, flash, g, redirect, render_template, request, url_for
Expand Down Expand Up @@ -46,6 +47,8 @@ def delete_message(target):
@blueprint.route('/<int:user_id>', methods=['GET'])
@login_required
def home_user(user_id: int):
if not Auth.authorized(user_id):
return redirect(url_for('auth.login'))
cur = db.get_db().cursor()
user_data = dict()
user_data['user_id'] = user_id
Expand Down Expand Up @@ -83,6 +86,8 @@ def home_user(user_id: int):
@blueprint.route('/<int:creator>/', methods=['POST'])
@login_required
def create_chat(creator):
if not Auth.authorized(creator):
return redirect(url_for('auth.login'))
# TODO: check that the current session for this client is logged as
# <creator>
error = None
Expand Down Expand Up @@ -120,6 +125,8 @@ def create_chat(creator):
@blueprint.route('/<int:user>/<int:other>', methods=['GET'])
@login_required
def display_chat(user, other):
if not Auth.authorized(user):
return redirect(url_for('auth.login'))
chat_info = {}
chat_info['this_user_id'] = user
chat_info['other_user_id'] = other
Expand Down Expand Up @@ -205,4 +212,6 @@ def display_chat(user, other):
@login_required
# TODO: delete
def delete_chat(creator, recipient):
if not Auth.authorized(creator):
return redirect(url_for('auth.login'))
return "DELETED chat between " + creator + " and " + recipient
7 changes: 7 additions & 0 deletions chatroom/Msg.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def __init__(self, data: str, stamp: int):
from chatroom.decorators.auth import login_required

from . import db
from . import Auth
from sqlite3 import Connection, Cursor, DatabaseError
from json import (load, loads, dump, dumps)
from os import fspath, path, SEEK_END, SEEK_SET
Expand Down Expand Up @@ -76,6 +77,8 @@ def get_msgstore(sender, receiver) -> str:
@blueprint.route('/<int:sender>/<int:recipient>/', methods=['POST'])
@login_required
def send_message(sender, recipient):
if not Auth.authorized(sender):
return redirect(url_for('auth.login'))
db_conn = db.get_db()
try:
# get the chat id
Expand Down Expand Up @@ -147,6 +150,8 @@ def send_message(sender, recipient):
@blueprint.route('/<int:msg_id>', methods=['GET', 'PUT'])
@login_required
def edit_message(msg_id):
if not Auth.authorized(sender):
return redirect(url_for('auth.login'))
# Get the message sender and receiver
cur = db.get_db().execute('''
SELECT sender,recipient FROM Messages WHERE msg_id = ?;
Expand Down Expand Up @@ -195,6 +200,8 @@ def edit_message(msg_id):
@blueprint.route('/<int:msg_id>', methods=['DELETE'])
@login_required
def delete_message(msg_id):
if not Auth.authorized(sender):
return redirect(url_for('auth.login'))
if request.method == 'DELETE':
# Build the url of the chat where the user will be redirected after deletion
cur = db.get_db().execute('''
Expand Down
29 changes: 16 additions & 13 deletions chatroom/Search.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@
from sqlite3 import Connection, Cursor, DatabaseError
from json import dumps
import logging
import sys

from flask import (
Blueprint, flash, g, redirect, render_template, request, session, url_for
)
Blueprint, flash, g, redirect, render_template, request, session, url_for, Flask
)
from flask.json import jsonify

blueprint = Blueprint('search', __name__, url_prefix='/search')


#NOTE: This route should not have a @login_required decorator fot the following reasons:
# - Doesn't work
# - No real reason to be authenticated to query for registered usernames
@blueprint.route('/', methods=['GET'])
@login_required
def search_user():
'''This method allows searching for users in the database.
Expand All @@ -28,21 +30,20 @@ def search_user():
equal to the argument of the query. If the match is in the middle of the string
that user is not added to the array.
'''
logging.debug(f"search query: {request.args}")
logging.warning(f"search query: {request.args}")
print(f"search query: {request.args}", flush=True, file=sys.stderr)
matching_users = []
# Does not match any user if the search query is empty
if request.method == 'GET' and 'user' in request.args and len(request.args['user']) > 0 :
query = request.args['user']

db_conn = db.get_db()

try:
cursor = db_conn.execute('''
SELECT user_id, username
FROM Users
WHERE username LIKE ?
ORDER BY username DESC;
''', [query + "%"])
SELECT user_id, username
FROM Users
WHERE username LIKE ?
ORDER BY username DESC;
''', [query + "%"])

for row in cursor.fetchall():
matching_users.append({
Expand All @@ -52,5 +53,7 @@ def search_user():
except DatabaseError as err:
print('SQLite error: %s' % (' '.join(err.args)))
print("Exception class is: ", err.__class__)
logging.debug(f"{len(matching_users)} mathches found: {matching_users}")
logging.warning(f"{len(matching_users)} mathches found: {matching_users}")
print(f"{len(matching_users)} mathches found: {matching_users}", flush=True)
return jsonify(matching_users)

2 changes: 1 addition & 1 deletion chatroom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def create_app(testing=None):
logging.basicConfig(
filename=os.path.join(app.instance_path, 'chatroom.log'),
filemode='w',
level=logging.DEBUG,
level=logging.WARNING,
encoding='utf-8')
logging.info(f"Application launched at {datetime.now()}")

Expand Down
24 changes: 14 additions & 10 deletions chatroom/decorators/auth.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
from chatroom import db
from functools import wraps

from flask import g, url_for, redirect
from flask import g, url_for, redirect, session

# decorator to allow the operation iff the user is logged in to a session
def login_required(f):
@wraps(f)
def login_required(fn):
@wraps(fn)
def login_wrapper(*args, **kwargs):
if 'user_id' in kwargs:
if 'user_id' in session:
# check for a session in the db
cur = db.get_db().execute('''
SELECT userref FROM Sessions WHERE userref = ?;''', [kwargs['user_id']])
if cur.fetchone() is not None:
SELECT userref
FROM Sessions
WHERE userref = ?;''', [session['user_id']])
row = cur.fetchone()
print('user is', row['userref'])
if row is not None:
print('session ok')
return f(*args, **kwargs)
print('session fail')
return redirect(url_for('auth.login'))
return login_wrapper
return fn(*args, **kwargs)
print('session fail')
return redirect(url_for('auth.login'))
return login_wrapper
2 changes: 2 additions & 0 deletions chatroom/static/jquery-3.6.1.min.js

Large diffs are not rendered by default.

85 changes: 85 additions & 0 deletions chatroom/static/myrequests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
function edit_msg(event) {
var formData = {
msg: $("#message").val(),
};

$.ajax({
type: "PUT",
url: this.action,
data: JSON.stringify(formData),
contentType: "application/json",
dataType: "json",
encode: true,
}).done(function (data) {
window.location.href = data['url']
//alert("Message updated successfully");
}).fail(function(data) {
//alert("Failed message update");
}).always(function(data) {
console.log("msg-edit: ", JSON.stringify(data));
});

event.preventDefault();
}

function delete_msg(event) {
$.ajax({
type: "DELETE",
url: this.href,
contentType: "application/json",
dataType: "json",
encode: true,
}).done(function (data) {
window.location.href = data['url']
alert("Message deleted successfully");
}).fail(function(data) {
alert("Failed message deletion");
}).always(function(data) {
console.log("msg-delete");
});

event.preventDefault();
}

function search_user(event) {
var url = document.URL;

$.ajax({
type: "GET",
url: "/search/",
data: { "user": $("#query").val()},
contentType: "application/json",
dataType: "json",
encode: true,
success: function(data) {
// process the json data
matching_users = data
select_node = document.getElementById("matching-users");
while (select_node.firstChild) {
select_node.removeChild(select_node.lastChild);
}
for (const user of matching_users) {
var opt = document.createElement('option');
opt.value = user.user_id
opt.innerHTML = user.username + " (user_id: " + user.user_id + ")";
select_node.appendChild(opt);
}
select_node.firstChild.selected = true;
console.log("user-search: " + query);
}
}).fail(function(data) {
alert("Failed query");
}).always(function(data) {
console.log("user-search", data);
});

event.preventDefault();
}

$(document).ready(function () {
$("#msg-edit-form").on("submit", edit_msg);
$("#msg-delete").on("click", delete_msg);
$("#user-search").on("change", search_user);
$("#user-search").on("submit", search_user);
});

Loading

0 comments on commit c46405b

Please sign in to comment.