From 70a86731f875603f5ed4fb464ad2b17e5e7177ad Mon Sep 17 00:00:00 2001 From: James Kiger <68701146+jamesrkiger@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:23:20 -0500 Subject: [PATCH] feat(organizations): update page access permissions TASK-977 (#5219) ## Description For various pages in account settings, we previously used a wrapper component to verify whether the user was the owner of their org before allowing them to access the page. With the organizations project, this permissions check needs to allow for checking multiple role options. We also need to be able to check whether an org is an mmo for the members page. This PR overhauls the old wrapper component to allow for these checks. --- .../requireOrgOwner.component.tsx | 33 ------------- .../validateOrgPermissions.component.tsx | 49 +++++++++++++++++++ jsapp/js/account/routes.tsx | 45 +++++++++++++---- 3 files changed, 84 insertions(+), 43 deletions(-) delete mode 100644 jsapp/js/account/organizations/requireOrgOwner.component.tsx create mode 100644 jsapp/js/account/organizations/validateOrgPermissions.component.tsx diff --git a/jsapp/js/account/organizations/requireOrgOwner.component.tsx b/jsapp/js/account/organizations/requireOrgOwner.component.tsx deleted file mode 100644 index 841505f0ac..0000000000 --- a/jsapp/js/account/organizations/requireOrgOwner.component.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, {Suspense, useEffect} from 'react'; -import {useNavigate} from 'react-router-dom'; -import LoadingSpinner from 'js/components/common/loadingSpinner'; -import {ACCOUNT_ROUTES} from 'js/account/routes.constants'; -import {useOrganizationQuery} from 'js/account/stripe.api'; - -interface Props { - children: React.ReactNode; - redirect?: boolean; -} - -export const RequireOrgOwner = ({children, redirect = true}: Props) => { - const navigate = useNavigate(); - const orgQuery = useOrganizationQuery(); - - // Redirect to Account Settings if you're not the owner - useEffect(() => { - if ( - redirect && - !orgQuery.isPending && - orgQuery.data && - !orgQuery.data.is_owner - ) { - navigate(ACCOUNT_ROUTES.ACCOUNT_SETTINGS); - } - }, [redirect, orgQuery.isSuccess, orgQuery.data, navigate]); - - return redirect && orgQuery.data?.is_owner ? ( - {children} - ) : ( - - ); -}; diff --git a/jsapp/js/account/organizations/validateOrgPermissions.component.tsx b/jsapp/js/account/organizations/validateOrgPermissions.component.tsx new file mode 100644 index 0000000000..8d2e83792e --- /dev/null +++ b/jsapp/js/account/organizations/validateOrgPermissions.component.tsx @@ -0,0 +1,49 @@ +import React, {Suspense, useEffect} from 'react'; +import {useNavigate} from 'react-router-dom'; +import LoadingSpinner from 'js/components/common/loadingSpinner'; +import {ACCOUNT_ROUTES} from 'js/account/routes.constants'; +import {useOrganizationQuery} from 'js/account/stripe.api'; +import {OrganizationUserRole} from '../stripe.types'; + +interface Props { + children: React.ReactNode; + validRoles?: OrganizationUserRole[]; + mmoOnly?: boolean; + redirect?: boolean; +} + +/** + * Use to handle display of pages that should only be accessible to certain user roles + * or members of MMOs. Defaults to allowing access for all users, so you must supply + * any restrictions. + */ +export const ValidateOrgPermissions = ({ + children, + validRoles = undefined, + mmoOnly = false, + redirect = true, +}: Props) => { + const navigate = useNavigate(); + const orgQuery = useOrganizationQuery(); + const hasValidRole = validRoles ? validRoles.includes( + orgQuery.data?.request_user_role ?? OrganizationUserRole.member + ) : true; + const hasValidOrg = mmoOnly ? orgQuery.data?.is_mmo : true; + + // Redirect to Account Settings if conditions not met + useEffect(() => { + if ( + redirect && + orgQuery.data && + (!hasValidRole || !hasValidOrg) + ) { + navigate(ACCOUNT_ROUTES.ACCOUNT_SETTINGS); + } + }, [redirect, orgQuery.data, navigate]); + + return redirect && hasValidRole && hasValidOrg ? ( + {children} + ) : ( + + ); +}; diff --git a/jsapp/js/account/routes.tsx b/jsapp/js/account/routes.tsx index 452eeb064f..1046d5ed85 100644 --- a/jsapp/js/account/routes.tsx +++ b/jsapp/js/account/routes.tsx @@ -1,7 +1,8 @@ import React from 'react'; import {Navigate, Route} from 'react-router-dom'; import RequireAuth from 'js/router/requireAuth'; -import {RequireOrgOwner} from 'js/account/organizations/requireOrgOwner.component'; +import {ValidateOrgPermissions} from 'js/account/organizations/validateOrgPermissions.component'; +import {OrganizationUserRole} from './stripe.types'; import { ACCOUNT_ROUTES, AccountSettings, @@ -35,9 +36,9 @@ export default function routes() { index element={ - + - + } /> @@ -46,9 +47,9 @@ export default function routes() { index element={ - + - + } /> @@ -57,9 +58,14 @@ export default function routes() { index element={ - + - + } /> @@ -67,7 +73,16 @@ export default function routes() { path={ACCOUNT_ROUTES.USAGE_PROJECT_BREAKDOWN} element={ - + + + } /> @@ -93,7 +108,9 @@ export default function routes() { path={ACCOUNT_ROUTES.ORGANIZATION_MEMBERS} element={ - Organization members view to be implemented + + Organization members view to be implemented + } /> @@ -101,7 +118,15 @@ export default function routes() { path={ACCOUNT_ROUTES.ORGANIZATION_SETTINGS} element={ - Organization settings view to be implemented + + Organization settings view to be implemented + } />