Skip to content

Commit ed63312

Browse files
committed
πŸ› Fix app layer not raising HTTPException
1 parent fcc6be4 commit ed63312

File tree

5 files changed

+102
-69
lines changed

5 files changed

+102
-69
lines changed

β€Žbackend/apps/northbound_app.pyβ€Ž

Lines changed: 96 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import logging
2+
from http import HTTPStatus
3+
from http.client import HTTPException
24
from typing import Optional, Dict
35
import uuid
46

57
from fastapi import APIRouter, Body, Header, Request
68

7-
from consts.exceptions import UnauthorizedError
9+
from consts.exceptions import UnauthorizedError, LimitExceededError, SignatureValidationError
810
from services.northbound_service import (
911
NorthboundContext,
1012
get_conversation_history,
@@ -51,25 +53,28 @@ async def _parse_northbound_context(request: Request) -> NorthboundContext:
5153
request_body = ""
5254

5355
validate_aksk_authentication(request.headers, request_body)
54-
except Exception as e:
55-
raise UnauthorizedError(f"AK/SK authentication failed: {str(e)}")
56+
except (UnauthorizedError, LimitExceededError, SignatureValidationError) as e:
57+
raise e
58+
except Exception:
59+
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Internal Server Error: cannot parse northbound context")
60+
5661

5762
# 2. Parse JWT token
5863
auth_header = _get_header(request.headers, "Authorization")
5964
if not auth_header:
60-
raise UnauthorizedError("No authorization header found. Cannot authenticate.")
65+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: No authorization header found")
6166

6267
# Use auth_utils to parse JWT token
6368
try:
6469
user_id, tenant_id = get_current_user_id(auth_header)
6570

6671
if not user_id:
67-
raise UnauthorizedError("Missing user_id in JWT token")
72+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: missing user_id in JWT token")
6873
if not tenant_id:
69-
raise UnauthorizedError("No related tenant_id found with user_id in JWT token")
74+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: unregistered user_id in JWT token")
7075

71-
except Exception as e:
72-
raise UnauthorizedError(f"Error occurred when parsing JWT: {str(e)}")
76+
except Exception:
77+
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Internal Server Error: cannot parse JWT token")
7378

7479
request_id = _get_header(request.headers, "X-Request-Id") or str(uuid.uuid4())
7580

@@ -94,38 +99,84 @@ async def run_chat(
9499
query: str = Body(..., embed=True),
95100
idempotency_key: Optional[str] = Header(None, alias="Idempotency-Key"),
96101
):
97-
ctx: NorthboundContext = await _parse_northbound_context(request)
98-
return await start_streaming_chat(
99-
ctx=ctx,
100-
external_conversation_id=conversation_id,
101-
agent_name=agent_name,
102-
query=query,
103-
idempotency_key=idempotency_key,
104-
)
102+
try:
103+
ctx: NorthboundContext = await _parse_northbound_context(request)
104+
return await start_streaming_chat(
105+
ctx=ctx,
106+
external_conversation_id=conversation_id,
107+
agent_name=agent_name,
108+
query=query,
109+
idempotency_key=idempotency_key,
110+
)
111+
except UnauthorizedError:
112+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: AK/SK authentication failed")
113+
except LimitExceededError:
114+
raise HTTPException(status_code=HTTPStatus.TOO_MANY_REQUESTS, detail="Too Many Requests: rate limit exceeded")
115+
except SignatureValidationError:
116+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: invalid signature")
117+
except Exception:
118+
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Internal Server Error")
119+
105120

106121

107122
@router.get("/chat/stop/{conversation_id}")
108123
async def stop_chat_stream(request: Request, conversation_id: str):
109-
ctx: NorthboundContext = await _parse_northbound_context(request)
110-
return await stop_chat(ctx=ctx, external_conversation_id=conversation_id)
124+
try:
125+
ctx: NorthboundContext = await _parse_northbound_context(request)
126+
return await stop_chat(ctx=ctx, external_conversation_id=conversation_id)
127+
except UnauthorizedError:
128+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: AK/SK authentication failed")
129+
except LimitExceededError:
130+
raise HTTPException(status_code=HTTPStatus.TOO_MANY_REQUESTS, detail="Too Many Requests: rate limit exceeded")
131+
except SignatureValidationError:
132+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: invalid signature")
133+
except Exception:
134+
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Internal Server Error")
111135

112136

113137
@router.get("/conversations/{conversation_id}")
114138
async def get_history(request: Request, conversation_id: str):
115-
ctx: NorthboundContext = await _parse_northbound_context(request)
116-
return await get_conversation_history(ctx=ctx, external_conversation_id=conversation_id)
139+
try:
140+
ctx: NorthboundContext = await _parse_northbound_context(request)
141+
return await get_conversation_history(ctx=ctx, external_conversation_id=conversation_id)
142+
except UnauthorizedError:
143+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: AK/SK authentication failed")
144+
except LimitExceededError:
145+
raise HTTPException(status_code=HTTPStatus.TOO_MANY_REQUESTS, detail="Too Many Requests: rate limit exceeded")
146+
except SignatureValidationError:
147+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: invalid signature")
148+
except Exception:
149+
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Internal Server Error")
117150

118151

119152
@router.get("/agents")
120153
async def list_agents(request: Request):
121-
ctx: NorthboundContext = await _parse_northbound_context(request)
122-
return await get_agent_info_list(ctx=ctx)
154+
try:
155+
ctx: NorthboundContext = await _parse_northbound_context(request)
156+
return await get_agent_info_list(ctx=ctx)
157+
except UnauthorizedError:
158+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: AK/SK authentication failed")
159+
except LimitExceededError:
160+
raise HTTPException(status_code=HTTPStatus.TOO_MANY_REQUESTS, detail="Too Many Requests: rate limit exceeded")
161+
except SignatureValidationError:
162+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: invalid signature")
163+
except Exception:
164+
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Internal Server Error")
123165

124166

125167
@router.get("/conversations")
126168
async def list_convs(request: Request):
127-
ctx: NorthboundContext = await _parse_northbound_context(request)
128-
return await list_conversations(ctx=ctx)
169+
try:
170+
ctx: NorthboundContext = await _parse_northbound_context(request)
171+
return await list_conversations(ctx=ctx)
172+
except UnauthorizedError:
173+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: AK/SK authentication failed")
174+
except LimitExceededError:
175+
raise HTTPException(status_code=HTTPStatus.TOO_MANY_REQUESTS, detail="Too Many Requests: rate limit exceeded")
176+
except SignatureValidationError:
177+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: invalid signature")
178+
except Exception:
179+
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Internal Server Error")
129180

130181

131182
@router.put("/conversations/{conversation_id}/title")
@@ -135,14 +186,24 @@ async def update_convs_title(
135186
title: str,
136187
idempotency_key: Optional[str] = Header(None, alias="Idempotency-Key"),
137188
):
138-
ctx: NorthboundContext = await _parse_northbound_context(request)
139-
result = await update_conversation_title(
140-
ctx=ctx,
141-
external_conversation_id=conversation_id,
142-
title=title,
143-
idempotency_key=idempotency_key,
144-
)
145-
from fastapi.responses import JSONResponse
146-
147-
headers_out = {"Idempotency-Key": result.get("idempotency_key", ""), "X-Request-Id": ctx.request_id}
148-
return JSONResponse(content=result, headers=headers_out)
189+
try:
190+
ctx: NorthboundContext = await _parse_northbound_context(request)
191+
result = await update_conversation_title(
192+
ctx=ctx,
193+
external_conversation_id=conversation_id,
194+
title=title,
195+
idempotency_key=idempotency_key,
196+
)
197+
from fastapi.responses import JSONResponse
198+
199+
headers_out = {"Idempotency-Key": result.get("idempotency_key", ""), "X-Request-Id": ctx.request_id}
200+
return JSONResponse(content=result, headers=headers_out)
201+
202+
except UnauthorizedError:
203+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: AK/SK authentication failed")
204+
except LimitExceededError:
205+
raise HTTPException(status_code=HTTPStatus.TOO_MANY_REQUESTS, detail="Too Many Requests: rate limit exceeded")
206+
except SignatureValidationError:
207+
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="Unauthorized: invalid signature")
208+
except Exception:
209+
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Internal Server Error")

