Skip to content

MM-69450: Notify users when GitHub SAML SSO or token auth fails#1032

Merged
jgheithcock merged 2 commits into
masterfrom
mm-69450-surface-auth-errors
Jul 2, 2026
Merged

MM-69450: Notify users when GitHub SAML SSO or token auth fails#1032
jgheithcock merged 2 commits into
masterfrom
mm-69450-surface-auth-errors

Conversation

@jgheithcock

@jgheithcock jgheithcock commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

When GitHub API calls fail due to revoked tokens (401) or SAML SSO authorization issues (403), the plugin previously logged the errors server-side but gave users no indication they needed to reconnect. This routes those failures through the existing handleRevokedToken flow, which force-disconnects the account and sends the bot DM telling the user to run /github connect again.

Changes:

  • Add isGitHubAuthFailure to detect 401 and SAML-specific 403 errors from both go-github (REST) and githubv4 (GraphQL) error shapes.
  • Extend useGitHubClient and wire up getLHSData / getPrsDetails paths that previously bypassed it.
  • Bubble up the first per-org error from GetLHSData so SAML failures are not swallowed when other orgs succeed.
  • Add unit tests for error detection and notification behavior.

Ticket Link

https://mattermost.atlassian.net/browse/MM-69450

Checklist

  • make check-style
  • go test ./server/plugin/...
  • Stash regression check: SAML 403 test cases fail without the fix, pass with it

Change Impact: High 🔴

Reasoning: These changes alter GitHub authentication-failure handling in a user-facing, security-adjacent flow and affect multiple plugin paths that interact with GitHub APIs. They also change when users are disconnected and notified, which is a critical behavior change with broad blast radius.

Regression Risk: Moderate to high, because the new error classification spans REST and GraphQL clients, includes SAML/401/403 edge cases, and touches shared GitHub client logic used across PR/LHS data fetching paths. Some affected branches are error-driven and harder to exercise exhaustively.

** QA Recommendation:** Manual QA is recommended, especially for GitHub token revocation and SAML SSO failure scenarios. Validate that affected users are disconnected and notified correctly, and confirm unrelated GitHub errors do not trigger the revoked-token flow.

Surface hidden GitHub auth failures (401 and SAML 403) by routing them
through the existing handleRevokedToken path, which force-disconnects
the account and sends the bot DM prompting reconnect via /github connect.
@jgheithcock
jgheithcock requested a review from a team as a code owner June 30, 2026 02:51
@jgheithcock

Copy link
Copy Markdown
Contributor Author

@coderabbitai

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

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: 5bd9f97a-27c8-450e-9c70-757ee0d5f3fe

📥 Commits

Reviewing files that changed from the base of the PR and between 45e237b and cb9d731.

📒 Files selected for processing (4)
  • server/plugin/api.go
  • server/plugin/graphql/lhs_request.go
  • server/plugin/plugin.go
  • server/plugin/plugin_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/plugin/graphql/lhs_request.go
  • server/plugin/plugin.go
  • server/plugin/plugin_test.go

📝 Walkthrough

Walkthrough

This PR adds GitHub auth-failure detection and uses it to revoke stored tokens or continue partial responses across PR detail, LHS, and sidebar flows. It also propagates the first fetch error from concurrent calls and adds tests for the new auth-handling paths.

Changes

Auth failure detection and token handling

Layer / File(s) Summary
Auth-failure classification helpers
server/plugin/plugin.go
Adds isGitHubAuthFailure, isSAMLError, and preferAuthErr, and updates useGitHubClient to revoke tokens when auth failures are detected.
Concurrent PR detail fetch with error capture
server/plugin/api.go
fetchPRDetails now returns an error alongside PRDetails, and getPrsDetails captures concurrent fetch errors, revokes on auth failure, and returns the response.
LHS data fetch error propagation and revocation
server/plugin/graphql/lhs_request.go, server/plugin/api.go
GetLHSData returns the first per-org error with partial results; fetchLHSData uses updated wrapping; getLHSData revokes on auth failure; getSidebarContent keeps partial sidebar output on auth failures.
Auth-failure and revocation test coverage
server/plugin/plugin_test.go
Adds auth-failure classification tests and revoked-token notification coverage, plus supporting imports and mock expectations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested labels: 2: Dev Review, 3: QA Review

Suggested reviewers: ogi-m, nang2049

Poem

A rabbit sniffed a 401 breeze,
And SAML rustled in the trees.
Partial results still hopped by,
While revoked tokens waved goodbye. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 summarizes the main change: notifying users when GitHub SAML SSO or token authentication fails.
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-69450-surface-auth-errors

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

@jgheithcock

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
server/plugin/plugin_test.go (1)

412-459: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Coverage gap: wrapped errors and saml_failure substring untested.

isGitHubAuthFailure (plugin.go) unwraps via errors.As, and the GraphQL path matches two distinct substrings, "SAML enforcement" and "saml_failure". This table only exercises directly-typed errors and the "SAML enforcement" substring — neither a wrapped *github.ErrorResponse (e.g. fmt.Errorf("...: %w", ghErr)) nor the "saml_failure" substring path is covered.

