payroll/README.md
Padreug 36e1643470 docs: correct how the employee payslip surface is gated
The README and operations notes claimed the /my/* endpoints "stay reachable
regardless of LNBITS_ADMIN_EXTENSIONS" because "the API routes are mounted
instance-wide". Both halves are wrong, and the smoke test on bohm caught it:
an invoice-key request from employee_1 came back with

    {"detail": "Extension 'payroll' not enabled."}

LNbits gates every extension path per user via check_user_extension_access
(lnbits/decorators.py:420), not just the UI listing. Two consequences the
docs now state instead of contradicting:

- An employee needs `payroll` among their active extensions before an
  invoice key gets them anywhere. Enable it through
  LNBITS_USER_DEFAULT_EXTENSIONS or per account in the Admin UI.
- `payroll` must stay OUT of LNBITS_ADMIN_EXTENSIONS. That list trips the
  earlier branch of the same check — "User not authorized for extension" —
  which a non-admin can never clear, so it would permanently lock employees
  out of their own payslips. It also buys nothing: check_super_user on the
  router and user.super_user on the page route are what actually restrict
  the console, and a non-admin key was verified to get 401 from
  /contracts and /users.

Also notes the wart this exposes: an employee with the extension enabled
sees "Payroll" in their menu and gets a 403 page, because the only page
route is the operator console. Filed as a follow-up rather than fixed here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jy52j9GRZ6XKa1Zt21LLj
2026-08-31 16:16:19 +02:00

2.5 KiB

Payroll

Scheduled recurring payouts between LNbits wallets, driven by the instance super user.

Pick a user from the account directory, point the payout at one of their wallets, set an amount + currency, a start date, how often it pays and how many times — Payroll then moves the money on schedule, from a source wallet you control, and keeps a ledger of every attempt.

Status

Early. Built for aiolabs LNbits instances; see docs/ for the data model and operational notes.

Who can use it

Everything under /payroll/api/v1/ that reads the account directory or touches a contract is gated on check_super_user — payroll moves money between accounts the operator does not own, so wallet-level admin keys are deliberately not enough. (require_admin_key authorises writes to your own wallet only; it is not instance-admin. See the auth table in the workspace CLAUDE.md.)

Add payroll to LNBITS_ADMIN_EXTENSIONS so the extension is hidden from ordinary users in the UI as well.

Do not add payroll to LNBITS_ADMIN_EXTENSIONS — see below.

The employee-facing surface — /api/v1/my/payouts, /my/payouts.csv, /my/contracts — is gated on a wallet invoice key instead. Whoever holds the read key for a wallet can see what payroll paid into that wallet, and nothing else.

Employees must have the extension enabled

LNbits gates every extension route per user, not just the UI (check_user_extension_access, lnbits/decorators.py:420). An employee who does not have payroll among their active extensions gets

{"detail": "Extension 'payroll' not enabled."}

from /my/payouts, invoice key or not. So enable payroll for anyone who should read their own payslips — via LNBITS_USER_DEFAULT_EXTENSIONS, or per account in the Admin UI.

That is also why payroll must stay out of LNBITS_ADMIN_EXTENSIONS. That list triggers the harder branch of the same check — "User not authorized for extension" — which a non-admin can never clear, so it would make the employee surface permanently unreachable. Restricting the operator console does not depend on it anyway: every super-user route is gated in code by check_super_user, and the page route checks user.super_user.

Development

make          # black + ruff + mypy
make test     # pytest

The dev LNbits compose mounts ~/dev/shared/extensions/ at /shared with LNBITS_EXTENSIONS_PATH=/shared, so this checkout is the installed extension. FakeWallet is enough — no regtest needed for anything except end-to-end Lightning behaviour.

Licence

MIT