Skip to content

Commit 9ef80b3

Browse files
committed
Fix routing logic to show 404 page instead of 403 on bad urls
- Refactor routing to separate recipe and handle routes from static file serving. - Enhance the `RecipePageNotFound` exception to use HTTP 404 status code. - Update `serve_static_file` function to improve handling of paths without extensions. - Ensure static files are served when 404 or 405 exceptions occur instead of a catch-all route - Improve error handling in URL shortener to raise `HTTPException` instead of returning a response. - Extend `HandleAdmin` to include user search fields, read-only fields, and list filters.
1 parent b42c1c4 commit 9ef80b3

File tree

5 files changed

+54
-46
lines changed

5 files changed

+54
-46
lines changed

handles/admin.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
from django.contrib import admin
22

3+
from app_users.admin import AppUserAdmin
34
from .models import Handle
45

56

67
@admin.register(Handle)
78
class HandleAdmin(admin.ModelAdmin):
8-
search_fields = ["name", "redirect_url"]
9+
search_fields = ["name", "redirect_url"] + [
10+
f"user__{field}" for field in AppUserAdmin.search_fields
11+
]
12+
readonly_fields = ["user", "created_at", "updated_at"]
13+
14+
list_filter = [
15+
("user", admin.EmptyFieldListFilter),
16+
("redirect_url", admin.EmptyFieldListFilter),
17+
]

routers/root.py

+22-26
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
from daras_ai_v2.settings import templates
4444
from handles.models import Handle
4545
from routers.custom_api_router import CustomAPIRouter
46-
from routers.static_pages import serve_static_file
4746

4847
app = CustomAPIRouter()
4948

@@ -569,34 +568,30 @@ def chat_lib_route(request: Request, integration_id: str, integration_name: str
569568
)
570569

571570

