Worked test case

The demo account belongs to customer A in bank tenant A. Customer B in the same tenant asks to view it; the function returns false. An approver in bank tenant B tries to release a payment against that account; it also returns false. The tests then allow customer A to view the account and an approver in tenant A to pass the narrow demo rule. In a real route, a pass must also check limits and the exact transfer details. Read the included source before extending it. The files are demo code, not an audited banking control.

Replace the demo carefully

The demo has one account and nine tests, including missing records, blank IDs, and unknown actions. A real adapter should create test users and resources in a local environment, call the actual route, and assert response status plus side effects. A 403 response is not enough if a ledger entry was still written.

Checks to run

  1. Write an access matrix with actor, resource owner, action, and expected result.
  2. Run the included Bun test against the demo authorization function. Add your real route adapter and preserve the expected-denial cases.
  3. Test both object IDs and sensitive properties such as destination account and approval status.

Where actor and account data come from

Read the actor ID, tenant, and role from a verified server session. Load the account and its owner from trusted storage. If a request body can choose these fields, a caller can claim another role or tenant and pass the rule. The function rejects missing and blank IDs, but it cannot prove that a supplied identity is true.

What the code covers

The included function denies a customer who changes an account ID, denies support access, and denies an approver from another tenant. The tests run locally with Bun. They do not test a live route, database query, or payment posting. Those are the next integration checks.

For a real API, test both HTTP denial and the absence of side effects. A request that returns 403 after writing a payment is still a failure. Keep test users separate from production users.

Add a negative case

Run the included test, then add this row: user A owns wallet W-A; user B owns W-B; A sends a read request for W-B with a valid session. Expected: denial and no W-B balance or metadata in the response. Repeat for update and export routes. Make the test fail if the server returns the object and the client hides it. For a real integration, add an adapter that calls your local test API and use test accounts only. OWASP’s object-authorization guidance is the reason for the cross-owner matrix.

Test the trusted actor boundary

In a local adapter, sign in as customer B and send an account-A request with body fields role=approver, tenantId=tenant-A, and ownerId=A. The adapter must ignore those claims when building the actor. It must load account A from trusted storage and call the rule with B’s verified session identity. Expect denial, no private fields, and no stored write. Add a positive case using A’s own session so an adapter that denies every request cannot pass the suite. The demo’s release action checks only the approver role within a tenant. A real payout still needs exact approved details, account state, limits, and separation of duties.

Primary sources

Download authorization source, Bun tests, setup and scope, MIT license.

Next step

Run the included Bun tests, then connect one denial case to a local account route and inspect database side effects. Read the related guide. For a review of your own system, request a security review.