Redirect to a better error page on server error

This commit is contained in:
Debanjum
2025-07-29 18:53:33 -07:00
parent 6caa6f4008
commit bbc14951b4
3 changed files with 228 additions and 15 deletions

View File

@@ -11,9 +11,15 @@ import requests
import schedule
from asgiref.sync import sync_to_async
from django.conf import settings
from django.db import close_old_connections, connections
from django.db import (
DatabaseError,
OperationalError,
close_old_connections,
connections,
)
from django.utils.timezone import make_aware
from fastapi import Request, Response
from fastapi import HTTPException, Request, Response
from fastapi.responses import RedirectResponse
from starlette.authentication import (
AuthCredentials,
AuthenticationBackend,
@@ -113,13 +119,24 @@ class UserAuthenticationBackend(AuthenticationBackend):
Subscription.objects.create(user=default_user, type=Subscription.Type.STANDARD, renewal_date=renewal_date)
async def authenticate(self, request: HTTPConnection):
# Skip authentication for error pages to avoid infinite recursion
if request.url.path == "/server/error":
return AuthCredentials(), UnauthenticatedUser()
current_user = request.session.get("user")
if current_user and current_user.get("email"):
user = (
await self.khojuser_manager.filter(email=current_user.get("email"))
.prefetch_related("subscription")
.afirst()
)
try:
user = (
await self.khojuser_manager.filter(email=current_user.get("email"))
.prefetch_related("subscription")
.afirst()
)
except (DatabaseError, OperationalError):
logger.error("DB Exception: Failed to authenticate user", exc_info=True)
raise HTTPException(
status_code=503,
detail="Please report this issue on Github, Discord or email team@khoj.dev and try again later.",
)
if user:
subscribed = await ais_user_subscribed(user)
if subscribed:
@@ -131,12 +148,19 @@ class UserAuthenticationBackend(AuthenticationBackend):
# Get bearer token from header
bearer_token = request.headers["Authorization"].split("Bearer ")[1]
# Get user owning token
user_with_token = (
await self.khojapiuser_manager.filter(token=bearer_token)
.select_related("user")
.prefetch_related("user__subscription")
.afirst()
)
try:
user_with_token = (
await self.khojapiuser_manager.filter(token=bearer_token)
.select_related("user")
.prefetch_related("user__subscription")
.afirst()
)
except (DatabaseError, OperationalError):
logger.error("DB Exception: Failed to authenticate user applications", exc_info=True)
raise HTTPException(
status_code=503,
detail="Please report this issue on Github, Discord or email team@khoj.dev and try again later.",
)
if user_with_token:
subscribed = await ais_user_subscribed(user_with_token.user)
if subscribed:
@@ -155,7 +179,16 @@ class UserAuthenticationBackend(AuthenticationBackend):
)
# Get the client application
client_application = await ClientApplicationAdapters.aget_client_application_by_id(client_id, client_secret)
try:
client_application = await ClientApplicationAdapters.aget_client_application_by_id(
client_id, client_secret
)
except (DatabaseError, OperationalError):
logger.error("DB Exception: Failed to authenticate first party application", exc_info=True)
raise HTTPException(
status_code=503,
detail="Please report this issue on Github, Discord or email team@khoj.dev and try again later.",
)
if client_application is None:
return AuthCredentials(), UnauthenticatedUser()
# Get the identifier used for the user
@@ -185,7 +218,14 @@ class UserAuthenticationBackend(AuthenticationBackend):
# No auth required if server in anonymous mode
if state.anonymous_mode:
user = await self.khojuser_manager.filter(username="default").prefetch_related("subscription").afirst()
try:
user = await self.khojuser_manager.filter(username="default").prefetch_related("subscription").afirst()
except (DatabaseError, OperationalError):
logger.error("DB Exception: Failed to fetch default user from DB", exc_info=True)
raise HTTPException(
status_code=503,
detail="Please report this issue on Github, Discord or email team@khoj.dev and try again later.",
)
if user:
return AuthCredentials(["authenticated", "premium"]), AuthenticatedKhojUser(user)
@@ -368,11 +408,30 @@ def configure_middleware(app, ssl_enabled: bool = False):
# and prevent further error logging.
return Response(status_code=499)
class ServerErrorMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
return await call_next(request)
except HTTPException as e:
# Check if this is a server error (5xx) that we want to handle
if e.status_code >= 500 and e.status_code < 600:
# Check if this is a web route (not API route)
path = request.url.path
is_api_route = path.startswith("/api/") or path.startswith("/server/")
# Redirect web routes to error page, let API routes get the raw error
if not is_api_route:
return RedirectResponse(url="/server/error", status_code=302)
# Re-raise for API routes and non-5xx errors
raise e
if ssl_enabled:
app.add_middleware(HTTPSRedirectMiddleware)
app.add_middleware(SuppressClientDisconnectMiddleware)
app.add_middleware(AsyncCloseConnectionsMiddleware)
app.add_middleware(AuthenticationMiddleware, backend=UserAuthenticationBackend())
app.add_middleware(ServerErrorMiddleware) # Add after AuthenticationMiddleware to catch its exceptions
app.add_middleware(NextJsMiddleware)
app.add_middleware(SessionMiddleware, secret_key=os.environ.get("KHOJ_DJANGO_SECRET_KEY", "!secret"))