571+
# this must be before the recipe_or_handle_route so that we can serve handles
572+
@gui.route(app, "/{page_slug}/")
573+
def recipe_or_handle_route(request: Request, page_slug: str):
574+
try:
575+
return render_recipe_page(request, page_slug, RecipeTabs.run, None)
576+
except RecipePageNotFound:
577+
pass
578+
try:
579+
return render_handle_page(request, page_slug)
580+
except Handle.DoesNotExist:
581+
pass
582+
raise HTTPException(status_code=404)
583+
584+
572585
@gui.route(
573586
app,
574-
"/{path:path}",
575-
"/{page_slug}/",
587+
"/{page_slug}/", # yes this is redundant, but helps to reverse urls
576588
"/{page_slug}/{run_slug}/",
577589
"/{page_slug}/{run_slug}-{example_id}/",
578590
)
579-
def recipe_or_handle_or_static(
580-
request: Request, page_slug=None, run_slug=None, example_id=None, path=None
591+
def recipe_page_route(
592+
request: Request, page_slug: str, run_slug: str = None, example_id: str = None
581593
):
582-
path = furl(request.url).pathstr.lstrip("/")
583-
584-
parts = path.strip("/").split("/")
585-
if len(parts) in {1, 2}:
586-
try:
587-
example_id = parts[1].split("-")[-1] or None
588-
except IndexError:
589-
example_id = None
590-
try:
591-
return render_recipe_page(request, parts[0], RecipeTabs.run, example_id)
592-
except RecipePageNotFound:
593-
pass
594-
try:
595-
return render_handle_page(request, parts[0])
596-
except Handle.DoesNotExist:
597-
pass
598-
599-
return serve_static_file(request, path)
594+
return render_recipe_page(request, page_slug, RecipeTabs.run, example_id)
600595

601596

602597
def render_handle_page(request: Request, name: str):
@@ -614,8 +609,9 @@ def render_handle_page(request: Request, name: str):
614609
raise HTTPException(status_code=404)
615610

616611

617-
class RecipePageNotFound(Exception):
618-
pass
612+
class RecipePageNotFound(HTTPException):
613+
def __init__(self) -> None:
614+
super().__init__(status_code=404)
619615

620616

621617
def render_recipe_page(
@@ -724,7 +720,7 @@ class RecipeTabs(TabData, Enum):
724720
run = TabData(
725721
title=f"{icons.run} Run",
726722
label="",
727-
route=recipe_or_handle_or_static,
723+
route=recipe_page_route,
728724
)
729725
examples = TabData(
730726
title=f"{icons.example} Examples",

routers/static_pages.py

+16-17
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import requests
77
from starlette.requests import Request
88
from starlette.responses import (
9-
Response,
109
RedirectResponse,
1110
HTMLResponse,
1211
PlainTextResponse,
12+
Response,
1313
)
1414
from starlette.status import HTTP_308_PERMANENT_REDIRECT, HTTP_401_UNAUTHORIZED
1515

@@ -24,20 +24,20 @@
2424
app = CustomAPIRouter()
2525

2626

27-
def serve_static_file(request: Request, path: str):
27+
def serve_static_file(request: Request) -> Response | None:
2828
bucket = gcs_bucket()
2929

30-
if path.endswith("/"):
31-
# relative css/js paths dont work with trailing slashes
32-
return RedirectResponse(
33-
os.path.join("/", path.strip("/")), status_code=HTTP_308_PERMANENT_REDIRECT
34-
)
35-
36-
path = path or "index"
37-
gcs_path = os.path.join(settings.GS_STATIC_PATH, path)
30+
relpath = request.url.path.strip("/") or "index"
31+
gcs_path = os.path.join(settings.GS_STATIC_PATH, relpath)
3832

3933
# if the path has no extension, try to serve a .html file
40-
if not os.path.splitext(gcs_path)[1]:
34+
if not os.path.splitext(relpath)[1]:
35+
# relative css/js paths in html won't work if a trailing slash is present in the url
36+
if request.url.path.lstrip("/").endswith("/"):
37+
return RedirectResponse(
38+
os.path.join("/", relpath),
39+
status_code=HTTP_308_PERMANENT_REDIRECT,
40+
)
4141
html_url = bucket.blob(gcs_path + ".html").public_url
4242
r = requests.get(html_url)
4343
if r.ok:
@@ -51,12 +51,11 @@ def serve_static_file(request: Request, path: str):
5151
)
5252
return HTMLResponse(html, status_code=r.status_code)
5353

54-
url = bucket.blob(gcs_path).public_url
55-
r = requests.head(url)
56-
if r.ok:
57-
return RedirectResponse(url, status_code=HTTP_308_PERMANENT_REDIRECT)
58-
59-
return Response(status_code=r.status_code)
54+
blob = bucket.blob(gcs_path)
55+
if blob.exists():
56+
return RedirectResponse(
57+
blob.public_url, status_code=HTTP_308_PERMANENT_REDIRECT
58+
)
6059

6160

6261
@gui.route(app, "/internal/webflow-upload/")

server.py

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from daras_ai_v2.pydantic_validation import convert_errors
1919
from daras_ai_v2.settings import templates
2020
from gooeysite import wsgi
21+
from routers.static_pages import serve_static_file
2122

2223
assert wsgi
2324

@@ -124,6 +125,9 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE
124125
@app.exception_handler(HTTP_404_NOT_FOUND)
125126
@app.exception_handler(HTTP_405_METHOD_NOT_ALLOWED)
126127
async def not_found_exception_handler(request: Request, exc: HTTPException):
128+
resp = serve_static_file(request)
129+
if resp is not None:
130+
return resp
127131
return await _exc_handler(request, exc, "errors/404.html")
128132

129133

url_shortener/routers.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from django.db.models import F
2-
from fastapi import Request
2+
from fastapi import Request, HTTPException
33
from fastapi.responses import RedirectResponse
44
from fastapi.responses import Response
55

@@ -15,7 +15,7 @@ def url_shortener(hashid: str, request: Request):
1515
try:
1616
surl = ShortenedURL.objects.get_by_hashid(hashid)
1717
except ShortenedURL.DoesNotExist:
18-
return Response(status_code=404)
18+
raise HTTPException(status_code=404)
1919
# ensure that the url is not disabled and has not exceeded max clicks
2020
if surl.disabled or (surl.max_clicks and surl.clicks >= surl.max_clicks):
2121
return Response(status_code=410, content="This link has expired")

0 commit comments

Comments
 (0)