signalapp/Signal-Server · error · BadRequestException

Recovery password required for authentication when…

Error message

Recovery password required for authentication when recovering an account by identifier

What it means

recoverAccount requires recoveryPassword (the authentication recovery password) in the RegistrationRequest when recovering an account by identifier. If ArrayUtils.isEmpty finds it missing or empty, the controller throws a BadRequestException (HTTP 400). This password authenticates the caller to the existing account.

Solutions

  1. Populate RegistrationRequest.recoveryPassword with the stored account recovery password before sending the recovery request.
  2. Ensure the client persists the recovery password (from registration/backup time) so it survives restarts and can be supplied here.
  3. Update client request serialization to include recoveryPassword — older client builds predating this requirement will always 400.
  4. If the recovery password is genuinely lost, use an alternate account recovery path (e.g. registration lock / recovery phrase) instead.

Example fix

// before
RegistrationRequest req = new RegistrationRequest(attrs, aciKey, pniKey, null /* recoveryPassword */);
// after
RegistrationRequest req = new RegistrationRequest(attrs, aciKey, pniKey, storedRecoveryPassword);
Defensive patterns

Strategy: validation

Validate before calling

if (ArrayUtils.isEmpty(request.recoveryPassword())) {
  throw new IllegalArgumentException("authentication recoveryPassword is required for recovery by identifier");
}

Type guard

boolean hasAuthRecoveryPassword(RegistrationRequest r) {
  return r.recoveryPassword() != null && r.recoveryPassword().length > 0;
}

Try / catch

try {
  recoverAccount(request);
} catch (BadRequestException e) {
  if (e.getMessage().startsWith("Recovery password required for authentication")) {
    promptUserToReenterRecoveryPassword();
  } else throw e;
}

Prevention

When it happens

Trigger: Submitting a recovery-by-identifier registration request whose top-level recoveryPassword field is null or an empty/blank array.

Common situations: Clients upgraded to a server version requiring the recovery password field while still sending the old request shape; app storage lost the recovery password (fresh install, cleared data) so the field is serialized as null; API tests omitting the new field.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/30e7dc33272c29f0. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java:404

      final AccountIdentityResponse accountIdentityResponse = new AccountIdentityResponseBuilder(account).build();
      return new AccountCreationResponse(accountIdentityResponse, false);
    } catch (ReceiptAlreadyRedeemedException _) {
      throw new NotAuthorizedException("Receipt already redeemed");
    }
  }

  private AccountCreationResponse recoverAccount(final UUID accountIdentifier,
      final String password,
      final RegistrationRequest registrationRequest,
      final String userAgent,
      final String signalAgent) throws RegistrationLockFailureException, RateLimitExceededException {

    if (!dynamicConfigurationManager.getConfiguration().getLoginPurchaseConfiguration().enabled()) {
      throw new BadRequestException("login purchases are not enabled");
    }

    if (ArrayUtils.isEmpty(registrationRequest.recoveryPassword())) {
      throw new BadRequestException("Recovery password required for authentication when recovering an account by identifier");
    }

    if (registrationRequest.accountAttributes().recoveryPassword().isEmpty()) {
      throw new BadRequestException("Recovery password required for for storage when recovering an account by identifier");
    }

    if (registrationRequest.pniIdentityKey() == null) {
      throw new BadRequestException("Must specify a PNI-associated identity key when recovering an account by identifier");
    }

    final Account existingAccount = accounts.getByAccountIdentifier(accountIdentifier)
            .orElseThrow(ForbiddenException::new);

    final boolean passwordVerified = existingAccount.getAccountRecoveryPassword()
        .map(saltedRecoveryPasswordHash -> PhoneNumberRecoveryPasswordsManager.verify(saltedRecoveryPasswordHash, registrationRequest.recoveryPassword()))
        .orElse(false);

    if (!passwordVerified) {

View on GitHub (pinned to 100ab61c82)