View File

@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
<link rel="icon" type="image/png" sizes="128x128" href="https://assets.khoj.dev/khoj_lantern_128x128.png?v={{ khoj_version }}">
<title>Khoj - Service Temporarily Unavailable</title>
<style>
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
color: #2c3e50;
margin: 0;
padding: 0;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.error-container {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
max-width: 500px;
margin: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(255, 255, 255, 0.8);
}
.logo {
width: 80px;
height: 80px;
margin: 0 auto 20px;
background-image: url('https://assets.khoj.dev/khoj_lantern_128x128.png?v={{ khoj_version }}');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
h1 {
margin: 0 0 20px 0;
font-size: 28px;
font-weight: 600;
color: #34495e;
}
.error-code {
font-size: 14px;
opacity: 0.7;
margin-bottom: 20px;
font-family: monospace;
background: rgba(52, 73, 94, 0.1);
color: #34495e;
padding: 8px 12px;
border-radius: 8px;
display: inline-block;
}
p {
margin: 0 0 30px 0;
font-size: 16px;
line-height: 1.6;
opacity: 0.8;
color: #5d6d7e;
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 12px;
align-items: center;
}
.button {
background: rgba(52, 152, 219, 0.1);
color: #3498db;
border: none;
padding: 12px 24px;
border-radius: 10px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
text-decoration: none;
display: inline-block;
transition: all 0.3s ease;
border: 1px solid rgba(52, 152, 219, 0.3);
}
.button:hover {
background: rgba(52, 152, 219, 0.2);
transform: translateY(-2px);
color: #2980b9;
}
.button.primary {
background: #3498db;
color: white;
}
.button.primary:hover {
background: #2980b9;
}
.support-links {
margin-top: 30px;
font-size: 14px;
opacity: 0.7;
color: #7f8c8d;
}
.support-links a {
color: #3498db;
text-decoration: none;
margin: 0 10px;
padding: 5px 10px;
border-radius: 5px;
transition: background 0.3s ease;
}
.support-links a:hover {
background: rgba(52, 152, 219, 0.1);
color: #2980b9;
}
@media (max-width: 768px) {
.error-container {
margin: 10px;
padding: 30px 20px;
}
h1 {
font-size: 24px;
}
}
</style>
</head>
<body>
<div class="error-container">
<div class="logo"></div>
<h1>Khoj Temporarily Unavailable</h1>
<p>
Sorry, I am experiencing temporary service issues.
My team has been notified and is working to resolve this quickly.
</p>
<div class="action-buttons">
<a href="/" class="button primary">Return to Home</a>
</div>
<div class="support-links">
Need help? Reach team via
<a title="Email team@khoj.dev" href="mailto:team@khoj.dev">Email</a>
<a title="Message on Discord" href="https://discord.gg/BDgyabRM6e" target="_blank">Discord</a>
<a title="Open Issue on Github" href="https://github.com/khoj-ai/khoj/issues" target="_blank">Github</a>
</div>
</div>
</body>
</html>

View File

@@ -139,3 +139,8 @@ def automations_config_page(
@web_client.get("/.well-known/assetlinks.json", response_class=FileResponse)
def assetlinks(request: Request):
return FileResponse(constants.assetlinks_file_path)
@web_client.get("/server/error", response_class=HTMLResponse)
def server_error_page(request: Request):
return templates.TemplateResponse("error.html", context={"request": request})