GoAuth
API Reference

Error Reference

GoAuth uses structured error responses across all modules. Service and HTTP errors use types.GoAuthError, providing a machine-readable code, a…

GoAuth uses structured error responses across all modules. Service and HTTP errors use *types.GoAuthError, providing a machine-readable code, a human-readable message, and an HTTP status code.

Lifecycle APIs such as auth.New, Use, Initialize, and Shutdown return ordinary error values. Storage methods use sentinel errors including models.ErrNotFound and models.ErrConflict. Do not assume every error can be directly asserted to *types.GoAuthError.

GoAuthError Struct

type GoAuthError struct {
    Code       ErrorCode `json:"code"`
    Message    string    `json:"message"`
    StatusCode int       `json:"-"`
    Details    any       `json:"details,omitempty"`
    Err        error     `json:"-"`
}
FieldTypeJSONDescription
CodeErrorCode (string)"code"Machine-readable error code (e.g. "USER_NOT_FOUND")
Messagestring"message"Human-readable error description
StatusCodeinthidden (json:"-")HTTP status code used in the response
Detailsany"details" (omitted if nil)Additional context (e.g. 2FA challenge data)
Errerrorhidden (json:"-")Wrapped underlying error for debugging

Methods

Error() string -- Implements the error interface. Returns Message if no wrapped error, or "Message: wrapped error" if one exists.

Wrap(err error) *GoAuthError -- Sets the underlying error and returns the same GoAuthError for chaining. Use this to attach a root cause while preserving the structured error:

return types.NewDatabaseError().Wrap(err)

Unwrap() error -- Returns the underlying error, enabling errors.Is() and errors.As() to traverse the error chain:

if errors.Is(authErr.Unwrap(), sql.ErrNoRows) {
    // handle missing row
}

Constructor

func NewGoAuthError(code ErrorCode, message string, statusCode int) *GoAuthError

Creates a custom GoAuthError with any code, message, and status code. Most common errors have dedicated factory functions (listed below).

Standard Error Response

All error responses follow this JSON shape:

{
  "code": "ERROR_CODE",
  "message": "Human-readable description of the error",
  "details": null
}

The details field is omitted from the response when nil. It is populated for specific errors like TWO_FACTOR_REQUIRED, where it carries challenge metadata.

The HTTP status code is set on the response header, not in the JSON body.


Error Codes by Category

User Errors

ConstantCodeHTTP StatusDefault Message
ErrUserNotFoundUSER_NOT_FOUND404 Not FoundUser not found
ErrUserAlreadyExistsUSER_ALREADY_EXISTS409 ConflictUser with this email already exists
ErrUserNotActiveUSER_NOT_ACTIVE403 ForbiddenUser account is not active
ErrUserBlockedUSER_BLOCKED403 ForbiddenUser account is blocked
ErrEmailNotVerifiedEMAIL_NOT_VERIFIED403 ForbiddenEmail address not verified
ErrPhoneNotVerifiedPHONE_NOT_VERIFIED403 ForbiddenPhone number not verified
ErrAccountDeactivatedACCOUNT_DEACTIVATED403 ForbiddenAccount has been deactivated
ErrEmailAlreadyVerifiedEMAIL_ALREADY_VERIFIED400 Bad RequestEmail address already verified
ErrPhoneAlreadyVerifiedPHONE_ALREADY_VERIFIED400 Bad RequestPhone number already verified
ErrPhoneAlreadyInUsePHONE_ALREADY_IN_USE400 Bad RequestPhone number already in use
ErrUsernameAlreadyExistsUSERNAME_ALREADY_EXISTS400 Bad RequestUsername already exists

Authentication Errors

ConstantCodeHTTP StatusDefault Message
ErrInvalidCredentialsINVALID_CREDENTIALS401 UnauthorizedInvalid email or password
ErrInvalidTokenINVALID_TOKEN401 UnauthorizedInvalid or expired token
ErrTokenExpiredTOKEN_EXPIRED401 UnauthorizedToken has expired
ErrTokenNotFoundTOKEN_NOT_FOUND400 Bad RequestToken not found
ErrTokenAlreadyUsedTOKEN_ALREADY_USED400 Bad RequestToken already used
ErrInvalidRefreshTokenINVALID_REFRESH_TOKEN401 UnauthorizedInvalid refresh token
ErrAccountLockedACCOUNT_LOCKED423 LockedAccount is temporarily locked due to too many failed attempts
ErrPasswordExpiredPASSWORD_EXPIRED401 UnauthorizedPassword has expired

Session Errors

ConstantCodeHTTP StatusDefault Message
ErrInvalidSessionINVALID_SESSION401 UnauthorizedInvalid session
ErrSessionNotFoundSESSION_NOT_FOUND404 Not FoundSession not found
ErrSessionExpiredSESSION_EXPIRED401 UnauthorizedSession has expired
ErrSessionInvalidSESSION_INVALID401 UnauthorizedSession is invalid

Validation Errors

ConstantCodeHTTP StatusDefault Message
ErrInvalidEmailINVALID_EMAIL400 Bad RequestInvalid email format
ErrInvalidPasswordINVALID_PASSWORD400 Bad RequestInvalid password format
ErrInvalidPhoneINVALID_PHONE400 Bad RequestInvalid phone number format
ErrMissingFieldsMISSING_FIELDS400 Bad RequestMissing required fields
ErrInvalidRequestBodyINVALID_REQUEST_BODY400 Bad Request(custom message)
ErrInvalidJSONINVALID_JSON400 Bad RequestInvalid JSON format
ErrValidationVALIDATION_ERROR(not assigned by default)(not assigned by default)
ErrInvalidRequestIPINVALID_REQUEST_IP(not assigned by default)(not assigned by default)

ErrMissingFields accepts a fields parameter: when provided, the message becomes "Missing required fields: email, password".

ErrInvalidRequestBody is used by NewValidationError(message) which accepts a custom message.

Authorization Errors

ConstantCodeHTTP StatusDefault Message
ErrUnauthorizedUNAUTHORIZED401 UnauthorizedUnauthorized access
ErrForbiddenFORBIDDEN403 ForbiddenAccess forbidden
ErrInvalidCSRFINVALID_CSRF403 ForbiddenInvalid CSRF token
ErrCaptchaRequiredCAPTCHA_REQUIRED403 ForbiddenCaptcha token required
ErrCaptchaFailedCAPTCHA_FAILED403 ForbiddenCaptcha verification failed

Two-Factor Authentication Errors

ConstantCodeHTTP StatusDefault Message
ErrTwoFactorRequiredTWO_FACTOR_REQUIRED200 OKTwo-factor authentication required
ErrTwoFactorAlreadyEnabledTWO_FACTOR_ALREADY_ENABLED400 Bad RequestTwo-factor authentication is already enabled
ErrTwoFactorNotEnabledTWO_FACTOR_NOT_ENABLED400 Bad RequestTwo-factor authentication is not enabled
ErrTwoFactorNotFoundTWO_FACTOR_NOT_FOUND400 Bad RequestTwo-factor authentication not found
ErrTwoFactorInvalidTWO_FACTOR_INVALID400 Bad RequestTwo-factor authentication is invalid
ErrTwoFactorExpiredTWO_FACTOR_EXPIRED400 Bad RequestTwo-factor authentication has expired
ErrTwoFactorAlreadyUsedTWO_FACTOR_ALREADY_USED400 Bad RequestTwo-factor authentication has already been used

TWO_FACTOR_REQUIRED uses HTTP 200 intentionally -- it is not an error but a challenge response. The details field carries challenge metadata (e.g. available 2FA methods).

OAuth Errors

ConstantCodeHTTP StatusDefault Message
ErrOAuthProviderNotFoundOAUTH_PROVIDER_NOT_FOUND400 Bad RequestOAuth provider '<provider>' not found or not configured
ErrOAuthProviderDisabledOAUTH_PROVIDER_DISABLED400 Bad RequestOAuth provider '<provider>' is disabled
ErrOAuthInvalidStateOAUTH_INVALID_STATE400 Bad RequestInvalid OAuth state parameter
ErrOAuthStateExpiredOAUTH_STATE_EXPIRED400 Bad RequestOAuth state has expired. Please try again.
ErrOAuthStateUsedOAUTH_STATE_USED400 Bad RequestOAuth state has already been used
ErrOAuthTokenExchangeOAUTH_TOKEN_EXCHANGE_FAILED400 Bad RequestFailed to exchange authorization code for tokens
ErrOAuthUserInfoOAUTH_USER_INFO_FAILED400 Bad RequestFailed to retrieve user information from OAuth provider
ErrOAuthEmailExistsOAUTH_EMAIL_EXISTS409 ConflictAn account with this email already exists
ErrOAuthSignupDisabledOAUTH_SIGNUP_DISABLED403 ForbiddenSignup via OAuth is disabled
ErrOAuthProviderErrorOAUTH_PROVIDER_ERROR400 Bad RequestOAuth provider returned an error
ErrOAuthNotLinkedOAUTH_NOT_LINKED400 Bad RequestOAuth provider '<provider>' is not linked to this account
ErrOAuthAlreadyLinkedOAUTH_ALREADY_LINKED409 ConflictOAuth provider '<provider>' is already linked to this account
ErrOAuthEmailRequiredOAUTH_EMAIL_REQUIRED400 Bad RequestEmail address is required from OAuth provider
ErrOAuthEmailUnverifiedOAUTH_EMAIL_UNVERIFIED403 ForbiddenThe provider has not verified the email; signup refused unless AllowUnverifiedEmailSignup is set
ErrOAuthAccountLinkingDisabledOAUTH_ACCOUNT_LINKING_DISABLED403 ForbiddenAccount linking via OAuth is disabled

