async destroy()

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 {};
  }