{"record":{"id":"39df165af5d19203","repo":"actualbudget/actual","slug":"forbidden-39df16","errorCode":"forbidden","errorMessage":"forbidden: permission-not-found","messagePattern":"forbidden: permission-not-found","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"packages/sync-server/src/app-openid.ts","lineNumber":31,"sourceCode":"\nconst app = express();\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\napp.use(requestLoggerMiddleware);\n\nconst openIdConfigRateLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5,\n  legacyHeaders: false,\n  standardHeaders: true,\n  message: { status: 'error', reason: 'too-many-requests' },\n});\n\nexport { app as handlers, openIdConfigRateLimiter };\n\napp.post('/enable', validateSessionMiddleware, async (req, res) => {\n  if (!isAdmin(res.locals.user_id)) {\n    res.status(403).send({\n      status: 'error',\n      reason: 'forbidden',\n      details: 'permission-not-found',\n    });\n    return;\n  }\n\n  const { error } = (await enableOpenID(req.body)) || {};\n\n  if (error) {\n    res.status(500).send({ status: 'error', reason: error });\n    return;\n  }\n  res.send({ status: 'ok' });\n});\n\napp.post('/disable', validateSessionMiddleware, async (req, res) => {\n  if (!isAdmin(res.locals.user_id)) {","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-openid.ts#L13-L49","documentation":"The POST /openid (OpenID enable) endpoint is restricted to server administrators. If the authenticated session's user_id is not the owner/admin, the endpoint responds 403 with reason 'forbidden' and details 'permission-not-found'. This mirrors the permission model of other admin-only sync-server endpoints.","triggerScenarios":"A logged-in non-admin user (any user_id other than the owner) calls POST /openid on the sync-server with a valid session.","commonSituations":"Multi-user sync-server setups where a non-owner tries to enable OpenID authentication; automated scripts authenticating as a regular user instead of the owner; calling the endpoint from a client whose session cookie belongs to the wrong account.","solutions":["Authenticate as the owner (first/bootstrapped) user of the sync-server","Log in with the owner's credentials to obtain an admin session before calling POST /openid","If you are the owner, verify the session user_id resolves as admin (it is the first user / role 'admin')","Avoid exposing admin endpoints to non-admin clients"],"exampleFix":"null","handlingStrategy":"validation","validationCode":"// ensure the session belongs to the owner before calling\nconst session = await getOwnerSession(baseUrl, ownerPassword);\n// then use session cookie for POST /openid","typeGuard":"null","tryCatchPattern":"if (res.status === 403) {\n  throw new Error('admin (owner) session required to manage OpenID');\n}","preventionTips":["Use the owner account for server administration endpoints","Don't expose admin endpoints to regular-user clients","Check which user your session cookie belongs to before admin calls","In multi-user setups, document that only the first/owner user can change auth config"],"tags":["authorization","http-403","openid","admin-only","permissions"],"backgroundTag":"insufficient-permissions","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}