Skip to content

Commit a7f2f4a

Browse files
committed
fix(breach-alerts): subscribe to updated primary email address
1 parent c27d616 commit a7f2f4a

File tree

3 files changed

+30
-3
lines changed

3 files changed

+30
-3
lines changed

src/db/tables/subscribers.integration.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import createDbConnection from "../connect";
99
import { updatePrimaryEmail } from "./subscribers";
1010
import { getSha1 } from "../../utils/fxa";
1111

12+
vi.mock("../../utils/hibp", () => ({
13+
subscribeHash: vi.fn().mockResolvedValue(undefined),
14+
}));
15+
1216
const conn = createDbConnection();
1317

1418
afterEach(async () => {
@@ -90,6 +94,19 @@ describe("updatePrimaryEmail", () => {
9094
expect(updated.primary_sha1).toBe(getSha1(newEmail));
9195
});
9296

97+
it("calls subscribeHash with the sha1 of the new primary email", async () => {
98+
const { subscribeHash } = await import("../../utils/hibp");
99+
const newEmail = faker.internet.email().toLowerCase();
100+
101+
const [subscriber] = await conn("subscribers")
102+
.insert(seeds.subscribers())
103+
.returning("*");
104+
105+
await updatePrimaryEmail(subscriber, newEmail);
106+
107+
expect(subscribeHash).toHaveBeenCalledWith(getSha1(newEmail));
108+
});
109+
93110
it("logs the error and returns null when the transaction throws", async () => {
94111
const [subscriber] = await conn("subscribers")
95112
.insert(seeds.subscribers())

src/db/tables/subscribers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SerializedSubscriber } from "../../next-auth.js";
99
import { config } from "../../config";
1010
import { logger } from "../../app/functions/server/logging";
1111
import { getSha1 } from "../../utils/fxa";
12+
import { subscribeHash } from "../../utils/hibp";
1213
import { type Knex } from "knex";
1314

1415
const knex = createDbConnection();
@@ -88,6 +89,10 @@ async function updatePrimaryEmail(
8889
// even if it's not typed as a JS date object:
8990
updated_at: knex.fn.now() as unknown as Date,
9091
});
92+
93+
// Subscribe the new primary email's hash to HIBP breach notifications
94+
await subscribeHash(getSha1(updatedEmail.toLowerCase()));
95+
9196
return subscriberTableUpdated[0] ?? null;
9297
});
9398
return subscriberTableUpdated;

src/utils/email.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import crypto from "crypto";
88
import { SentMessageInfo } from "nodemailer/lib/smtp-transport/index.js";
99
import { config } from "../config";
1010
import { logger } from "../app/functions/server/logging";
11+
import { captureException } from "@sentry/node";
1112

1213
// The SMTP transport object. This is initialized to a nodemailer transport
1314
// object while reading SMTP credentials, or to a dummy function in debug mode.
@@ -86,12 +87,16 @@ export async function sendEmail(
8687
} catch (e) {
8788
if (e instanceof Error) {
8889
logger.error("error_sending_email", {
89-
message: e.message,
90-
stack: e.stack,
90+
error: {
91+
message: e.message,
92+
stack: e.stack,
93+
},
9194
});
95+
captureException(e);
9296
/* c8 ignore next 3 */
9397
} else {
94-
logger.error("error_sending_email", { message: JSON.stringify(e) });
98+
logger.error("error_sending_email", { error: JSON.stringify(e) });
99+
captureException(e);
95100
}
96101
throw e;
97102
}

0 commit comments

Comments
 (0)