Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PIMS-2027 BCSC Provider Options #2675

Merged
merged 10 commits into from
Sep 18, 2024
3 changes: 3 additions & 0 deletions express-api/src/constants/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const config = {
title: 'PIMS',
uri: process.env.FRONTEND_URL,
},
keycloak: {
client_id: process.env.SSO_CLIENT_ID,
},
};

const getConfig = () => {
Expand Down
1 change: 1 addition & 0 deletions express-api/src/controllers/lookup/lookupController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ export const lookupAll = async (req: Request, res: Response) => {
),
Config: {
contactEmail: cfg.contact.toEmail,
bcscIdentifier: cfg.keycloak.client_id,
},
};
return res.status(200).send(returnObj);
Expand Down
2 changes: 2 additions & 0 deletions express-api/src/routes/lookup.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ components:
contactEmail:
type: string
example: [email protected]
bcscIdentifier:
type: string
ConstructionTypes:
type: array
items:
Expand Down
3 changes: 2 additions & 1 deletion express-api/src/typeorm/Entities/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export class User extends BaseEntity {
@Column({ type: 'timestamp', nullable: true })
ApprovedOn: Date;

@Column({ type: 'uuid', nullable: true })
// Using varchar instead of uuid because some providers use characters outside [0-9a-f]
@Column({ type: 'character varying', nullable: true, length: 36 })
KeycloakUserId: string;

// Agency Relations
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class KeycloakUserIdType1726615038425 implements MigrationInterface {
name = 'KeycloakUserIdType1726615038425';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" ALTER COLUMN keycloak_user_id TYPE CHARACTER VARYING(36) USING keycloak_user_id::TEXT;`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" ALTER COLUMN keycloak_user_id TYPE UUID USING keycloak_user_id::UUID;`,
);
}
}
10 changes: 8 additions & 2 deletions react-app/src/components/users/UserDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useParams } from 'react-router-dom';
import useDataSubmitter from '@/hooks/useDataSubmitter';
import { Role, Roles } from '@/constants/roles';
import { LookupContext } from '@/contexts/lookupContext';
import { getProvider } from '@/utilities/helperFunctions';

interface IUserDetail {
onClose: () => void;
Expand Down Expand Up @@ -50,8 +51,13 @@ const UserDetail = ({ onClose }: IUserDetail) => {
Role: lookupData?.Roles?.find((role) => role.Id === data?.RoleId),
};

const provider = useMemo(
() => getProvider(data?.Username, lookupData?.Config.bcscIdentifier),
[data],
);

const userProfileData = {
Provider: data?.Username.includes('idir') ? 'IDIR' : 'BCeID',
Provider: provider,
Email: data?.Email,
FirstName: data?.FirstName,
LastName: data?.LastName,
Expand Down Expand Up @@ -102,7 +108,7 @@ const UserDetail = ({ onClose }: IUserDetail) => {

useEffect(() => {
profileFormMethods.reset({
Provider: data?.Username.includes('idir') ? 'IDIR' : 'BCeID',
Provider: provider,
Email: userProfileData.Email,
FirstName: userProfileData.FirstName,
LastName: userProfileData.LastName,
Expand Down
13 changes: 2 additions & 11 deletions react-app/src/components/users/UsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Agency } from '@/hooks/api/useAgencyApi';
import { User } from '@/hooks/api/useUsersApi';
import { LookupContext } from '@/contexts/lookupContext';
import { Role } from '@/constants/roles';
import { getProvider } from '@/utilities/helperFunctions';

const CustomMenuItem = (props: PropsWithChildren & { value: string }) => {
const theme = useTheme();
Expand Down Expand Up @@ -175,17 +176,7 @@ const UsersTable = (props: IUsersTable) => {
field: 'Username',
headerName: 'Provider',
width: 125,
valueGetter: (value) => {
const username: string = value;
if (username && !username.includes('@')) return undefined;
const provider = username.split('@').at(1);
switch (provider) {
case 'idir':
return 'IDIR';
default:
return 'BCeID';
}
},
valueGetter: (value) => getProvider(value, lookup?.data?.Config.bcscIdentifier),
},
{
field: 'Agency',
Expand Down
1 change: 1 addition & 0 deletions react-app/src/hooks/api/useLookupApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface LookupAll {
RegionalDistricts: Partial<RegionalDistrict>[];
Config: {
contactEmail: string;
bcscIdentifier?: string;
};
}

Expand Down
25 changes: 22 additions & 3 deletions react-app/src/pages/AccessRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React, { useContext, useEffect, useMemo } from 'react';
import pendingImage from '@/assets/images/pending.svg';
import { Box, Button, Grid, Paper, Typography } from '@mui/material';
import AutocompleteFormField from '@/components/form/AutocompleteFormField';
Expand All @@ -18,6 +18,7 @@ import TextFormField from '@/components/form/TextFormField';
import { useGroupedAgenciesApi } from '@/hooks/api/useGroupedAgenciesApi';
import { SnackBarContext } from '@/contexts/snackbarContext';
import { LookupContext } from '@/contexts/lookupContext';
import { getProvider } from '@/utilities/helperFunctions';

interface StatusPageTemplateProps {
blurb: JSX.Element;
Expand All @@ -41,10 +42,16 @@ const StatusPageTemplate = (props: StatusPageTemplateProps) => {
const RequestForm = ({ submitHandler }: { submitHandler: (d: any) => void }) => {
const keycloak = useSSO();
const agencyOptions = useGroupedAgenciesApi().agencyOptions;
const lookup = useContext(LookupContext);

const provider = useMemo(
() => getProvider(keycloak.user?.preferred_username, lookup?.data?.Config.bcscIdentifier),
[keycloak.user, lookup],
);

const formMethods = useForm({
defaultValues: {
UserName: keycloak.user?.username,
Provider: provider,
FirstName: keycloak.user?.first_name,
LastName: keycloak.user?.last_name,
Email: keycloak.user?.email,
Expand All @@ -54,12 +61,24 @@ const RequestForm = ({ submitHandler }: { submitHandler: (d: any) => void }) =>
},
});

useEffect(() => {
formMethods.reset({
Provider: provider,
FirstName: keycloak.user?.first_name || '',
LastName: keycloak.user?.last_name || '',
Email: keycloak.user?.email || '',
Notes: '',
Agency: '',
Position: '',
});
}, [provider, keycloak.user]);

return (
<>
<FormProvider {...formMethods}>
<Grid spacing={2} container>
<Grid item xs={6}>
<TextFormField fullWidth name={'UserName'} label={'IDIR/BCeID'} disabled />
<TextFormField fullWidth name={'Provider'} label={'Provider'} disabled />
</Grid>
<Grid item xs={6}>
<TextFormField fullWidth name={'Email'} label={'Email'} disabled />
Expand Down
20 changes: 20 additions & 0 deletions react-app/src/utilities/helperFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,23 @@ export const getValueByNestedKey = <T extends Record<string, any>>(obj: T, key:
}
return result;
};

/**
* Returns the provider based on the username and optional BCSC identifier.
* @param username The username to check for provider information.
* @param bcscIdentifier The optional BCSC identifier to check for BCSC provider.
* @returns The provider name ('IDIR', 'BCeID', 'BCSC') based on the username or an empty string if no match.
*/
export const getProvider = (username: string, bcscIdentifier?: string) => {
if (!username) return '';
switch (true) {
case username.includes('idir'):
return 'IDIR';
case username.includes('bceid'):
return 'BCeID';
case username.includes(bcscIdentifier):
return 'BC Services Card';
default:
return '';
}
};
Loading