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

User account recovery settings cannot be edited.

Error message

User account recovery settings cannot be edited.

What it means

Thrown in AccountRecoveryUserSettingsSetService::set() when the user's existing account recovery setting is already 'approved'. Policy allows a user to enroll only once: re-enrollment, de-enrollment or any edit of an approved setting is rejected with a BadRequestException.

Solutions

  1. Fetch current settings first and skip the save if already approved
  2. Return the existing setting idempotently for repeated approved submissions
  3. For a genuine policy change (de-enroll/re-enroll), an administrator must reset the setting per organization policy
  4. Fix client-side double submission (disable submit, dedupe retries)

Example fix

// before
$service->set($data); // throws if already approved
// after
$current = (new AccountRecoveryUserSettingsGetService())->get($userId);
if (!$current || !$current->isApproved()) {
    $service->set($data);
}
Defensive patterns

Strategy: validation

Validate before calling

const current = await getUserSetting(userId); if (current && current.status === 'approved') return current; // skip save

Type guard

const canEdit = (s) => s == null || s.status !== 'approved';

Try / catch

try { await setSettings(data); } catch (e) { if (e.status === 400 && /cannot be edited/.test(e.message)) { /* reload and show current state */ } }

Prevention

When it happens

Trigger: PATCH/POST to the account recovery user settings endpoint with status=approved when the current setting is already approved; replaying an enrollment request; a client re-submitting settings after a successful enroll.

Common situations: Double-submission of the enrollment form (network retry, double click); clients not fetching current settings before saving; tests reusing a fixture user that is already enrolled.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/a88f3913687a71a1. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryUserSettings/AccountRecoveryUserSettingsSetService.php:96

            ->fetchTable('Passbolt/AccountRecovery.AccountRecoveryUserSettings');
        $this->AccountRecoveryPrivateKeys = $this
            ->fetchTable('Passbolt/AccountRecovery.AccountRecoveryPrivateKeys');
        $this->AccountRecoveryPrivateKeyPasswords = $this
            ->fetchTable('Passbolt/AccountRecovery.AccountRecoveryPrivateKeyPasswords');
        $this->uac = $uac;
    }

    /**
     * @param array $data Payload
     * @return \Passbolt\AccountRecovery\Model\Entity\AccountRecoveryUserSetting
     */
    public function set(array $data): AccountRecoveryUserSetting
    {
        // Ensure user can only enroll once
        // It's not possible for a user to enroll and de-enroll or enroll and re-enroll
        $currentSettings = (new AccountRecoveryUserSettingsGetService())->get($this->uac->getId());
        if (isset($currentSettings) && $currentSettings->isApproved()) {
            throw new BadRequestException(__('User account recovery settings cannot be edited.'));
        }

        $setting = $this->patchEntity($data);
        $this->AccountRecoveryUserSettings->saveOrFail($setting);

        return $setting;
    }

    /**
     * @param array $data Payload
     * @return \Passbolt\AccountRecovery\Model\Entity\AccountRecoveryUserSetting
     */
    public function patchEntity(array $data): AccountRecoveryUserSetting
    {
        $this->data = $data;
        $this->organizationPolicy = (new AccountRecoveryOrganizationPolicyGetService())->getOrFail();
        $status = $data['status'] ?? '';
        $setting = $this->validateAccountRecoveryUserSetting($status);

View on GitHub (pinned to 31c1bbc10f)