Skip to content

refactor(auth/mfa): use sentinel errors in mapMFAServiceError#883

Open
cristim wants to merge 3 commits into
feat/multicloud-web-frontendfrom
fix/512-wave20
Open

refactor(auth/mfa): use sentinel errors in mapMFAServiceError#883
cristim wants to merge 3 commits into
feat/multicloud-web-frontendfrom
fix/512-wave20

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 30, 2026

Summary

  • Replaces brittle substring matching in mapMFAServiceError with typed sentinel errors checked via errors.Is, so renaming an error message string in the service can never silently drift the HTTP status code
  • Adds 8 new exported sentinels in internal/auth/errors.go covering all MFA lifecycle error conditions, each wrapping the original message string so existing substring-based tests continue to pass
  • Adds 18 new tests: 9 sentinel-identity tests (assert errors.Is on real service returns) + 9 handler-mapping tests (assert each sentinel routes to the expected HTTP status code via mapMFAServiceError)

Closes #512

Test plan

  • go test ./internal/auth/ -count=1 passes (506 tests, +9 new sentinel-identity tests)
  • go test ./internal/api/ -count=1 passes (1364 tests, +9 new handler-mapping tests)
  • go vet ./internal/auth/... ./internal/api/... clean
  • No strings.Contains(err.Error(), ...) remains in mapMFAServiceError; isResetPasswordClientError (out of scope per issue) is unchanged

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling and classification for multi-factor authentication operations to provide clearer, more consistent error messages and appropriate HTTP response codes.
  • Tests

    • Expanded test coverage for authentication and MFA error scenarios to ensure robust error handling across various user-correctable conditions.

Review Change Stack

…512)

Replace substring matching in mapMFAServiceError with typed sentinel
errors checked via errors.Is. Define 8 new exported sentinels in
internal/auth/errors.go (ErrMFAInvalidPassword, ErrMFAInvalidCode,
ErrMFACodeRequired, ErrMFANoEnrollmentInProgress, ErrMFAEnrollmentExpired,
ErrMFANotEnabled, ErrMFANotConfigured, ErrMFAAuthFailed). Each service
method in service_mfa.go and the one site in service.go now wraps the
appropriate sentinel via fmt.Errorf("%w", ...) so renaming an error string
in the service can never silently drift the HTTP status code.

Add 18 new tests: 9 sentinel-identity tests in service_mfa_test.go
(assert errors.Is on real service calls) and 9 handler-mapping tests
in handler_auth_test.go (assert each sentinel routes to the expected
HTTP status via mapMFAServiceError). Message strings are preserved
unchanged so existing substring-based tests continue to pass.
@cristim cristim added triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline impact/internal Team-internal only effort/s Hours type/chore Maintenance / non-user-visible labels May 30, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c570d846-bfd6-4a17-9bca-dbabeb744077

📥 Commits

Reviewing files that changed from the base of the PR and between 4956d66 and 8ce2a43.

📒 Files selected for processing (7)
  • internal/api/handler_auth.go
  • internal/api/handler_auth_test.go
  • internal/auth/errors.go
  • internal/auth/service.go
  • internal/auth/service_mfa.go
  • internal/auth/service_mfa_test.go
  • internal/auth/service_test.go

📝 Walkthrough

Walkthrough

This PR refactors MFA error handling from brittle substring matching to robust sentinel-based error detection. Eight new exported error sentinels are defined in the auth package, service methods now wrap and return these sentinels, and the API handler detects them via errors.Is for reliable HTTP status mapping. Comprehensive tests verify sentinel propagation and handler classification.

Changes

MFA Sentinel Error Refactoring

Layer / File(s) Summary
MFA sentinel error definitions
internal/auth/errors.go
Introduces eight exported error sentinels for MFA operations (invalid password/code, enrollment state, configuration, auth failure) to enable robust sentinel-based error detection.
Service layer returns wrapped MFA sentinels
internal/auth/service.go, internal/auth/service_mfa.go, internal/auth/service_mfa_test.go, internal/auth/service_test.go
verifyPasswordAndMFA, MFASetup, MFAEnable, MFADisable, and MFARegenerateRecoveryCodes now return fmt.Errorf("%w", sentinel) instead of generic strings. New tests verify each method returns the expected sentinel via errors.Is.
API handler detects sentinels and maps to HTTP codes
internal/api/handler_auth.go, internal/api/handler_auth_test.go
mapMFAServiceError replaces substring matching with errors.Is checks, classifying user-correctable errors (invalid password/code, enrollment state, MFA configuration) as HTTP 400 and auth failures as HTTP 401. Handler tests now wrap mocked errors with sentinels and verify correct HTTP code classification.

Sequence Diagram(s)

No sequence diagrams generated. The primary flow (service returns sentinel → handler detects sentinel → HTTP code returned) is straightforward and better represented by the review stack flowchart.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes span six files with mixed complexity: low-complexity sentinel definitions and test assertions, medium-complexity handler and service refactoring with logic density around error classification. Sentinel wrapping and detection patterns are consistent across methods, reducing heterogeneous reasoning required per file.

Possibly related PRs

  • LeanerCloud/CUDly#551: Both PRs modify internal/auth/service.go's authentication path, but this PR introduces MFA-specific sentinel errors while #551 addresses the user-facing login error message unification.

Poem

🐰 From substrings we did flee,
With sentinels wild and free,
Each error now has a name,
No silent drifting—errors stay the same!
HTTP codes ring true and clear,
The MFA path's crystal sphere. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.12% 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 PR title clearly and specifically describes the main change: refactoring to use sentinel errors in mapMFAServiceError instead of substring matching.
Linked Issues check ✅ Passed The PR fully implements the sentinel-error refactor as specified in issue #512: new exported sentinel errors were added, service methods wrap them with %w, mapMFAServiceError uses errors.Is instead of substring matching, and comprehensive tests validate both sentinel identity and correct handler mapping.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #512 objectives: no unrelated modifications are present, and changes are focused on the MFA sentinel-error refactoring and test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/512-wave20

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

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

✅ Actions performed

Review triggered.

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.

… test

- Add mockStore.AssertExpectations(t) to 8 sentinel-identity tests in
  service_mfa_test.go that called On() without asserting all expectations
  were met (violates feedback_mock_assert_expectations pattern).
- Add mockAuth.AssertExpectations(t) to 8 new handler MFA tests in
  handler_auth_test.go for the same reason.
- Add TestMapMFAServiceError_AuthFailed_Is401 to cover the ErrMFAAuthFailed
  -> 401 branch in mapMFAServiceError, which had no corresponding test case.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

✅ Actions performed

Review triggered.

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.

TestLogin_WithMFA_NoSecret was using assert.Contains on err.Error()
(substring matching) for the ErrMFANotConfigured case. Since service.go
now wraps the sentinel via %w, update the assertion to assert.ErrorIs
so renaming the message string would be caught at compile/test time,
consistent with the sentinel-identity pattern from issue #512.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

✅ Actions performed

Review triggered.

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.

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

✅ Actions performed

Review triggered.

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.

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

✅ Actions performed

Review triggered.

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.

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

Labels

effort/s Hours impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant