{"record":{"id":"943e75591f170635","repo":"passbolt/passbolt_api","slug":"an-openpgp-key-must-be-provided","errorCode":null,"errorMessage":"An OpenPGP key must be provided.","messagePattern":"An OpenPGP key must be provided\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Service/Setup/AbstractCompleteService.php","lineNumber":145,"sourceCode":"        $token->setDirty('active', false);\n    }\n\n    /**\n     * Return the gpg key entity for matching the requesting id\n     *\n     * @param string $userId the user uuid\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the gpg key is not provided or not a valid OpenPGP key\n     * @throws \\App\\Error\\Exception\\CustomValidationException if armored key content cannot be validated\n     * @throws \\App\\Error\\Exception\\ValidationException if key cannot be validated against model rules\n     * @return \\App\\Model\\Entity\\Gpgkey entity\n     */\n    protected function getAndAssertGpgkey(string $userId): Gpgkey\n    {\n        $data = $this->request->getData();\n        $armoredKey = $data['gpgkey']['armored_key'] ?? null;\n\n        if (empty($armoredKey) || !is_string($armoredKey)) {\n            throw new BadRequestException(__('An OpenPGP key must be provided.'));\n        }\n\n        try {\n            return $this->Gpgkeys->buildEntityFromArmoredKey($armoredKey, $userId);\n        } catch (ValidationException $exception) {\n            // Remap errors to match sent data\n            throw new CustomValidationException($exception->getMessage(), ['gpgkey' => $exception->getErrors()]);\n        }\n    }\n}\n","sourceCodeStart":127,"sourceCodeEnd":156,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Service/Setup/AbstractCompleteService.php#L127-L156","documentation":"Thrown by getAndAssertGpgkey when the request payload has no usable OpenPGP armored key at data['gpgkey']['armored_key']. Completing setup requires publishing the user's public key so the server can encrypt secrets to them; without it setup cannot finish.","triggerScenarios":"POST /setup/complete/{userId}/{tokenId} with a body missing the gpgkey.armored_key field, sending null, or sending a non-string (e.g. a parsed object); sending an empty string.","commonSituations":"Client forgot to include the key in the JSON body; key serialized under a wrong field name; content-type issue so the body is not parsed; front-end sends an object instead of the armored string.","solutions":["Include a valid armored public key in the request body: {\"gpgkey\": {\"armored_key\": \"-----BEGIN PGP PUBLIC KEY BLOCK-----...\"}}","Check the request Content-Type is application/json and the key is a plain string, not nested differently","Generate/export the user key first (e.g. via openpgp) before calling the complete endpoint","Verify the client library version still matches the API payload contract"],"exampleFix":"// before\nawait fetch(url, {method:'POST', body: JSON.stringify({authenticationtoken: {token}})});\n// after\nawait fetch(url, {method:'POST', body: JSON.stringify({authenticationtoken: {token}, gpgkey: {armored_key: publicKeyArmor}})});","handlingStrategy":"validation","validationCode":"const armored = body?.gpgkey?.armored_key;\nif (typeof armored !== 'string' || !armored.startsWith('-----BEGIN PGP PUBLIC KEY BLOCK-----')) throw new Error('armored key required');","typeGuard":"function hasArmoredKey(d): d is {gpgkey:{armored_key:string}} { return typeof d?.gpgkey?.armored_key === 'string' && d.gpgkey.armored_key.length > 0; }","tryCatchPattern":"try { await completeSetup(userId, token, payload); }\ncatch (e) { if (isBadRequestMissingGpgkey(e)) { /* prompt user to attach key */ } }","preventionTips":["Always build the payload with gpgkey.armored_key before calling setup/complete","Send Content-Type: application/json","Export the public key as a plain string, not a parsed object","Validate the armor block client-side before the request"],"tags":["setup","openpgp","missing-payload-field"],"backgroundTag":"missing-required-argument","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}