Skip to content

Commit

Permalink
feat(marble-admin): gracefully reject marble admin user (#424)
Browse files Browse the repository at this point in the history
  • Loading branch information
balzdur authored Apr 9, 2024
1 parent dfd9aa5 commit 7650bc5
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/app-builder/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions packages/app-builder/src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
56 changes: 54 additions & 2 deletions packages/app-builder/src/routes/_builder+/_layout.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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(),
Expand Down Expand Up @@ -275,3 +289,41 @@ export default function Builder() {
</PermissionsProvider>
);
}

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 (
<div className="bg-purple-05 flex size-full items-center justify-center">
<div className="bg-grey-00 flex max-w-md flex-col items-center gap-4 rounded-2xl p-10 text-center shadow-md">
<h1 className="text-l text-purple-110 font-semibold">
{t('common:error_boundary.marble_admin.title')}
</h1>
<p className="text-s mb-6">
{t('common:error_boundary.marble_admin.subtitle')}
</p>
<div className="mb-1">
<Form action={getRoute('/ressources/auth/logout')} method="POST">
<Button
type="submit"
onClick={() => {
void segment.reset();
}}
>
<Icon icon="logout" className="size-5" />
{t('common:auth.logout')}
</Button>
</Form>
</div>
</div>
</div>
);
}

captureRemixErrorBoundaryError(error);

return <ErrorComponent error={error} />;
}
2 changes: 2 additions & 0 deletions packages/app-builder/src/utils/http/http-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { json } from '@remix-run/node';

import {
BAD_REQUEST,
CONFLICT,
FORBIDDEN,
INTERNAL_SERVER_ERROR,
NOT_FOUND,
Expand All @@ -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);

0 comments on commit 7650bc5

Please sign in to comment.