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:
YummyOreo 2022-07-06 05:59:40 -05:00 committed by GitHub
parent ebf5825040
commit e794bbd68d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 178 additions and 0 deletions

View file

@ -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> {

View file

@ -121,6 +121,15 @@ router.delete(
asyncHandler(UserController.deleteUser)
);
router.patch(
"/reset",
authenticateRequest({
requireFreshToken: true,
}),
RateLimit.userReset,
asyncHandler(UserController.resetUser)
);
router.patch(
"/name",
authenticateRequest({

View file

@ -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 });
}

View file

@ -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 });
}

View file

@ -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 });
}

View file

@ -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;

View file

@ -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,

View file

@ -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 },

View file

@ -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();
});

View file

@ -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>