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

Invalid request. Keys are required for this change.

Error message

Invalid request. Keys are required for this change.

What it means

This BadRequestException is thrown when an enabled-to-enabled policy change request provides only one half of a key rotation: a new public key without the revoked (old) key, or a revoked key without the new key. Key rotation and policy changes (e.g. mandatory to opt-in) must be accompanied by a consistent pair of keys, or by neither when only the policy changes and the key is reused.

Solutions

  1. If rotating the key: provide BOTH the new armored public key and the old key marked as revoked in the same request.
  2. If only changing the policy (e.g. mandatory to opt-in) with no rotation: provide NEITHER key and instead pass the current public_key_id so it can be reused.
  3. Audit the client payload to ensure the new-key and revoked-key fields are always set together or not at all.
  4. Use the official passbolt client/CLI for organization policy changes instead of hand-built requests.

Example fix

// before (new key without revocation)
await passbolt.setAccountRecoveryOrganizationPolicy({
  policy: 'opt-in',
  account_recovery_organization_public_key: newArmoredKey
});

// after (complete rotation: new key + revoked old key)
await passbolt.setAccountRecoveryOrganizationPolicy({
  policy: 'opt-in',
  account_recovery_organization_public_key: newArmoredKey,
  account_recovery_organization_revoked_key: oldArmoredKey
});
Defensive patterns

Strategy: validation

Validate before calling

const hasNew = !!payload.account_recovery_organization_public_key;
const hasRevoked = !!payload.account_recovery_organization_revoked_key;
if (hasNew !== hasRevoked) {
  throw new Error('Key rotation requires both the new public key and the revoked key, or neither.');
}

Type guard

function hasCompleteKeyPair(p) {
  const hasNew = 'account_recovery_organization_public_key' in p && p.account_recovery_organization_public_key;
  const hasRevoked = 'account_recovery_organization_revoked_key' in p && p.account_recovery_organization_revoked_key;
  return hasNew === hasRevoked;
}

Try / catch

try {
  await passbolt.setAccountRecoveryOrganizationPolicy(payload);
} catch (e) {
  if (e.status === 400 && /Keys are required for this change/.test(e.message)) {
    console.error('Provide both new key and revoked key, or neither (policy-only change with public_key_id).');
  }
  throw e;
}

Prevention

When it happens

Trigger: POST/PUT to the account recovery organization settings with current policy enabled and new policy enabled, where exactly one of 'account_recovery_organization_public_key' (new key) or 'account_recovery_organization_revoked_key' is present but not both. Raised in AccountRecoveryOrganizationPolicySetService::set() at line 92.

Common situations: A client implements key rotation but forgets to mark the old key as revoked; an admin wants to change the policy (mandatory to opt-in) but the client library injects a new key without the revocation record; partially migrated request builders after an API version change.

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/2f72b975a56426b8. Report an issue: GitHub.

Appendix: source

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

            return $this->enablePolicy($uac, $newPolicy);
        }

        // if enabled => disabled
        if ($this->isDisabling()) {
            // if new key or passwords provided
            if ($isNewKeyProvided || $isPrivateKeyPasswordsProvided) {
                throw new BadRequestException(__('Invalid request. New key or passwords are not required.'));
            }

            // save new disabled policy, disable previous key and delete backups if any
            return $this->disablePolicy($uac);
        }

        // if enabled => enabled
        // e.g it's policy change like mandatory => opt-in
        // and/or a possible key rotation
        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.'));
                }

View on GitHub (pinned to 31c1bbc10f)