{"record":{"id":"b5d03e50d534e456","repo":"passbolt/passbolt_api","slug":"invalid-verify-token-expiry","errorCode":null,"errorMessage":"Invalid verify token expiry.","messagePattern":"Invalid verify token expiry\\.","errorType":"http","errorClass":"InvalidVerifyTokenException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltCe/JwtAuthentication/src/Service/VerifyToken/VerifyTokenValidationService.php","lineNumber":67,"sourceCode":"\n    /**\n     * Assert that the token expiry is valid and not set too far in the future.\n     *\n     * @param mixed $verifyTokenExpiry unix timestamp\n     * @return void\n     * @throws \\Passbolt\\JwtAuthentication\\Error\\Exception\\VerifyToken\\InvalidVerifyTokenException if the token is expired.\n     */\n    protected function validateTokenExpiry(mixed $verifyTokenExpiry): void\n    {\n        $maxTokenExpiry = DateTime::now()\n            ->modify('+' . Configure::read(self::VERIFY_TOKEN_EXPIRY_CONFIG_KEY))\n            ->toUnixString();\n        if (\n            !isset($verifyTokenExpiry) ||\n            !is_numeric($verifyTokenExpiry) ||\n            $verifyTokenExpiry > $maxTokenExpiry\n        ) {\n            throw new InvalidVerifyTokenException(__('Invalid verify token expiry.'));\n        }\n        if ($verifyTokenExpiry < time()) {\n            throw new ExpiredVerifyTokenAccessException(\n                __('Attempt to access an expired verify token.')\n            );\n        }\n    }\n\n    /**\n     * Assert verify token is a UUID\n     *\n     * @param mixed $verifyToken token\n     * @return void\n     * @throws \\Passbolt\\JwtAuthentication\\Error\\Exception\\VerifyToken\\InvalidVerifyTokenException if the format is not valid.\n     * @throws \\Cake\\ORM\\Exception\\PersistenceFailedException\n     */\n    protected function validateFormat(mixed $verifyToken): void\n    {","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/JwtAuthentication/src/Service/VerifyToken/VerifyTokenValidationService.php#L49-L85","documentation":"validateTokenExpiry throws InvalidVerifyTokenException when a verify token's expiry value is missing, non-numeric, or greater than the maximum allowed expiry (maxTokenExpiry). The verify token (used e.g. for account recovery/verify flows) must carry a numeric expiry within the permitted window, otherwise it is considered malformed/tampered.","triggerScenarios":"validateToken receiving a verify token whose stored/serialized expiry is absent, a non-numeric string, or a timestamp beyond the configured maximum lifetime — e.g. a client self-issuing a verify token with an arbitrarily long expiry.","commonSituations":"Tampered or hand-crafted verify token payloads; version drift where an older client wrote a different expiry format; server-side lowering of the max expiry after tokens were issued; corrupted token storage.","solutions":["Re-issue the verify token through the proper server endpoint so the expiry is generated within the allowed window.","Check the token payload contains a numeric expiry field (unix seconds) not exceeding maxTokenExpiry.","Align client and server versions so expiry format matches what ValidateTokenService expects.","If max expiry config was tightened, force users to restart the verify flow rather than reusing old tokens."],"exampleFix":"// before\nconst verifyToken = {user_id: uid, token: randomToken(), expiry: 'in two weeks'}; // non-numeric, unbounded\n// after\nconst verifyToken = {user_id: uid, token: randomToken(), expiry: Math.floor(Date.now()/1000) + 10*60}; // numeric, within max","handlingStrategy":"validation","validationCode":"const expiry = verifyToken.expiry;\nconst maxExpiry = maxTokenExpiry;\nif (typeof expiry !== 'number' || !Number.isFinite(expiry) || expiry > maxExpiry) throw new Error('verify token expiry must be a numeric timestamp within the allowed window');","typeGuard":"function hasValidExpiryShape(tok: unknown): tok is {expiry: number} {\n  return typeof tok === 'object' && tok !== null && typeof (tok as any).expiry === 'number' && Number.isFinite((tok as any).expiry);\n}","tryCatchPattern":"try {\n  $validationService->validateToken($verifyToken);\n} catch (InvalidVerifyTokenException $e) {\n  throw new BadRequestException('Malformed verify token: regenerate it via the verify endpoint', 400, $e);\n}","preventionTips":["Always let the server generate verify tokens; never hand-craft expiry values","Keep client and server versions aligned on token payload format","If max expiry config is tightened, invalidate outstanding tokens and restart flows"],"tags":["verify-token","validation","jwt","security"],"backgroundTag":"invalid-config-value","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}