{"record":{"id":"de079737f8420a65","repo":"hcengineering/platform","slug":"user-already-authorized","errorCode":null,"errorMessage":"User already authorized","messagePattern":"User already authorized","errorType":"http","errorClass":"ApiError","httpStatus":409,"severity":"warning","filePath":"services/telegram-bot/pod-telegram-bot/src/server.ts","lineNumber":114,"sourceCode":"  )\n\n  app.post(\n    '/auth',\n    wrapRequest(async (req, res, token) => {\n      if (req.body == null || typeof req.body !== 'object') {\n        throw new ApiError(400)\n      }\n\n      const { code } = req.body\n\n      if (code == null || code === '' || typeof code !== 'string') {\n        throw new ApiError(400)\n      }\n\n      const integration = await getAnyIntegrationByAccount(token.account)\n\n      if (integration !== undefined) {\n        throw new ApiError(409, 'User already authorized')\n      }\n\n      const person = await getAccountPerson(token.account)\n      if (person === undefined) {\n        throw new ApiError(404, 'Person not found')\n      }\n\n      const newRecord = await worker.authorizeUser(code, token.account, token.workspace)\n      if (newRecord === undefined) {\n        throw new ApiError(500)\n      }\n\n      void worker.limiter.add(newRecord.telegramId, async () => {\n        ctx.info('Connected account', { account: token.account, username: newRecord.username })\n        const message = await translate(telegram.string.AccountConnectedHtml, {\n          app: config.App,\n          name: `${person.firstName} ${person.lastName}`\n        })","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/telegram-bot/pod-telegram-bot/src/server.ts#L96-L132","documentation":"ApiError(409, 'User already authorized') is thrown by the telegram-bot token-exchange endpoint when getAnyIntegrationByAccount(token.account) returns an existing integration. It means this account has already completed the Telegram authorization flow, so creating a second integration for the same account would duplicate it; the server rejects the request with HTTP 409 Conflict.","triggerScenarios":"A user who previously linked their Telegram account re-submits an OAuth code to the exchange endpoint; getAnyIntegrationByAccount finds an integration for token.account and the handler throws before calling worker.authorizeUser.","commonSituations":"Double-clicking the 'Connect Telegram' button, retrying a flow that actually succeeded on the first attempt (client never saw the success response), or re-running an onboarding script against an already-connected account.","solutions":["Check the account's existing integration first and treat 409 as success / show 'already connected' in the UI instead of an error.","Before re-authorizing, remove or disconnect the existing integration for the account, then retry the flow.","Guard the client flow with a state flag so the exchange request fires only once per authorization attempt.","Make the endpoint idempotent: if an integration already exists for the account, return 200 with the existing record instead of throwing."],"exampleFix":"// before\nif (integration !== undefined) {\n  throw new ApiError(409, 'User already authorized')\n}\n// after\nif (integration !== undefined) {\n  return { status: 200, result: integration } // idempotent: already connected\n}","handlingStrategy":"try-catch","validationCode":"const existing = await getAnyIntegrationByAccount(account)\nif (existing !== undefined) {\n  return existing // already authorized; skip the exchange call entirely\n}","typeGuard":"function isAlreadyAuthorized(err: unknown): err is ApiError {\n  return err instanceof ApiError && (err as ApiError).message === 'User already authorized'\n}","tryCatchPattern":"try {\n  await exchangeToken(token, code)\n} catch (err) {\n  if (err instanceof ApiError && err.message === 'User already authorized') {\n    showAlreadyConnected(); return\n  }\n  throw err\n}","preventionTips":["Check for an existing integration before starting the authorization flow.","Disable the connect button once the account is linked.","Treat 409 from the endpoint as a success condition in retry logic."],"tags":["conflict","http-409","oauth","duplicate-record"],"backgroundTag":"duplicate-authorization-conflict","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}