mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-25 16:28:04 +08:00
feat(security): add revoke all tokens button
With this, user can sign out all sessions if they think their account has bee compromised.
This commit is contained in:
parent
0de54f889b
commit
87e882b94d
6 changed files with 126 additions and 1 deletions
|
|
@ -877,3 +877,11 @@ export async function toggleBan(
|
|||
banned: !user.banned,
|
||||
});
|
||||
}
|
||||
|
||||
export async function revokeAllTokens(
|
||||
req: MonkeyTypes.Request
|
||||
): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
await FirebaseAdmin().auth().revokeRefreshTokens(uid);
|
||||
return new MonkeyResponse("All tokens revoked");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -650,4 +650,14 @@ router.post(
|
|||
asyncHandler(UserController.sendForgotPasswordEmail)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/revokeAllTokens",
|
||||
RateLimit.userRevokeAllTokens,
|
||||
authenticateRequest({
|
||||
requireFreshToken: true,
|
||||
noCache: true,
|
||||
}),
|
||||
asyncHandler(UserController.revokeAllTokens)
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -483,6 +483,13 @@ export const userForgotPasswordEmail = rateLimit({
|
|||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userRevokeAllTokens = rateLimit({
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 10 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userProfileGet = rateLimit({
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 100 * REQUEST_MULTIPLIER,
|
||||
|
|
|
|||
|
|
@ -264,4 +264,8 @@ export default class Users {
|
|||
payload: { hourOffset },
|
||||
});
|
||||
}
|
||||
|
||||
async revokeAllTokens(): Ape.EndpointResponse {
|
||||
return await this.httpClient.post(`${BASE_PATH}/revokeAllTokens`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -991,6 +991,73 @@ list["resetSettings"] = new SimplePopup(
|
|||
}
|
||||
);
|
||||
|
||||
list["revokeAllTokens"] = new SimplePopup(
|
||||
"revokeAllTokens",
|
||||
"text",
|
||||
"Revoke All Tokens",
|
||||
[
|
||||
{
|
||||
placeholder: "Password",
|
||||
type: "password",
|
||||
initVal: "",
|
||||
},
|
||||
],
|
||||
"Are you sure you want to this? This will log you out of all devices.",
|
||||
"revoke all",
|
||||
async (_thisPopup, password) => {
|
||||
try {
|
||||
const user = Auth?.currentUser;
|
||||
const snapshot = DB.getSnapshot();
|
||||
if (!user || !snapshot) return;
|
||||
|
||||
if (user.providerData.find((p) => p?.providerId === "password")) {
|
||||
const credential = EmailAuthProvider.credential(
|
||||
user.email as string,
|
||||
password
|
||||
);
|
||||
await reauthenticateWithCredential(user, credential);
|
||||
} else {
|
||||
await reauthenticateWithPopup(user, AccountController.gmailProvider);
|
||||
}
|
||||
Loader.show();
|
||||
const response = await Ape.users.revokeAllTokens();
|
||||
Loader.hide();
|
||||
|
||||
if (response.status !== 200) {
|
||||
return Notifications.add(
|
||||
"Failed to revoke all tokens: " + response.message,
|
||||
-1
|
||||
);
|
||||
}
|
||||
|
||||
Notifications.add("All tokens revoked", 1);
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
Loader.hide();
|
||||
const typedError = e as FirebaseError;
|
||||
if (typedError.code === "auth/wrong-password") {
|
||||
Notifications.add("Incorrect password", -1);
|
||||
} else {
|
||||
Notifications.add("Something went wrong: " + e, -1);
|
||||
}
|
||||
}
|
||||
},
|
||||
(thisPopup) => {
|
||||
const user = Auth?.currentUser;
|
||||
const snapshot = DB.getSnapshot();
|
||||
if (!user || !snapshot) return;
|
||||
if (!user.providerData.find((p) => p?.providerId === "password")) {
|
||||
thisPopup.inputs[0].hidden = true;
|
||||
thisPopup.buttonText = "reauthenticate to revoke all tokens";
|
||||
}
|
||||
},
|
||||
(_thisPopup) => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
||||
list["unlinkDiscord"] = new SimplePopup(
|
||||
"unlinkDiscord",
|
||||
"text",
|
||||
|
|
@ -1385,6 +1452,14 @@ $("#resetSettingsButton").on("click", () => {
|
|||
list["resetSettings"].show();
|
||||
});
|
||||
|
||||
$("#revokeAllTokens").on("click", () => {
|
||||
if (!ConnectionState.get()) {
|
||||
Notifications.add("You are offline", 0, { duration: 2 });
|
||||
return;
|
||||
}
|
||||
list["revokeAllTokens"].show();
|
||||
});
|
||||
|
||||
$(".pageSettings #resetPersonalBestsButton").on("click", () => {
|
||||
if (!ConnectionState.get()) {
|
||||
Notifications.add("You are offline", 0, { duration: 2 });
|
||||
|
|
|
|||
|
|
@ -130,7 +130,6 @@
|
|||
<div class="button addPresetButton"><i class="fas fa-plus"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sectionSpacer"></div>
|
||||
<div class="section">
|
||||
<div class="groupTitle">
|
||||
<i class="fas fa-user"></i>
|
||||
|
|
@ -2869,6 +2868,28 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section revokeAllTokens">
|
||||
<div class="groupTitle">
|
||||
<i class="fas fa-user-slash"></i>
|
||||
<span>revoke all tokens</span>
|
||||
</div>
|
||||
<div class="text">
|
||||
Revokes all tokens connected to your account. Do this if you think
|
||||
someone else has access to your account.
|
||||
<br />
|
||||
<span class="red">This will log you out of all devices.</span>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<div
|
||||
class="button danger"
|
||||
id="revokeAllTokens"
|
||||
tabindex="0"
|
||||
onclick="this.blur();"
|
||||
>
|
||||
revoke all tokens
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section resetSettings">
|
||||
<div class="groupTitle">
|
||||
<i class="fas fa-redo-alt"></i>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue