Skip to content

[MM-67557] Return 406 when requesting json to html endpoints#37441

Open
larkox wants to merge 4 commits into
masterfrom
MM-67557
Open

[MM-67557] Return 406 when requesting json to html endpoints#37441
larkox wants to merge 4 commits into
masterfrom
MM-67557

Conversation

@larkox

@larkox larkox commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

When a request with an accept header JSON, we still serve html if it hits an html only endpoint. We want to avoid that, among other things, to better handle from the clients when adding a new server.

We also add to the error what is the base url of the server, so we can improve how we handle adding a server in the clients.

Related PR

Mobile mattermost/mattermost-mobile#9922

Ticket Link

Fix https://mattermost.atlassian.net/browse/MM-67557

Release Note

HTML returning endpoint now fail with a 406 error when fetch with a JSON accept header

Change Impact: 🟠 Medium

Regression Risk: This changes the HTTP response contract for multiple authentication-related HTML endpoints when Accept: application/json is present (switching from HTML to a 406 JSON error), which may affect clients that assumed HTML was returned. Risk is mitigated by explicit exclusions for OAuth and mobile requests and by added handler-focused tests, but coverage for every impacted route/client variation may be incomplete.

QA Recommendation: Targeted manual QA for SAML, magic-link, OAuth completion/login/signup flows, and at least one representative HTML login endpoint using both browser-style Accept and Accept: application/json, including mobile client behavior.

@mm-cloud-bot mm-cloud-bot added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e4c99319-0561-4428-b9f7-4289bab7fd33

📥 Commits

Reviewing files that changed from the base of the PR and between 1f043e0 and 4bfa544.

📒 Files selected for processing (3)
  • server/channels/utils/accept.go
  • server/channels/utils/api.go
  • server/channels/web/handlers_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/channels/utils/accept.go
  • server/channels/web/handlers_test.go
  • server/channels/utils/api.go

📝 Walkthrough

Walkthrough

HTML endpoint handlers now detect requests expecting JSON and return a localized 406 JSON error. Magic-link, OAuth, and SAML routes are wired through HTML-specific handler constructors, with coverage for header parsing and rejection behavior.

Changes

HTML endpoint negotiation

Layer / File(s) Summary
Request detection and error response
server/channels/utils/accept.go, server/channels/utils/accept_test.go, server/channels/utils/api.go, server/i18n/en.json
Adds case-insensitive Accept header matching for application/json and a localized JSON 406 error payload containing the endpoint ID, message, status code, and base URL.
HTML handler guard and constructors
server/channels/web/handlers.go, server/channels/web/handlers_test.go
Adds HTML handler metadata and constructors, rejects applicable JSON-expected requests while bypassing OAuth API and mobile requests, and tests the resulting response.
HTML route wrapper migration
server/channels/web/magic_link.go, server/channels/web/oauth.go, server/channels/web/saml.go
Routes magic-link, OAuth, and SAML HTML endpoints through the new HTML handler wrappers.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Handler.ServeHTTP
  participant shouldRejectJSONRequest
  participant WriteHTMLEndpointJSONRequestError
  Client->>Handler.ServeHTTP: Request HTML endpoint with Accept application/json
  Handler.ServeHTTP->>shouldRejectJSONRequest: Evaluate handler and client conditions
  shouldRejectJSONRequest-->>Handler.ServeHTTP: Reject request
  Handler.ServeHTTP->>WriteHTMLEndpointJSONRequestError: Write localized 406 JSON response
  WriteHTMLEndpointJSONRequestError-->>Client: Return application/json error payload
Loading

