From 7650bc5111bd062b9d77973e038bef0900da56a1 Mon Sep 17 00:00:00 2001 From: Thomas Lathuiliere <40292402+balzdur@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:57:58 +0200 Subject: [PATCH] feat(marble-admin): gracefully reject marble admin user (#424) --- .../app-builder/public/locales/en/common.json | 2 + packages/app-builder/src/models/user.ts | 3 + .../src/routes/_builder+/_layout.tsx | 56 ++++++++++++++++++- .../src/utils/http/http-responses.ts | 2 + 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/app-builder/public/locales/en/common.json b/packages/app-builder/public/locales/en/common.json index e44705254..566acf070 100644 --- a/packages/app-builder/public/locales/en/common.json +++ b/packages/app-builder/public/locales/en/common.json @@ -37,6 +37,8 @@ "go_back": "Go back", "error_boundary.default.title": "Did you lose your marbles?", "error_boundary.default.subtitle": "It looks like something went wrong.", + "error_boundary.marble_admin.title": "You are a Marble Admin", + "error_boundary.marble_admin.subtitle": "The current application is not available for Marble Admins. Please sign in with a different account.", "or": "or", "loading": "Loading...", "understand": "I understand", diff --git a/packages/app-builder/src/models/user.ts b/packages/app-builder/src/models/user.ts index 29f7bc9cd..97aca92d5 100644 --- a/packages/app-builder/src/models/user.ts +++ b/packages/app-builder/src/models/user.ts @@ -78,3 +78,6 @@ export function adaptUser(user: UserDto): User { } export const isAdmin = (user: CurrentUser) => user.role === 'ADMIN'; + +export const isMarbleAdmin = (user: CurrentUser) => + user.role === 'MARBLE_ADMIN'; diff --git a/packages/app-builder/src/routes/_builder+/_layout.tsx b/packages/app-builder/src/routes/_builder+/_layout.tsx index 2bb8daf14..f609d9dcc 100644 --- a/packages/app-builder/src/routes/_builder+/_layout.tsx +++ b/packages/app-builder/src/routes/_builder+/_layout.tsx @@ -1,10 +1,11 @@ import { + ErrorComponent, navigationI18n, PermissionsProvider, SidebarButton, SidebarLink, } from '@app-builder/components'; -import { isAdmin } from '@app-builder/models'; +import { isAdmin, isMarbleAdmin } from '@app-builder/models'; import { useRefreshToken } from '@app-builder/routes/ressources+/auth+/refresh'; import { LanguagePicker } from '@app-builder/routes/ressources+/user+/language'; import { ChatlioWidget } from '@app-builder/services/chatlio/ChatlioWidget'; @@ -18,10 +19,19 @@ import { } from '@app-builder/services/segment'; import { getFullName } from '@app-builder/services/user'; import { getClientEnv } from '@app-builder/utils/environment'; +import { conflict } from '@app-builder/utils/http/http-responses'; +import { CONFLICT } from '@app-builder/utils/http/http-status-codes'; import { getRoute } from '@app-builder/utils/routes'; import * as Popover from '@radix-ui/react-popover'; import { json, type LoaderFunctionArgs } from '@remix-run/node'; -import { Form, Outlet, useLoaderData } from '@remix-run/react'; +import { + Form, + isRouteErrorResponse, + Outlet, + useLoaderData, + useRouteError, +} from '@remix-run/react'; +import { captureRemixErrorBoundaryError } from '@sentry/remix'; import clsx from 'clsx'; import { type Namespace } from 'i18next'; import { useState } from 'react'; @@ -35,6 +45,10 @@ export async function loader({ request }: LoaderFunctionArgs) { failureRedirect: getRoute('/sign-in'), }); + if (isMarbleAdmin(user)) { + throw conflict("Marble Admins can't access the app builder."); + } + const [organizationDetail, orgUsers, orgTags] = await Promise.all([ organization.getCurrentOrganization(), organization.listUsers(), @@ -275,3 +289,41 @@ export default function Builder() { ); } + +export function ErrorBoundary() { + const error = useRouteError(); + const { t } = useTranslation(handle.i18n); + + // Handle Marble Admins, do not capture error in Sentry + if (isRouteErrorResponse(error) && error.status === CONFLICT) { + return ( +
+
+

+ {t('common:error_boundary.marble_admin.title')} +

+

+ {t('common:error_boundary.marble_admin.subtitle')} +

+
+
+ +
+
+
+
+ ); + } + + captureRemixErrorBoundaryError(error); + + return ; +} diff --git a/packages/app-builder/src/utils/http/http-responses.ts b/packages/app-builder/src/utils/http/http-responses.ts index 777d9c59f..b5a12bce6 100644 --- a/packages/app-builder/src/utils/http/http-responses.ts +++ b/packages/app-builder/src/utils/http/http-responses.ts @@ -2,6 +2,7 @@ import { json } from '@remix-run/node'; import { BAD_REQUEST, + CONFLICT, FORBIDDEN, INTERNAL_SERVER_ERROR, NOT_FOUND, @@ -18,5 +19,6 @@ export const badRequest = errorResponse(BAD_REQUEST); export const unauthorized = errorResponse(UNAUTHORIZED); export const forbidden = errorResponse(FORBIDDEN); export const notFound = errorResponse(NOT_FOUND); +export const conflict = errorResponse(CONFLICT); export const internalServerError = errorResponse(INTERNAL_SERVER_ERROR);