✅ Suggested additional sub-tests
+	t.Run("401 wrapped ErrorResponse", func(t *testing.T) {
+		ghErr := &github.ErrorResponse{Response: &http.Response{StatusCode: http.StatusUnauthorized}}
+		err := fmt.Errorf("github request failed: %w", ghErr)
+		require.True(t, isGitHubAuthFailure(err))
+	})
+
+	t.Run("403 saml_failure graphql string", func(t *testing.T) {
+		err := errors.New("error in executing query: GraphQL: saml_failure")
+		require.True(t, isGitHubAuthFailure(err))
+	})
🤖 Prompt for 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.

In `@server/plugin/plugin_test.go` around lines 412 - 459, Add test coverage for
the missing `isGitHubAuthFailure` paths in `plugin_test.go`: the current
`TestIsGitHubAuthFailure` only checks direct errors and the `"SAML enforcement"`
string, but not wrapped `*github.ErrorResponse` values or the `"saml_failure"`
GraphQL substring. Extend the existing subtests to verify `isGitHubAuthFailure`
still returns true when a `github.ErrorResponse` is wrapped with `fmt.Errorf(...
%w ...)`, and add a separate case that exercises the `saml_failure` match in
`isGitHubAuthFailure` so both `errors.As` unwrapping and all GraphQL substring
branches are covered.
🤖 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/plugin/api.go`:
- Around line 788-793: The concurrent error capture in the collector flow
currently keeps the first error seen in the fetchErr/fetchErrMu block, which can
let a non-auth failure mask a later 401/SAML auth failure. Update the error
selection logic in the relevant collector paths so that auth-related errors take
precedence over previously stored non-auth errors, while still preserving the
first auth error once seen. Use the existing fetchErr handling around the
collector goroutines in api.go to locate the fix and apply the same priority
logic in both affected blocks.
- Around line 1124-1130: The GetLHSData call already returns partial LHS slices
alongside err, but this wrapper discards them by replacing the results with
empty slices on any error. Update the return path in this api wrapper to
preserve reviewResp, assignmentResp, and openPRResp even when err is non-nil,
while still calling handleRevokedToken(c.GHInfo) for isGitHubAuthFailure(err)
before propagating the error.

In `@server/plugin/graphql/lhs_request.go`:
- Around line 43-47: The LHS request error handling in lhs_request.go is only
preserving the first failure via firstErr, which can hide a later SAML 403/auth
failure from the caller. Update the logic around the org loop and the return
path in the relevant fetching function(s) to either aggregate all org errors or
explicitly prefer auth/SAML failures over earlier transient errors. Use the
existing symbols around the error handling block (including firstErr and the org
iteration in the LHS fetch function) to ensure the returned error reflects the
most important failure.

---

Nitpick comments:
In `@server/plugin/plugin_test.go`:
- Around line 412-459: Add test coverage for the missing `isGitHubAuthFailure`
paths in `plugin_test.go`: the current `TestIsGitHubAuthFailure` only checks
direct errors and the `"SAML enforcement"` string, but not wrapped
`*github.ErrorResponse` values or the `"saml_failure"` GraphQL substring. Extend
the existing subtests to verify `isGitHubAuthFailure` still returns true when a
`github.ErrorResponse` is wrapped with `fmt.Errorf(... %w ...)`, and add a
separate case that exercises the `saml_failure` match in `isGitHubAuthFailure`
so both `errors.As` unwrapping and all GraphQL substring branches are covered.
🪄 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: 550d8bef-4f04-44f6-980d-bd728dba579b

📥 Commits

Reviewing files that changed from the base of the PR and between 3fe4e89 and 45e237b.

📒 Files selected for processing (4)
  • server/plugin/api.go
  • server/plugin/graphql/lhs_request.go
  • server/plugin/plugin.go
  • server/plugin/plugin_test.go

Comment thread server/plugin/api.go Outdated
Comment thread server/plugin/api.go
Comment thread server/plugin/graphql/lhs_request.go Outdated
Comment thread server/plugin/api.go

wg.Wait()

if isGitHubAuthFailure(fetchErr) {

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.

Nit: return an HTTP error after handling revocation instead of shipping empty results.

@nang2049 nang2049 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.

Thanks @jgheithcock added a few comments :)

…serving partial LHS data

Prioritize GitHub auth errors over transient failures in concurrent PR
fetches and per-org LHS requests so 401/SAML failures are not masked.
Return 401 from getPrsDetails after revocation, preserve partial sidebar
data on auth errors, and add isGitHubAuthFailure test coverage for
wrapped errors and saml_failure GraphQL strings.
@jgheithcock
jgheithcock requested a review from nang2049 July 1, 2026 19:23

@nang2049 nang2049 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.

LGTM!

@jgheithcock
jgheithcock merged commit f49b3c1 into master Jul 2, 2026
19 checks passed
@jgheithcock
jgheithcock deleted the mm-69450-surface-auth-errors branch July 2, 2026 13:59
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.

2 participants