{"record":{"id":"86851725bb5676e4","repo":"pear-devs/pear-desktop","slug":"forbidden","errorCode":null,"errorMessage":"Forbidden","messagePattern":"Forbidden","errorType":"http","errorClass":null,"httpStatus":403,"severity":"info","filePath":"src/plugins/api-server/backend/routes/auth.ts","lineNumber":70,"sourceCode":"    if (config.authorizedClients.includes(id)) {\n      // SKIP CHECK\n    } else if (config.authStrategy === AuthStrategy.AUTH_AT_FIRST) {\n      const result = await dialog.showMessageBox({\n        title: t('plugins.api-server.dialog.request.title'),\n        message: t('plugins.api-server.dialog.request.message', {\n          origin: getConnInfo(ctx).remote.address,\n          ID: id,\n        }),\n        buttons: [\n          t('plugins.api-server.dialog.request.buttons.allow'),\n          t('plugins.api-server.dialog.request.buttons.deny'),\n        ],\n        defaultId: 1,\n        cancelId: 1,\n      });\n\n      if (result.response === 1) {\n        ctx.status(403);\n        return ctx.body(null);\n      }\n    } else if (config.authStrategy === AuthStrategy.NONE) {\n      // SKIP CHECK\n    }\n\n    if (!config.authorizedClients.includes(id)) {\n      setConfig({\n        authorizedClients: [...config.authorizedClients, id],\n      });\n    }\n\n    const token = await sign(\n      {\n        id,\n        iat: ~~(Date.now() / 1000),\n      } satisfies JWTPayload,\n      config.secret,","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/pear-devs/pear-desktop/blob/1e2aac5706c870c93ed74ba8727ae8390d3b0fa5/src/plugins/api-server/backend/routes/auth.ts#L52-L88","documentation":"'Forbidden' is the HTTP 403 body returned by the auth route's register() handler when the user declines the authorization dialog shown for a new client. The handler shows a dialog (with buttons and defaultId/cancelId of 1); if result.response === 1 (the cancel/decline button), it sets status 403 and returns a null body. This is the deliberate user-rejection path during client pairing, not a bug.","triggerScenarios":"A client attempts to register/pair via the auth route while authStrategy is not NONE, and the user clicks the 'Cancel'/'Deny' button in the popped-up dialog.","commonSituations":"User accidentally dismissing the pairing prompt, pairing attempt from an unrecognized machine the user intentionally rejects, or an automation script triggering the dialog while the user is away so it times out to the cancel action.","solutions":["Retry registration from the client and click 'Allow' in the dialog this time.","If the client is trusted, pre-add its id to authorizedClients in the config so registration isn't needed.","If running headless/automated, switch config.authStrategy to AuthStrategy.NONE or pre-authorize clients to avoid interactive prompts."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":"const isForbidden = (res: Response): boolean => res.status === 403;","tryCatchPattern":"const res = await fetch(registerUrl, opts);\nif (res.status === 403) {\n  // user declined the pairing dialog — inform user; only retry on explicit consent\n  throw new Error('Pairing declined by user');\n}","preventionTips":["Don't auto-retry registration on 403 — it pops another dialog and annoys the user.","Pre-authorize known client ids in authorizedClients for unattended setups.","Use AuthStrategy.NONE only on trusted local networks."],"tags":["api-server","http-403","authorization","pairing","user-consent"],"backgroundTag":"http-403-forbidden","analyzedSha":"1e2aac5706c870c93ed74ba8727ae8390d3b0fa5","analyzedAt":"2026-08-27T20:01:08.614Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}