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

The user OpenPGP key does not exist, or is invalid, or has…

Error message

The user OpenPGP key does not exist, or is invalid, or has been deleted.

What it means

assertUserData checks the loaded user has an associated gpgkey with a valid fingerprint and armored_key. BadRequestException (400) is thrown when the user record exists but their OpenPGP key is missing, malformed, or the fingerprint fails PublicKeyValidationService validation.

Solutions

  1. Have the user upload a valid OpenPGP key via the passbolt UI or the /users/<id>/gpgkey endpoint
  2. Verify the gpgkeys table row exists for that user_id with non-empty armored_key and fingerprint
  3. Re-import the user key (e.g. using passbolt CLI or send the key again during account setup)
  4. Check the stored fingerprint is a valid 40-char hex fingerprint; fix via key re-upload

Example fix

// before: user created without key
$users->save(new User(['username' => 'x', ...])); // no gpgkey
// after
$user = $users->register(...);
$gpgkeys->importForUser($user, $armoredKey);
Defensive patterns

Strategy: validation

Validate before calling

if (!user.gpgkey || !user.gpgkey.armored_key || !/^[0-9A-F]{40}$/.test(user.gpgkey.fingerprint || '')) throw new Error('user has no valid OpenPGP key');

Type guard

function hasValidGpgKey(u) { return Boolean(u && u.gpgkey && typeof u.gpgkey.armored_key === 'string' && typeof u.gpgkey.fingerprint === 'string' && /^[0-9A-F]{40}$/.test(u.gpgkey.fingerprint)); }

Try / catch

try { await login(); } catch (e) { if (/OpenPGP key does not exist/.test(e.message)) { await promptUserToUploadKey(); } }

Prevention

When it happens

Trigger: POST /auth/jwt/login for a user whose gpgkeys row is absent (key never uploaded or deleted), whose armored_key/fingerprint columns are not strings, or whose stored fingerprint is invalid (wrong length/charset).

Common situations: User account created via API/CLI without uploading a GPG key; key deleted from profile during key rotation; database restore that lost gpgkeys rows; corrupted armored key from a bad import.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

Thrown at plugins/PassboltCe/JwtAuthentication/src/Authenticator/GpgJwtAuthenticator.php:408

    }

    /**
     * @param mixed $userData data
     * @throws \Cake\Http\Exception\BadRequestException
     * @return void
     */
    public function assertUserData(mixed $userData): void
    {
        if (
            !isset($userData->gpgkey) ||
            !isset($userData->gpgkey->fingerprint) ||
            !isset($userData->gpgkey->armored_key) ||
            !is_string($userData->gpgkey->fingerprint) ||
            !PublicKeyValidationService::isValidFingerprint($userData->gpgkey->fingerprint) ||
            !is_string($userData->gpgkey->armored_key)
        ) {
            $msg = __('The user OpenPGP key does not exist, or is invalid, or has been deleted.');
            throw new BadRequestException($msg);
        }
    }

    /**
     * @param mixed $armoredChallenge challenge
     * @throws \InvalidArgumentException if armored challenge is invalid
     * @return void
     */
    public function assertArmoredChallenge(mixed $armoredChallenge): void
    {
        $this->assertGpgMessageIsValid($this->gpg, $armoredChallenge, __('The user challenge is missing or invalid.'));
    }

    /**
     * @param mixed $version version
     * @throws \Exception if version is not supported
     * @return void
     */

View on GitHub (pinned to 31c1bbc10f)