Skip to content

Commit b5ddc2a

Browse files
committed
enable privacy popups for everyobe
1 parent 5cad176 commit b5ddc2a

File tree

7 files changed

+158
-18
lines changed

7 files changed

+158
-18
lines changed

functions/utilities/getTypeSafeUser.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ const getTypeSafeUser = (user) => {
6666
}
6767

6868
module.exports = {
69-
getTypeSafeUser
69+
getTypeSafeUser,
70+
getGuardianEmails
7071
}

functions/utilities/privacy/debugRouter.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ const {
1515
acceptedByKey,
1616
variantModeKey,
1717
latestTouchKey,
18+
ageSetKey
1819
} = require("./privacyKeys");
19-
20-
// /api/privacy/student/ultra-secret/view (GET)
21-
// /api/privacy/student/ultra-secret/reset (POST)
20+
const {
21+
getGuardianEmails
22+
} = require("../getTypeSafeUser");
2223

2324
const userDB = firebase.firestore().collection("users");
2425

@@ -39,6 +40,7 @@ const getViewData = (req) => {
3940
acceptedByKey,
4041
variantModeKey,
4142
latestTouchKey,
43+
ageSetKey,
4244
"birthMonth",
4345
"birthYear",
4446
];
@@ -49,6 +51,7 @@ const getViewData = (req) => {
4951
data[key] = req.user[key] || null;
5052
});
5153

54+
data.guardianEmails = getGuardianEmails(req.user) || [];
5255
data.privacyState = req.user.privacy || null;
5356
data.isUnderThirteen = isUnderThirteen(req.user);
5457

