Skip to content

refactor: standardize error response format in auth and oauth handlers#53

Open
NirmalMishra08 wants to merge 3 commits into
roshankumar0036singh:mainfrom
NirmalMishra08:feat/27-normalise-error-helper
Open

refactor: standardize error response format in auth and oauth handlers#53
NirmalMishra08 wants to merge 3 commits into
roshankumar0036singh:mainfrom
NirmalMishra08:feat/27-normalise-error-helper

Conversation

@NirmalMishra08

@NirmalMishra08 NirmalMishra08 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Checklist

  • I have read and signed the CLA by commenting I have read the CLA and agree to its terms. on this PR.

  • My changes follow the project's coding style.

  • I have tested my changes.

  • Added structured error helper for consistent API error responses

  • Standardized error codes, messages, and details fields

  • Migrated auth_handler.go and oauth_handler.go to use the new error format

  • Removed inconsistent JSON error response shapes

Summary by CodeRabbit

  • Bug Fixes
    • Standardized error response format across authentication and OAuth endpoints for consistency.
    • Error responses now include explicit error codes and detailed messages across user registration, login, password management, session handling, and token endpoints.
    • Improved error information to help developers better debug API integration issues.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@NirmalMishra08, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 58 minutes and 29 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 619c8e1e-8133-4edd-9d53-3aac5bc9a216

📥 Commits

Reviewing files that changed from the base of the PR and between fb52137 and 52631b5.

📒 Files selected for processing (6)
  • internal/handler/auth_handler.go
  • internal/handler/auth_handler_test.go
  • internal/handler/oauth_handler.go
  • internal/handler/oauth_handler_test.go
  • internal/service/auth_service.go
  • internal/utils/response.go
📝 Walkthrough

Walkthrough

This PR standardizes API error responses across auth and OAuth handlers by introducing a centralized StructuredError function and replacing scattered error response patterns with a consistent JSON format including explicit error codes and messages.

Changes

Error Response Standardization

Layer / File(s) Summary
Response utilities refactoring
internal/utils/response.go
ErrorDetail struct gains an optional Details field. New exported error-code constants (ErrValidation, ErrUnauthorized, ErrForbidden, ErrInternalServer, ErrGeneric) are introduced. A new StructuredError(code, message, details) function centralizes error response construction. Existing helpers (ValidationErrorResponse, UnauthorizedResponse, ForbiddenResponse) are refactored to use the new function with standardized error codes.
Auth endpoints standardization
internal/handler/auth_handler.go
All auth endpoint error paths (Register, VerifyEmail, ResendVerification, ForgotPassword, ResetPassword, UpdateProfile, ChangePassword, DeleteAccount, GetAuditLogs, Login, RefreshToken, Logout, LogoutAll, GetMe, GetSessions, RevokeSession) are updated to emit StructuredError responses with explicit error codes and messages. ChangePassword includes special handling for "incorrect current password" errors with a mapped error code and status code.
OAuth endpoints standardization
internal/handler/oauth_handler.go
Token handler error responses replace ad-hoc gin.H payloads with StructuredError calls using OAuth-style codes (UNSUPPORTED_GRANT_TYPE, INVALID_CLIENT, INVALID_GRANT). UserInfo handler similarly replaces inline error objects with structured responses using codes (INVALID_TOKEN, USER_NOT_FOUND, FETCH_USER_FAILED).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • Normalize API error shapes #27: Both changes standardize inconsistent error responses into a shared structured error shape across auth and OAuth handlers, implementing a centralized error helper pattern.

Poem

A rabbit hops through error paths so old,
Now bundled neat in structures bright and bold,
Each handler sings the same harmonious tune,
With error codes that make responses swoon!
Consistency blooms where chaos once did reign. 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: standardizing error response formats across auth and oauth handlers, which is the primary objective of the changeset.
Description check ✅ Passed The description covers the main changes including the structured error helper, standardized error codes/messages, handler migrations, and removal of inconsistent responses. All checklist items are completed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@NirmalMishra08

