Skip to content

Commit

Permalink
Merge pull request #514 from IABTechLab/ajy-UID2-3878-Refactor-some-u…
Browse files Browse the repository at this point in the history
…ser-invite-code

Refactor some user invite code
  • Loading branch information
alex-yau-ttd committed Sep 4, 2024
2 parents d6f8d16 + 8a0108e commit 131a9f3
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 110 deletions.
3 changes: 2 additions & 1 deletion src/api/configureApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import makeMetricsApiMiddleware from './middleware/metrics';
import { createParticipantsRouter } from './routers/participants/participantsRouter';
import { createSitesRouter } from './routers/sitesRouter';
import { createUsersRouter } from './routers/usersRouter';
import { API_PARTICIPANT_MEMBER_ROLE_NAME } from './services/kcUsersService';
import { LoggerService } from './services/loggerService';
import { UserService } from './services/userService';

Expand Down Expand Up @@ -134,7 +135,7 @@ export function configureAndStartApi(useMetrics: boolean = true, portNumber: num
bypassHandlerForPaths(
claimCheck((claim: Claim) => {
const roles = claim.resource_access?.self_serve_portal_apis?.roles || [];
return roles.includes('api-participant-member');
return roles.includes(API_PARTICIPANT_MEMBER_ROLE_NAME);
}),
...BYPASS_CLAIM_PATHS,
...BYPASS_AUTH_PATHS
Expand Down
10 changes: 5 additions & 5 deletions src/api/controllers/userController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { ParticipantStatus } from '../entities/Participant';
import { getTraceId } from '../helpers/loggingHelpers';
import { getKcAdminClient } from '../keycloakAdminClient';
import {
assignClientRoleToUser,
assignApiParticipantMemberRole,
queryUsersByEmail,
sendInviteEmail,
sendInviteEmailToNewUser,
} from '../services/kcUsersService';
import { LoggerService } from '../services/loggerService';
import { SelfResendInvitationParser, UserService } from '../services/userService';
Expand Down Expand Up @@ -66,7 +66,7 @@ export class UserController {
const kcAdminClient = await getKcAdminClient();
const promises = [
req.user!.$query().patch({ acceptedTerms: true }),
assignClientRoleToUser(kcAdminClient, req.user?.email!, 'api-participant-member'),
assignApiParticipantMemberRole(kcAdminClient, req.user?.email!),
];
await Promise.all(promises);
res.sendStatus(200);
Expand All @@ -85,7 +85,7 @@ export class UserController {
res.sendStatus(200);
}
logger.info(`Resending invitation email for ${email}, keycloak ID ${user[0].id}`);
await sendInviteEmail(kcAdminClient, user[0]);
await sendInviteEmailToNewUser(kcAdminClient, user[0]);
res.sendStatus(200);
}

Expand Down Expand Up @@ -116,7 +116,7 @@ export class UserController {
}

logger.info(`Resending invitation email for ${req.user?.email}, keycloak ID ${user[0].id}`);
await sendInviteEmail(kcAdminClient, user[0]);
await sendInviteEmailToNewUser(kcAdminClient, user[0]);
res.sendStatus(200);
}

Expand Down
59 changes: 33 additions & 26 deletions src/api/routers/participants/participantsCreation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AuditAction, AuditTrailEvents } from '../../entities/AuditTrail';
import {
Participant,
ParticipantCreationPartial,
ParticipantStatus
ParticipantStatus,
} from '../../entities/Participant';
import { User, UserCreationPartial } from '../../entities/User';
import { UserRoleId } from '../../entities/UserRole';
Expand All @@ -16,26 +16,26 @@ import { getKcAdminClient } from '../../keycloakAdminClient';
import { addSite, getSiteList, setSiteClientTypes } from '../../services/adminServiceClient';
import {
mapClientTypesToAdminEnums,
SiteCreationRequest
SiteCreationRequest,
} from '../../services/adminServiceHelpers';
import {
constructAuditTrailObject,
performAsyncOperationWithAuditTrail
performAsyncOperationWithAuditTrail,
} from '../../services/auditTrailService';
import {
assignClientRoleToUser,
assignApiParticipantMemberRole,
createNewUser,
sendInviteEmail
sendInviteEmailToNewUser,
} from '../../services/kcUsersService';
import {
getParticipantTypesByIds,
ParticipantRequest,
sendNewParticipantEmail
sendNewParticipantEmail,
} from '../../services/participantsService';
import { findUserByEmail } from '../../services/usersService';
import {
ParticipantCreationAndApprovalPartial,
ParticipantCreationRequest
ParticipantCreationRequest,
} from './participantClasses';

export async function validateParticipantCreationRequest(
Expand Down Expand Up @@ -95,23 +95,13 @@ const createUserAndAssociatedParticipant = async (
});
};

