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

Release/2024 02 12 v2 #476

Merged
merged 20 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ executors:
# DB URL for the jest tests per ormconfig.test.ts
TEST_DATABASE_URL: "postgres://bloom-ci@localhost:5432/bloom"
PARTNERS_PORTAL_URL: "http://localhost:3001"

jobs:
setup:
executor: standard-node
Expand Down
3 changes: 2 additions & 1 deletion backend/core/src/auth/dto/user-create.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ export class UserCreateDto extends OmitType(UserDto, [
passwordConfirmation: string

@Expose()
@IsOptional({ groups: [ValidationsGroupsEnum.default] })
@IsEmail({}, { groups: [ValidationsGroupsEnum.default] })
@Match("email", { groups: [ValidationsGroupsEnum.default] })
@EnforceLowerCase()
emailConfirmation: string
emailConfirmation?: string

@Expose()
@IsOptional({ groups: [ValidationsGroupsEnum.default] })
Expand Down
3 changes: 2 additions & 1 deletion backend/core/src/auth/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { Request as ExpressRequest, Response } from "express"
import { UserProfileUpdateDto } from "../dto/user-profile.dto"
import { Listing } from "../../listings/entities/listing.entity"
import { ListingsService } from "../../listings/listings.service"
import { getPublicEmailURL } from "../../shared/utils/get-public-email-url"

dayjs.extend(advancedFormat)

Expand Down Expand Up @@ -621,7 +622,7 @@ export class UserService {
}

private static getPublicConfirmationUrl(appUrl: string, user: User) {
return `${appUrl}?token=${user.confirmationToken}`
return getPublicEmailURL(appUrl, user.confirmationToken)
}

private static getPartnersConfirmationUrl(appUrl: string, user: User) {
Expand Down
3 changes: 2 additions & 1 deletion backend/core/src/email/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Translation } from "../translations/entities/translation.entity"
import { ListingEventType, Unit } from "../../types"
import { formatLocalDate } from "../shared/utils/format-local-date"
import { formatCommunityType } from "../listings/helpers"
import { getPublicEmailURL } from "../shared/utils/get-public-email-url"

type EmailAttachmentData = {
data: string
Expand Down Expand Up @@ -244,8 +245,8 @@ export class EmailService {
const jurisdiction = await this.getUserJurisdiction(user)
void (await this.loadTranslations(jurisdiction, user.language))
const compiledTemplate = this.template("forgot-password")
const resetUrl = `${appUrl}/reset-password?token=${user.resetToken}`

const resetUrl = getPublicEmailURL(appUrl, user.resetToken, "/reset-password")
if (this.configService.get<string>("NODE_ENV") == "production") {
Logger.log(
`Preparing to send a forget password email to ${user.email} from ${jurisdiction.emailFromAddress}...`
Expand Down
18 changes: 18 additions & 0 deletions backend/core/src/shared/utils/get-public-email-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Creates a email URL object from passed url applies redirectUrl and listingId query params if they exist
* If they do not exist, the return value will be the email url with just the necessary token
*/

export const getPublicEmailURL = (url: string, token: string, actionPath?: string): string => {
const urlObj = new URL(url)

const redirectUrl = urlObj.searchParams.get("redirectUrl")
const listingId = urlObj.searchParams.get("listingId")

let emailUrl = `${urlObj.origin}${urlObj.pathname}/${actionPath ? actionPath : ""}?token=${token}`

if (!!redirectUrl && !!listingId) {
emailUrl = emailUrl.concat(`&redirectUrl=${redirectUrl}&listingId=${listingId}`)
}
return emailUrl
}
25 changes: 22 additions & 3 deletions backend/core/test/user/user.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}
await supertest(app.getHttpServer()).post(`/user/`).send(userCreateDto).expect(400)
})
Expand All @@ -129,6 +130,7 @@ describe("UsersService", () => {
lastName: "Last",
dob: new Date(),
confirmedAt: new Date(),
appUrl: "http://localhost",
}
await supertest(app.getHttpServer())
.post(`/user/`)
Expand Down Expand Up @@ -190,6 +192,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}
const mockWelcome = jest.spyOn(testEmailService, "welcome")
const res = await supertest(app.getHttpServer())
Expand All @@ -216,6 +219,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}
await supertest(app.getHttpServer())
.post(`/user/`)
Expand Down Expand Up @@ -250,6 +254,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}
await supertest(app.getHttpServer()).post(`/user/`).send(userCreateDto).expect(400)
userCreateDto.passwordConfirmation = "Abcdef1!"
Expand All @@ -273,6 +278,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}
const res = await supertest(app.getHttpServer())
.post(`/user`)
Expand All @@ -299,6 +305,7 @@ describe("UsersService", () => {
email: "[email protected]",
agreedToTermsOfService: false,
jurisdictions: [],
appUrl: "http://localhost",
}
await supertest(app.getHttpServer())
.put(`/user/${user2UpdateDto.id}`)
Expand All @@ -321,6 +328,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}
await supertest(app.getHttpServer())
.post(`/user/`)
Expand All @@ -329,7 +337,7 @@ describe("UsersService", () => {
.expect(201)
await supertest(app.getHttpServer())
.post("/user/resend-confirmation")
.send({ email: userCreateDto.email })
.send({ email: userCreateDto.email, appUrl: "http://localhost" })
.expect(201)
})

