Skip to content

Commit a08f9df

Browse files
committed
impr: store emailVerified status in database (@fehmer)
1 parent 6dad541 commit a08f9df

File tree

7 files changed

+92
-3
lines changed

7 files changed

+92
-3
lines changed

backend/__tests__/api/controllers/user.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,66 @@ describe("user controller test", () => {
253253
});
254254
});
255255
});
256+
describe("getUser", () => {
257+
const getUserMock = vi.spyOn(UserDal, "getUser");
258+
const updateEmailMock = vi.spyOn(UserDal, "updateEmail");
259+
beforeEach(() => {
260+
getUserMock.mockReset();
261+
updateEmailMock.mockReset();
262+
});
263+
it("should update emailVerified if undefined", async () => {
264+
//GIVEN
265+
getUserMock.mockResolvedValue({
266+
uid,
267+
email: "old",
268+
} as any);
269+
mockAuth.modifyToken({ email_verified: false, email: "old" });
270+
271+
//WHEN
272+
await mockApp
273+
.get("/users")
274+
.set("Authorization", `Bearer ${uid}`)
275+
.expect(200);
276+
277+
//THEN
278+
expect(updateEmailMock).toHaveBeenCalledWith(uid, "old", false);
279+
});
280+
it("should update emailVerified if changed", async () => {
281+
//GIVEN
282+
getUserMock.mockResolvedValue({
283+
uid,
284+
emailVerified: false,
285+
email: "old",
286+
} as any);
287+
mockAuth.modifyToken({ email_verified: true, email: "next" });
288+
289+
//WHEN
290+
await mockApp
291+
.get("/users")
292+
.set("Authorization", `Bearer ${uid}`)
293+
.expect(200);
294+
295+
//THEN
296+
expect(updateEmailMock).toHaveBeenCalledWith(uid, "next", true);
297+
});
298+
it("should not update emailVerified if not changed", async () => {
299+
//GIVEN
300+
getUserMock.mockResolvedValue({
301+
uid,
302+
emailVerified: false,
303+
} as any);
304+
mockAuth.modifyToken({ email_verified: false });
305+
306+
//WHEN
307+
await mockApp
308+
.get("/users")
309+
.set("Authorization", `Bearer ${uid}`)
310+
.expect(200);
311+
312+
//THEN
313+
expect(updateEmailMock).not.toHaveBeenCalled();
314+
});
315+
});
256316
describe("sendVerificationEmail", () => {
257317
const adminGetUserMock = vi.fn();
258318
const adminGenerateVerificationLinkMock = vi.fn();

backend/__tests__/dal/user.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ describe("UserDal", () => {
102102
expect(insertedUser.email).toBe(newUser.email);
103103
expect(insertedUser.uid).toBe(newUser.uid);
104104
expect(insertedUser.name).toBe(newUser.name);
105+
expect(insertedUser.emailVerified).toBe(false);
105106
});
106107

107108
it("should error if the user already exists", async () => {
@@ -1167,14 +1168,18 @@ describe("UserDal", () => {
11671168
});
11681169
it("should update", async () => {
11691170
//given
1170-
const { uid } = await UserTestData.createUser({ email: "init" });
1171+
const { uid } = await UserTestData.createUser({
1172+
email: "init",
1173+
emailVerified: true,
1174+
});
11711175

11721176
//when
11731177
await expect(UserDAL.updateEmail(uid, "next")).resolves.toBe(true);
11741178

11751179
//then
11761180
const read = await UserDAL.getUser(uid, "read");
11771181
expect(read.email).toEqual("next");
1182+
expect(read.emailVerified).toEqual(false);
11781183
});
11791184
});
11801185
describe("resetPb", () => {

backend/__tests__/middlewares/auth.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const mockDecodedToken: DecodedIdToken = {
2020
uid: "123456789",
2121
2222
iat: 0,
23+
email_verified: true,
2324
} as DecodedIdToken;
2425

2526
vi.spyOn(AuthUtils, "verifyIdToken").mockResolvedValue(mockDecodedToken);
@@ -62,6 +63,7 @@ describe("middlewares/auth", () => {
6263
type: "None",
6364
uid: "",
6465
email: "",
66+
emailVerified: false,
6567
},
6668
},
6769
};
@@ -122,6 +124,7 @@ describe("middlewares/auth", () => {
122124
expect(decodedToken?.type).toBe("Bearer");
123125
expect(decodedToken?.email).toBe(mockDecodedToken.email);
124126
expect(decodedToken?.uid).toBe(mockDecodedToken.uid);
127+
expect(decodedToken?.emailVerified).toBe(mockDecodedToken.email_verified);
125128
expect(nextFunction).toHaveBeenCalledOnce();
126129

127130
expect(prometheusIncrementAuthMock).toHaveBeenCalledWith("Bearer");

backend/src/api/controllers/user.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,18 @@ export async function getUser(req: MonkeyRequest): Promise<GetUserResponse> {
605605
);
606606
delete relevantUserInfo.customThemes;
607607

608+
//update users emailVerified status if it changed
609+
const { email, emailVerified } = req.ctx.decodedToken;
610+
if (emailVerified !== undefined && emailVerified !== userInfo.emailVerified) {
611+
void addImportantLog(
612+
"user_verify_email",
613+
`emailVerified changed to ${emailVerified} for email ${email}`,
614+
uid
615+
);
616+
await UserDAL.updateEmail(uid, email, emailVerified);
617+
userInfo.emailVerified = emailVerified;
618+
}
619+
608620
const userData: User = {
609621
...relevantUserInfo,
610622
resultFilterPresets,

backend/src/dal/user.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export async function addUser(
8787
custom: {},
8888
},
8989
testActivity: {},
90+
emailVerified: false,
9091
};
9192

9293
const result = await getUsersCollection().updateOne(
@@ -231,9 +232,14 @@ export async function updateQuoteRatings(
231232

232233
export async function updateEmail(
233234
uid: string,
234-
email: string
235+
email: string,
236+
emailVerified: boolean = true
235237
): Promise<boolean> {
236-
await updateUser({ uid }, { $set: { email } }, { stack: "update email" });
238+
await updateUser(
239+
{ uid },
240+
{ $set: { email, emailVerified } },
241+
{ stack: "update email" }
242+
);
237243

238244
return true;
239245
}

backend/src/middlewares/auth.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export type DecodedToken = {
2626
type: "Bearer" | "ApeKey" | "None" | "GithubWebhook";
2727
uid: string;
2828
email: string;
29+
emailVerified?: boolean;
2930
};
3031

3132
const DEFAULT_OPTIONS: RequestAuthenticationOptions = {
@@ -189,6 +190,7 @@ async function authenticateWithBearerToken(
189190
type: "Bearer",
190191
uid: decodedToken.uid,
191192
email: decodedToken.email ?? "",
193+
emailVerified: decodedToken.email_verified,
192194
};
193195
} catch (error) {
194196
if (

packages/schemas/src/users.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ export const UserSchema = z.object({
271271
quoteMod: QuoteModSchema.optional(),
272272
resultFilterPresets: z.array(ResultFiltersSchema).optional(),
273273
testActivity: TestActivitySchema.optional(),
274+
emailVerified: z.boolean().optional(),
274275
});
275276
export type User = z.infer<typeof UserSchema>;
276277

0 commit comments

Comments
 (0)