export async function createParticipant(req: ParticipantRequest, res: Response) {
const participantRequest = ParticipantCreationRequest.parse(req.body);
const traceId = getTraceId(req);

const validationError = await validateParticipantCreationRequest(participantRequest);
if (validationError) {
return res.status(400).send(validationError);
}

const requestingUser = await findUserByEmail(req.auth?.payload?.email as string);
const user = UserCreationPartial.parse({
...req.body,
acceptedTerms: false,
});

async function createParticipant(
email: string,
participantRequest: z.infer<typeof ParticipantCreationRequest>,
user: z.infer<typeof UserCreationPartial>,
traceId: string
) {
const types = await getParticipantTypesByIds(participantRequest.participantTypes);
const apiRoles = await ApiRole.query().findByIds(participantRequest.apiRoles);

let site;
if (!participantRequest.siteId) {
Expand All @@ -127,7 +117,7 @@ export async function createParticipant(req: ParticipantRequest, res: Response)
// existing site. Update client types
setSiteClientTypes({ siteId: participantRequest.siteId, types });
}

const apiRoles = await ApiRole.query().findByIds(participantRequest.apiRoles);
const parsedParticipantRequest = ParticipantCreationAndApprovalPartial.parse({
name: participantRequest.participantName,
types,
Expand All @@ -136,6 +126,7 @@ export async function createParticipant(req: ParticipantRequest, res: Response)
crmAgreementNumber: participantRequest.crmAgreementNumber,
});

const requestingUser = await findUserByEmail(email);
const auditTrailInsertObject = constructAuditTrailObject(
requestingUser!,
AuditTrailEvents.ManageParticipant,
Expand Down Expand Up @@ -172,11 +163,27 @@ export async function createParticipant(req: ParticipantRequest, res: Response)
);

// assign proper api access
assignClientRoleToUser(kcAdminClient, user.email, 'api-participant-member');
await assignApiParticipantMemberRole(kcAdminClient, user.email);

// send email
await sendInviteEmail(kcAdminClient, newKcUser);
await sendInviteEmailToNewUser(kcAdminClient, newKcUser);
});
}

export async function handleCreateParticipant(req: ParticipantRequest, res: Response) {
const participantRequest = ParticipantCreationRequest.parse(req.body);
const traceId = getTraceId(req);

const validationError = await validateParticipantCreationRequest(participantRequest);
if (validationError) {
return res.status(400).send(validationError);
}
const user = UserCreationPartial.parse({
...req.body,
acceptedTerms: false,
});
const email = req.auth?.payload?.email as string;
await createParticipant(email, participantRequest, user, traceId);

return res.sendStatus(200);
}
Expand Down
79 changes: 8 additions & 71 deletions src/api/routers/participants/participantsRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
ParticipantDTO,
ParticipantStatus,
} from '../../entities/Participant';
import { UserDTO, UserJobFunction } from '../../entities/User';
import { UserDTO } from '../../entities/User';
import { siteIdNotSetError } from '../../helpers/errorHelpers';
import { getTraceId } from '../../helpers/loggingHelpers';
import { getKcAdminClient } from '../../keycloakAdminClient';
Expand Down Expand Up @@ -42,11 +42,7 @@ import {
constructAuditTrailObject,
performAsyncOperationWithAuditTrail,
} from '../../services/auditTrailService';
import {
assignClientRoleToUser,
createNewUser,
sendInviteEmail,
} from '../../services/kcUsersService';
import { assignApiParticipantMemberRole } from '../../services/kcUsersService';
import {
addSharingParticipants,
deleteSharingParticipants,
Expand All @@ -60,18 +56,14 @@ import {
UserParticipantRequest,
} from '../../services/participantsService';
import { getSignedParticipants } from '../../services/signedParticipantsService';
import {
createUserInPortal,
findUserByEmail,
getAllUserFromParticipant,
} from '../../services/usersService';
import { getAllUserFromParticipant } from '../../services/usersService';
import { createBusinessContactsRouter } from '../businessContactsRouter';
import { createParticipantUsersRouter } from '../participantUsersRouter';
import { getParticipantAppNames, setParticipantAppNames } from './participantsAppIds';
import { createParticipant, createParticipantFromRequest } from './participantsCreation';
import { createParticipantFromRequest, handleCreateParticipant } from './participantsCreation';
import { getParticipantDomainNames, setParticipantDomainNames } from './participantsDomainNames';
import { getParticipantKeyPairs } from './participantsKeyPairs';
import { getParticipantUsers } from './participantsUsers';
import { getParticipantUsers, handleInviteUserToParticipant } from './participantsUsers';

export type AvailableParticipantDTO = Required<Pick<ParticipantDTO, 'name' | 'siteId' | 'types'>>;

Expand Down Expand Up @@ -175,7 +167,7 @@ export function createParticipantsRouter() {
await setSiteClientTypes(data);
await Promise.all(
usersFromParticipant.map((currentUser) =>
assignClientRoleToUser(kcAdminClient, currentUser.email, 'api-participant-member')
assignApiParticipantMemberRole(kcAdminClient, currentUser.email)
)
);

Expand Down Expand Up @@ -210,7 +202,7 @@ export function createParticipantsRouter() {
}
);

participantsRouter.put('/', createParticipant);
participantsRouter.put('/', handleCreateParticipant);

participantsRouter.use('/:participantId', verifyAndEnrichParticipant);

Expand All @@ -220,62 +212,7 @@ export function createParticipantsRouter() {
return res.status(200).json(participant);
});

