passbolt/passbolt_api · error · Cake\Http\Exception\BadRequestException

Invalid request. Passwords are required for this change.

Error message

Invalid request. Passwords are required for this change.

What it means

This BadRequestException is thrown during an organization key rotation when existing user backups (account_recovery_private_key_passwords rows) are present in the database, but the request does not include the re-encrypted private key passwords. Because the organization private key changes, all stored backups must be re-encrypted against the new key and re-submitted, otherwise existing users would lose recovery capability.

Solutions

  1. Before rotating, ensure every user with a backup re-encrypts their private key password data against the new organization public key and include it in 'account_recovery_private_key_passwords'.
  2. If no backups should exist, verify and clear stale rows first — the check triggers only when backupsExists() is true.
  3. Include the full set of passwords (count must match stored backups; see assertPasswordsCount) encrypted with the new key.
  4. Use the official passbolt key-rotation workflow/UI which collects re-encrypted passwords from users before applying rotation.

Example fix

// before (rotation without re-encrypted backups)
await passbolt.rotateAccountRecoveryKey({
  account_recovery_organization_public_key: newArmoredKey,
  account_recovery_organization_revoked_key: oldArmoredKey
});

// after (rotation including re-encrypted passwords)
await passbolt.rotateAccountRecoveryKey({
  account_recovery_organization_public_key: newArmoredKey,
  account_recovery_organization_revoked_key: oldArmoredKey,
  account_recovery_private_key_passwords: reencryptedPasswords
});
Defensive patterns

Strategy: validation

Validate before calling

const current = await passbolt.getAccountRecoveryOrganizationPolicy();
const hasBackups = current.backupsCount > 0; // or check via API
if (hasBackups && !payload.account_recovery_private_key_passwords?.length) {
  throw new Error('Existing backups detected: include re-encrypted account_recovery_private_key_passwords with the rotation.');
}

Try / catch

try {
  await passbolt.rotateAccountRecoveryKey(payload);
} catch (e) {
  if (e.status === 400 && /Passwords are required for this change/.test(e.message)) {
    // start the user-facing flow to collect re-encrypted passwords, then retry
    return startReencryptionFlow();
  }
  throw e;
}

Prevention

When it happens

Trigger: Key rotation request (new public key + revoked key provided) where backupsExists() finds stored private key password backups, but 'account_recovery_private_key_passwords' is absent from the payload. Raised in AccountRecoveryOrganizationPolicySetService::set() at line 109.

Common situations: An admin rotates the organization recovery key without first collecting and re-encrypting all users' backup passwords; a script automates rotation assuming an empty backups table; a client version predating the backups-required rule; testing on a instance where backups were created after the payload was built.

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 passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/37136fc53397e311. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php:109

        if (($isNewKeyProvided && !$isRevokedKeyProvided) || (!$isNewKeyProvided && $isRevokedKeyProvided)) {
            throw new BadRequestException(__('Invalid request. Keys are required for this change.'));
        }

        // if key provided or revocation provided
        $newKey = null;
        $oldKey = null;
        $passwords = null;
        /** @psalm-suppress RedundantCondition */
        if ($isNewKeyProvided && $isRevokedKeyProvided) {
            // assert old and new key$newKey
            $newKey = $this->buildPublicKeyEntityFromDataOrFail($uac);
            $oldKey = $this->buildRevokedKeyEntityFromDataOrFail($uac);

            // If some existing backups are present
            // assert new backups are provided
            if ($this->backupsExists()) {
                if (!$isPrivateKeyPasswordsProvided) {
                    throw new BadRequestException(__('Invalid request. Passwords are required for this change.'));
                }
                // assert passwords backups format and numbers
                $passwords = $this->buildPasswordEntitiesFromDataOrFail($uac, $newKey);
            }
            $newPolicy->account_recovery_organization_public_key = $newKey;
        } else {
            // If key is not changing reuse the old one
            if (!isset($newPolicy->public_key_id)) {
                throw new CustomValidationException(__('Could not validate public key data.'), [
                    'public_key_id' => [
                        '_required' => __('An organization public key is required.'),
                    ],
                ]);
            } else {
                if ($newPolicy->public_key_id !== $this->getCurrentPolicyEntity()->public_key_id) {
                    throw new CustomValidationException(__('Could not validate public key data.'), [
                        'public_key_id' => [
                            'notCurrentPublicKeyId' => __('The public_key_id must match current policy public_key_id.'),

View on GitHub (pinned to 31c1bbc10f)