Copy link
Copy Markdown
Contributor Author

@roshankumar0036singh I have added some changes. These are not final changes i just want to know is the modified helper function good or i need to change. I will fix the duplicates that is right now coming.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/utils/response.go (1)

84-103: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

BadRequestResponse and InternalServerErrorResponse are not refactored to use StructuredError.

These two functions still emit the old response shape ("message" at the top level, no "error" object). This creates inconsistency in the API—some error responses will have {"success":false,"error":{...}} while these will have {"success":false,"message":"..."}.

Consider refactoring these to also use StructuredError with appropriate codes (ErrValidation / ErrInternalServer).

♻️ Suggested refactor
 // BadRequestResponse returns a 400 Bad Request response
 func BadRequestResponse(c interface{}, message string) {
 	// Type assertion to *gin.Context
 	if ctx, ok := c.(*gin.Context); ok {
-		ctx.JSON(400, Response{
-			Success: false,
-			Message: message,
-		})
+		ctx.JSON(400, StructuredError(ErrValidation, message, nil))
 	}
 }

 // InternalServerErrorResponse returns a 500 Internal Server Error response
 func InternalServerErrorResponse(c interface{}, message string) {
 	// Type assertion to *gin.Context
 	if ctx, ok := c.(*gin.Context); ok {
-		ctx.JSON(500, Response{
-			Success: false,
-			Message: message,
-		})
+		ctx.JSON(500, StructuredError(ErrInternalServer, message, nil))
 	}
 }
🤖 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 `@internal/utils/response.go` around lines 84 - 103, BadRequestResponse and
InternalServerErrorResponse still emit the old top-level "message" shape; update
both to build and return a StructuredError (instead of the legacy Message field)
with Success:false and Error:StructuredError{Code: ErrValidation /
ErrInternalServer, Message: message} and send that via ctx.JSON, ensuring you
use the StructuredError type and the ErrValidation and ErrInternalServer codes
so the API error shape matches other handlers.
internal/handler/auth_handler.go (1)

141-154: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing return after error response causes double response.

When ResetPassword fails, the handler sends the error JSON but then falls through to also send the success response on line 153. This will cause a runtime panic or corrupted response.

🐛 Proposed fix
 	if err := h.authService.ResetPassword(req.Token, req.Password); err != nil {
 		c.JSON(http.StatusBadRequest, utils.StructuredError("PASSWORD_RESET_FAILED", "Password reset failed", err.Error()))
+		return
 	}
🤖 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 `@internal/handler/auth_handler.go` around lines 141 - 154, In ResetPassword
(func ResetPassword on AuthHandler) you send an error response when
h.authService.ResetPassword(req.Token, req.Password) returns an error but do not
return, causing the success response to be sent as well; fix by exiting the
handler immediately after writing the error JSON (i.e., add a return right after
the c.JSON call that uses utils.StructuredError for "PASSWORD_RESET_FAILED") so
the subsequent c.JSON success response is not executed.
🧹 Nitpick comments (4)
internal/handler/oauth_handler.go (1)

233-238: ⚡ Quick win

extractBearerToken error response not migrated to StructuredError.

When the bearer token is missing or malformed, the handler still returns gin.H{"error": err.Error()} (lines 236). This deviates from the standardized response shape used elsewhere in the same function.

♻️ Suggested fix
 	token, err := extractBearerToken(c)
 	if err != nil {
-		c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
+		c.JSON(http.StatusUnauthorized, utils.StructuredError("INVALID_TOKEN", "Token extraction failed", err.Error()))
 		return
 	}
🤖 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 `@internal/handler/oauth_handler.go` around lines 233 - 238, The error path in
UserInfo still returns gin.H{"error": err.Error()} after calling
extractBearerToken; replace that response with the standardized StructuredError
payload used elsewhere in this handler (construct a StructuredError containing
the error message and appropriate fields/status) so the handler returns the same
response shape on token-extraction failures; update the code in the UserInfo
function immediately after extractBearerToken to build and return a
StructuredError instead of gin.H.
internal/utils/response.go (2)

