Skip to content

Commit

Permalink
PIMS-2081 Frontend Authorization Changes (#2685)
Browse files Browse the repository at this point in the history
Co-authored-by: LawrenceLau2020 <[email protected]>
  • Loading branch information
dbarkowsky and LawrenceLau2020 committed Sep 18, 2024
1 parent e191fdb commit 4c89950
Show file tree
Hide file tree
Showing 15 changed files with 50 additions and 35 deletions.
2 changes: 1 addition & 1 deletion react-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const Router = () => {
element={
auth.keycloak.isAuthenticated &&
auth.pimsUser.data?.Status === 'Active' &&
auth.keycloak.user?.client_roles?.length ? (
auth.pimsUser.data?.RoleId ? (
showMap()
) : (
<BaseLayout displayFooter>
Expand Down
6 changes: 3 additions & 3 deletions react-app/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const AppBrand = () => {

const Header: React.FC = () => {
const auth = useContext(AuthContext);
const { logout, isAuthenticated, login, user } = useSSO();
const { logout, isAuthenticated, login } = useSSO();
const theme = useTheme();
const navigate = useNavigate();

Expand Down Expand Up @@ -105,9 +105,9 @@ const Header: React.FC = () => {
<Box textAlign={'center'} alignItems={'center'} gap={'32px'} display={'flex'}>
{isAuthenticated &&
auth?.pimsUser?.data?.Status === 'Active' &&
auth.keycloak.user?.client_roles?.length && (
auth?.pimsUser?.data?.RoleId && (
<>
{user.client_roles?.includes(Roles.ADMIN) ? (
{auth.pimsUser.hasOneOfRoles([Roles.ADMIN]) ? (
<>
<Typography
id="admin-button"
Expand Down
5 changes: 2 additions & 3 deletions react-app/src/components/map/controls/FilterControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ const FilterControl = (props: FilterControlProps) => {
options={
agencyOptions?.filter(
(option) =>
user.keycloak.hasRoles([Roles.ADMIN, Roles.AUDITOR], {
requireAllRoles: false,
}) || usersAgenciesData?.includes(option.value),
user.pimsUser?.hasOneOfRoles([Roles.ADMIN, Roles.AUDITOR]) ||
usersAgenciesData?.includes(option.value),
) ?? []
}
allowNestedIndent
Expand Down
6 changes: 3 additions & 3 deletions react-app/src/components/map/parcelPopup/ParcelPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const ParcelPopup = (props: ParcelPopupProps) => {
const [tabValue, setTabValue] = useState<string>('0');
const { size = 'large', scrollOnClick } = props;

const { keycloak } = useContext(AuthContext);
const { pimsUser } = useContext(AuthContext);

const {
data: ltsaData,
Expand All @@ -65,7 +65,7 @@ export const ParcelPopup = (props: ParcelPopupProps) => {
useEffect(() => {
if (parcelData && clickPosition) {
refreshLtsa();
if (keycloak.hasRoles([Roles.ADMIN])) {
if (pimsUser.hasOneOfRoles([Roles.ADMIN])) {
refreshBCA();
}
}
Expand Down Expand Up @@ -148,7 +148,7 @@ export const ParcelPopup = (props: ParcelPopupProps) => {
>
<Tab label="Parcel Layer" value="0" />
<Tab label="LTSA" value="1" />
{keycloak.hasRoles([Roles.ADMIN]) ? <Tab label="BCA" value="2" /> : <></>}
{pimsUser.hasOneOfRoles([Roles.ADMIN]) ? <Tab label="BCA" value="2" /> : <></>}
</TabList>

<TabPanel value="0" sx={tabPanelStyle}>
Expand Down
6 changes: 3 additions & 3 deletions react-app/src/components/projects/ProjectDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ interface ProjectInfo extends Project {
const ProjectDetail = (props: IProjectDetail) => {
const navigate = useNavigate();
const { id } = useParams();
const { keycloak } = useContext(AuthContext);
const { pimsUser } = useContext(AuthContext);
const lookup = useContext(LookupContext);
const api = usePimsApi();
const { data: lookupData, getLookupValueById } = useContext(LookupContext);
Expand All @@ -88,8 +88,8 @@ const ProjectDetail = (props: IProjectDetail) => {
}
}, [data]);

const isAuditor = keycloak.hasRoles([Roles.AUDITOR]);
const isAdmin = keycloak.hasRoles([Roles.ADMIN]);
const isAuditor = pimsUser.hasOneOfRoles([Roles.AUDITOR]);
const isAdmin = pimsUser.hasOneOfRoles([Roles.ADMIN]);

const { submit: deleteProject, submitting: deletingProject } = useDataSubmitter(
api.projects.deleteProjectById,
Expand Down
4 changes: 2 additions & 2 deletions react-app/src/components/projects/ProjectDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const ProjectGeneralInfoDialog = (props: IProjectGeneralInfoDialog) => {
const { open, postSubmit, onCancel, initialValues } = props;
const api = usePimsApi();
const { data: lookupData } = useContext(LookupContext);
const { keycloak } = useContext(AuthContext);
const { pimsUser } = useContext(AuthContext);
const { submit, submitting } = useDataSubmitter(api.projects.updateProject);
const [approvedStatus, setApprovedStatus] = useState<number>(null);
const projectFormMethods = useForm({
Expand Down Expand Up @@ -167,7 +167,7 @@ export const ProjectGeneralInfoDialog = (props: IProjectGeneralInfoDialog) => {
const status = projectFormMethods.watch('StatusId');
const requireNotificationAcknowledge =
approvedStatus == status && status !== initialValues?.StatusId;
const isAdmin = keycloak.hasRoles([Roles.ADMIN]);
const isAdmin = pimsUser.hasOneOfRoles([Roles.ADMIN]);
console.log('project form values', projectFormMethods.getValues());
return (
<ConfirmDialog
Expand Down
4 changes: 2 additions & 2 deletions react-app/src/components/projects/ProjectForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ interface IProjectGeneralInfoForm {

export const ProjectGeneralInfoForm = (props: IProjectGeneralInfoForm) => {
const { data: lookupData } = useContext(LookupContext);
const { keycloak } = useContext(AuthContext);
const canEdit = keycloak.hasRoles([Roles.ADMIN]);
const { pimsUser } = useContext(AuthContext);
const canEdit = pimsUser.hasOneOfRoles([Roles.ADMIN]);

return (
<Grid mt={'1rem'} spacing={2} container>
Expand Down
4 changes: 2 additions & 2 deletions react-app/src/components/property/PropertyDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface IPropertyDetail {
const PropertyDetail = (props: IPropertyDetail) => {
const navigate = useNavigate();
const params = useParams();
const { keycloak } = useContext(AuthContext);
const { pimsUser } = useContext(AuthContext);
const { getLookupValueById } = useContext(LookupContext);
const parcelId = isNaN(Number(params.parcelId)) ? null : Number(params.parcelId);
const buildingId = isNaN(Number(params.buildingId)) ? null : Number(params.buildingId);
Expand Down Expand Up @@ -100,7 +100,7 @@ const PropertyDetail = (props: IPropertyDetail) => {
const userAgencyIds = userAgencies.map((a) => a.Id);

const canEdit =
keycloak.hasRoles([Roles.ADMIN]) ||
pimsUser.hasOneOfRoles([Roles.ADMIN]) ||
userAgencyIds.includes(parcel?.parsedBody?.AgencyId) ||
userAgencyIds.includes(building?.parsedBody?.AgencyId);

Expand Down
4 changes: 2 additions & 2 deletions react-app/src/components/table/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -436,8 +436,8 @@ export const FilterSearchDataGrid = (props: FilterSearchDataGridProps) => {
: `(${props.rowCountProp ?? 0} rows)`;
}, [props.tableOperationMode, rowCount, props.rowCountProp]);

const { keycloak } = useContext(AuthContext);
const isAuditor = keycloak.hasRoles([Roles.AUDITOR]);
const { pimsUser } = useContext(AuthContext);
const isAuditor = pimsUser.hasOneOfRoles([Roles.AUDITOR]);

return (
<>
Expand Down
4 changes: 2 additions & 2 deletions react-app/src/components/users/UserDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface UserProfile extends User {

const UserDetail = ({ onClose }: IUserDetail) => {
const { id } = useParams();
const { keycloak, pimsUser } = useContext(AuthContext);
const { pimsUser } = useContext(AuthContext);
const { data: lookupData, getLookupValueById } = useContext(LookupContext);
const api = usePimsApi();

Expand Down Expand Up @@ -100,7 +100,7 @@ const UserDetail = ({ onClose }: IUserDetail) => {
mode: 'onBlur',
});

const canEdit = keycloak.hasRoles([Roles.ADMIN]);
const canEdit = pimsUser.hasOneOfRoles([Roles.ADMIN]);

useEffect(() => {
refreshData();
Expand Down
8 changes: 4 additions & 4 deletions react-app/src/constants/roles.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/**
* @enum
* These must match the role names in Keycloak exactly.
* The values in this enum must exactly mirror the IDs in the Role table.
*/
export enum Roles {
ADMIN = 'Administrator',
GENERAL_USER = 'General User',
AUDITOR = 'Auditor',
ADMIN = '00000000-0000-0000-0000-000000000000',
GENERAL_USER = '00000000-0000-0000-0000-000000000001',
AUDITOR = '00000000-0000-0000-0000-000000000002',
}

export interface KeycloakRole {
Expand Down
5 changes: 1 addition & 4 deletions react-app/src/guards/AuthRouteGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ const AuthRouteGuard = (props: AuthGuardProps) => {

if (authStateContext.pimsUser?.data) {
// Redirect from page if lacking roles
if (
permittedRoles &&
!authStateContext.keycloak.hasRoles(permittedRoles, { requireAllRoles: false })
) {
if (permittedRoles && !authStateContext.pimsUser?.hasOneOfRoles(permittedRoles)) {
navigate(redirectRoute ?? '/');
}
// Redirect from page if user does not have Active status
Expand Down
2 changes: 1 addition & 1 deletion react-app/src/hooks/api/useUserAgencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const useUserAgencies = () => {
const userContext = useContext(AuthContext);
const { ungroupedAgencies, agencyOptions } = useGroupedAgenciesApi();
const api = usePimsApi();
const isAdmin = userContext.keycloak.hasRoles([Roles.ADMIN]);
const isAdmin = userContext.pimsUser?.hasOneOfRoles([Roles.ADMIN]);
const { data: userAgencies, refreshData: refreshUserAgencies } = useDataLoader(() =>
api.users.getUsersAgencyIds(userContext.keycloak.user.preferred_username),
);
Expand Down
19 changes: 19 additions & 0 deletions react-app/src/hooks/usePimsUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ import { useSSO } from '@bcgov/citz-imb-sso-react';
import usePimsApi from './usePimsApi';
import useDataLoader from './useDataLoader';
import { User } from './api/useUsersApi';
import { Roles } from '@/constants/roles';

export interface IPimsUser {
data?: User;
refreshData: () => Promise<User>;
isLoading: boolean;
hasOneOfRoles: (requiredRoles: Roles[]) => boolean;
}

/**
* A hook that retrieves the active user's info from the database.
* It uses the username provided by Keycloak to find the corresponding user.
* It includes a function `hasOneOfRoles` to allow for role-based permissions checks.
* @returns Object containing the user's database record and functions related to the loading of this data.
*/
const usePimsUser = () => {
const keycloak = useSSO();
const api = usePimsApi();
Expand All @@ -18,10 +26,21 @@ const usePimsUser = () => {
loadOnce();
}

/**
* Checks the user's roles and returns a boolean if they have the given required roles.
* @param {Roles[]} requiredRoles An array of required Roles.
* @returns True|False depending on user role and required roles.
*/
const hasOneOfRoles = (requiredRoles: Roles[]): boolean => {
if (!data || !data.RoleId || !requiredRoles || !requiredRoles.length) return false;
return requiredRoles.includes(data.RoleId as Roles);
};

return {
data,
refreshData,
isLoading,
hasOneOfRoles,
};
};

Expand Down
6 changes: 3 additions & 3 deletions react-app/src/pages/AccessRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,14 @@ export const AccessRequest = () => {

if (
auth.pimsUser?.data?.Status &&
auth.pimsUser.data?.Status === 'Active' &&
auth.keycloak.user?.client_roles?.length
auth.pimsUser?.data?.Status === 'Active' &&
auth.pimsUser?.data?.RoleId
) {
return <Navigate replace to={'/'} />;
}

const selectPageContent = () => {
if (auth.pimsUser.data?.Status === 'Active' && !auth.keycloak.user?.client_roles?.length) {
if (auth.pimsUser.data?.Status === 'Active' && !auth.pimsUser.data?.RoleId) {
return (
<>
<Typography mb={'2rem'} variant="h2">
Expand Down

0 comments on commit 4c89950

Please sign in to comment.