passbolt/passbolt_api · error · ForbiddenException

This operation is not allowed. The current page does not…

Error message

This operation is not allowed. The current page does not match the total number of pages.

What it means

Thrown by TransfersUpdateService::assertTransitionAllowed when a mobile transfer is updated to status COMPLETE while the current_page field does not equal total_pages - 1. Passbolt requires every page of the transfer payload to be uploaded before the transfer can be marked complete, so completing early is rejected as a forbidden state transition.

Solutions

  1. Ensure the client uploads all pages and sets current_page = total_pages - 1 in the same request that sets status to complete
  2. Check the client's page counter is 0-based to match total_pages - 1
  3. If the payload shrank, start a new transfer with the correct total_pages instead of forcing complete on this one

Example fix

// before
await updateTransfer(id, { status: 'complete', current_page: 3 }); // total_pages: 5
// after
await updateTransfer(id, { status: 'complete', current_page: 4 }); // total_pages - 1
Defensive patterns

Strategy: validation

Validate before calling

const totalPages = transfer.totalPages;
const currentPage = transfer.currentPage;
if (status === 'complete' && currentPage !== totalPages - 1) {
  throw new Error(`Upload remaining pages: ${totalPages - 1 - currentPage} left`);
}
await updateTransfer(id, { status, current_page: currentPage });

Try / catch

try {
  await api.updateTransfer(id, { status: 'complete', current_page: lastPageIndex });
} catch (e) {
  if (e.code === 403 && /does not match the total number of pages/.test(e.message)) {
    // resume uploading remaining pages, then retry
  }
}

Prevention

When it happens

Trigger: Calling PUT/PUT JSON on /transfers/{id}.json with body {"status":"complete"} (via TransfersUpdateService::update) when current_page != total_pages - 1 — e.g. completing after uploading fewer pages than the transfer declared, or uploading the last page to the wrong page index.

Common situations: Mobile app bugs in page-index bookkeeping (0-based pages vs 1-based UI counters); client retrying the final page upload after an off-by-one; a transfer created with total_pages larger than the data actually chunked into.

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/35eeb6fa87f9f786. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/Mobile/src/Service/Transfers/TransfersUpdateService.php:144

                || $updated->status === Transfer::TRANSFER_STATUS_COMPLETE
                || $updated->status === Transfer::TRANSFER_STATUS_ERROR)
            &&
            ($original->status === Transfer::TRANSFER_STATUS_CANCEL
                || $original->status === Transfer::TRANSFER_STATUS_COMPLETE)
        ) {
            $msg = __('This operation is not allowed.') . ' ';
            $msg .= __('The operation is already over.');
            throw new ForbiddenException($msg);
        }

        // Cannot "complete" without being on last page
        if (
            $updated->status === Transfer::TRANSFER_STATUS_COMPLETE &&
            $updated->current_page !== $original->total_pages - 1
        ) {
            $msg = __('This operation is not allowed.') . ' ';
            $msg .= __('The current page does not match the total number of pages.');
            throw new ForbiddenException($msg);
        }
    }

    /**
     * Check if operation is allowed
     *
     * @param \Passbolt\Mobile\Model\Entity\Transfer $transfer entity
     * @param \App\Utility\UserAccessControl $uac user access control object
     * @throws \Cake\Http\Exception\ForbiddenException if operation is not allowed for example:
     * - Transfer or AuthToken is for another user
     * - Authentication token is expired
     * @return void
     */
    private function assertOperationIsAllowed(Transfer $transfer, UserAccessControl $uac): void
    {
        if ($transfer->user_id !== $uac->getId()) {
            throw new ForbiddenException(__('This operation is not allowed for this user.'));
        }

View on GitHub (pinned to 31c1bbc10f)