Skip to content

Commit

Permalink
Merge branch 'main' into 628/demographic-spoken-language-bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
mcgarrye authored May 29, 2024
2 parents deae059 + 4f60761 commit 1414e9c
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 12 deletions.
11 changes: 11 additions & 0 deletions api/src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,17 @@ export class UserService {
UserViews.full,
);

const isPartnerPortalUser =
storedUser.userRoles?.isAdmin ||
storedUser.userRoles?.isJurisdictionalAdmin ||
storedUser.userRoles?.isPartner;
const isUserSiteMatch =
(isPartnerPortalUser && dto.appUrl === process.env.PARTNERS_PORTAL_URL) ||
(!isPartnerPortalUser &&
dto.appUrl === storedUser.jurisdictions?.[0]?.publicUrl);
// user on wrong site, return neutral message and don't send email
if (!isUserSiteMatch) return { success: true };

const payload = {
id: storedUser.id,
exp: Number.parseInt(dayjs().add(1, 'hour').format('X')),
Expand Down
108 changes: 106 additions & 2 deletions api/test/integration/user.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,9 +532,13 @@ describe('User Controller Tests', () => {
expect(userPostResend.hitConfirmationUrl).toBeNull();
});

it('should set resetToken when forgot-password is called', async () => {
it('should set resetToken when forgot-password is called by public user on the public site', async () => {
const juris = await prisma.jurisdictions.create({
data: jurisdictionFactory(),
});

const userA = await prisma.userAccounts.create({
data: await userFactory(),
data: await userFactory({ jurisdictionIds: [juris.id] }),
});

const mockforgotPassword = jest.spyOn(testEmailService, 'forgotPassword');
Expand All @@ -543,6 +547,7 @@ describe('User Controller Tests', () => {
.set({ passkey: process.env.API_PASS_KEY || '' })
.send({
email: userA.email,
appUrl: juris.publicUrl,
} as EmailAndAppUrl)
.expect(200);

Expand All @@ -558,6 +563,105 @@ describe('User Controller Tests', () => {
expect(mockforgotPassword.mock.calls.length).toBe(1);
});

it('should not set resetToken when forgot-password is called by public user on the partners site', async () => {
const juris = await prisma.jurisdictions.create({
data: jurisdictionFactory(),
});

const userA = await prisma.userAccounts.create({
data: await userFactory({ jurisdictionIds: [juris.id] }),
});

const mockforgotPassword = jest.spyOn(testEmailService, 'forgotPassword');
const res = await request(app.getHttpServer())
.put(`/user/forgot-password/`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.send({
email: userA.email,
appUrl: process.env.PARTNERS_PORTAL_URL,
} as EmailAndAppUrl)
.expect(200);

expect(res.body.success).toBe(true);

const userPostResend = await prisma.userAccounts.findUnique({
where: {
id: userA.id,
},
});

expect(userPostResend.resetToken).toBeNull();
expect(mockforgotPassword.mock.calls.length).toBe(0);
});

it('should set resetToken when forgot-password is called by partner user on the partners site', async () => {
const juris = await prisma.jurisdictions.create({
data: jurisdictionFactory(),
});

const userA = await prisma.userAccounts.create({
data: await userFactory({
roles: { isAdmin: true },
jurisdictionIds: [juris.id],
}),
});

const mockforgotPassword = jest.spyOn(testEmailService, 'forgotPassword');
const res = await request(app.getHttpServer())
.put(`/user/forgot-password/`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.send({
email: userA.email,
appUrl: process.env.PARTNERS_PORTAL_URL,
} as EmailAndAppUrl)
.expect(200);

expect(res.body.success).toBe(true);

const userPostResend = await prisma.userAccounts.findUnique({
where: {
id: userA.id,
},
});

expect(userPostResend.resetToken).not.toBeNull();
expect(mockforgotPassword.mock.calls.length).toBe(1);
});

it('should not set resetToken when forgot-password is called by partner user on the public site', async () => {
const juris = await prisma.jurisdictions.create({
data: jurisdictionFactory(),
});

const userA = await prisma.userAccounts.create({
data: await userFactory({
roles: { isAdmin: true },
jurisdictionIds: [juris.id],
}),
});

const mockforgotPassword = jest.spyOn(testEmailService, 'forgotPassword');
const res = await request(app.getHttpServer())
.put(`/user/forgot-password/`)
.set({ passkey: process.env.API_PASS_KEY || '' })
.send({
email: userA.email,
appUrl: juris.publicUrl,
} as EmailAndAppUrl)
.expect(200);

expect(res.body.success).toBe(true);

const userPostResend = await prisma.userAccounts.findUnique({
where: {
id: userA.id,
},
});

expect(userPostResend.resetToken).toBeNull();
expect(mockforgotPassword.mock.calls.length).toBe(0);
});

it('should create public user', async () => {
const juris = await prisma.jurisdictions.create({
data: jurisdictionFactory(),
Expand Down
106 changes: 105 additions & 1 deletion api/test/unit/services/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,12 +670,13 @@ describe('Testing user service', () => {
});

describe('forgotPassword', () => {
it('should set resetToken', async () => {
it('should set resetToken when public user on public site', async () => {
const id = randomUUID();
const email = '[email protected]';

prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({
id,
jurisdictions: [{ publicUrl: 'http://localhost:3000' }],
});
prisma.userAccounts.update = jest.fn().mockResolvedValue({
id,
Expand Down Expand Up @@ -705,6 +706,109 @@ describe('Testing user service', () => {
});
});

it('should not set resetToken when public user on partner site', async () => {
const id = randomUUID();
const email = '[email protected]';

prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({
id,
jurisdictions: [{ publicUrl: 'http://localhost:3000' }],
});
prisma.userAccounts.update = jest.fn().mockResolvedValue({
id,
resetToken: 'example reset token',
});
emailService.forgotPassword = jest.fn();

await service.forgotPassword({
email,
appUrl: process.env.PARTNERS_PORTAL_URL,
});
expect(prisma.userAccounts.findUnique).toHaveBeenCalledWith({
include: {
jurisdictions: true,
listings: true,
userRoles: true,
},
where: {
email,
id: undefined,
},
});
expect(prisma.userAccounts.update).not.toHaveBeenCalled();
});

it('should set resetToken when partner user on partner site', async () => {
const id = randomUUID();
const email = '[email protected]';

prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({
id,
userRoles: { isAdmin: true },
});
prisma.userAccounts.update = jest.fn().mockResolvedValue({
id,
resetToken: 'example reset token',
});
emailService.forgotPassword = jest.fn();

await service.forgotPassword({
email,
appUrl: process.env.PARTNERS_PORTAL_URL,
});
expect(prisma.userAccounts.findUnique).toHaveBeenCalledWith({
include: {
jurisdictions: true,
listings: true,
userRoles: true,
},
where: {
email,
id: undefined,
},
});
expect(prisma.userAccounts.update).toHaveBeenCalledWith({
data: {
resetToken: expect.anything(),
},
where: {
id,
},
});
});

it('should not set resetToken when partner user on public site', async () => {
const id = randomUUID();
const email = '[email protected]';

prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({
id,
userRoles: { isAdmin: true },
});
prisma.userAccounts.update = jest.fn().mockResolvedValue({
id,
resetToken: 'example reset token',
});
emailService.forgotPassword = jest.fn();

await service.forgotPassword({
email,
appUrl: 'http://localhost:3000',
});
expect(prisma.userAccounts.findUnique).toHaveBeenCalledWith({
include: {
jurisdictions: true,
listings: true,
userRoles: true,
},
where: {
email,
id: undefined,
},
});
expect(prisma.userAccounts.update).not.toHaveBeenCalled();
});

it('should error when trying to set resetToken on nonexistent user', async () => {
const email = '[email protected]';

Expand Down
9 changes: 2 additions & 7 deletions shared-helpers/src/auth/Timeout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,8 @@ import React, { createElement, FunctionComponent, useContext, useEffect, useStat
import { AuthContext } from "./AuthContext"
import { ConfigContext } from "./ConfigContext"
import { Button } from "@bloom-housing/ui-seeds"
import {
NavigationContext,
Modal,
setSiteAlertMessage,
AlertTypes,
t,
} from "@bloom-housing/ui-components"
import { Modal, setSiteAlertMessage, AlertTypes, t } from "@bloom-housing/ui-components"
import { NavigationContext } from "@bloom-housing/doorway-ui-components"

const PROMPT_TIMEOUT = 60000
const events = ["mousemove", "keypress", "scroll"]
Expand Down
4 changes: 2 additions & 2 deletions sites/partners/cypress/e2e/default/03-listing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ describe("Listing Management Tests", () => {
cy.getByID("buildingAddress.state").contains("CA")
cy.getByID("buildingAddress.zipCode").contains(listing["buildingAddress.zipCode"])
cy.getByID("yearBuilt").contains(listing["yearBuilt"])
cy.getByID("longitude").contains("-121.950481")
cy.getByID("latitude").contains("37.762983")
cy.getByID("longitude").contains("-121.95")
cy.getByID("latitude").contains("37.76")
cy.getByID("reservedCommunityType").contains(listing["reservedCommunityType.id"])
cy.getByID("reservedCommunityDescription").contains(listing["reservedCommunityDescription"])
cy.getByTestId("unit-types-or-individual").contains("Unit Types")
Expand Down

0 comments on commit 1414e9c

Please sign in to comment.