38-54: ⚡ Quick win

Use ErrGeneric constant instead of hardcoded "ERROR" string.

ErrorResponse uses "ERROR" directly while you've defined ErrGeneric = "ERROR" above. For consistency and maintainability, reference the constant.

♻️ Suggested fix
 	return Response{
 		Success: false,
 		Error: &ErrorDetail{
-			Code:    "ERROR",
+			Code:    ErrGeneric,
 			Message: errMsg,
 		},
 	}
🤖 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 `@internal/utils/response.go` around lines 38 - 54, Replace the hardcoded error
code string in ErrorResponse with the ErrGeneric constant: locate the
ErrorResponse function and change the Code field of the returned ErrorDetail
from "ERROR" to ErrGeneric so the function uses the shared constant for
consistency and maintainability.

57-63: 💤 Low value

Clarify the semantics of ValidationErrorResponse.

The refactored function now places the caller's message into Details while using a fixed "Invalid request" as the message. This changes the API contract—previously the message parameter was the primary message.

If this is intentional (i.e., details provide more granular info), consider renaming the parameter to details for clarity. Otherwise, if the caller's message should remain the primary message, swap the arguments.

🤖 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 `@internal/utils/response.go` around lines 57 - 63, ValidationErrorResponse
currently passes the caller's message into the Details slot of StructuredError
while using a fixed "Invalid request" as the primary message, changing the
previous API semantics; either rename the parameter in ValidationErrorResponse
from message to details and update call sites to reflect that semantics, or keep
the original semantics by swapping the arguments so the caller's message is
passed as the primary message to StructuredError (use ErrValidation,
callerMessage, "Invalid request" or similar); locate the function
ValidationErrorResponse and the StructuredError invocation to apply the change
and update any callers accordingly.
internal/handler/auth_handler.go (1)

508-595: ⚖️ Poor tradeoff

Google/GitHub OAuth callbacks and MFA endpoints still use old error response patterns.

These handlers continue to use utils.ErrorResponse and utils.UnauthorizedResponse/utils.ValidationErrorResponse rather than the new StructuredError pattern. This creates inconsistent error response shapes across the API.

Consider migrating these in a follow-up to maintain a unified error contract.

Also applies to: 601-689, 698-769

🤖 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 `@internal/handler/auth_handler.go` around lines 508 - 595, Handlers
GoogleLogin and GoogleCallback (and other OAuth/MFA handlers referenced) still
return errors using utils.ErrorResponse / UnauthorizedResponse /
ValidationErrorResponse; migrate them to the new StructuredError pattern so
error responses have a consistent shape. For each error branch in GoogleLogin,
GoogleCallback and the GitHub/MFA handlers, construct a StructuredError instance
with the same HTTP status, human message and underlying error/details, and
return that via c.JSON(status, structuredError) (or the app's
StructuredError-to-response helper) instead of calling
utils.ErrorResponse/UnauthorizedResponse/ValidationErrorResponse; update all
occurrences (including state validation, token exchange, user fetch, login
failures, and cookie errors) to use StructuredError while preserving the
original status codes and messages.
🤖 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.

Outside diff comments:
In `@internal/handler/auth_handler.go`:
- Around line 141-154: In ResetPassword (func ResetPassword on AuthHandler) you
send an error response when h.authService.ResetPassword(req.Token, req.Password)
returns an error but do not return, causing the success response to be sent as
well; fix by exiting the handler immediately after writing the error JSON (i.e.,
add a return right after the c.JSON call that uses utils.StructuredError for
"PASSWORD_RESET_FAILED") so the subsequent c.JSON success response is not
executed.

In `@internal/utils/response.go`:
- Around line 84-103: BadRequestResponse and InternalServerErrorResponse still
emit the old top-level "message" shape; update both to build and return a
StructuredError (instead of the legacy Message field) with Success:false and
Error:StructuredError{Code: ErrValidation / ErrInternalServer, Message: message}
and send that via ctx.JSON, ensuring you use the StructuredError type and the
ErrValidation and ErrInternalServer codes so the API error shape matches other
handlers.

