Skip to content

Commit c264e69

Browse files
committed
Sort-of port update-remote-settings cron to TS
The request was to port it to TypeScript even though this code couldn't run yet, because we might be enabling this again soonish. Thus, this ports it to TypeScript, recovers the module that it referenced but no longer existed, replaces that module's use of the `got` package with `fetch`, and ensures it builds. I don't have the right access to be able to verify that it actually runs as expected though.
1 parent 5f2dc9e commit c264e69

File tree

3 files changed

+151
-58
lines changed

3 files changed

+151
-58
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"dev:cron:monthly-activity": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/monthlyActivity.tsx",
1414
"dev:cron:db-delete-unverified-subscribers": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/deleteUnverifiedSubscribers.ts",
1515
"dev:cron:db-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/syncBreaches.ts",
16+
"dev:cron:remote-settings-pull-breaches": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/updateBreachesInRemoteSettings.ts",
1617
"dev:cron:onerep-limits-alert": "tsx --tsconfig tsconfig.cronjobs.json src/scripts/cronjobs/onerepStatsAlert.ts",
1718
"dev:nimbus": "node --watch-path config/nimbus.yaml src/scripts/build/nimbusTypes.js",
1819
"build": "npm run get-location-data && npm run build-glean && npm run build-nimbus && next build && npm run build-cronjobs",
@@ -29,7 +30,7 @@
2930
"cron:breach-alerts": "node src/scripts/emailBreachAlerts.js",
3031
"cron:db-delete-unverified-subscribers": "node dist/scripts/cronjobs/deleteUnverifiedSubscribers.js",
3132
"cron:db-pull-breaches": "node dist/scripts/cronjobs/syncBreaches.js",
32-
"cron:remote-settings-pull-breaches": "node scripts/updatebreaches.js",
33+
"cron:remote-settings-pull-breaches": "node dist/scripts/cronjobs/updateBreachesInRemoteSettings.js",
3334
"cron:onerep-limits-alert": "node dist/scripts/cronjobs/onerepStatsAlert.js",
3435
"db:migrate": "node -r dotenv-flow/config node_modules/knex/bin/cli.js migrate:latest --knexfile src/db/knexfile.js",
3536
"db:rollback": "node -r dotenv-flow/config node_modules/knex/bin/cli.js migrate:rollback --knexfile src/db/knexfile.js",

scripts/updatebreaches.js

-57
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"use strict";
2+
3+
/**
4+
* Cron: Daily
5+
* From all the HIBP breaches, we parse out the new breaches that are not already present
6+
* in firefox remote settings, and update the data source accordingly
7+
*/
8+
9+
/*
10+
*
11+
*
12+
*
13+
*
14+
*********************************** Warning ***********************************
15+
*
16+
* This script was in the repository unused, and referenced a module
17+
* `remote-settings.js` that no longer existed at the time of writing (it was
18+
* deleted in commit c727bfe968937e51b0cd42efefd010c7c401aeae).
19+
* I dug it up from the Git history, inlined it in this file, and made sure
20+
* it compiled, but I didn't get to actually run it.
21+
*
22+
* Thus, it could be used as a starting point when re-enabling the remote
23+
* settings upload, but shouldn't be expected to work without modifications.
24+
*
25+
*********************************** Warning ***********************************
26+
*
27+
*
28+
*
29+
*
30+
*
31+
*
32+
*
33+
*
34+
*/
35+
36+
import AppConstants from "../../appConstants";
37+
import * as HIBP from "../../utils/hibp";
38+
import type { Breach } from "../../app/functions/universal/breach";
39+
40+
type RemoteSettingsBreach = Pick<
41+
Breach,
42+
"Name" | "Domain" | "BreachDate" | "PwnCount" | "AddedDate" | "DataClasses"
43+
>;
44+
45+
const BREACHES_COLLECTION = "fxmonitor-breaches";
46+
const FX_RS_COLLECTION = `${AppConstants.FX_REMOTE_SETTINGS_WRITER_SERVER}/buckets/main-workspace/collections/${BREACHES_COLLECTION}`;
47+
const FX_RS_RECORDS = `${FX_RS_COLLECTION}/records`;
48+
const FX_RS_WRITER_USER = AppConstants.FX_REMOTE_SETTINGS_WRITER_USER;
49+
const FX_RS_WRITER_PASS = AppConstants.FX_REMOTE_SETTINGS_WRITER_PASS;
50+
51+
async function whichBreachesAreNotInRemoteSettingsYet(breaches: Breach[]) {
52+
const response = await fetch(FX_RS_RECORDS, {
53+
headers: {
54+
Authorization: `Basic ${Buffer.from(FX_RS_WRITER_USER + ":" + FX_RS_WRITER_PASS).toString("base64")}`,
55+
},
56+
});
57+
const fxRSRecords = await response.json();
58+
const remoteSettingsBreachesSet = new Set(
59+
fxRSRecords.body.data.map((b: Breach) => b.Name),
60+
);
61+
62+
return breaches.filter(({ Name }) => !remoteSettingsBreachesSet.has(Name));
63+
}
64+
65+
async function postNewBreachToBreachesCollection(data: RemoteSettingsBreach) {
66+
// Create the record
67+
const response = await fetch(FX_RS_RECORDS, {
68+
method: "POST",
69+
body: JSON.stringify(data),
70+
headers: {
71+
"Content-Type": "application/json",
72+
Authorization: `Basic ${Buffer.from(FX_RS_WRITER_USER + ":" + FX_RS_WRITER_PASS).toString("base64")}`,
73+
},
74+
});
75+
return response.json();
76+
}
77+
78+
async function requestReviewOnBreachesCollection() {
79+
const response = await fetch(FX_RS_COLLECTION, {
80+
method: "PATCH",
81+
body: JSON.stringify({ data: { status: "to-review" } }),
82+
headers: {
83+
"Content-Type": "application/json",
84+
Authorization: `Basic ${Buffer.from(FX_RS_WRITER_USER + ":" + FX_RS_WRITER_PASS).toString("base64")}`,
85+
},
86+
});
87+
return response.json();
88+
}
89+
90+
if (
91+
!AppConstants.FX_REMOTE_SETTINGS_WRITER_USER ||
92+
!AppConstants.FX_REMOTE_SETTINGS_WRITER_PASS ||
93+
!AppConstants.FX_REMOTE_SETTINGS_WRITER_SERVER
94+
) {
95+
console.error(
96+
"updatebreaches requires FX_REMOTE_SETTINGS_WRITER_SERVER, FX_REMOTE_SETTINGS_WRITER_USER, FX_REMOTE_SETTINGS_WRITER_PASS.",
97+
);
98+
process.exit(1);
99+
}
100+
101+
(async () => {
102+
const allHibpBreaches = (await HIBP.req("/breaches")) as { body: Breach[] };
103+
const verifiedSiteBreaches = allHibpBreaches.body.filter((breach) => {
104+
return (
105+
!breach.IsRetired &&
106+
!breach.IsSpamList &&
107+
!breach.IsFabricated &&
108+
breach.IsVerified &&
109+
breach.Domain !== ""
110+
);
111+
});
112+
const verifiedSiteBreachesWithPWs = verifiedSiteBreaches.filter((breach) =>
113+
breach.DataClasses.includes("Passwords"),
114+
);
115+
116+
const newBreaches = await whichBreachesAreNotInRemoteSettingsYet(
117+
verifiedSiteBreachesWithPWs,
118+
);
119+
120+
if (newBreaches.length <= 0) {
121+
console.log("No new breaches detected.");
122+
process.exit(0);
123+
}
124+
125+
console.log(`${newBreaches.length} new breach(es) found.`);
126+
127+
for (const breach of newBreaches) {
128+
const data: RemoteSettingsBreach = {
129+
Name: breach.Name,
130+
Domain: breach.Domain,
131+
BreachDate: breach.BreachDate,
132+
PwnCount: breach.PwnCount,
133+
AddedDate: breach.AddedDate,
134+
DataClasses: breach.DataClasses,
135+
};
136+
137+
console.log("New breach detected: \n", data);
138+
139+
try {
140+
await postNewBreachToBreachesCollection(data);
141+
} catch (e) {
142+
console.error(e);
143+
process.exit(1);
144+
}
145+
}
146+
147+
console.log("Requesting review on breaches collection");
148+
await requestReviewOnBreachesCollection();
149+
})();

0 commit comments

Comments
 (0)