actualbudget/actual · error

forbidden

forbidden

Error message

forbidden: permission-not-found

What it means

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.

Source

Thrown at packages/sync-server/src/app-openid.ts:31

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(requestLoggerMiddleware);

const openIdConfigRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  legacyHeaders: false,
  standardHeaders: true,
  message: { status: 'error', reason: 'too-many-requests' },
});

export { app as handlers, openIdConfigRateLimiter };

app.post('/enable', validateSessionMiddleware, async (req, res) => {
  if (!isAdmin(res.locals.user_id)) {
    res.status(403).send({
      status: 'error',
      reason: 'forbidden',
      details: 'permission-not-found',
    });
    return;
  }

  const { error } = (await enableOpenID(req.body)) || {};

  if (error) {
    res.status(500).send({ status: 'error', reason: error });
    return;
  }
  res.send({ status: 'ok' });
});

app.post('/disable', validateSessionMiddleware, async (req, res) => {
  if (!isAdmin(res.locals.user_id)) {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Authenticate as the owner (first/bootstrapped) user of the sync-server
  2. Log in with the owner's credentials to obtain an admin session before calling POST /openid
  3. If you are the owner, verify the session user_id resolves as admin (it is the first user / role 'admin')
  4. Avoid exposing admin endpoints to non-admin clients

Example fix

null
Defensive patterns

Strategy: validation

Validate before calling

// ensure the session belongs to the owner before calling
const session = await getOwnerSession(baseUrl, ownerPassword);
// then use session cookie for POST /openid

Type guard

null

Try / catch

if (res.status === 403) {
  throw new Error('admin (owner) session required to manage OpenID');
}

Prevention

When it happens

Trigger: A logged-in non-admin user (any user_id other than the owner) calls POST /openid on the sync-server with a valid session.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/39df165af5d19203. Report an issue: GitHub.