Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ env/
*.db
.coverage
*.vscode
.env
39 changes: 25 additions & 14 deletions config.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
# Import os
import os
from os import getenv, path
from datetime import timedelta

basedir = os.path.abspath(os.path.dirname(__file__))
from dotenv import load_dotenv

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


class Config:
load_dotenv()

# Configuration
SECRET_KEY = os.environ.get("SECRET_KEY", os.urandom(32))
JWT_SECRET_KEY = os.environ.get("JWT_SECRET_KEY", os.urandom(32))
JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1)
SECRET_KEY = getenv("SECRET_KEY", "main-secret")

TESTING = False
DEBUG = False

PREFERRED_URL_SCHEME = "https"

SAML_CONFIG = os.path.join(basedir, "config/saml/")
FRONTEND_URL = os.environ.get("FRONTEND_URL", "http://localhost:3000")
SAML_CONFIG = path.join(basedir, "config/saml/")
FRONTEND_URL = getenv("FRONTEND_URL", "http://localhost:3000")

SENTRY_DSN = os.environ.get("SENTRY_DSN", "")
SENTRY_TRACES_SAMPLE_RATE = float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", 1.0))
SENTRY_PROFILES_SAMPLE_RATE = float(
os.environ.get("SENTRY_PROFILES_SAMPLE_RATE", 1.0)
)
SENTRY_DSN = getenv("SENTRY_DSN", "")
SENTRY_TRACES_SAMPLE_RATE = float(getenv("SENTRY_TRACES_SAMPLE_RATE", 1.0))
SENTRY_PROFILES_SAMPLE_RATE = float(getenv("SENTRY_PROFILES_SAMPLE_RATE", 1.0))

SQLALCHEMY_DATABASE_URI = os.environ.get(
SQLALCHEMY_DATABASE_URI = getenv(
"DB", "postgresql+psycopg2://postgres:root@localhost/labconnect"
)

TOKEN_BLACKLIST = set()
JWT_SECRET_KEY = getenv("JWT_SECRET_KEY", "jwt-secret")
JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1)
JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=7)
JWT_SESSION_COOKIE = timedelta(hours=1)
JWT_TOKEN_LOCATION = ["cookies"]
JWT_COOKIE_CSRF_PROTECT = True
JWT_CSRF_CHECK_FORM = True
JWT_COOKIE_SECURE = True
JWT_COOKIE_SAMESITE = "Strict"
JWT_ACCESS_COOKIE_NAME = "access_token"
JWT_REFRESH_COOKIE_NAME = "refresh_token"


class TestingConfig(Config):
TESTING = True
DEBUG = True
JWT_COOKIE_SECURE = False


class ProductionConfig(Config):
Expand Down
28 changes: 23 additions & 5 deletions db_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
UserDepartments,
UserMajors,
UserSavedOpportunities,
Codes,
)

app = create_app()
Expand Down Expand Up @@ -115,8 +116,20 @@