Expand All @@ -343,6 +351,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}
await supertest(app.getHttpServer())
.post(`/user/`)
Expand All @@ -358,14 +367,14 @@ describe("UsersService", () => {
.expect(200)
await supertest(app.getHttpServer())
.post("/user/resend-confirmation")
.send({ email: userCreateDto.email })
.send({ email: userCreateDto.email, appUrl: "http://localhost" })
.expect(406)
})

it("should return 404 if there is no user to resend confirmation to", async () => {
await supertest(app.getHttpServer())
.post("/user/resend-confirmation")
.send({ email: "[email protected]" })
.send({ email: "[email protected]", appUrl: "http://localhost" })
.expect(404)
})

Expand All @@ -379,6 +388,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}
const mockWelcome = jest.spyOn(testEmailService, "welcome")
await supertest(app.getHttpServer())
Expand Down Expand Up @@ -450,6 +460,7 @@ describe("UsersService", () => {
lastName: "Last",
dob: new Date(),
language: Language.en,
appUrl: "http://localhost",
}

const userCreateResponse = await supertest(app.getHttpServer())
Expand Down Expand Up @@ -527,6 +538,7 @@ describe("UsersService", () => {
lastName: "Last",
dob: new Date(),
language: Language.en,
appUrl: "http://localhost",
}

const userBCreateDto: UserCreateDto = {
Expand All @@ -539,6 +551,7 @@ describe("UsersService", () => {
lastName: "Last",
dob: new Date(),
language: Language.en,
appUrl: "http://localhost",
}

const { userId: userAId } = await createAndConfirmUser(userACreateDto)
Expand Down Expand Up @@ -685,6 +698,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}
const res = await supertest(app.getHttpServer())
.post(`/user`)
Expand Down Expand Up @@ -725,6 +739,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}

await supertest(app.getHttpServer())
Expand Down Expand Up @@ -821,6 +836,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}

await supertest(app.getHttpServer())
Expand Down Expand Up @@ -896,6 +912,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}

await supertest(app.getHttpServer())
Expand Down Expand Up @@ -980,6 +997,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}