Some OAuth factory functions accept a provider or message parameter that is interpolated into the default message.

Organization Errors

ConstantCodeHTTP StatusDefault Message
ErrOrgNotFoundORG_NOT_FOUND404 Not FoundOrganization not found
ErrOrgSlugTakenORG_SLUG_TAKEN409 ConflictOrganization slug is already taken
ErrOrgMemberExistsORG_MEMBER_EXISTS409 ConflictUser is already a member of this organization
ErrOrgMemberNotFoundORG_MEMBER_NOT_FOUND404 Not FoundOrganization member not found
ErrOrgNotMemberORG_NOT_MEMBER403 ForbiddenYou are not a member of this organization
ErrOrgInsufficientRoleORG_INSUFFICIENT_ROLE403 ForbiddenInsufficient role for this operation
ErrOrgCannotRemoveOwnerORG_CANNOT_REMOVE_OWNER403 ForbiddenCannot remove the organization owner
ErrOrgMaxMembersORG_MAX_MEMBERS403 ForbiddenOrganization has reached the maximum number of members
ErrInvitationNotFoundINVITATION_NOT_FOUND404 Not FoundInvitation not found
ErrInvitationExpiredINVITATION_EXPIRED400 Bad RequestInvitation has expired
ErrInvitationExistsINVITATION_ALREADY_EXISTS409 ConflictA pending invitation already exists for this email
ErrInvitationEmailMismatchINVITATION_EMAIL_MISMATCH403 ForbiddenYour email does not match the invitation

Verification Errors

ConstantCodeHTTP StatusDefault Message
ErrInvalidVerificationTokenINVALID_VERIFICATION_TOKEN400 Bad RequestInvalid verification token
ErrVerificationTokenExpiredVERIFICATION_TOKEN_EXPIRED400 Bad RequestVerification token has expired
ErrInvalidVerificationCodeINVALID_VERIFICATION_CODE400 Bad RequestInvalid verification code
ErrVerificationCodeExpiredVERIFICATION_CODE_EXPIRED400 Bad RequestVerification code has expired

System Errors

ConstantCodeHTTP StatusDefault Message
ErrInternalErrorINTERNAL_ERROR500 Internal Server ErrorAn unexpected error occurred
ErrDatabaseErrorDATABASE_ERROR500 Internal Server ErrorDatabase operation failed
ErrEmailSendFailedEMAIL_SEND_FAILED500 Internal Server ErrorFailed to send email
ErrSmsSendFailedSMS_SEND_FAILED500 Internal Server ErrorFailed to send SMS
ErrConfigurationErrorCONFIGURATION_ERROR500 Internal Server ErrorConfiguration error
ErrMethodNotAllowedMETHOD_NOT_ALLOWED405 Method Not AllowedMethod not allowed

Other Errors

ConstantCodeHTTP StatusDefault Message
ErrUnknownUNKNOWN_ERROR500 Internal Server ErrorAn unexpected error occurred
ErrCustomCUSTOM_ERROR(caller-defined)(caller-defined)

NewCustomError(message) creates an error with no HTTP status code set -- the caller is expected to set StatusCode before returning it to an HTTP handler.


Handling Errors in Client Code

Checking Error Codes

if authErr != nil && authErr.Code == types.ErrUserNotFound {
    // handle user not found
}

Wrapping Errors

user, err := db.FindByEmail(ctx, email)
if err != nil {
    return types.NewDatabaseError().Wrap(err)
}

Using errors.Is with Wrapped Errors

authErr := someOperation()
if authErr != nil && errors.Is(authErr, sql.ErrNoRows) {
    // the underlying cause was a missing DB row
}

Factory Functions with Parameters

Several factory functions accept parameters for dynamic messages:

types.NewMissingFieldsError("email, password")
// Message: "Missing required fields: email, password"

types.NewOAuthProviderNotFoundError("github")
// Message: "OAuth provider 'github' not found or not configured"

types.NewInternalError("failed to hash password")
// Message: "failed to hash password"

types.NewValidationError("password must be at least 8 characters")
// Message: "password must be at least 8 characters"
Edit this page on GitHub

On this page