lab_manager_rows = (
("led", "Duy", "Le", "CSCI", "database database database"),
("turner", "Wes","Turner","CSCI","open source stuff is cool",),
("kuzmin","Konstantine","Kuzmin","CSCI","java, psoft, etc.",),
(
"turner",
"Wes",
"Turner",
"CSCI",
"open source stuff is cool",
),
(
"kuzmin",
"Konstantine",
"Kuzmin",
"CSCI",
"java, psoft, etc.",
),
("goldd", "David", "Goldschmidt", "CSCI", "VIM master"),
("rami", "Rami", "Rami", "MTLE", "cubes are cool"),
("holm", "Mark", "Holmes", "MATH", "all about that math"),
Expand Down Expand Up @@ -399,7 +412,12 @@
db.session.add(row)
db.session.commit()

participates_rows = (("cenzar", 1),("cenzar", 2),("test", 3),("test", 4),)
participates_rows = (
("cenzar", 1),
("cenzar", 2),
("test", 3),
("test", 4),
)

for r in participates_rows:
row = Participates()
Expand All @@ -409,7 +427,7 @@
db.session.add(row)
db.session.commit()

tables = [
tables = [
ClassYears,
Courses,
Leads,
Expand All @@ -425,7 +443,7 @@
UserCourses,
UserDepartments,
UserMajors,
UserSavedOpportunities
UserSavedOpportunities,
]

for table in tables:
Expand Down
Binary file modified docs/database_docs/Labconnect_DB.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion labconnect/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ class LocationEnum(EnumPython):
REMOTE = "Remote"


class OrJSONProvider(JSONProvider):
class LabManagerTypeEnum(EnumPython):
PI = "Principal Investigator"
CO_PI = "Co-Principal Investigator"
LAB_MANAGER = "Lab Manager"
POST_DOC = "Post Doctoral Researcher"
GRAD_STUDENT = "Graduate Student"


class OrJSONProvider(JSONProvider):
@staticmethod
def dumps(obj, *, option=None, **kwargs):
if option is None:
Expand Down
105 changes: 68 additions & 37 deletions labconnect/main/auth_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@
from uuid import uuid4

from flask import current_app, make_response, redirect, request, abort
from flask_jwt_extended import create_access_token
from flask_jwt_extended import (
get_jwt_identity,
create_access_token,
create_refresh_token,
set_access_cookies,
set_refresh_cookies,
unset_jwt_cookies,
jwt_required,
)
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from werkzeug.wrappers.response import Response

from labconnect import db
from labconnect.helpers import prepare_flask_request
Expand Down Expand Up @@ -35,7 +44,7 @@ def generate_temporary_code(user_email: str, registered: bool) -> str:
return code


def validate_code_and_get_user_email(code: str) -> tuple[str | None, bool | None]:
def validate_code_and_get_user_email(code: str) -> tuple[str, bool] | tuple[None, None]:
code_data = db.session.execute(db.select(Codes).where(Codes.code == code)).scalar()
if not code_data:
return None, None
Expand All @@ -44,20 +53,20 @@ def validate_code_and_get_user_email(code: str) -> tuple[str | None, bool | None
expire = code_data.expires_at
registered = code_data.registered

if user_email and expire and expire > datetime.now():
# If found, delete the code to prevent reuse
db.session.delete(code_data)
return user_email, registered
elif expire:
# If the code has expired, delete it
db.session.delete(code_data)
if user_email and expire:
if expire > datetime.now():
# If found, delete the code to prevent reuse
db.session.delete(code_data)
return user_email, registered
else:
# If the code has expired, delete it
db.session.delete(code_data)

return None, None


@main_blueprint.get("/login")
def saml_login():

def saml_login() -> Response:
# In testing skip RPI login purely for local development
if current_app.config["TESTING"] and (
current_app.config["FRONTEND_URL"] == "http://localhost:3000"
Expand All @@ -76,7 +85,7 @@ def saml_login():


@main_blueprint.post("/callback")
def saml_callback():
def saml_callback() -> Response:
# Process SAML response
req = prepare_flask_request(request)
auth = OneLogin_Saml2_Auth(req, custom_base_path=current_app.config["SAML_CONFIG"])
Expand All @@ -100,12 +109,33 @@ def saml_callback():
return redirect(f"{current_app.config['FRONTEND_URL']}/callback/?code={code}")

error_reason = auth.get_last_error_reason()
return {"errors": errors, "error_reason": error_reason}, 500
return make_response({"errors": errors, "error_reason": error_reason}, 500)


@main_blueprint.post("/register")
def registerUser():
@main_blueprint.post("/token")
def tokenRoute() -> Response:
if request.json is None or request.json.get("code", None) is None:
return make_response({"msg": "Missing JSON body in request"}, 400)

# Validate the temporary code
code = request.json["code"]
if code is None:
return make_response({"msg": "Missing code in request"}, 400)

user_email, registered = validate_code_and_get_user_email(code)
if user_email is None:
return make_response({"msg": "Invalid code"}, 400)

access_token = create_access_token(identity=user_email)
refresh_token = create_refresh_token(identity=user_email)
resp = make_response({"registered": registered})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp


@main_blueprint.post("/register")
def registerUser() -> Response:
# Gather the new user's information
json_data = request.get_json()
if not json_data:
Expand Down Expand Up @@ -162,28 +192,11 @@ def registerUser():
db.session.add(management_permissions)

db.session.commit()
return {"msg": "New user added"}


@main_blueprint.post("/token")
def tokenRoute():
if request.json is None or request.json.get("code", None) is None:
return {"msg": "Missing JSON body in request"}, 400
# Validate the temporary code
code = request.json["code"]
if code is None:
return {"msg": "Missing code in request"}, 400
user_email, registered = validate_code_and_get_user_email(code)

if user_email is None:
return {"msg": "Invalid code"}, 400

token = create_access_token(identity=[user_email, datetime.now()])
return {"token": token, "registered": registered}
return make_response({"msg": "New user added"})


@main_blueprint.get("/metadata/")
def metadataRoute():
def metadataRoute() -> Response:
req = prepare_flask_request(request)
auth = auth = OneLogin_Saml2_Auth(
req, custom_base_path=current_app.config["SAML_CONFIG"]
Expand All @@ -200,7 +213,25 @@ def metadataRoute():
return resp


@main_blueprint.get("/authcheck")
@jwt_required()
def authcheck() -> Response:
return make_response({"msg": "authenticated"})


@main_blueprint.get("/token/refresh")
@jwt_required(refresh=True)
def refresh() -> Response:
# Refreshing expired Access token
user_id = get_jwt_identity()
access_token = create_access_token(identity=str(user_id))
resp = make_response({"msg": "refresh successful"})
set_access_cookies(resp, access_token)
return resp


@main_blueprint.get("/logout")
def logout():
# TODO: add token to blacklist
return {"msg": "logout successful"}
def logout() -> Response:
resp = make_response({"msg": "logout successful"})
unset_jwt_cookies(resp)
return resp
Loading
Loading