Skip to content

Commit efe6e59

Browse files
Auth upgrades (#89)
* Update authentication API * fix foreign keys! * add ruff changes from updated version * test profile url shows up in db --------- Co-authored-by: Gabe <[email protected]>
1 parent e35908f commit efe6e59

30 files changed

+222
-144
lines changed

.github/workflows/ruff.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ jobs:
77
- uses: actions/checkout@v4
88
- uses: chartboost/ruff-action@v1
99
with:
10-
version: 0.4.4
10+
version: 0.6.9

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ google_key.json
99
__pycache__/
1010
*.py[cod]
1111
*$py.class
12+
13+
.venv
14+
.DS_Store

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
fastapi==0.109.1
33
gunicorn==21.2.0
44
uvicorn[standard]==0.27.1
5-
sqlalchemy==2.0.27
5+
sqlalchemy[asyncio]==2.0.27
66
asyncpg==0.29.0
77
alembic==1.13.1
88
google-api-python-client==2.143.0
@@ -13,7 +13,7 @@ xmltodict==0.13.0 # for parsing responses from sfu it's auth api
1313
requests==2.31.0
1414

1515
# dev
16-
ruff
16+
ruff==0.6.9
1717
# pre-commit
1818

1919
# test

src/alembic/env.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import asyncio
22
from logging.config import fileConfig
33

4+
from sqlalchemy import pool
5+
from sqlalchemy.engine import Connection
6+
from sqlalchemy.ext.asyncio import async_engine_from_config
7+
48
import auth.tables
59
import blog.tables
610
import database
711
import officers.tables
812
from alembic import context
9-
from sqlalchemy import pool
10-
from sqlalchemy.engine import Connection
11-
from sqlalchemy.ext.asyncio import async_engine_from_config
1213

1314
# this is the Alembic Config object, which provides
1415
# access to the values within the .ini file in use.

src/alembic/versions/166f3772fce7_auth_officer_init.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import Union
1212

1313
import sqlalchemy as sa
14+
1415
from alembic import op
1516

1617
# revision identifiers, used by Alembic.
@@ -26,6 +27,7 @@ def upgrade() -> None:
2627
sa.Column("computing_id", sa.String(32), primary_key=True),
2728
sa.Column("first_logged_in", sa.DateTime, nullable=False, default=datetime(2024, 6, 16)),
2829
sa.Column("last_logged_in", sa.DateTime, nullable=False, default=datetime(2024, 6, 16)),
30+
sa.Column("profile_picture_url", sa.Text(), nullable=True),
2931
)
3032
op.create_table(
3133
"user_session",
@@ -56,7 +58,7 @@ def upgrade() -> None:
5658
sa.Column("discord_id", sa.String(length=18), nullable=True),
5759
sa.Column("discord_name", sa.String(length=32), nullable=True),
5860
sa.Column("discord_nickname", sa.String(length=32), nullable=True),
59-
sa.Column("computing_id", sa.String(length=32), sa.ForeignKey("user_session.computing_id"), primary_key=True),
61+
sa.Column("computing_id", sa.String(length=32), sa.ForeignKey("site_user.computing_id"), primary_key=True),
6062
sa.Column("phone_number", sa.String(length=24), nullable=True),
6163
sa.Column("github_username", sa.String(length=39), nullable=True),
6264
sa.Column("google_drive_email", sa.String(length=256), nullable=True),

src/alembic/versions/2a6ea95342dc_blog_posts.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
from typing import Union
1010

1111
import sqlalchemy as sa
12+
1213
from alembic import op
1314

1415
# revision identifiers, used by Alembic.
1516
revision: str = "2a6ea95342dc"
16-
down_revision: str | None = "43f71e4bd6fc"
17+
down_revision: str | None = "166f3772fce7"
1718
branch_labels: str | Sequence[str] | None = None
1819
depends_on: str | Sequence[str] | None = None
1920

src/auth/crud.py

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import logging
22
from datetime import datetime, timedelta
3-
from typing import Optional
43

54
import sqlalchemy
6-
from auth.tables import SiteUser, UserSession
75
from sqlalchemy.ext.asyncio import AsyncSession
86

7+
from auth.tables import SiteUser, UserSession
8+
from auth.types import SiteUserData
9+
910

1011
async def create_user_session(db_session: AsyncSession, session_id: str, computing_id: str):
1112
"""
@@ -35,13 +36,6 @@ async def create_user_session(db_session: AsyncSession, session_id: str, computi
3536
# update the last time the user logged in to now
3637
existing_user.last_logged_in=datetime.now()
3738
else:
38-
new_user_session = UserSession(
39-
issue_time=datetime.now(),
40-
session_id=session_id,
41-
computing_id=computing_id,
42-
)
43-
db_session.add(new_user_session)
44-
4539
# add new user to User table if it's their first time logging in
4640
query = sqlalchemy.select(SiteUser).where(SiteUser.computing_id == computing_id)
4741
existing_user = (await db_session.scalars(query)).first()
@@ -53,30 +47,20 @@ async def create_user_session(db_session: AsyncSession, session_id: str, computi
5347
)
5448
db_session.add(new_user)
5549

50+
new_user_session = UserSession(
51+
issue_time=datetime.now(),
52+
session_id=session_id,
53+
computing_id=computing_id,
54+
)
55+
db_session.add(new_user_session)
56+
5657

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

6263

63-
async def check_user_session(db_session: AsyncSession, session_id: str) -> dict:
64-
query = sqlalchemy.select(UserSession).where(UserSession.session_id == session_id)
65-
existing_user_session = (await db_session.scalars(query)).first()
66-
67-
if existing_user_session:
68-
query = sqlalchemy.select(SiteUser).where(SiteUser.computing_id == existing_user_session.computing_id)
69-
existing_user = (await db_session.scalars(query)).first()
70-
return {
71-
"is_valid": True,
72-
"computing_id": existing_user_session.computing_id,
73-
"first_logged_in": existing_user.first_logged_in.isoformat(),
74-
"last_logged_in": existing_user.last_logged_in.isoformat()
75-
}
76-
else:
77-
return {"is_valid": False}
78-
79-
8064
async def get_computing_id(db_session: AsyncSession, session_id: str) -> str | None:
8165
query = sqlalchemy.select(UserSession).where(UserSession.session_id == session_id)
8266
existing_user_session = (await db_session.scalars(query)).first()
@@ -92,7 +76,8 @@ async def task_clean_expired_user_sessions(db_session: AsyncSession):
9276
await db_session.commit()
9377

9478

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

114-
return {
115-
"computing_id": user_session.computing_id,
116-
"first_logged_in": user.first_logged_in.isoformat(),
117-
"last_logged_in": user.last_logged_in.isoformat()
118-
}
99+
return SiteUserData(
100+
user_session.computing_id,
101+
user.first_logged_in.isoformat(),
102+
user.last_logged_in.isoformat(),
103+
user.profile_picture_url
104+
)
105+
106+
107+
# update the optional user info for a given site user (e.g., display name, profile picture, ...)
108+
async def update_site_user(
109+
db_session: AsyncSession,
110+
session_id: str,
111+
profile_picture_url: str
112+
) -> None | SiteUserData:
113+
query = (
114+
sqlalchemy
115+
.select(UserSession)
116+
.where(UserSession.session_id == session_id)
117+
)
118+
user_session = await db_session.scalar(query)
119+
if user_session is None:
120+
return None
121+
122+
query = (
123+
sqlalchemy
124+
.update(SiteUser)
125+
.where(SiteUser.computing_id == user_session.computing_id)
126+
.values(profile_picture_url = profile_picture_url)
127+
.returning(SiteUser) # returns all columns of SiteUser
128+
)
129+
user = await db_session.scalar(query)
130+
if user is None:
131+
return None
132+
133+
return SiteUserData(
134+
user_session.computing_id,
135+
user.first_logged_in.isoformat(),
136+
user.last_logged_in.isoformat(),
137+
user.profile_picture_url
138+
)

src/auth/tables.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from datetime import datetime
22

3+
from sqlalchemy import Column, DateTime, ForeignKey, String, Text
4+
35
from constants import COMPUTING_ID_LEN, SESSION_ID_LEN
46
from database import Base
5-
from sqlalchemy import Column, DateTime, ForeignKey, String
67

78

89
class UserSession(Base):
@@ -37,3 +38,6 @@ class SiteUser(Base):
3738
# note: default date (for pre-existing columns) is June 16th, 2024
3839
first_logged_in = Column(DateTime, nullable=False, default=datetime(2024, 6, 16))
3940
last_logged_in = Column(DateTime, nullable=False, default=datetime(2024, 6, 16))
41+
42+
# optional user information for display purposes
43+
profile_picture_url = Column(Text, nullable=True)

src/auth/types.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass
5+
class SiteUserData:
6+
computing_id: str
7+
first_logged_in: str
8+
last_logged_in: str
9+
profile_picture_url: None | str
10+
11+
def serializable_dict(self):
12+
return {
13+
"computing_id": self.computing_id,
14+
"first_logged_in": self.first_logged_in,
15+
"last_logged_in": self.last_logged_in,
16+
"profile_picture_url": self.profile_picture_url
17+
}

0 commit comments

Comments
 (0)