Skip to content

fix: answer unknown guest tokens with 403 instead of an error page - #97

Merged
mpge merged 2 commits into
mainfrom
fix/guest-token-not-found
Oct 5, 2026
Merged

mpge merged 2 commits into
mainfrom
fix/guest-token-not-found

Conversation

@mpge

@mpge mpge commented Oct 5, 2026

Copy link
Copy Markdown
Member

Problem

TicketService.findByGuestToken throws EntityNotFoundException for a token that matches no ticket. Nothing mapped that exception to a status. The servlet container forwarded it to /error, and the host's own security refuses /error for an anonymous caller. In practice a guest with an unknown token got 401 with an empty body, not the 500 we expected. A host without that security would show a 500 error page.

Fix

The six guest-token endpoints now catch the lookup failure and return 403 {"error":"Invalid guest access token"}:

  • widget: GET /tickets/{token}, GET /tickets/{token}/replies, POST /tickets/{token}/replies
  • guest: GET /tickets/{token}, GET /tickets/{token}/replies, POST /tickets/{token}/replies

This follows the repo's convention. There is no @ControllerAdvice; controllers catch EntityNotFoundException themselves (as SkillController does) and return Map.of("error", ...) bodies (as AgentTicketController does).

Why 403 rather than 404: the NestJS reference's GuestAccessGuard throws ForbiddenException('Invalid guest access token') for an unknown token, so this matches it. The dotnet and phoenix ports return 404 for the same case, so the ports are not consistent with each other.

Tests

GuestTokenNotFoundTest boots the host on a random port and sends real HTTP requests, with no service stubs and no MockMvc.

  • It is parameterised over the six endpoints. Each case asserts 403 and the exact body.
  • Before the fix, all six failed with expected: 403 but was: 401.
  • A positive control checks that a real ticket's token still gets 200.

Results

  • ./gradlew test (H2): 370 tests, 0 failures, 0 errors, 9 skipped (the skips were already there)
  • ./gradlew checkstyleMain checkstyleTest: BUILD SUCCESSFUL, with no warnings in the touched files

Seen, not fixed here

GET /escalated/api/guest/tickets/{token} with a valid token serialises the Ticket entity graph cyclically. The response was about 45 KB of repeated nesting (activities -> ticket -> ...). That is why the positive control asserts the status only. It needs a separate fix: a DTO or @JsonIgnore on the back-references.

Related: #96, whose wrong-token test can then use the real endpoint instead of a stub.

TicketService.findByGuestToken throws EntityNotFoundException, which no
handler mapped. The container forwarded it to /error, which host security
refuses for anonymous callers, so guests got 401 with no body (500 on hosts
without that security). The widget and guest-access token endpoints now
return 403 {"error":"Invalid guest access token"}, matching the NestJS
reference's GuestAccessGuard.
# Conflicts:
#	src/main/java/dev/escalated/controllers/widget/GuestAccessController.java
#	src/main/java/dev/escalated/controllers/widget/WidgetController.java
@mpge
mpge merged commit ab4bcb4 into main Oct 5, 2026
4 checks passed
@mpge mpge mentioned this pull request Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant