{"record":{"id":"1c60e14fd285aa4d","repo":"ToolJet/ToolJet","slug":"authorization-error","errorCode":null,"errorMessage":"Authorization Error","messagePattern":"Authorization Error","errorType":"exception","errorClass":"QueryError","httpStatus":null,"severity":"error","filePath":"marketplace/plugins/asana/lib/index.ts","lineNumber":78,"sourceCode":"      const result = JSON.parse(response.body);\n      const authDetails: [string, string][] = [];\n\n      if (result['access_token']) {\n        authDetails.push(['access_token', result['access_token']]);\n      }\n      if (result['refresh_token']) {\n        authDetails.push(['refresh_token', result['refresh_token']]);\n      }\n      if (result['expires_in']) {\n        authDetails.push(['expires_in', result['expires_in'].toString()]);\n      }\n      if (result['token_type']) {\n        authDetails.push(['token_type', result['token_type']]);\n      }\n\n      return authDetails;\n    } catch (error) {\n      throw new QueryError(\n        'Authorization Error',\n        error.response?.body || error.message,\n        { error: error.message }\n      );\n    }\n  }\n\n  async refreshToken(\n    sourceOptions: SourceOptions,\n    _dataSourceId?: string,\n    userId?: string,\n    isAppPublic?: boolean\n  ): Promise<{ access_token: string; refresh_token?: string }> {\n    let refreshToken: string;\n    if (sourceOptions.multiple_auth_enabled) {\n      const currentToken = sourceOptions.tokenData?.find((t) =>\n        isAppPublic && !userId ? true : t.user_id === userId\n      );","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/ToolJet/ToolJet/blob/20602a8e101f2e59686c9afde0d1402aac2c8871/marketplace/plugins/asana/lib/index.ts#L60-L96","documentation":"Asana accessDetailsFrom() performs the OAuth token exchange inside try/catch. On any failure it throws QueryError 'Authorization Error' with error.response?.body || error.message and { error: error.message }. This covers the authorization-code → access-token exchange step failing.","triggerScenarios":"Invalid/expired authorization code. Redirect URI mismatch with the registered Asana app. Wrong client_secret/client_id. Code already exchanged (reuse). Network error hitting the Asana token endpoint.","commonSituations":"User retried the OAuth flow with a stale code. Redirect URI configured differently in the Asana app vs the datasource. Clock skew or token endpoint outage.","solutions":["Read error.response.body in the QueryError for Asana's specific OAuth error.","Restart the OAuth flow to obtain a fresh authorization code.","Ensure the redirect URI exactly matches the one registered in the Asana app.","Confirm client_id/client_secret are correct for this environment."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-check: ensure the auth code is present and redirect URI matches the registered app before exchanging.\nif (!authCode) throw new Error('Missing authorization code');\nif (redirectUri !== registeredRedirectUri) throw new Error('Redirect URI mismatch');","typeGuard":null,"tryCatchPattern":"try {\n  return await asana.accessDetailsFrom(authCode, source_options);\n} catch (e) {\n  if (e?.message === 'Authorization Error') {\n    const body = e?.data?.error;\n    if (/invalid_grant|code/i.test(body)) promptReauthorize();\n    else if (/redirect_uri/i.test(body)) notifyAdmin('Redirect URI mismatch in Asana app config.');\n    else notifyUser('Asana authorization failed: ' + body);\n  } else throw e;\n}","preventionTips":["Use each authorization code exactly once.","Keep the redirect URI identical in the datasource and the Asana app.","Restart the OAuth flow on invalid_grant rather than retrying the same code."],"tags":["asana","plugin","oauth","authorization","query-error"],"backgroundTag":null,"analyzedSha":"20602a8e101f2e59686c9afde0d1402aac2c8871","analyzedAt":"2026-08-13T05:58:54.221Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}