Skip to content

Commit 22e3d4b

Browse files
authored
Deprecates branch_short_code, auto-refreshes default CSRF token and updates contributor section on README (#111)
2 parents 5e433a9 + 4af3002 commit 22e3d4b

File tree

14 files changed

+102
-212
lines changed

14 files changed

+102
-212
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ Please provide a concise summary of the changes:
7777
7878
- [ ] `app/app.py` – Modified `/authenticate` route logic
7979
- [ ] `app/pesu.py` – Updated scraping or authentication handling
80-
- [ ] `app/constants.py` – Added or updated constant values
8180
- [ ] `app/util.py` – General helper functions
8281

8382

.github/workflows/sync.yaml

Lines changed: 0 additions & 62 deletions
This file was deleted.

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ If the authentication fails, this field will not be present in the response.
133133
| `prn` | PRN of the user |
134134
| `srn` | SRN of the user |
135135
| `program` | Academic program that the user is enrolled into |
136-
| `branch_short_code` | Abbreviation of the branch that the user is pursuing |
137136
| `branch` | Complete name of the branch that the user is pursuing |
138137
| `semester` | Current semester that the user is in |
139138
| `section` | Section of the user |
@@ -173,7 +172,6 @@ print(response.json())
173172
"prn": "PES1201800001",
174173
"srn": "PES1201800001",
175174
"program": "Bachelor of Technology",
176-
"branch_short_code": "CSE",
177175
"branch": "Computer Science and Engineering",
178176
"semester": "NA",
179177
"section": "NA",
@@ -214,7 +212,7 @@ curl -X POST http://localhost:5000/authenticate \
214212

215213
Made with ❤️ by
216214

217-
[![Contributors](https://contrib.rocks/image?repo=pesu-dev/auth)](https://github.com/pesu-dev/auth/graphs/contributors)
215+
[![Contributors](https://contrib.rocks/image?repo=pesu-dev/auth&nocache=1)](https://github.com/pesu-dev/auth/graphs/contributors)
218216

219217
*Powered by [contrib.rocks](https://contrib.rocks)*
220218

app/app.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import argparse
22
import datetime
33
import logging
4+
import asyncio
45

56
import pytz
67
import uvicorn
@@ -18,6 +19,31 @@
1819

1920
IST = pytz.timezone("Asia/Kolkata")
2021
README_HTML_CACHE: str | None = None
22+
REFRESH_INTERVAL_SECONDS = 45 * 60
23+
CSRF_TOKEN_REFRESH_LOCK = asyncio.Lock()
24+
25+
26+
async def _refresh_csrf_token_with_lock():
27+
"""
28+
Refresh the CSRF token with a lock.
29+
"""
30+
logging.debug("Refreshing unauthenticated CSRF token...")
31+
async with CSRF_TOKEN_REFRESH_LOCK:
32+
await pesu_academy.prefetch_client_with_csrf_token()
33+
logging.info("Unauthenticated CSRF token refreshed successfully.")
34+
35+
36+
async def _csrf_token_refresh_loop():
37+
"""
38+
Background task to refresh the CSRF token periodically.
39+
"""
40+
while True:
41+
try:
42+
logging.debug("Refreshing unauthenticated CSRF token...")
43+
await _refresh_csrf_token_with_lock()
44+
except Exception:
45+
logging.exception("Failed to refresh unauthenticated CSRF token in the background.")
46+
await asyncio.sleep(REFRESH_INTERVAL_SECONDS)
2147

2248

2349
@asynccontextmanager
@@ -37,12 +63,26 @@ async def lifespan(app: FastAPI):
3763
logging.exception(
3864
"Failed to generate README.html on startup. Subsequent requests to /readme will attempt to regenerate it."
3965
)
66+
4067
# Prefetch PESUAcademy client for first request
4168
await pesu_academy.prefetch_client_with_csrf_token()
4269
logging.info("Prefetched a new PESUAcademy client with an unauthenticated CSRF token.")
70+
71+
# Start the periodic CSRF token refresh background task
72+
refresh_task = asyncio.create_task(_csrf_token_refresh_loop())
73+
logging.info("Started the unauthenticated CSRF token refresh background task.")
74+
4375
yield
4476

4577
# Shutdown
78+
refresh_task.cancel()
79+
try:
80+
await refresh_task
81+
except asyncio.CancelledError:
82+
logging.debug("Unauthenticated CSRF token refresh background task cancelled.")
83+
except Exception:
84+
logging.exception("Failed to cancel unauthenticated CSRF token refresh background task.")
85+
4686
await pesu_academy.close_client()
4787
logging.info("PESUAuth API shutdown.")
4888

@@ -164,7 +204,7 @@ async def authenticate(payload: RequestModel, background_tasks: BackgroundTasks)
164204
)
165205
)
166206
# Prefetch a new client with an unauthenticated CSRF token for the next request
167-
background_tasks.add_task(pesu_academy.prefetch_client_with_csrf_token)
207+
background_tasks.add_task(_refresh_csrf_token_with_lock)
168208

169209
# Validate the response
170210
try:

app/constants.py

Lines changed: 0 additions & 30 deletions
This file was deleted.

app/models/profile.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@ class ProfileModel(BaseModel):
2929
description="Academic program that the user is enrolled in.",
3030
json_schema_extra={"example": "Bachelor of Technology"},
3131
)
32-
branch_short_code: str | None = Field(
33-
None,
34-
title="Branch Short Code",
35-
description="Abbreviation of the branch the user is pursuing.",
36-
json_schema_extra={"example": "CSE"},
37-
)
3832
branch: str | None = Field(
3933
None,
4034
title="Branch",

app/models/request.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pydantic import BaseModel, field_validator, ConfigDict, Field
2-
from app.constants import PESUAcademyConstants
2+
from app.pesu import PESUAcademy
33
from typing import Literal
44

55

@@ -27,7 +27,7 @@ class RequestModel(BaseModel):
2727
json_schema_extra={"example": True},
2828
)
2929

30-
fields: list[Literal[*PESUAcademyConstants.DEFAULT_FIELDS]] | None = Field(
30+
fields: list[Literal[*PESUAcademy.DEFAULT_FIELDS]] | None = Field(
3131
None,
3232
title="Profile Fields",
3333
description=(

app/pesu.py

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import httpx
88
from selectolax.parser import HTMLParser, Node
9-
from app.constants import PESUAcademyConstants
109

1110
from app.exceptions.authentication import (
1211
ProfileFetchError,
@@ -21,6 +20,20 @@ class PESUAcademy:
2120
Class to interact with the PESU Academy website.
2221
"""
2322

23+
DEFAULT_FIELDS: list[str] = [
24+
"name",
25+
"prn",
26+
"srn",
27+
"program",
28+
"branch",
29+
"semester",
30+
"section",
31+
"email",
32+
"phone",
33+
"campus_code",
34+
"campus",
35+
]
36+
2437
PROFILE_PAGE_HEADER_TO_KEY_MAP = {
2538
"Name": "name",
2639
"PESU Id": "prn",
@@ -108,13 +121,6 @@ def parse_and_update():
108121
if mapped_key := self.PROFILE_PAGE_HEADER_TO_KEY_MAP.get(key):
109122
logging.debug(f"Adding key: '{mapped_key}', value: '{value}' to profile...")
110123
profile[mapped_key] = value
111-
if mapped_key == "branch" and (
112-
branch_short_code := self.map_branch_to_short_code(value)
113-
):
114-
profile["branch_short_code"] = branch_short_code
115-
logging.debug(
116-
f"Adding key: 'branch_short_code', value: '{branch_short_code}' to profile..."
117-
)
118124
else:
119125
raise ProfileParseError(
120126
f"Unknown key: '{key}' in the profile page. The webpage might have changed."
@@ -132,22 +138,6 @@ async def close_client(self):
132138
await self._client.aclose()
133139
self._client = None
134140

135-
@staticmethod
136-
def map_branch_to_short_code(branch: str) -> str | None:
137-
"""
138-
Map the branch name to its short code.
139-
140-
Args:
141-
branch (str): The full name of the branch.
142-
143-
Returns:
144-
Optional[str]: The short code for the branch if it exists, otherwise None.
145-
"""
146-
logging.warning(
147-
"Branch short code mapping will be deprecated in future versions. If you require acronyms, please do it application-side."
148-
)
149-
return PESUAcademyConstants.BRANCH_SHORT_CODES.get(branch)
150-
151141
async def get_profile_information(
152142
self, client: httpx.AsyncClient, username: str
153143
) -> dict[str, Any]:
@@ -253,9 +243,9 @@ async def authenticate(
253243
dict[str, Any]: A dictionary containing the authentication status, message, and optionally the profile information.
254244
"""
255245
# Default fields to fetch if fields is not provided
256-
fields = PESUAcademyConstants.DEFAULT_FIELDS if fields is None else fields
246+
fields = self.DEFAULT_FIELDS if fields is None else fields
257247
# Check if fields is not the default fields and enable field filtering
258-
field_filtering = fields != PESUAcademyConstants.DEFAULT_FIELDS
248+
field_filtering = fields != self.DEFAULT_FIELDS
259249

260250
logging.info(
261251
f"Connecting to PESU Academy with user={username}, profile={profile}, fields={fields} ..."

scripts/benchmark/unauthenticated_csrf_token_expiry.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ def test_response(response: dict, no_profile: bool) -> bool:
3838
default=10,
3939
help="The base interval between requests (default: 10 minutes)",
4040
)
41+
parser.add_argument(
42+
"--start-delay",
43+
type=int,
44+
default=0,
45+
help="The delay before starting the benchmark (default: 0 minutes)",
46+
)
4147
parser.add_argument(
4248
"--output",
4349
type=str,
@@ -49,7 +55,16 @@ def test_response(response: dict, no_profile: bool) -> bool:
4955
)
5056
args = parser.parse_args()
5157

52-
request_count, success, times, waiting_times = 0, list(), list(), [0]
58+
request_count, success, times, waiting_times = 0, list(), list(), [args.start_delay * 60]
59+
60+
for _ in tqdm(
61+
range(args.start_delay * 60),
62+
desc=f"Waiting {args.start_delay} minutes before starting the benchmark",
63+
leave=False,
64+
unit="s",
65+
):
66+
time.sleep(1)
67+
5368
while True:
5469
request_count += 1
5570
response, elapsed = make_request(

0 commit comments

Comments
 (0)