Suggested reviewers: devinbinnie

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: returning HTTP 406 for JSON requests to HTML endpoints.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch MM-67557

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/channels/web/handlers_test.go`:
- Around line 64-90: Add table-driven coverage in
TestHandlerRejectsJSONRequestOnHTMLEndpoint for both shouldRejectJSONRequest
bypasses: an OAuth API request and a request carrying the X-Mobile-App header.
Assert each request reaches the wrapped HTML handler and returns HTTP 200, while
retaining the existing baseline rejection assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bdd71a3d-20ce-4bcc-95a2-d7dbe2edab94

📥 Commits

Reviewing files that changed from the base of the PR and between 9588492 and 1f043e0.

📒 Files selected for processing (9)
  • server/channels/utils/accept.go
  • server/channels/utils/accept_test.go
  • server/channels/utils/api.go
  • server/channels/web/handlers.go
  • server/channels/web/handlers_test.go
  • server/channels/web/magic_link.go
  • server/channels/web/oauth.go
  • server/channels/web/saml.go
  • server/i18n/en.json

Comment thread server/channels/web/handlers_test.go
@larkox
larkox requested a review from devinbinnie July 13, 2026 12:55
@larkox larkox added the 2: Dev Review Requires review by a developer label Jul 13, 2026
@larkox

larkox commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@devinbinnie Asking your review specially because this might be also useful for desktop.

@devinbinnie devinbinnie left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues here, I think this should be tested with Desktop App before merge to make sure it isn't rejecting its requests, or we'd have to do some backwards compatibility stuff for older versions.

As far as I can tell, Electron.Net library doesn't prescribe a Content-Type so we should be fine, but we should verify before merging.

@devinbinnie devinbinnie added 4: Reviews Complete All reviewers have approved the pull request and removed 2: Dev Review Requires review by a developer labels Jul 13, 2026
@larkox

larkox commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@devinbinnie Just did a quick test, and seems to be working just fine.

@larkox

larkox commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

/update-branch

@larkox

larkox commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

/update-branch

@github-actions

Copy link
Copy Markdown
Documentation Impact Analysis — updates needed

Documentation Impact Analysis

Overall Assessment: Documentation Updates Recommended

Changes Summary

This PR adds a new request-rejection mechanism so that HTML-returning endpoints (/login/sso/saml, OAuth login/complete/signup flows, /login/one_time_link) now respond with HTTP 406 and a JSON error body when the request carries Accept: application/json. The JSON error body also includes the server's base URL, which the PR description explicitly cites as a new server-discovery signal for clients. Mobile clients sending X-Mobile-App and OAuth API callers are exempted from the rejection.

Documentation Impact Details

Change Type Files Changed Affected Personas Documentation Action Docs Location
New API behavior (HTTP 406 contract) server/channels/web/handlers.go, server/channels/web/saml.go, server/channels/web/oauth.go, server/channels/web/magic_link.go Developer / Integrator Document that HTML-only endpoints (SAML, OAuth redirect, magic link) return 406 Not Acceptable with a JSON body when Accept: application/json is sent; note the base_url field in the error response and the X-Mobile-App exemption New section needed — suggested: docs/source/integrations-guide/api.rst or a new docs/source/integrations-guide/server-connection.rst

Recommended Actions

  • Add a note in the developer/integrations documentation (e.g., docs/source/integrations-guide/api.rst or a new docs/source/integrations-guide/server-connection.rst) explaining that HTML-only authentication endpoints (/login/sso/saml, /oauth/{service}/login, /oauth/{service}/complete, /oauth/{service}/signup, /login/one_time_link, /oauth/authorize) return HTTP 406 Not Acceptable with a JSON body when the request includes Accept: application/json. The JSON body contains id, message, status_code, and base_url fields. Note that requests carrying the X-Mobile-App header or matching OAuth API patterns are exempted. This is useful for client-side server discovery.

Confidence

Medium — The new HTTP 406 response contract and the base_url field in the error body are clearly externally observable and are cited in the PR description as an intentional client-facing feature (server discovery). No existing documentation page was found that would become inaccurate, so no updates-required items exist. The recommendation to document this new contract is based on its relevance to developers building Mattermost clients or integration tooling.


@github-actions github-actions Bot added the Docs/Needed Requires documentation label Jul 15, 2026
@larkox
larkox requested a review from lieut-data July 17, 2026 08:47

@lieut-data lieut-data left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of comments below.

Should we consider applying a feature flag to this functionality before shipping?

method: http.MethodGet,
url: "http://localhost:8065/login/sso/saml",
headers: map[string]string{
"Accept": "application/json",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does mobile actually send application/json for these HTML-only endpoints?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but we have to test the logic for coverage and make sure these don't reject.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this is about testing /login/sso/saml forcing HTML. Can we clarify the name to emphasize this?

Comment on lines +392 to +397
// Some OAuth routes live outside /api/v4/ but are JSON API endpoints (token exchange,
// authorize POST, etc.), not browser pages. Skip rejection for those callers.
if IsOAuthAPICall(c.App, r) {
return false
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing we can't use the HTMLHandler because of the mobile comment below?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is my guess also.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, looking for clarity -- can you elaborate on why your code can't simply rely on the HTMLHandler always doing the right thing, but we need to test X-Mobile-App explicitly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is prepared so certain HTML based endpoints (mostly oAuth) are able to return json like errors. handleContextError handles this by looking at certain things:

  • webhook
  • API calls
  • Oauth endpoints
  • Mobile header

Webhook and API calls return by default json, so we don't have to do anything extra. They can be handled by isHTML === false.

OAuth Endpoints (which is also where the Mobile header should be used) are HTML endpoints (isHTML === true), but they may return a JSON object if they fail.

So this code is being extremely cautious. Even if it returns a json body with the error, the request is probably not expecting a json. But still, in the hopes of trying not to change existing behavior, we do these checks.

Now, what is the Mobile header? The mobile header was added 8 years ago in this PR: #8943 . I don't see this header in today's mobile code, therefore my guess this is related to the legacy mobile app, V1, or just very old versions, rendering this dead code.

// (redirects, session tokens). This matches existing error-handling behavior.
if r.Header.Get("X-Mobile-App") != "" {
return false
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, are mobile clients getting different responses than non-mobile clients?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't be surprised if this is some kind of legacy code for the old mobile app (not even v1). I am being careful just to keep the current behavior, based on Cursor recommendations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I'm looking for more than Cursor's recommendations here. Can you investigate the actual behaviour and update the code to reflect the requirements?

BaseURL string `json:"base_url"`
}{
Id: "api.context.html_endpoint_json_request.app_error",
Message: translateFunc("api.context.html_endpoint_json_request.app_error", map[string]any{"BaseURL": baseURL}),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to embed the base URL twice, or should we have a more generic error message?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generic message work too. Cursor added it and I thought it was a nice to have, but 0/5.

method: http.MethodGet,
url: "http://localhost:8065/login/sso/saml",
headers: map[string]string{
"Accept": "application/json",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this is about testing /login/sso/saml forcing HTML. Can we clarify the name to emphasize this?

Comment on lines +392 to +397
// Some OAuth routes live outside /api/v4/ but are JSON API endpoints (token exchange,
// authorize POST, etc.), not browser pages. Skip rejection for those callers.
if IsOAuthAPICall(c.App, r) {
return false
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, looking for clarity -- can you elaborate on why your code can't simply rely on the HTMLHandler always doing the right thing, but we need to test X-Mobile-App explicitly?

// (redirects, session tokens). This matches existing error-handling behavior.
if r.Header.Get("X-Mobile-App") != "" {
return false
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I'm looking for more than Cursor's recommendations here. Can you investigate the actual behaviour and update the code to reflect the requirements?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4: Reviews Complete All reviewers have approved the pull request Docs/Needed Requires documentation release-note Denotes a PR that will be considered when it comes time to generate release notes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants