Merge pull request #485 from gianantoniopini/develop

Delete Account: fix for Firebase cloud function and error handling improvements
This commit is contained in:
Amruth Pillai
2021-05-01 21:44:29 +05:30
committed by GitHub
3 changed files with 105 additions and 44 deletions

View File

@ -10,41 +10,53 @@ function timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
// eslint-disable-next-line no-await-in-loop
await callback(array[index], index, array);
const deleteUserFunctionHandler = async (_, { auth }) => {
if (!auth) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.',
);
}
}
try {
const userId = auth.uid;
const updates = {};
const userResumesDataSnapshot = await admin
.database()
.ref('resumes')
.orderByChild('user')
.equalTo(userId)
.once('value');
const userResumes = userResumesDataSnapshot.val();
if (userResumes) {
Object.keys(userResumes).forEach(async (resumeId) => {
updates[`resumes/${resumeId}`] = null;
});
}
const userDataSnapshot = await admin
.database()
.ref(`users/${userId}`)
.once('value');
const user = userDataSnapshot.val();
if (user) {
updates[`users/${userId}`] = null;
}
if (Object.keys(updates).length > 0) {
await admin.database().ref().update(updates);
}
return true;
} catch (error) {
throw new functions.https.HttpsError('internal', error.message);
}
};
exports.deleteUser = functions
.runWith({ memory: '256MB' })
.https.onCall((_, { auth }) => {
if (!auth) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.',
);
}
return new Promise((resolve) => {
const resumesRef = admin.database().ref('resumes');
resumesRef.once('value', async (snapshot) => {
const data = snapshot.val();
const resumes = Object.keys(data).filter(
(x) => data[x].user === auth.uid,
);
await asyncForEach(resumes, async (id) => {
await admin.database().ref(`resumes/${id}`).remove();
});
resolve();
});
});
});
.https.onCall(deleteUserFunctionHandler);
exports.printResume = functions
.runWith({ memory: '1GB' })

View File

@ -1,6 +1,7 @@
import React, { memo, useContext, useState } from 'react';
import { FaAngleDown } from 'react-icons/fa';
import { useTranslation, Trans } from 'react-i18next';
import { toast } from 'react-toastify';
import UserContext from '../../../../contexts/UserContext';
import Button from '../../../shared/Button';
import Heading from '../../../shared/Heading';
@ -17,6 +18,9 @@ const Settings = ({ id }) => {
const [deleteText, setDeleteText] = useState(
t('builder.settings.dangerZone.button'),
);
const [isDeleteAccountInProgress, setDeleteAccountInProgress] = useState(
false,
);
const dispatch = useDispatch();
const { deleteAccount } = useContext(UserContext);
@ -34,16 +38,21 @@ const Settings = ({ id }) => {
dispatch({ type: 'change_language', payload: lang });
};
const handleDeleteAccount = () => {
const handleDeleteAccount = async () => {
if (deleteText === t('builder.settings.dangerZone.button')) {
setDeleteText(t('shared.buttons.confirmation'));
return;
}
setDeleteText('Buh bye! :(');
setTimeout(() => {
deleteAccount();
}, 500);
setDeleteAccountInProgress(true);
try {
await deleteAccount();
} catch (error) {
toast.error('An error occurred deleting your account.');
setDeleteAccountInProgress(false);
setDeleteText(t('builder.settings.dangerZone.button'));
}
};
return (
@ -96,7 +105,11 @@ const Settings = ({ id }) => {
<p className="leading-loose">{t('builder.settings.dangerZone.text')}</p>
<div className="mt-4 flex">
<Button isDelete onClick={handleDeleteAccount}>
<Button
isDelete
onClick={handleDeleteAccount}
isLoading={isDeleteAccountInProgress}
>
{deleteText}
</Button>
</div>

View File

@ -73,19 +73,55 @@ const UserProvider = ({ children }) => {
navigate('/');
};
const reauthenticateWithGoogle = async () => {
const { currentUser } = firebase.auth();
const provider = new firebase.auth.GoogleAuthProvider();
try {
const userCredential = await currentUser.reauthenticateWithPopup(
provider,
);
return userCredential;
} catch (error) {
toast.error(error.message);
throw error;
}
};
const reauthenticate = async () => {
const { currentUser } = firebase.auth();
if (currentUser.isAnonymous) {
return;
}
const googleAuthProvider = new firebase.auth.GoogleAuthProvider();
const authProviderIsGoogle =
currentUser.providerData &&
currentUser.providerData.length > 0 &&
currentUser.providerData[0].providerId === googleAuthProvider.providerId;
if (authProviderIsGoogle) {
await reauthenticateWithGoogle();
} else {
const errorMessage = 'Unable to determine reauthentication method.';
toast.error(errorMessage);
throw new Error(errorMessage);
}
};
const deleteAccount = async () => {
const { currentUser } = firebase.auth();
const deleteUser = firebase.functions().httpsCallable('deleteUser');
deleteUser();
await reauthenticate();
await deleteUser();
try {
deleteUser();
await currentUser.delete();
} catch (e) {
if (e.code === 'auth/requires-recent-login') {
await loginWithGoogle();
await currentUser.delete();
}
} catch (error) {
toast.error(error.message);
} finally {
logout();
toast(