Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BEEEP] [PM-2844] Add custom error codes for server API exceptions #3141

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d5d5fe0
[PM-2844] Created ErrorMessages resource file
r-tome Jul 28, 2023
d9f65bc
[PM-2844] Created constants class with error codes that match the res…
r-tome Jul 28, 2023
e1f20ad
[PM-2844] Updated ExceptionHandlerFilterAttribute to obtain error mes…
r-tome Jul 28, 2023
b3e41cd
[PM-2844] Updated SyncController and PolicyService to use some ErrorC…
r-tome Jul 28, 2023
431b0cc
Merge branch 'main' into PM-2844-beeep-add-custom-error-codes-for-ser…
r-tome Dec 9, 2024
e304563
Merge branch 'main' into PM-2844-beeep-add-custom-error-codes-for-ser…
r-tome Dec 19, 2024
bfd79eb
Add ErrorMessages class for centralized error handling
r-tome Dec 19, 2024
9015b31
Add new error code for invalid login attempts
r-tome Dec 19, 2024
2876b6d
Add new error code ERR008 for invalid login
r-tome Dec 19, 2024
2b0e7d6
Add Portuguese localization for error messages
r-tome Dec 19, 2024
9f16f31
Refactor ExceptionHandlerFilterAttribute to use ErrorMessages for loc…
r-tome Dec 19, 2024
287e0af
Add support for Portuguese localization in UseCoreLocalization method…
r-tome Dec 19, 2024
671bc9c
Add localization support for error messages in request validators
r-tome Dec 19, 2024
b65b96d
Merge branch 'main' into PM-2844-beeep-add-custom-error-codes-for-ser…
r-tome Dec 20, 2024
c3bd05b
Refactor BaseRequestValidatorTests to use ResourceManagerStringLocali…
r-tome Dec 20, 2024
05b35e5
Refactor BaseRequestValidator to use a dedicated error string localiz…
r-tome Dec 20, 2024
b796e7c
Refactor error string localization in BaseRequestValidator and Except…
r-tome Dec 20, 2024
51c64ac
Refactor error codes
r-tome Dec 20, 2024
a178a77
Add ErrorCode enum and extension method for error code formatting
r-tome Dec 23, 2024
29c0261
Add IErrorMessageService and ErrorMessageService for improved error m…
r-tome Dec 23, 2024
c5c3912
Add constructor to BadRequestException for ErrorCode handling
r-tome Dec 23, 2024
20e673d
Refactor error handling in validators to use IErrorMessageService for…
r-tome Dec 23, 2024
ee800ff
Remove obsolete ErrorCodes class to streamline error handling
r-tome Dec 23, 2024
6536988
Rename error message default resource
r-tome Dec 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions src/Api/Utilities/ExceptionHandlerFilterAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Text;
using Bit.Api.Models.Public.Response;
using Bit.Core.Billing;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.IdentityModel.Tokens;
Expand All @@ -21,7 +23,7 @@ public ExceptionHandlerFilterAttribute(bool publicApi)

public override void OnException(ExceptionContext context)
{
var errorMessage = "An error has occurred.";
var errorMessage = GetFormattedMessageFromErrorCode(context);

var exception = context.Exception;
if (exception == null)
Expand All @@ -46,10 +48,6 @@ public override void OnException(ExceptionContext context)
internalErrorModel = new InternalApi.ErrorResponseModel(badRequestException.ModelState);
}
}
else
{
errorMessage = badRequestException.Message;
}
}
else if (exception is StripeException { StripeError.Type: "card_error" } stripeCardErrorException)
{
Expand All @@ -67,7 +65,6 @@ public override void OnException(ExceptionContext context)
}
else if (exception is GatewayException)
{
errorMessage = exception.Message;
context.HttpContext.Response.StatusCode = 400;
}
else if (exception is BillingException billingException)
Expand Down Expand Up @@ -106,7 +103,6 @@ public override void OnException(ExceptionContext context)
}
else if (exception is NotSupportedException && !string.IsNullOrWhiteSpace(exception.Message))
{
errorMessage = exception.Message;
context.HttpContext.Response.StatusCode = 400;
}
else if (exception is ApplicationException)
Expand All @@ -115,17 +111,17 @@ public override void OnException(ExceptionContext context)
}
else if (exception is NotFoundException)
{
errorMessage = "Resource not found.";
errorMessage = GetFormattedMessageFromErrorCode(context, ErrorCode.CommonResourceNotFound);
context.HttpContext.Response.StatusCode = 404;
}
else if (exception is SecurityTokenValidationException)
{
errorMessage = "Invalid token.";
errorMessage = GetFormattedMessageFromErrorCode(context, ErrorCode.CommonInvalidToken);
context.HttpContext.Response.StatusCode = 403;
}
else if (exception is UnauthorizedAccessException)
{
errorMessage = "Unauthorized.";
errorMessage = GetFormattedMessageFromErrorCode(context, ErrorCode.CommonUnauthorized);
context.HttpContext.Response.StatusCode = 401;
}
else if (exception is ConflictException)
Expand All @@ -150,7 +146,7 @@ public override void OnException(ExceptionContext context)
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ExceptionHandlerFilterAttribute>>();
logger.LogError(0, exception, exception.Message);
errorMessage = "An unhandled server error has occurred.";
errorMessage = GetFormattedMessageFromErrorCode(context, ErrorCode.CommonUnhandledError);
context.HttpContext.Response.StatusCode = 500;
}