---

Nitpick comments:
In `@internal/handler/auth_handler.go`:
- Around line 508-595: Handlers GoogleLogin and GoogleCallback (and other
OAuth/MFA handlers referenced) still return errors using utils.ErrorResponse /
UnauthorizedResponse / ValidationErrorResponse; migrate them to the new
StructuredError pattern so error responses have a consistent shape. For each
error branch in GoogleLogin, GoogleCallback and the GitHub/MFA handlers,
construct a StructuredError instance with the same HTTP status, human message
and underlying error/details, and return that via c.JSON(status,
structuredError) (or the app's StructuredError-to-response helper) instead of
calling utils.ErrorResponse/UnauthorizedResponse/ValidationErrorResponse; update
all occurrences (including state validation, token exchange, user fetch, login
failures, and cookie errors) to use StructuredError while preserving the
original status codes and messages.

In `@internal/handler/oauth_handler.go`:
- Around line 233-238: The error path in UserInfo still returns gin.H{"error":
err.Error()} after calling extractBearerToken; replace that response with the
standardized StructuredError payload used elsewhere in this handler (construct a
StructuredError containing the error message and appropriate fields/status) so
the handler returns the same response shape on token-extraction failures; update
the code in the UserInfo function immediately after extractBearerToken to build
and return a StructuredError instead of gin.H.

In `@internal/utils/response.go`:
- Around line 38-54: Replace the hardcoded error code string in ErrorResponse
with the ErrGeneric constant: locate the ErrorResponse function and change the
Code field of the returned ErrorDetail from "ERROR" to ErrGeneric so the
function uses the shared constant for consistency and maintainability.
- Around line 57-63: ValidationErrorResponse currently passes the caller's
message into the Details slot of StructuredError while using a fixed "Invalid
request" as the primary message, changing the previous API semantics; either
rename the parameter in ValidationErrorResponse from message to details and
update call sites to reflect that semantics, or keep the original semantics by
swapping the arguments so the caller's message is passed as the primary message
to StructuredError (use ErrValidation, callerMessage, "Invalid request" or
similar); locate the function ValidationErrorResponse and the StructuredError
invocation to apply the change and update any callers accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4deed7f3-ef4c-4820-8810-49c39e37a4ad

📥 Commits

Reviewing files that changed from the base of the PR and between d62de40 and fb52137.

📒 Files selected for processing (3)
  • internal/handler/auth_handler.go
  • internal/handler/oauth_handler.go
  • internal/utils/response.go

@roshankumar0036singh

Copy link
Copy Markdown
Owner

handle the nitpick comments by coderabbit and check the cause of failure for the ci

@NirmalMishra08

Copy link
Copy Markdown
Contributor Author

@roshankumar0036singh changing the error response format is causing error

@roshankumar0036singh

Copy link
Copy Markdown
Owner

@NirmalMishra08 ResetPassword missing return after error response
ValidationErrorResponse silently changes its message
UnauthorizedResponse and ForbiddenResponse lose their Message field
Error code constants are defined but most call sites don't use them
ChangePassword error handling uses err.Error() string comparison
oauth_handler.go still accesses repository layer directly and run go fmt

@roshankumar0036singh

Copy link
Copy Markdown
Owner

@NirmalMishra08 Two tests are failing, bcz the tests were written against the old response shape and haven't been updated to match the new StructuredError format

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
26.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@roshankumar0036singh

Copy link
Copy Markdown
Owner

@NirmalMishra08 do check the issues flagged by sonar and the exsitence of duplicate code blocks in the pr

@roshankumar0036singh

Copy link
Copy Markdown
Owner

@NirmalMishra08 are you there resolev the sonar issues and check this once UnauthorizedResponse now returns a structured error but still sets Message on the root

The old implementation set Message at the root level without an error object. The new one wraps it correctly via StructuredError, but any client relying on the old shape will break.

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