const userCreateResponse = await supertest(app.getHttpServer())
Expand Down Expand Up @@ -1066,6 +1084,7 @@ describe("UsersService", () => {
middleName: "Mid",
lastName: "Last",
dob: new Date(),
appUrl: "http://localhost",
}

await supertest(app.getHttpServer())
Expand Down
2 changes: 1 addition & 1 deletion backend/core/types/src/backend-swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3979,7 +3979,7 @@ export interface UserCreate {
passwordConfirmation: string

/** */
emailConfirmation: string
emailConfirmation?: string

/** */
appUrl?: string
Expand Down
3 changes: 3 additions & 0 deletions build/docker/Dockerfile.sites-generic
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ARG GTM_KEY
ARG FEATURE_LISTINGS_APPROVAL
ARG SHOW_PROFESSIONAL_PARTNERS
ARG NOTIFICATIONS_SIGN_UP_URL
ARG SHOW_MANDATED_ACCOUNTS


# The base image will contain all the source code needed for our site
Expand Down Expand Up @@ -121,6 +122,7 @@ ARG GTM_KEY
ARG FEATURE_LISTINGS_APPROVAL
ARG SHOW_PROFESSIONAL_PARTNERS
ARG NOTIFICATIONS_SIGN_UP_URL
ARG SHOW_MANDATED_ACCOUNTS


# We need to have this nested 2 layers deep due to hardcoded paths in package.json
Expand All @@ -143,6 +145,7 @@ ENV GTM_KEY=$GTM_KEY
ENV FEATURE_LISTINGS_APPROVAL=$FEATURE_LISTINGS_APPROVAL
ENV SHOW_PROFESSIONAL_PARTNERS=$SHOW_PROFESSIONAL_PARTNERS
ENV NOTIFICATIONS_SIGN_UP_URL=$NOTIFICATIONS_SIGN_UP_URL
ENV SHOW_MANDATED_ACCOUNTS=$SHOW_MANDATED_ACCOUNTS



Expand Down
2 changes: 2 additions & 0 deletions ci/buildspec/build_public.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ phases:
--build-arg "GTM_KEY=${GTM_KEY}"
--build-arg "SHOW_PROFESSIONAL_PARTNERS=$SHOW_PROFESSIONAL_PARTNERS"
--build-arg "NOTIFICATIONS_SIGN_UP_URL=$NOTIFICATIONS_SIGN_UP_URL"
--build-arg "SHOW_MANDATED_ACCOUNTS=$SHOW_MANDATED_ACCOUNTS"
--target test
-t sites/public:test
.
Expand All @@ -64,6 +65,7 @@ phases:
--build-arg "GTM_KEY=${GTM_KEY}"
--build-arg "SHOW_PROFESSIONAL_PARTNERS=$SHOW_PROFESSIONAL_PARTNERS"
--build-arg "NOTIFICATIONS_SIGN_UP_URL=$NOTIFICATIONS_SIGN_UP_URL"
--build-arg "SHOW_MANDATED_ACCOUNTS=$SHOW_MANDATED_ACCOUNTS"
--target run
-t sites/public:run-candidate
.
Expand Down
2 changes: 1 addition & 1 deletion doorway-ui-components/src/headers/SiteHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const SiteHeader = (props: SiteHeaderProps) => {
const logoOffset = document.getElementById("site-header-logo")?.offsetLeft
const linksOffset = document.getElementById("site-header-links")?.offsetLeft
if (linksOffset === undefined || logoOffset === undefined) return
return linksOffset === 0 || linksOffset === logoOffset
return linksOffset === 0
? setNavbarClass("site-header__navbar-wrapped")
: setNavbarClass("site-header__navbar-inline")
}
Expand Down
2 changes: 1 addition & 1 deletion doorway-ui-components/src/notifications/AlertBox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
--background-color: var(--bloom-color-primary-light);
--alert-background-color: var(--bloom-color-alert-light);
--alert-invert-background-color: var(--bloom-color-alert);
--notice-background-color: var(--bloom-color-primary-light);
--notice-background-color: var(--bloom-color-primary-lightest);
--notice-invert-background-color: var(--bloom-color-primary);
--success-background-color: var(--bloom-color-success-light);
--success-invert-background-color: var(--bloom-color-success);
Expand Down
23 changes: 14 additions & 9 deletions shared-helpers/src/auth/AuthContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import qs from "qs"
import axiosStatic from "axios"
import { ConfigContext } from "./ConfigContext"
import { createAction, createReducer } from "typesafe-actions"
import { getListingRedirectUrl } from "../utilities/getListingRedirectUrl"

type ContextProps = {
amiChartsService: AmiChartsService
Expand Down Expand Up @@ -63,9 +64,9 @@ type ContextProps = {
) => Promise<User | undefined>
signOut: () => void
confirmAccount: (token: string) => Promise<User | undefined>
forgotPassword: (email: string) => Promise<string | undefined>
createUser: (user: UserCreate) => Promise<UserBasic | undefined>
resendConfirmation: (email: string) => Promise<Status | undefined>
forgotPassword: (email: string, listingIdRedirect?: string) => Promise<string | undefined>
createUser: (user: UserCreate, listingIdRedirect?: string) => Promise<UserBasic | undefined>
resendConfirmation: (email: string, listingIdRedirect?: string) => Promise<Status | undefined>
initialStateLoaded: boolean
loading: boolean
profile?: User
Expand Down Expand Up @@ -286,33 +287,37 @@ export const AuthProvider: FunctionComponent<React.PropsWithChildren> = ({ child
dispatch(stopLoading())
}
},
createUser: async (user: UserCreate) => {
createUser: async (user: UserCreate, listingIdRedirect) => {
dispatch(startLoading())
const appUrl = getListingRedirectUrl(listingIdRedirect)
try {
const response = await userService?.create({
body: { ...user, appUrl: window.location.origin },
body: { ...user, appUrl },
})
return response
} finally {
dispatch(stopLoading())
}
},
resendConfirmation: async (email: string) => {
resendConfirmation: async (email: string, listingIdRedirect) => {
dispatch(startLoading())
const appUrl = getListingRedirectUrl(listingIdRedirect)
try {
const response = await userService?.resendConfirmation({
body: { email, appUrl: window.location.origin },
body: { email, appUrl },
})
return response
} finally {
dispatch(stopLoading())
}
},
forgotPassword: async (email) => {
forgotPassword: async (email, listingIdRedirect) => {
dispatch(startLoading())
try {
const appUrl = getListingRedirectUrl(listingIdRedirect)

const response = await userService?.forgotPassword({
body: { email, appUrl: window.location.origin },
body: { email, appUrl },
})
return response?.message
} finally {
Expand Down
Loading
Loading