mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-02 13:18:18 +00:00
Redirect to a better error page on server error
This commit is contained in:
@@ -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"))
|
||||
|
||||
|
||||
149
src/khoj/interface/web/error.html
Normal file
149
src/khoj/interface/web/error.html
Normal 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>
|
||||
@@ -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})
|
||||
|
||||
Reference in New Issue
Block a user