Expand All @@ -172,4 +168,11 @@ public override void OnException(ExceptionContext context)
context.Result = new ObjectResult(errorModel);
}
}

private string GetFormattedMessageFromErrorCode(ExceptionContext context, ErrorCode? alternativeErrorCode = null)
{
var errorMessageService = context.HttpContext.RequestServices.GetRequiredService<IErrorMessageService>();

return errorMessageService.GetErrorMessage(context.Exception, alternativeErrorCode);
}
}
2 changes: 1 addition & 1 deletion src/Api/Vault/Controllers/SyncController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public async Task<SyncResponseModel> Get([FromQuery] bool excludeDomains = false
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new BadRequestException("User not found.");
throw new BadRequestException(ErrorCode.CommonUserNotFound);
}

var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
Expand Down
22 changes: 21 additions & 1 deletion src/Core/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
<EmbeddedResource Include="licensing.cer" />
<EmbeddedResource Include="licensing_dev.cer" />
<EmbeddedResource Include="MailTemplates\Handlebars\**\*.hbs" />
<EmbeddedResource Update="Resources\ErrorMessages.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ErrorMessages.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Resources\ErrorMessages.pt.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ErrorMessages.pt.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
Expand Down Expand Up @@ -63,7 +71,19 @@
</ItemGroup>

<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Properties\" />
</ItemGroup>

<ItemGroup>
<Compile Update="Resources\ErrorMessages.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ErrorMessages.resx</DependentUpon>
</Compile>
<Compile Update="Resources\ErrorMessages.pt.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ErrorMessages.pt.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>
26 changes: 26 additions & 0 deletions src/Core/Enums/ErrorCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Bit.Core.Enums;

public enum ErrorCode
{
// Common errors (0000-0999)
CommonError = 0000,
CommonUserNotFound = 0001,
CommonOrganizationNotFound = 0002,
CommonUnauthorized = 0401,
CommonInvalidToken = 0403,
CommonResourceNotFound = 0404,
CommonUnhandledError = 0500,

// Identity errors (1000-1999)
IdentityInvalidUsernameOrPassword = 1001,
IdentitySsoRequired = 1002,
IdentityEncryptionKeyMigrationRequired = 1003
}

public static class ErrorCodeExtensions
{
public static string ToErrorCodeString(this ErrorCode code)
{
return ((int)code).ToString("D4"); // Formats to 4 digits with leading zeros
}
}
7 changes: 6 additions & 1 deletion src/Core/Exceptions/BadRequestException.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Bit.Core.Enums;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Bit.Core.Exceptions;

Expand All @@ -11,6 +12,10 @@ public BadRequestException(string message)
: base(message)
{ }

public BadRequestException(ErrorCode errorCode)
: base(errorCode.ToErrorCodeString())
{ }

public BadRequestException(string key, string errorMessage)
: base("The model state is invalid.")
{
Expand Down
48 changes: 48 additions & 0 deletions src/Core/Resources/ErrorMessages.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions src/Core/Resources/ErrorMessages.pt.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions src/Core/Resources/ErrorMessages.pt.resx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>

<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">

</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="0000" xml:space="preserve">
<value>Ocorreu um erro.</value>
</data>
<data name="0002" xml:space="preserve">
<value>Organização não encontrada.</value>
</data>
<data name="0001" xml:space="preserve">
<value>Utilizador não encontrado.</value>
</data>
<data name="0404" xml:space="preserve">
<value>Recurso não encontrado.</value>
</data>
<data name="0403" xml:space="preserve">
<value>Token inválido.</value>
</data>
<data name="0401" xml:space="preserve">
<value>Não autorizado.</value>
</data>
<data name="0500" xml:space="preserve">
<value>Ocorreu um erro não tratado no servidor.</value>
</data>
<data name="1001" xml:space="preserve">
<value>Nome de utilizador ou palavra-passe incorretos. Tente novamente.</value>
</data>
<data name="1003" xml:space="preserve">
<value>Encryption key migration is required. Please log in to the web vault at</value>
</data>
</root>
48 changes: 48 additions & 0 deletions src/Core/Resources/ErrorMessages.resx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>

<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">

</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="0000" xml:space="preserve">
<value>An error has occurred.</value>
</data>
<data name="0001" xml:space="preserve">
<value>User not found.</value>
</data>
<data name="0002" xml:space="preserve">
<value>Organization not found.</value>
</data>
<data name="0403" xml:space="preserve">
<value>Invalid token.</value>
</data>
<data name="0401" xml:space="preserve">
<value>Unauthorized.</value>
</data>
<data name="0404" xml:space="preserve">
<value>Resource not found.</value>
</data>
<data name="0500" xml:space="preserve">
<value>An unhandled server error has occurred.</value>
</data>
<data name="1003" xml:space="preserve">
<value>Encryption key migration is required. Please log in to the web vault at</value>
</data>
<data name="1001" xml:space="preserve">
<value>Username or password is incorrect. Try again.</value>
</data>
</root>
9 changes: 9 additions & 0 deletions src/Core/Services/IErrorMessageService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Bit.Core.Enums;

namespace Bit.Core.Services;

public interface IErrorMessageService
{
string GetErrorMessage(ErrorCode errorCode);
string GetErrorMessage(Exception exception, ErrorCode? alternativeErrorCode = null);
}
Loading
Loading