{"record":{"id":"fdac31d6992c8e70","repo":"actualbudget/actual","slug":"authorization-failed-you-can-close-this-window-an","errorCode":null,"errorMessage":"Authorization failed. You can close this window and try again.","messagePattern":"Authorization failed\\. You can close this window and try again\\.","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"packages/sync-server/src/app-enablebanking/app-enablebanking.ts","lineNumber":163,"sourceCode":"        '<script>setTimeout(function(){window.close()},1000)</script></body></html>',\n    );\n  } catch (error) {\n    const errorResult = {\n      error: error instanceof Error ? error.message : 'unknown error',\n    };\n\n    completedAuths.set(state, errorResult);\n    setTimeout(() => completedAuths.delete(state), COMPLETED_AUTH_TTL_MS);\n\n    const pending = pendingAuths.get(state);\n    if (pending) {\n      pending.reject(error);\n      cleanupPendingAuth(state);\n    }\n\n    debug('Callback auth error: %s', error);\n    res\n      .status(500)\n      .send(\n        '<html><body><p>Authorization failed. You can close this window and try again.</p></body></html>',\n      );\n  }\n});\n\napp.use(validateSessionMiddleware);\n\n// --- Poll/complete-auth coordination ---\n\ntype PendingAuth = {\n  id: string;\n  resolve: (value: unknown) => void;\n  reject: (reason: unknown) => void;\n  timer: ReturnType<typeof setTimeout>;\n};\n\n// NOTE: These in-memory maps make the auth handoff process-local.","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-enablebanking/app-enablebanking.ts#L145-L181","documentation":"During the Enable Banking callback, the pending authorization promise rejected (the token exchange or provider call threw). The handler catches it, cleans up the pending auth state, and returns HTTP 500 with this HTML page. It is a generic catch-all for anything failing after a well-formed callback (bad code, provider outage, expired/unknown state session).","triggerScenarios":"The deferred promise registered for this `state` rejects — e.g. token exchange with Enable Banking fails, the code is invalid or already used, the pending auth expired, or the bank API returns an error during session creation.","commonSituations":"Authorization codes being replayed after a refresh; Enable Banking API outages; expired pending auth because the user took too long at the bank's login page; TLS or network errors between the sync-server and Enable Banking.","solutions":["Check sync-server logs (debug output shows 'Callback auth error: %s') for the underlying cause","Restart the bank-linking flow to get a fresh code and state","Verify Enable Banking API credentials/app IDs are correct and the provider is reachable","Retry after a short delay if the provider was temporarily unavailable"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await linkBankViaEnableBanking();\n} catch (err) {\n  logger.error('enablebanking callback failed', err);\n  // prompt user to restart the bank-linking flow with a fresh state\n}","preventionTips":["Complete the bank authorization promptly to avoid expired pending auth","Monitor sync-server logs ('Callback auth error') for root causes","Validate Enable Banking credentials before linking","Treat this as retryable: always restart a fresh authorization rather than reusing codes"],"tags":["oauth","http-500","enablebanking","token-exchange","callback"],"backgroundTag":"oauth-callback-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}