Define server error messages to display in FE from the server (#12973)

Currently, when a server query or mutation from the front-end fails, the
error message defined server-side is displayed in a snackbar in the
front-end.
These error messages usually contain technical details that don't belong
to the user interface, such as "ObjectMetadataCollection not found" or
"invalid ENUM value for ...".

**BE**
In addition to the original error message that is still needed (for the
request response, debugging, sentry monitoring etc.), we add a
`displayedErrorMessage` that will be used in the snackbars. It's only
relevant to add it for the messages that will reach the FE (ie. not in
jobs or in rest api for instance) and if it can help the user sort out /
fix things (ie. we do add displayedErrorMessage for "Cannot create
multiple draft versions for the same workflow" or "Cannot delete
[field], please update the label identifier field first", but not
"Object metadata does not exist"), even if in practice in the FE users
should not be able to perform an action that will not work (ie should
not be able to save creation of multiple draft versions of the same
workflows).

**FE**
To ease the usage we replaced enqueueSnackBar with enqueueErrorSnackBar
and enqueueSuccessSnackBar with an api that only requires to pass on the
error.
If no displayedErrorMessage is specified then the default error message
is `An error occured.`
This commit is contained in:
Marie
2025-07-03 14:42:10 +02:00
committed by GitHub
parent 1f1318febf
commit 288f0919db
133 changed files with 1501 additions and 711 deletions
@@ -2,8 +2,12 @@ import { CustomException } from 'src/utils/custom-exception';
export class AuthException extends CustomException {
declare code: AuthExceptionCode;
constructor(message: string, code: AuthExceptionCode) {
super(message, code);
constructor(
message: string,
code: AuthExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: string } = {},
) {
super(message, code, userFriendlyMessage);
}
}
@@ -1,5 +1,7 @@
import { Catch, ExceptionFilter } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import {
AuthException,
AuthExceptionCode,
@@ -16,26 +18,39 @@ export class AuthGraphqlApiExceptionFilter implements ExceptionFilter {
catch(exception: AuthException) {
switch (exception.code) {
case AuthExceptionCode.CLIENT_NOT_FOUND:
throw new NotFoundError(exception.message);
throw new NotFoundError(exception.message, {
userFriendlyMessage: exception.userFriendlyMessage,
});
case AuthExceptionCode.INVALID_INPUT:
throw new UserInputError(exception.message);
throw new UserInputError(exception.message, {
userFriendlyMessage: exception.userFriendlyMessage,
});
case AuthExceptionCode.FORBIDDEN_EXCEPTION:
case AuthExceptionCode.INSUFFICIENT_SCOPES:
case AuthExceptionCode.OAUTH_ACCESS_DENIED:
case AuthExceptionCode.SSO_AUTH_FAILED:
case AuthExceptionCode.USE_SSO_AUTH:
case AuthExceptionCode.SIGNUP_DISABLED:
case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED:
case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED:
case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE:
case AuthExceptionCode.INVALID_JWT_TOKEN_TYPE:
throw new ForbiddenError(exception.message);
throw new ForbiddenError(exception.message, {
userFriendlyMessage: exception.userFriendlyMessage,
});
case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED:
case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED:
throw new ForbiddenError(exception.message, {
userFriendlyMessage: t`Authentication is not enabled with this provider.`,
});
case AuthExceptionCode.EMAIL_NOT_VERIFIED:
case AuthExceptionCode.INVALID_DATA:
throw new ForbiddenError(exception.message, {
subCode: AuthExceptionCode.EMAIL_NOT_VERIFIED,
userFriendlyMessage: t`Email is not verified.`,
});
case AuthExceptionCode.UNAUTHENTICATED:
throw new AuthenticationError(exception.message, {
userFriendlyMessage: t`You must be authenticated to perform this action.`,
});
case AuthExceptionCode.USER_NOT_FOUND:
case AuthExceptionCode.WORKSPACE_NOT_FOUND:
throw new AuthenticationError(exception.message);
@@ -169,6 +169,9 @@ export class AuthService {
throw new AuthException(
'Wrong password',
AuthExceptionCode.FORBIDDEN_EXCEPTION,
{
userFriendlyMessage: t`Wrong password`,
},
);
}
@@ -2,6 +2,7 @@ import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { t } from '@lingui/core/macro';
import { TWENTY_ICONS_BASE_URL } from 'twenty-shared/constants';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { Repository } from 'typeorm';
@@ -67,6 +68,9 @@ export class SignInUpService {
throw new AuthException(
'Email is required',
AuthExceptionCode.INVALID_INPUT,
{
userFriendlyMessage: t`Email is required`,
},
);
}
@@ -111,6 +115,9 @@ export class SignInUpService {
throw new AuthException(
'Password too weak',
AuthExceptionCode.INVALID_INPUT,
{
userFriendlyMessage: t`Password too weak`,
},
);
}
@@ -130,6 +137,9 @@ export class SignInUpService {
throw new AuthException(
'Wrong password',
AuthExceptionCode.FORBIDDEN_EXCEPTION,
{
userFriendlyMessage: t`Wrong password`,
},
);
}
}
@@ -153,6 +163,9 @@ export class SignInUpService {
throw new AuthException(
'Email is required',
AuthExceptionCode.INVALID_INPUT,
{
userFriendlyMessage: t`Email is required`,
},
);
}
@@ -194,6 +207,9 @@ export class SignInUpService {
throw new AuthException(
'Workspace is not ready to welcome new members',
AuthExceptionCode.FORBIDDEN_EXCEPTION,
{
userFriendlyMessage: t`Workspace is not ready to welcome new members`,
},
);
}
@@ -207,6 +223,9 @@ export class SignInUpService {
throw new AuthException(
'User is not part of the workspace',
AuthExceptionCode.FORBIDDEN_EXCEPTION,
{
userFriendlyMessage: t`User is not part of the workspace`,
},
);
}
}
@@ -340,6 +359,9 @@ export class SignInUpService {
throw new AuthException(
'Email is required',
AuthExceptionCode.INVALID_INPUT,
{
userFriendlyMessage: t`Email is required`,
},
);
}