@@ -79,10 +82,15 @@ router.post("/reset", studentMiddleware, async (req, res) => {
7982
[acceptedByKey]: req.body.acceptedByKey || null,
8083
[variantModeKey]: req.body.variantMode || null,
8184
[latestTouchKey]: req.body.latestTouch || null,
82-
birthMonth: req.body.birthMonth || "1",
83-
birthYear: req.body.birthYear || "2025",
85+
birthMonth: req.body.birthMonth || null,
86+
birthYear: req.body.birthYear || null,
8487
};
8588

89+
if (req.body.emails) {
90+
updateBody.emails = req.body.emails;
91+
updateBody.guardianEmail = null;
92+
}
93+
8694
await userDB.doc(req.user.uid).update(updateBody);
8795

8896
return res.status(200).send(getResponseBody(req, updateBody));

functions/utilities/privacy/privacyKeys.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const guardianPrivacyAuthTokenKey = "guardianPrivacyAuthToken";
1616
const acceptedKey = `${latestPrivacyVersion}-accepted`;
1717
const acceptedByKey = `${latestPrivacyVersion}-acceptedBy`;
1818
const variantModeKey = `${latestPrivacyVersion}-variant`;
19+
const ageSetKey = `${latestPrivacyVersion}-ageSet`;
1920

2021
module.exports = {
2122
firstSeenKey,
@@ -26,4 +27,5 @@ module.exports = {
2627
acceptedKey,
2728
acceptedByKey,
2829
variantModeKey,
30+
ageSetKey
2931
};

functions/utilities/privacy/router.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ const {
1212
guardianPrivacyAuthTokenKey,
1313
acceptedKey,
1414
acceptedByKey,
15-
latestTouchKey
15+
latestTouchKey,
16+
ageSetKey
1617
} = require("./privacyKeys");
1718
const debugRouter = require("./debugRouter");
19+
const {
20+
getGuardianEmails
21+
} = require("../getTypeSafeUser");
1822

1923
router.use("/student/ultra-secret", debugRouter);
2024

@@ -140,14 +144,18 @@ router.get("/student/delay", studentMiddleware, async (req, res) => {
140144

141145
router.post("/student/update", studentMiddleware, async (req, res) => {
142146
try {
147+
const emails = mergeAccountEmailsWithReqBodyGuardianEmails(req, res);
148+
const hasGuardianEmails = emails.some(e => e?.type === 'guardian');
149+
const guardianTouchKey = req.user[userNeedsGuardianTouchKey] || Date.now();
150+
143151
const updateBody = {
144-
emails: mergeAccountEmailsWithReqBodyGuardianEmails(req, res),
152+
emails,
145153
guardianEmail: null,
146154
[guardianPrivacyAuthTokenKey]:
147155
req.user[guardianPrivacyAuthTokenKey] ||
148156
generateGuardianPrivacyAuthToken(),
149157
[userNeedsGuardianTouchKey]:
150-
req.user[userNeedsGuardianTouchKey] || Date.now(),
158+
hasGuardianEmails ? guardianTouchKey : null,
151159
[firstSeenKey]: req.user[firstSeenKey] || Date.now(),
152160
[dueByKey]: req.user[dueByKey] || Date.now() + SevenDays,
153161
[latestTouchKey]: Date.now(),
@@ -168,6 +176,47 @@ router.post("/student/update", studentMiddleware, async (req, res) => {
168176
}
169177
});
170178

179+
router.post("/student/update-age", studentMiddleware, async (req, res) => {
180+
try {
181+
const month = req.body.month;
182+
const year = req.body.year;
183+
184+
if (
185+
typeof month !== "number" ||
186+
month < 1 ||
187+
month > 12 ||
188+
typeof year !== "number" ||
189+
year < 1900 ||
190+
year > new Date().getFullYear()
191+
) {
192+
return res.status(200).send({
193+
success: false,
194+
error: "Invalid month or year",
195+
data: null,
196+
});
197+
}
198+
199+
const updateBody = {
200+
birthMonth: String(month),
201+
birthYear: String(year),
202+
[ageSetKey]: Date.now(),
203+
};
204+
205+
await userDB.doc(req.user.uid).update(updateBody);
206+
207+
return res
208+
.status(200)
209+
.send({ success: true, data: updateBody, error: null });
210+
} catch (e) {
211+
console.error("Failed to update age", req.user.uid, e);
212+
return res.status(200).send({
213+
success: false,
214+
error: "Failed to update age",
215+
data: null,
216+
});
217+
}
218+
});
219+
171220
router.get("/student/accept", studentMiddleware, async (req, res) => {
172221
try {
173222
const updateBody = {

functions/utilities/privacy/utils.js

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ const {
66
acceptedKey,
77
variantModeKey,
88
} = require("./privacyKeys");
9+
const {
10+
getGuardianEmails
11+
} = require("../getTypeSafeUser");
912

1013
const userDB = firebase.firestore().collection("users");
1114

@@ -41,6 +44,10 @@ const isUnderThirteen = (user) => {
4144
return false;
4245
}
4346

47+
if (user.code) {
48+
return false; // assume teachers are over 13
49+
}
50+
4451
return true;
4552
};
4653

@@ -56,8 +63,8 @@ const getPrivacyVariant = (user) => {
5663
}
5764
}
5865

59-
const shouldRetryPopup = (lastTouched, isUnder13) => {
60-
if (isUnder13) return false;
66+
const shouldRetryPopup = (lastTouched, isUnder13, guardianEmails) => {
67+
if (isUnder13 && guardianEmails.length) return false;
6168

6269
try {
6370
if (!lastTouched) return false;
@@ -68,20 +75,38 @@ const shouldRetryPopup = (lastTouched, isUnder13) => {
6875
}
6976
}
7077

78+
const hasValidAge = (email, user) => {
79+
try {
80+
if (!email.includes("@mcmill.co.uk")) return true; // only enforce for test accounts for now
81+
if (user.code) return true; // assume teachers are valid
82+
83+
const birthYear = getCleanNumber(user?.birthYear);
84+
const birthMonth = getCleanNumber(user?.birthMonth);
85+
86+
if (!birthYear || !birthMonth) return false;
87+
if (birthYear < 1900 || birthYear > new Date().getFullYear()) return false;
88+
if (birthMonth < 1 || birthMonth > 12) return false;
89+
90+
return true;
91+
} catch {
92+
return true; // fail safe
93+
}
94+
}
95+
7196
const getPrivacyState = (email, user) => {
7297
const isUnder13 = isUnderThirteen(user);
7398
const variant = getPrivacyVariant(user);
99+
const guardianEmails = getGuardianEmails(user);
100+
const validAge = hasValidAge(email, user);
74101

75102
const useUnder13 = isUnder13 && variant !== 'year8webinar'
76103

77-
if (
78-
!email.includes("@mcmill.co.uk") ||
79-
process.env.IS_FIREBASE_CLI == "true"
80-
) {
104+
if (!validAge) {
105+
// User has invalid age data
81106
return {
82107
debug: 1,
83-
visible: false,
84-
mode: "none",
108+
visible: true,
109+
mode: "update-age",
85110
variant
86111
};
87112
}
@@ -100,7 +125,7 @@ const getPrivacyState = (email, user) => {
100125
const dueBy = user[dueByKey];
101126
const latestTouch = user[latestTouchKey];
102127

103-
const shouldRetry = shouldRetryPopup(latestTouch, useUnder13);
128+
const shouldRetry = shouldRetryPopup(latestTouch, useUnder13, guardianEmails);
104129

105130
const delayResponse = {
106131
visible: true,

scripts/privacy-reset-bulk.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const fb = require('firebase-admin');
4+
const downloadUsers = require('./utils/downloadUsers');
5+
const {
6+
firstSeenKey,
7+
latestTouchKey,
8+
dueByKey,
9+
userNeedsGuardianTouchKey,
10+
guardianPrivacyAuthTokenKey,
11+
acceptedKey,
12+
acceptedByKey,
13+
variantModeKey,
14+
ageSetKey
15+
} = require('../functions/utilities/privacy/privacyKeys');
16+
const { getGuardianEmails } = require('../functions/utilities/getTypeSafeUser');
17+
18+
const userDb = fb.firestore().collection('users');
19+
20+
const dry = true;
21+
22+
const run = async () => {
23+
let users = await downloadUsers();
24+
const file = path.join(__dirname, `../private/tmp-users.json`);
25+
const accounts = JSON.parse(fs.readFileSync(file)).users;
26+
27+
for (let a of accounts) {
28+
const u = users[a.localId];
29+
if (!u) continue;
30+
31+
const emails = getGuardianEmails(u);
32+
33+
if (u[userNeedsGuardianTouchKey] && !emails.length) {
34+
console.log('Resetting privacy touch for user without guardian email:', a.localId);
35+
36+
if (!dry) {
37+
await userDb.doc(a.localId).set({
38+
[firstSeenKey]: fb.firestore.FieldValue.delete(),
39+
[latestTouchKey]: fb.firestore.FieldValue.delete(),
40+
[dueByKey]: fb.firestore.FieldValue.delete(),
41+
[userNeedsGuardianTouchKey]: fb.firestore.FieldValue.delete(),
42+
[guardianPrivacyAuthTokenKey]: fb.firestore.FieldValue.delete(),
43+
[acceptedKey]: fb.firestore.FieldValue.delete(),
44+
[acceptedByKey]: fb.firestore.FieldValue.delete(),
45+
[variantModeKey]: fb.firestore.FieldValue.delete(),
46+
[ageSetKey]: fb.firestore.FieldValue.delete(),
47+
}, {merge: true});
48+
}
49+
}
50+
}
51+
}
52+
53+
run();

scripts/privacy-reset.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
acceptedKey,
1212
acceptedByKey,
1313
variantModeKey,
14+
ageSetKey
1415
} = require('../functions/utilities/privacy/privacyKeys');
1516

1617
// ---------------------
@@ -51,6 +52,7 @@ const run = async () => {
5152
[acceptedKey]: fb.firestore.FieldValue.delete(),
5253
[acceptedByKey]: fb.firestore.FieldValue.delete(),
5354
[variantModeKey]: fb.firestore.FieldValue.delete(),
55+
[ageSetKey]: fb.firestore.FieldValue.delete(),
5456
}, {merge: true});
5557

5658
console.log(`User "${email}" privacy data deleted`);

0 commit comments

Comments
 (0)