Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented /api/session Endpoint #37

Merged
merged 8 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion server/app.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import os

from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy

import routes.user as user
import routes.session as session

import config

import routes.user as user
from utils import is_authenticated

basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join(
basedir, config.DATABASE_NAME
)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
CORS(app, resources={r"/api/*": {"origins": "*"}})
db = SQLAlchemy(app=app)


# debugging endpoint
@app.route("/api", methods=["GET", "POST", "PUT", "DELETE"])
@is_authenticated
def api_handler():
data = request.json
print(data)
Expand All @@ -31,11 +38,20 @@ def user_handler(id):
return user.router(id)


# session endpoint
@app.route("/api/session", defaults={"id": None}, methods=["POST"])
@app.route("/api/session/<id>", methods=["GET", "DELETE"])
def session_handler(id):
return session.router(id)


if __name__ == "__main__":
with app.app_context():
from models.user import User
from models.session import Session

db.metadata._add_table(User.__tablename__, None, User.__table__)
db.metadata._add_table(Session.__tablename__, None, Session.__table__)
db.create_all()

app.run(host="0.0.0.0", port=5000, debug=True)
45 changes: 45 additions & 0 deletions server/models/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from app import db


class Session(db.Model):
__tablename__ = "sessions"

_count = 0

id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True)
token = db.Column(db.String(24), unique=True, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)

def __init__(self, token, user_id):
# account for existing sessions before class is first initialized
sessions = db.session.query(self.__class__).all()
count = len(sessions)

if count != 0:
# account for existing users with some deleted
if sessions[-1].id >= Session._count:
Session._count = sessions[-1].id
else:
Session._count = count

# initialze the session
self.id = Session._count
self.token = token
self.user_id = user_id

# increment the count to track session ids
Session._count += 1

def __repr__(self):
return "<Session %r>" % self.token

def json(self):
return {"id": self.id, "token": self.token, "user_id": self.user_id}

def save_to_db(self):
db.session.add(self)
db.session.commit()

def delete_from_db(self):
db.session.delete(self)
db.session.commit()
11 changes: 8 additions & 3 deletions server/models/user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
from app import db


Expand All @@ -13,9 +12,15 @@ class User(db.Model):

def __init__(self, username, password):
# account for existing users before class is first initialized
count = db.session.query(self.__class__).count()
users = db.session.query(self.__class__).all()
count = len(users)

if count != 0:
User._count = count
# account for existing users with some deleted
if users[-1].id >= User._count:
User._count = users[-1].id
else:
User._count = count

# initialze the user
self.id = User._count
Expand Down
5 changes: 3 additions & 2 deletions server/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
black==23.7.0
black==23.3.0
blinker==1.6.2
click==8.1.4
click==8.1.3
Flask==2.3.2
Flask-Cors==4.0.0
Flask-SQLAlchemy==3.0.5
itsdangerous==2.1.2
Jinja2==3.1.2
Expand Down
111 changes: 111 additions & 0 deletions server/routes/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from flask import jsonify, request

import secrets
import string


def router(id):
if request.method == "GET":
return get(id)
if request.method == "POST":
return post()
if request.method == "DELETE":
return delete(id)

res = {"status": "Unsupported HTTP request method."}

return jsonify(res), 500


def get(id):
res = {"status": ""}

from app import app, db
from models.user import User
from models.session import Session

with app.app_context():
session = db.session.query(Session).filter_by(id=id).first()

if session is None:
res["status"] = "Session not found."
return jsonify(res), 404

user = db.session.query(User).filter_by(id=session.user_id).first()

if user is None:
res["status"] = "User not found."
return jsonify(res), 404

res["status"] = "Success."
res["user"] = user.json()

return jsonify(res), 200


def post():
data = request.json

username = data.get("username")
password = data.get("password")

res = {"status": ""}

if username is None or password is None:
res["status"] = "Both `username` and `password` are required."
return jsonify(res), 400

from app import app, db
from models.user import User
from models.session import Session

with app.app_context():
user = (
db.session.query(User)
.filter_by(username=username, password=password)
.first()
)

if user is None:
res["status"] = "Username and password not found."
return jsonify(res), 404

token = "".join(
secrets.choice(string.ascii_uppercase + string.ascii_lowercase)
for _ in range(24)
)

# ensure token is unique
while db.session.query(Session).filter_by(token=token).first() is not None:
token = "".join(
secrets.choice(string.ascii_uppercase + string.ascii_lowercase)
for _ in range(24)
)

session = Session(token, user.id)
session.save_to_db()

res["status"] = "Session created."
res["session"] = session.json()

return jsonify(res), 201


def delete(id):
res = {"status": ""}

from app import app, db
from models.session import Session

with app.app_context():
session = db.session.query(Session).filter_by(id=id).first()

if session is None:
res["status"] = "Session token not found."
return jsonify(res), 404

session.delete_from_db()

res["status"] = "Session deleted."

return jsonify(res), 200
6 changes: 4 additions & 2 deletions server/routes/user.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from flask import jsonify, request

from models.user import User


# user router
def router(id):
Expand All @@ -28,6 +26,7 @@ def get(id):
res = {"status": ""}

from app import app, db
from models.user import User

with app.app_context():
users = []
Expand Down Expand Up @@ -59,6 +58,7 @@ def post():
return jsonify(res), 400

from app import app, db
from models.user import User

with app.app_context():
user = db.session.query(User).filter_by(username=username).first()
Expand Down Expand Up @@ -89,6 +89,7 @@ def put(id):
return jsonify(res), 400

from app import app, db
from models.user import User

with app.app_context():
user = db.session.query(User).filter_by(id=id).first()
Expand All @@ -112,6 +113,7 @@ def delete(id):
res = {"status": ""}

from app import app, db
from models.user import User

with app.app_context():
user = db.session.query(User).filter_by(id=id).first()
Expand Down
29 changes: 29 additions & 0 deletions server/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from flask import request, jsonify

import functools


def is_authenticated(f):
def not_authenticated():
res = {"status": "Not authenticated."}
return jsonify(res), 401

@functools.wraps(f)
def wrapper(*args, **kwargs):
token = request.cookies.get("session")

if token is None:
return not_authenticated()

from app import app, db
from models.session import Session

with app.app_context():
session = db.session.query(Session).filter_by(token=token).first()

if session is None:
return not_authenticated()

return f(*args, **kwargs)

return wrapper