diff --git a/backend/requirements.txt b/backend/requirements.txt index fa6e10273..f51c43dfc 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -10,6 +10,7 @@ click==8.1.7 cryptography==42.0.7 dnspython==2.6.1 fastapi==0.110.2 +fastapi_limiter==0.1.6 fuzzywuzzy==0.18.0 h11==0.14.0 hypothesis==6.61.0 diff --git a/backend/server/routers/auth.py b/backend/server/routers/auth.py index 57e6c637f..f73f6b580 100644 --- a/backend/server/routers/auth.py +++ b/backend/server/routers/auth.py @@ -2,7 +2,8 @@ from typing import Annotated, Dict, Optional, Tuple, Union, cast from secrets import token_hex, token_urlsafe from time import time -from fastapi import APIRouter, Cookie, HTTPException, Response, Security +from fastapi import APIRouter, Cookie, Depends, HTTPException, Response, Security +from fastapi_limiter.depends import RateLimiter # type: ignore from pydantic import BaseModel from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR @@ -101,7 +102,7 @@ def _try_get_session_info_for_logout(session_token: SessionToken, refresh_token: -@router.post('/guest_login') +@router.post('/guest_login', dependencies=[Depends(RateLimiter(times=1, hours=1))]) def create_guest_session(res: Response) -> IdentityPayload: # create new login session for user in db, generating new tokens uid = insert_new_guest_user() diff --git a/backend/server/server.py b/backend/server/server.py index 2898a2dd4..b600ce80b 100644 --- a/backend/server/server.py +++ b/backend/server/server.py @@ -3,14 +3,19 @@ """ from contextlib import asynccontextmanager + +from fastapi_limiter import FastAPILimiter # type: ignore +import redis +import redis.asyncio from data.config import LIVE_YEAR from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from server.routers import auth, courses, followups, planner, programs, specialisations, user +from server.db.redis.conn import sdb @asynccontextmanager async def on_setup_and_shutdown(_app: FastAPI): - # TODO-OLLI(pm): actually use these + await FastAPILimiter.init(redis.asyncio.Redis(**sdb.get_connection_kwargs())) print("\n\nstartup\n\n") yield print("\n\nshutdown\n\n") diff --git a/backend/server/tests/courses/test_search_course.py b/backend/server/tests/courses/test_search_course.py index 916364245..56babc732 100644 --- a/backend/server/tests/courses/test_search_course.py +++ b/backend/server/tests/courses/test_search_course.py @@ -2,7 +2,7 @@ import requests -from server.tests.user.utility import get_token, get_token_headers +from server.tests.user.utility import clear, get_token, get_token_headers with open("./algorithms/tests/exampleUsers.json", encoding="utf8") as f: USER = json.load(f)["user_degree_wizard"] @@ -10,6 +10,7 @@ def test_search_course(): + clear() token = get_token() headers = get_token_headers(token) requests.post('http://127.0.0.1:8000/user/setupDegreeWizard', headers=headers, json=USER) @@ -19,6 +20,7 @@ def test_search_course(): def test_search_archives(): + clear() token = get_token() headers = get_token_headers(token) requests.post('http://127.0.0.1:8000/user/setupDegreeWizard', headers=headers, json=USER) @@ -28,6 +30,7 @@ def test_search_archives(): def test_search_title(): + clear() token = get_token() headers = get_token_headers(token) requests.post('http://127.0.0.1:8000/user/setupDegreeWizard', headers=headers, json=USER) @@ -36,6 +39,7 @@ def test_search_title(): def test_search_minor(): + clear() token = get_token() headers = get_token_headers(token) requests.post('http://127.0.0.1:8000/user/setupDegreeWizard', headers=headers, json=USER) diff --git a/frontend/src/pages/Login/Login.tsx b/frontend/src/pages/Login/Login.tsx index ba1fd0622..8fde235d6 100644 --- a/frontend/src/pages/Login/Login.tsx +++ b/frontend/src/pages/Login/Login.tsx @@ -2,6 +2,7 @@ import React, { useCallback } from 'react'; import { Link } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; import { guestLogin as guestLoginRequest, initiateCSEAuth } from 'utils/api/authApi'; +import openNotification from 'utils/openNotification'; import BackButton from 'assets/back.svg'; import SplashArt from 'assets/splashart.svg'; import PageTemplate from 'components/PageTemplate'; @@ -23,7 +24,18 @@ const Login = () => { // - quick api call before login, although this is probs BAD // -- can just check if a refresh token is given at the login routes const guestLogin = useCallback(async () => { - const res = await guestLoginRequest(); + let res; + try { + res = await guestLoginRequest(); + } catch (_) { + openNotification({ + type: 'error', + message: "Can't log in", + description: + 'If you attempted to continue as guest, you may have hit a timeout. Either log in with your zID or wait until the timeout has cleared. Otherwise, there may be an issue with your user.' + }); + return; + } queryClient.clear(); dispatch(updateIdentityWithAPIRes(res));