Skip to content

Commit

Permalink
chore: inactive user email
Browse files Browse the repository at this point in the history
fix inactive user logic and email messages
  • Loading branch information
jlangy committed Nov 29, 2024
1 parent 5f0820a commit bd680fa
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 26 deletions.
86 changes: 86 additions & 0 deletions lambda/__tests__/11.remove-inactive-users.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const testUser = {
idir_username: TEAM_ADMIN_IDIR_USERNAME_01,
},
clientData: {},
env: 'prod',
};

jest.mock('../app/src/keycloak/integration', () => {
Expand Down Expand Up @@ -114,6 +115,7 @@ describe('users and teams', () => {
clientId: testUser.clientData[0].client,
roles: testUser.clientData[0].roles[0],
teamAdmin: true,
env: 'prod',
});
expect(deleteResponse.status).toEqual(200);
const user = await models.user.findOne({ where: { idir_userid: TEAM_ADMIN_IDIR_USERID_01 }, raw: true });
Expand Down Expand Up @@ -295,3 +297,87 @@ describe('Deleted user emails', () => {
expect(deleteInactiveIntegrationEmails.length).toBe(0);
});
});

describe('Environment Check', () => {
beforeAll(async () => {
await cleanUpDatabaseTables();
});

afterEach(async () => {
jest.clearAllMocks();
await cleanUpDatabaseTables();
});

beforeEach(async () => {
await models.user.create({ idirUserid: SSO_TEAM_IDIR_USER, idirEmail: SSO_TEAM_IDIR_EMAIL });
await models.user.create({ idirUserid: TEAM_ADMIN_IDIR_USERID_01, idirEmail: TEAM_ADMIN_IDIR_EMAIL_01 });
createMockAuth(SSO_TEAM_IDIR_USER, SSO_TEAM_IDIR_EMAIL);
});

it('Only removes users in CSS when they are deleted from the production environment', async () => {
testUser.env = 'dev';
await deleteInactiveUsers(testUser);
let user = await models.user.findOne({ where: { idir_email: testUser.email }, raw: true });
expect(user).not.toBeNull();

testUser.env = 'test';
await deleteInactiveUsers(testUser);
user = await models.user.findOne({ where: { idir_email: testUser.email }, raw: true });
expect(user).not.toBeNull();

testUser.env = 'prod';
await deleteInactiveUsers(testUser);
user = await models.user.findOne({ where: { idir_email: testUser.email }, raw: true });
expect(user).toBeNull();
});

it('Sends role information for all environments, and team admin information only for production', async () => {
let emailList = createMockSendEmail();
const adminTeam = await createTeam({
name: 'test_team',
members: [
{
idirEmail: TEAM_ADMIN_IDIR_EMAIL_01,
role: 'admin',
},
],
});
const request = await buildIntegration({
projectName: 'Delete Inactive Users',
bceid: false,
prodEnv: false,
submitted: true,
teamId: adminTeam.body.id,
});

for (const env of ['dev', 'test', 'prod']) {
// Reset mocks between env tests
if (emailList.length) {
jest.clearAllMocks();
createMockAuth(SSO_TEAM_IDIR_USER, SSO_TEAM_IDIR_EMAIL);
emailList = createMockSendEmail();
}

testUser.env = env;
testUser.clientData = [{ client: request.body.clientId, roles: ['role1', 'role2'] }];
await deleteInactiveUsers(testUser);

const deleteInactiveIntegrationEmails = emailList.filter(
(email) => email.code === EMAILS.DELETE_INACTIVE_IDIR_USER,
);
expect(deleteInactiveIntegrationEmails.length).toBe(1);

// Only does team admin notification for prod users
if (env === 'prod') {
expect(deleteInactiveIntegrationEmails[0].body.includes('Team Admin')).toBeTruthy();
} else {
expect(deleteInactiveIntegrationEmails[0].body.includes('Team Admin')).not.toBeTruthy();
}

// Always sends role information
expect(deleteInactiveIntegrationEmails[0].body.includes('role1')).toBeTruthy();
expect(deleteInactiveIntegrationEmails[0].body.includes('role2')).toBeTruthy();
expect(deleteInactiveIntegrationEmails[0].body.includes(env)).toBeTruthy();
}
});
});
58 changes: 34 additions & 24 deletions lambda/app/src/controllers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,38 +174,48 @@ export const isAllowedToManageRoles = async (session: Session, integrationId: nu
};

export const deleteStaleUsers = async (
user: UserRepresentation & { clientData: { client: string; roles: string[] }[] },
user: UserRepresentation & { clientData: { client: string; roles: string[] }[]; env: string },
) => {
try {
const deletedFromProduction = user.env === 'prod';
const userHadRoles = user?.clientData && user?.clientData?.length > 0;
// Send formatted email with roles information to all team members if the deleted user had roles.
if (userHadRoles) {
user.clientData.map(async (cl: { client: string; roles: string[] }) => {
const integration = await models.request.findOne({
where: {
clientId: cl.client,
},
raw: true,
});
if (integration?.teamId && !integration.archived) {
const userEmails = await getAllEmailsOfTeam(integration.teamId);
let isTeamAdmin = false;
userEmails.map((u: any) => {
if (u.idir_email === user.email && u.role === 'admin') {
isTeamAdmin = true;
}
});
await sendTemplate(EMAILS.DELETE_INACTIVE_IDIR_USER, {
teamId: integration.teamId,
username: user.attributes.idir_username || user.username,
clientId: cl.client,
roles: cl.roles,
teamAdmin: isTeamAdmin,
await Promise.all(
user.clientData.map(async (cl: { client: string; roles: string[] }) => {
const integration = await models.request.findOne({
where: {
clientId: cl.client,
},
raw: true,
});
}
});
if (integration?.teamId && !integration.archived) {
const userEmails = await getAllEmailsOfTeam(integration.teamId);
let isTeamAdmin = false;
// Only production users affect team management
if (deletedFromProduction) {
userEmails.map((u) => {
if (u.idir_email === user.email && u.role === 'admin') {
isTeamAdmin = true;
}
});
}
await sendTemplate(EMAILS.DELETE_INACTIVE_IDIR_USER, {
teamId: integration.teamId,
username: user.attributes.idir_username || user.username,
clientId: cl.client,
roles: cl.roles,
env: user.env,
teamAdmin: isTeamAdmin,
});
}
}),
);
}

// User management handling only applies to production user deletions.
if (!deletedFromProduction) return true;

if (!user.attributes.idir_user_guid) throw new createHttpError.BadRequest('user guid is required');

const existingUser = await models.user.findOne({ where: { idir_userid: user.attributes.idir_user_guid } });
Expand Down
2 changes: 1 addition & 1 deletion lambda/app/src/queries/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const findAllowedTeamUsers = async (teamId: number, userId: number, optio
});
};

export const getAllEmailsOfTeam = async (teamId: number) => {
export const getAllEmailsOfTeam = async (teamId: number): Promise<{ idir_email: string; role: string }[]> => {
const [userEmails] = await sequelize.query(
'SELECT a.idir_email, b.role FROM users a join users_teams b ON a.id = b.user_id AND b.team_id = :teamId',
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<h3>Dear Pathfinder SSO friend,</h3>
<p>
The IDIR user with the idir username {{username}} has an inactive guid in our system. They have been removed from
client {{clientId}}{{#if roles.length}} and associated roles {{roles}}{{/if}}
client {{clientId}} in the {{env}} environment{{#if roles.length}} and associated roles {{roles}}{{/if}}
</p>
{{#if teamAdmin}}
<p>
Expand Down

0 comments on commit bd680fa

Please sign in to comment.