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
20 changes: 18 additions & 2 deletions bases/rsptx/book_server_api/routers/coach.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from .assessment import get_question_source, SelectQRequest

# Import function for fetching api - comment out for DEV purposes
from rsptx.auth.session import auth_manager
from rsptx.auth.session import get_optional_user
from rsptx.db.crud.crud import fetch_api_token
from rsptx.db.crud.course import fetch_course
from rsptx.db.crud.question import fetch_question
Expand Down Expand Up @@ -202,7 +202,7 @@ async def get_question_html(request: Request, div_id: str):
# @router.post("/ns/coach/parsons_scaffolding")
@router.post("/parsons_scaffolding")
async def parsons_scaffolding(
request: Request, course: Optional[str], user=Depends(auth_manager)
request: Request, course: Optional[str], user=Depends(get_optional_user)
):
# Get `course` directly from the query string
rslogger.warning(f"URL seen: {request.url}")
Expand Down Expand Up @@ -240,6 +240,22 @@ async def parsons_scaffolding(
else:
parsonsexample_code = "LLM-example"

if user is None:
# Browsing-mode/anonymous visitors can get the pre-authored backup
# Parsons problem, but never the CodeTailor/LLM-personalized one --
# that requires an account so we can call out to the LLM on their
# behalf and track usage.
if parsonsexample != "LLM-example" and parsonsexample_html:
return _build_static_parsons_response(
parsonsexample_code, parsons_attrs, personalization_level
)
return JSONResponse(
content={
"detail": "You need to be logged in to Runestone to access this resource"
},
status_code=status.HTTP_401_UNAUTHORIZED,
)

# Fetch API token
api_token = None
try:
Expand Down
13 changes: 13 additions & 0 deletions components/rsptx/auth/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ async def _load_user(user_id: str) -> AuthUserValidator:
load_user = cast(Callable[[str], Awaitable[AuthUserValidator]], _load_user)


async def get_optional_user(request: Request) -> Optional[AuthUserValidator]:
"""Return the authenticated user, or None for anonymous/browsing-mode visitors.

Unlike ``Depends(auth_manager)``, this never raises -- it lets the route
itself decide what to do for logged-out users (e.g. serve a static/backup
resource instead of one that requires an account).
"""
try:
return await auth_manager(request)
except NotAuthenticatedException:
return None


async def is_instructor(
request: Request, user: Optional[AuthUserValidator] = None
) -> bool:
Expand Down
Loading