Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/ruff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ jobs:
- uses: actions/checkout@v4
- uses: chartboost/ruff-action@v1
with:
version: 0.4.4
version: 0.6.9
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ google_key.json
__pycache__/
*.py[cod]
*$py.class

.venv
.DS_Store
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
fastapi==0.109.1
gunicorn==21.2.0
uvicorn[standard]==0.27.1
sqlalchemy==2.0.27
sqlalchemy[asyncio]==2.0.27
asyncpg==0.29.0
alembic==1.13.1
google-api-python-client==2.143.0
Expand All @@ -13,7 +13,7 @@ xmltodict==0.13.0 # for parsing responses from sfu it's auth api
requests==2.31.0

# dev
ruff
ruff==0.6.9
# pre-commit

# test
Expand Down
7 changes: 4 additions & 3 deletions src/alembic/env.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import asyncio
from logging.config import fileConfig

from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config

import auth.tables
import blog.tables
import database
import officers.tables
from alembic import context
from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
Expand Down
4 changes: 3 additions & 1 deletion src/alembic/versions/166f3772fce7_auth_officer_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Union

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
Expand All @@ -26,6 +27,7 @@ def upgrade() -> None:
sa.Column("computing_id", sa.String(32), primary_key=True),
sa.Column("first_logged_in", sa.DateTime, nullable=False, default=datetime(2024, 6, 16)),
sa.Column("last_logged_in", sa.DateTime, nullable=False, default=datetime(2024, 6, 16)),
sa.Column("profile_picture_url", sa.Text(), nullable=True),
)
op.create_table(
"user_session",
Expand Down Expand Up @@ -56,7 +58,7 @@ def upgrade() -> None:
sa.Column("discord_id", sa.String(length=18), nullable=True),
sa.Column("discord_name", sa.String(length=32), nullable=True),
sa.Column("discord_nickname", sa.String(length=32), nullable=True),
sa.Column("computing_id", sa.String(length=32), sa.ForeignKey("user_session.computing_id"), primary_key=True),
sa.Column("computing_id", sa.String(length=32), sa.ForeignKey("site_user.computing_id"), primary_key=True),
sa.Column("phone_number", sa.String(length=24), nullable=True),
sa.Column("github_username", sa.String(length=39), nullable=True),
sa.Column("google_drive_email", sa.String(length=256), nullable=True),
Expand Down
3 changes: 2 additions & 1 deletion src/alembic/versions/2a6ea95342dc_blog_posts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
from typing import Union

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "2a6ea95342dc"
down_revision: str | None = "43f71e4bd6fc"
down_revision: str | None = "166f3772fce7"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None

Expand Down
84 changes: 52 additions & 32 deletions src/auth/crud.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import logging
from datetime import datetime, timedelta
from typing import Optional

import sqlalchemy
from auth.tables import SiteUser, UserSession
from sqlalchemy.ext.asyncio import AsyncSession

from auth.tables import SiteUser, UserSession
from auth.types import SiteUserData


async def create_user_session(db_session: AsyncSession, session_id: str, computing_id: str):
"""
Expand Down Expand Up @@ -35,13 +36,6 @@ async def create_user_session(db_session: AsyncSession, session_id: str, computi
# update the last time the user logged in to now
existing_user.last_logged_in=datetime.now()
else:
new_user_session = UserSession(
issue_time=datetime.now(),
session_id=session_id,
computing_id=computing_id,
)
db_session.add(new_user_session)

# add new user to User table if it's their first time logging in
query = sqlalchemy.select(SiteUser).where(SiteUser.computing_id == computing_id)
existing_user = (await db_session.scalars(query)).first()
Expand All @@ -53,30 +47,20 @@ async def create_user_session(db_session: AsyncSession, session_id: str, computi
)
db_session.add(new_user)

new_user_session = UserSession(
issue_time=datetime.now(),
session_id=session_id,
computing_id=computing_id,
)
db_session.add(new_user_session)


async def remove_user_session(db_session: AsyncSession, session_id: str) -> dict:
query = sqlalchemy.select(UserSession).where(UserSession.session_id == session_id)
user_session = await db_session.scalars(query)
await db_session.delete(user_session.first())


async def check_user_session(db_session: AsyncSession, session_id: str) -> dict:
query = sqlalchemy.select(UserSession).where(UserSession.session_id == session_id)
existing_user_session = (await db_session.scalars(query)).first()

if existing_user_session:
query = sqlalchemy.select(SiteUser).where(SiteUser.computing_id == existing_user_session.computing_id)
existing_user = (await db_session.scalars(query)).first()
return {
"is_valid": True,
"computing_id": existing_user_session.computing_id,
"first_logged_in": existing_user.first_logged_in.isoformat(),
"last_logged_in": existing_user.last_logged_in.isoformat()
}
else:
return {"is_valid": False}


async def get_computing_id(db_session: AsyncSession, session_id: str) -> str | None:
query = sqlalchemy.select(UserSession).where(UserSession.session_id == session_id)
existing_user_session = (await db_session.scalars(query)).first()
Expand All @@ -92,7 +76,8 @@ async def task_clean_expired_user_sessions(db_session: AsyncSession):
await db_session.commit()


async def user_info(db_session: AsyncSession, session_id: str) -> None | dict:
# get the site user given a session ID; returns None when session is invalid
async def get_site_user(db_session: AsyncSession, session_id: str) -> None | SiteUserData:
query = (
sqlalchemy
.select(UserSession)
Expand All @@ -111,8 +96,43 @@ async def user_info(db_session: AsyncSession, session_id: str) -> None | dict:
if user is None:
return None

return {
"computing_id": user_session.computing_id,
"first_logged_in": user.first_logged_in.isoformat(),
"last_logged_in": user.last_logged_in.isoformat()
}
return SiteUserData(
user_session.computing_id,
user.first_logged_in.isoformat(),
user.last_logged_in.isoformat(),
user.profile_picture_url
)


# update the optional user info for a given site user (e.g., display name, profile picture, ...)
async def update_site_user(
db_session: AsyncSession,
session_id: str,
profile_picture_url: str
) -> None | SiteUserData:
query = (
sqlalchemy
.select(UserSession)
.where(UserSession.session_id == session_id)
)
user_session = await db_session.scalar(query)
if user_session is None:
return None

query = (
sqlalchemy
.update(SiteUser)
.where(SiteUser.computing_id == user_session.computing_id)
.values(profile_picture_url = profile_picture_url)
.returning(SiteUser) # returns all columns of SiteUser
)
user = await db_session.scalar(query)
if user is None:
return None

return SiteUserData(
user_session.computing_id,
user.first_logged_in.isoformat(),
user.last_logged_in.isoformat(),
user.profile_picture_url
)
6 changes: 5 additions & 1 deletion src/auth/tables.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from datetime import datetime

from sqlalchemy import Column, DateTime, ForeignKey, String, Text

from constants import COMPUTING_ID_LEN, SESSION_ID_LEN
from database import Base
from sqlalchemy import Column, DateTime, ForeignKey, String


class UserSession(Base):
Expand Down Expand Up @@ -37,3 +38,6 @@ class SiteUser(Base):
# note: default date (for pre-existing columns) is June 16th, 2024
first_logged_in = Column(DateTime, nullable=False, default=datetime(2024, 6, 16))
last_logged_in = Column(DateTime, nullable=False, default=datetime(2024, 6, 16))

# optional user information for display purposes
profile_picture_url = Column(Text, nullable=True)
17 changes: 17 additions & 0 deletions src/auth/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from dataclasses import dataclass


@dataclass
class SiteUserData:
computing_id: str
first_logged_in: str
last_logged_in: str
profile_picture_url: None | str

def serializable_dict(self):
return {
"computing_id": self.computing_id,
"first_logged_in": self.first_logged_in,
"last_logged_in": self.last_logged_in,
"profile_picture_url": self.profile_picture_url
}
Loading
Loading