const invitationParser = z.object({
firstName: z.string(),
lastName: z.string(),
email: z.string(),
jobFunction: z.nativeEnum(UserJobFunction),
});

participantsRouter.post(
'/:participantId/invite',
async (req: UserParticipantRequest, res: Response) => {
try {
const { participant, user } = req;
const { firstName, lastName, email, jobFunction } = invitationParser.parse(req.body);
const traceId = getTraceId(req);
// TODO: UID2-3878 - support user belonging to multiple participants by not 400ing here if the user already exists.
const existingUser = await findUserByEmail(email);
if (existingUser) {
return res.status(400).send('Error inviting user');
}
const kcAdminClient = await getKcAdminClient();
const auditTrailInsertObject = constructAuditTrailObject(
user!,
AuditTrailEvents.ManageTeamMembers,
{
action: AuditAction.Add,
firstName,
lastName,
email,
jobFunction,
},
participant!.id
);

await performAsyncOperationWithAuditTrail(auditTrailInsertObject, traceId, async () => {
const newUser = await createNewUser(kcAdminClient, firstName, lastName, email);
await createUserInPortal(
{
email,
jobFunction,
firstName,
lastName,
},
participant!.id
);
await sendInviteEmail(kcAdminClient, newUser);
});

return res.sendStatus(201);
} catch (err) {
if (err instanceof z.ZodError) {
return res.status(400).send(err.issues);
}
throw err;
}
}
);
participantsRouter.post('/:participantId/invite', handleInviteUserToParticipant);

participantsRouter.get(
'/:participantId/sharingPermission',
Expand Down
72 changes: 70 additions & 2 deletions src/api/routers/participants/participantsUsers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,78 @@
import { Response } from 'express';
import { z } from 'zod';

import { ParticipantRequest } from '../../services/participantsService';
import { getAllUserFromParticipant } from '../../services/usersService';
import { AuditAction, AuditTrailEvents } from '../../entities/AuditTrail';
import { UserJobFunction } from '../../entities/User';
import { getTraceId } from '../../helpers/loggingHelpers';
import { getKcAdminClient } from '../../keycloakAdminClient';
import {
constructAuditTrailObject,
performAsyncOperationWithAuditTrail,
} from '../../services/auditTrailService';
import { createNewUser, sendInviteEmailToNewUser } from '../../services/kcUsersService';
import { ParticipantRequest, UserParticipantRequest } from '../../services/participantsService';
import {
createUserInPortal,
findUserByEmail,
getAllUserFromParticipant,
} from '../../services/usersService';

export async function getParticipantUsers(req: ParticipantRequest, res: Response) {
const { participant } = req;
const users = await getAllUserFromParticipant(participant!);
return res.status(200).json(users);
}

const invitationParser = z.object({
firstName: z.string(),
lastName: z.string(),
email: z.string(),
jobFunction: z.nativeEnum(UserJobFunction),
});

export async function handleInviteUserToParticipant(req: UserParticipantRequest, res: Response) {
try {
const { participant, user } = req;
const { firstName, lastName, email, jobFunction } = invitationParser.parse(req.body);
const traceId = getTraceId(req);
// TODO: UID2-3878 - support user belonging to multiple participants by not 400ing here if the user already exists.
const existingUser = await findUserByEmail(email);
if (existingUser) {
return res.status(400).send('Error inviting user');
}
const kcAdminClient = await getKcAdminClient();
const auditTrailInsertObject = constructAuditTrailObject(
user!,
AuditTrailEvents.ManageTeamMembers,
{
action: AuditAction.Add,
firstName,
lastName,
email,
jobFunction,
},
participant!.id
);

await performAsyncOperationWithAuditTrail(auditTrailInsertObject, traceId, async () => {
const newUser = await createNewUser(kcAdminClient, firstName, lastName, email);
await createUserInPortal(
{
email,
jobFunction,
firstName,
lastName,
},
participant!.id
);
await sendInviteEmailToNewUser(kcAdminClient, newUser);
});

return res.sendStatus(201);
} catch (err) {
if (err instanceof z.ZodError) {
return res.status(400).send(err.issues);
}
throw err;
}
}
Loading

0 comments on commit 131a9f3

Please sign in to comment.