in packages/fxa-auth-server/lib/routes/account.ts [1814:1917]
async destroy(request: AuthRequest) {
this.log.begin('Account.destroy', request);
const { authPW, email: emailAddress } = request.payload as any;
await this.customs.check(request, emailAddress, 'accountDestroy');
let accountRecord: Account;
try {
accountRecord = await this.db.accountRecord(emailAddress);
} catch (err) {
if (err.errno === error.ERRNO.ACCOUNT_UNKNOWN) {
await this.customs.flag(request.app.clientAddress, {
email: emailAddress,
errno: err.errno,
});
}
throw err;
}
const sessionToken = request.auth && request.auth.credentials;
const hasTotpToken = await this.otpUtils.hasTotpToken(accountRecord);
// Someone tried to delete an account with TOTP but did not specify a session.
// This shouldn't happen in practice, but just in case we throw unverified session.
if (!sessionToken && hasTotpToken) {
throw error.unverifiedSession();
}
// If TOTP is enabled, ensure that the session has the correct assurance level before
// deleting account.
if (
sessionToken &&
hasTotpToken &&
(sessionToken.tokenVerificationId ||
(sessionToken.authenticatorAssuranceLevel as number) <= 1)
) {
throw error.unverifiedSession();
}
// We can also check that the email was verified. This proves the account is active and
// related to a valid email. Accounts that aren't activated get deleted automatically
// by cron jobs anyways...
if (
this.config.accountDestroy.requireVerifiedAccount &&
!accountRecord.emailVerified
) {
throw error.unverifiedAccount();
}
// Regardless of whether or not an account has TOTP, we must make sure the user actually
// owns the account. This means the sessionToken must be verified.
//
// The UI will request an OTP code verification before destroying the account in the event
// the session is currently unverified. The following check will ensure that this OTP code
// was actually provided by the user.
if (
this.config.accountDestroy.requireVerifiedSession &&
!sessionToken.tokenVerified
) {
throw error.unverifiedSession();
}
// In other scenarios, fall back to the default behavior and let the user
// delete the account. If they have a password set, we verify it here. Users
// that don't have a password set will be able to delete their account without
// this step.
if (accountRecord.verifierSetAt > 0) {
const password = new this.Password(
authPW,
accountRecord.authSalt,
accountRecord.verifierVersion
);
const isMatchingPassword = await this.signinUtils.checkPassword(
accountRecord,
password,
request.app.clientAddress
);
if (!isMatchingPassword) {
throw error.incorrectPassword(accountRecord.email, emailAddress);
}
}
await this.accountDeleteManager.quickDelete(
accountRecord.uid,
ReasonForDeletion.UserRequested
);
// data eng rely on this to delete the account data from BQ
this.log.info('accountDeleted.ByRequest', { uid: accountRecord.uid });
const result = await getAccountCustomerByUid(accountRecord.uid);
await this.accountTasks.deleteAccount({
uid: accountRecord.uid,
customerId: result?.stripeCustomerId,
reason: ReasonForDeletion.UserRequested,
});
this.glean.account.deleteComplete(request, { uid: accountRecord.uid });
return {};
}