β€Žbackend/apps/northbound_base_app.pyβ€Ž

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from http import HTTPStatus
12
import logging
23
from fastapi import FastAPI, HTTPException
34
from fastapi.responses import JSONResponse
@@ -36,34 +37,10 @@ async def northbound_http_exception_handler(request, exc):
3637
content={"message": exc.detail},
3738
)
3839

39-
@northbound_app.exception_handler(LimitExceededError)
40-
async def northbound_limit_exceeded_handler(request, exc):
41-
logger.warning(f"Northbound rate limit exceeded: {exc}")
42-
return JSONResponse(
43-
status_code=429,
44-
content={"message": f"Rate limit exceeded: {str(exc)}"},
45-
)
46-
47-
@northbound_app.exception_handler(UnauthorizedError)
48-
async def northbound_unauthorized_handler(request, exc):
49-
logger.warning(f"Northbound unauthorized: {exc}")
50-
return JSONResponse(
51-
status_code=401,
52-
content={"message": f"Unauthorized: {str(exc)}"},
53-
)
54-
55-
@northbound_app.exception_handler(SignatureValidationError)
56-
async def northbound_signature_error_handler(request, exc):
57-
logger.warning(f"Northbound signature error: {exc}")
58-
return JSONResponse(
59-
status_code=498,
60-
content={"message": f"Signature validation failed: {str(exc)}"},
61-
)
62-
6340
@northbound_app.exception_handler(Exception)
6441
async def northbound_generic_exception_handler(request, exc):
6542
logger.error(f"Northbound Generic Exception: {exc}")
6643
return JSONResponse(
67-
status_code=500,
44+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
6845
content={"message": "Internal server error, please try again later."},
6946
)

β€Žbackend/consts/exceptions.pyβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Northbound customize error class
22
class LimitExceededError(Exception):
3+
"""Raised when an outer platform calling too frequently"""
34
pass
45

56
class UnauthorizedError(Exception):
7+
"""Raised when a user from outer platform is unauthorized."""
68
pass
79

810
class SignatureValidationError(Exception):

β€Žtest/backend/app/test_northbound_app.pyβ€Ž

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,13 @@
2424

2525
consts_exceptions_mod = types.ModuleType("consts.exceptions")
2626

27-
2827
class LimitExceededError(Exception):
2928
pass
30-
31-
3229
class UnauthorizedError(Exception):
3330
pass
34-
35-
3631
class SignatureValidationError(Exception):
3732
pass
3833

39-
4034
consts_exceptions_mod.LimitExceededError = LimitExceededError
4135
consts_exceptions_mod.UnauthorizedError = UnauthorizedError
4236
consts_exceptions_mod.SignatureValidationError = SignatureValidationError

β€Žtest/backend/app/test_northbound_base_app.pyβ€Ž

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,8 @@ def test_http_exception_handler_registration(self):
127127
self.assertTrue(callable(app.exception_handlers[HTTPException]))
128128

129129
def test_custom_exception_handlers_registration(self):
130-
for exc in (LimitExceededError, UnauthorizedError, SignatureValidationError, Exception):
131-
self.assertIn(exc, app.exception_handlers)
132-
self.assertTrue(callable(app.exception_handlers[exc]))
130+
self.assertIn(Exception, app.exception_handlers)
131+
self.assertTrue(callable(app.exception_handlers[Exception]))
133132

134133
# -------------------------------------------------------------------
135134
# End-to-end sanity for health (dummy) endpoint – relies on router stub

0 commit comments

Comments
Β (0)