mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-17 11:06:17 +08:00
Adds a reset account button to the settings page (#3206) YummyOreo
* adds reset button * moves it up above the delete button * i am blind * i cant move stuff * adds reset for the profile * Adds reseting for ape keys, and presets, also adds more resets in the user settings * Delets all ape keys not just one * Prevents conflicts for delete endpoints * changes from reset to deleteAll in ape keys * Refactors the code to use Promise.all * adds reloading * unit tests ape key deletion * fixes some bugs * Fixes type o * More typeo fixes * Returns nothing for reset user * Simplifies code * changes from reset to deleteAll for all but users * Checks db not the memory * fixes typo * fixes the tests * fixes * fixes bugs and refactors some code * adds requireFreshToken to the authentication * migrates all reseting to the user endpoint * removes the delete all for ape unit test * to lazy to make commit message * parallelize the calls to the db (I think) * also resetting config * also resetting discordid and avatar * using unset * updated wording * level 1 message Co-authored-by: Miodec <bartnikjack@gmail.com>
This commit is contained in:
parent
ebf5825040
commit
e794bbd68d
10 changed files with 178 additions and 0 deletions
|
@ -7,6 +7,10 @@ import { getDiscordUser } from "../../utils/discord";
|
|||
import { buildAgentLog, sanitizeString } from "../../utils/misc";
|
||||
import * as George from "../../tasks/george";
|
||||
import admin from "firebase-admin";
|
||||
import { deleteAllApeKeys } from "../../dal/ape-keys";
|
||||
import { deleteAllPresets } from "../../dal/preset";
|
||||
import { deleteAll as deleteAllResults } from "../../dal/result";
|
||||
import { deleteConfig } from "../../dal/config";
|
||||
|
||||
export async function createNewUser(
|
||||
req: MonkeyTypes.Request
|
||||
|
@ -37,6 +41,24 @@ export async function deleteUser(
|
|||
return new MonkeyResponse("User deleted");
|
||||
}
|
||||
|
||||
export async function resetUser(
|
||||
req: MonkeyTypes.Request
|
||||
): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
|
||||
const userInfo = await UserDAL.getUser(uid, "reset user");
|
||||
await Promise.all([
|
||||
UserDAL.resetUser(uid),
|
||||
deleteAllApeKeys(uid),
|
||||
deleteAllPresets(uid),
|
||||
deleteAllResults(uid),
|
||||
deleteConfig(uid),
|
||||
]);
|
||||
Logger.logToDb("user_reset", `${userInfo.email} ${userInfo.name}`, uid);
|
||||
|
||||
return new MonkeyResponse("User reset");
|
||||
}
|
||||
|
||||
export async function updateName(
|
||||
req: MonkeyTypes.Request
|
||||
): Promise<MonkeyResponse> {
|
||||
|
|
|
@ -121,6 +121,15 @@ router.delete(
|
|||
asyncHandler(UserController.deleteUser)
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/reset",
|
||||
authenticateRequest({
|
||||
requireFreshToken: true,
|
||||
}),
|
||||
RateLimit.userReset,
|
||||
asyncHandler(UserController.resetUser)
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/name",
|
||||
authenticateRequest({
|
||||
|
|
|
@ -94,3 +94,7 @@ export async function deleteApeKey(uid: string, keyId: string): Promise<void> {
|
|||
throw new MonkeyError(404, "ApeKey not found");
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAllApeKeys(uid: string): Promise<void> {
|
||||
await db.collection<MonkeyTypes.ApeKey>(COLLECTION_NAME).deleteMany({ uid });
|
||||
}
|
||||
|
|
|
@ -16,3 +16,7 @@ export async function getConfig(uid: string): Promise<any> {
|
|||
const config = await db.collection<any>("configs").findOne({ uid });
|
||||
return config;
|
||||
}
|
||||
|
||||
export async function deleteConfig(uid: string): Promise<any> {
|
||||
return await db.collection<any>("configs").deleteOne({ uid });
|
||||
}
|
||||
|
|
|
@ -69,3 +69,7 @@ export async function removePreset(
|
|||
throw new MonkeyError(404, "Preset not found");
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAllPresets(uid: string): Promise<void> {
|
||||
await db.collection(COLLECTION_NAME).deleteMany({ uid });
|
||||
}
|
||||
|
|
|
@ -41,6 +41,43 @@ export async function deleteUser(uid: string): Promise<void> {
|
|||
await getUsersCollection().deleteOne({ uid });
|
||||
}
|
||||
|
||||
export async function resetUser(uid: string): Promise<void> {
|
||||
await getUsersCollection().updateOne(
|
||||
{ uid },
|
||||
{
|
||||
$set: {
|
||||
personalBests: {
|
||||
custom: {},
|
||||
quote: {},
|
||||
time: {},
|
||||
words: {},
|
||||
zen: {},
|
||||
},
|
||||
lbPersonalBests: {
|
||||
time: {},
|
||||
},
|
||||
completedTests: 0,
|
||||
startedTests: 0,
|
||||
timeTyping: 0,
|
||||
lbMemory: {},
|
||||
bananas: 0,
|
||||
profileDetails: {
|
||||
bio: "",
|
||||
keyboard: "",
|
||||
socialProfiles: {},
|
||||
},
|
||||
favoriteQuotes: {},
|
||||
customThemes: [],
|
||||
tags: [],
|
||||
},
|
||||
$unset: {
|
||||
discordAvatar: "",
|
||||
discordId: "",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const DAY_IN_SECONDS = 24 * 60 * 60;
|
||||
const THIRTY_DAYS_IN_SECONDS = DAY_IN_SECONDS * 30;
|
||||
|
||||
|
|
|
@ -286,6 +286,13 @@ export const userDelete = rateLimit({
|
|||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userReset = rateLimit({
|
||||
windowMs: 24 * ONE_HOUR_MS, // 1 day
|
||||
max: 3 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userCheckName = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
|
|
|
@ -27,6 +27,10 @@ export default class Users {
|
|||
return await this.httpClient.delete(BASE_PATH);
|
||||
}
|
||||
|
||||
async reset(): Ape.EndpointData {
|
||||
return await this.httpClient.patch(`${BASE_PATH}/reset`);
|
||||
}
|
||||
|
||||
async updateName(name: string): Ape.EndpointData {
|
||||
return await this.httpClient.patch(`${BASE_PATH}/name`, {
|
||||
payload: { name },
|
||||
|
|
|
@ -688,6 +688,75 @@ list["deleteAccount"] = new SimplePopup(
|
|||
}
|
||||
);
|
||||
|
||||
list["resetAccount"] = new SimplePopup(
|
||||
"resetAccount",
|
||||
"text",
|
||||
"Reset Account",
|
||||
[
|
||||
{
|
||||
placeholder: "Password",
|
||||
type: "password",
|
||||
initVal: "",
|
||||
},
|
||||
],
|
||||
"This is the last time you can change your mind. After pressing the button everything is gone.",
|
||||
"Reset",
|
||||
async (_thisPopup, password: string) => {
|
||||
//
|
||||
try {
|
||||
const user = Auth.currentUser;
|
||||
if (user === null) 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);
|
||||
}
|
||||
Notifications.add("Resetting settings...", 0);
|
||||
UpdateConfig.reset();
|
||||
Loader.show();
|
||||
Notifications.add("Resetting account and stats...", 0);
|
||||
const response = await Ape.users.reset();
|
||||
|
||||
if (response.status !== 200) {
|
||||
Loader.hide();
|
||||
return Notifications.add(
|
||||
"There was an error resetting your account. Please try again.",
|
||||
-1
|
||||
);
|
||||
}
|
||||
Loader.hide();
|
||||
Notifications.add("Reset complete", 1);
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 3000);
|
||||
} catch (e) {
|
||||
const typedError = e as FirebaseError;
|
||||
Loader.hide();
|
||||
if (typedError.code === "auth/wrong-password") {
|
||||
Notifications.add("Incorrect password", -1);
|
||||
} else {
|
||||
Notifications.add("Something went wrong: " + e, -1);
|
||||
}
|
||||
}
|
||||
},
|
||||
(thisPopup) => {
|
||||
const user = Auth.currentUser;
|
||||
if (user === null) return;
|
||||
|
||||
if (!user.providerData.find((p) => p?.providerId === "password")) {
|
||||
thisPopup.inputs = [];
|
||||
thisPopup.buttonText = "Reauthenticate to reset";
|
||||
}
|
||||
},
|
||||
(_thisPopup) => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
||||
list["clearTagPb"] = new SimplePopup(
|
||||
"clearTagPb",
|
||||
"text",
|
||||
|
@ -1178,6 +1247,10 @@ $(".pageSettings #deleteAccount").on("click", () => {
|
|||
list["deleteAccount"].show();
|
||||
});
|
||||
|
||||
$(".pageSettings #resetAccount").on("click", () => {
|
||||
list["resetAccount"].show();
|
||||
});
|
||||
|
||||
$("#apeKeysPopup .generateApeKey").on("click", () => {
|
||||
list["generateApeKey"].show();
|
||||
});
|
||||
|
|
|
@ -2471,6 +2471,20 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section resetAccount needsAccount hidden">
|
||||
<h1>reset account</h1>
|
||||
<div class="text">Completely resets your account to a blank state.</div>
|
||||
<div class="buttons">
|
||||
<div
|
||||
class="button danger"
|
||||
id="resetAccount"
|
||||
tabindex="0"
|
||||
onclick="this.blur();"
|
||||
>
|
||||
reset account
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section deleteAccount needsAccount hidden">
|
||||
<h1>delete account</h1>
|
||||
<div class="text">Deletes your account and all data connected to it.</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue