actualbudget/actual · error

error

Error message

error

What it means

When loginWithOpenIdSetup() returns an error, the /login endpoint responds 400 with { status: 'error', reason: error }. This covers OpenID Connect setup failures — provider discovery failing, missing/misconfigured issuer or client credentials, or the setup module rejecting request parameters. The raw setup error string is passed through as the reason.

Solutions

  1. Read the 'reason' field of the 400 response — it carries the concrete OpenID error
  2. Verify OPENID_ISSUER, OPENID_CLIENT_ID, OPENID_CLIENT_SECRET (and discovery URL) are correct and reachable from the server
  3. Test the discovery document manually (curl <issuer>/.well-known/openid-configuration) from the server host
  4. Match OPENID_AUTH_METHOD to what the IdP supports and supply the password if required
  5. After fixing config, restart the sync-server so the OpenID client re-initializes

Example fix

// before
const { error, url } = await loginWithOpenIdSetup(req.body.returnUrl, req.body.password);
// error: 'failed to discover issuer'
// after
// OPENID_ISSUER=https://idp.example.com/realms/main (corrected, reachable)
const { error, url } = await loginWithOpenIdSetup(req.body.returnUrl, req.body.password);
if (!error) res.send({ status: 'ok', data: { returnUrl: url } });
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: IdP discovery reachable from server
const cfg = await fetch(`${OPENID_ISSUER}/.well-known/openid-configuration`);
if (!cfg.ok) throw new Error('OpenID discovery unreachable');

Type guard

function isOpenIdSetupError(res, body) {
  return res.status === 400 && body?.status === 'error' && typeof body?.reason === 'string';
}

Try / catch

const res = await request('/login', { method: 'POST', body: { loginMethod: 'openid', returnUrl, password } });
if (res.status === 400) {
  const body = await res.json();
  if (body?.status === 'error') {
    showLoginError(body.reason); // e.g. discovery failed / bad client credentials
    return;
  }
}

Prevention

When it happens

Trigger: POST to /login with loginMethod 'openid' and a valid returnUrl, but loginWithOpenIdSetup fails: unreachable discovery document, invalid client_id/client_secret, unsupported auth method, or bad openid configuration values.

Common situations: Wrong OPENID_ISSUER or OPENID_CLIENT_ID/SECRET env vars; IdP discovery endpoint unreachable from the server (firewall, DNS); IdP metadata lacks required endpoints; auth method unsupported by the provider; password omitted or wrong when OPENID_AUTH_METHOD requires it.

Related errors


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

Appendix: source

Thrown at packages/sync-server/src/app-account.js:110

          return;
        }
      }
      break;
    }
    case 'openid': {
      if (!isValidRedirectUrl(req.body.returnUrl)) {
        res
          .status(400)
          .send({ status: 'error', reason: 'Invalid redirect URL' });
        return;
      }

      const { error, url } = await loginWithOpenIdSetup(
        req.body.returnUrl,
        req.body.password,
      );
      if (error) {
        res.status(400).send({ status: 'error', reason: error });
        return;
      }
      res.send({ status: 'ok', data: { returnUrl: url } });
      return;
    }

    default:
      tokenRes = await loginWithPassword(req.body.password);
      break;
  }
  const { error, token } = tokenRes;

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

  res.send({ status: 'ok', data: { token } });

View on GitHub (pinned to d4334cb6e6)