Check permissions
Three flavors of check, one per layer: middleware gates routes, booleans branch logic, throwers guard services.
Route middleware
Section titled “Route middleware”import { gate, gateAny, gateAll } from "@warlock.js/access";
router.post("/orders", createOrder, { middleware: [authMiddleware([]), gate("orders.create")],});
router.get("/reports", viewReports, { middleware: [authMiddleware([]), gateAny(["reports.view", "reports.admin"])],});Always stack these after authMiddleware — they read request.user. A denied check returns 403 before the controller runs.
Booleans
Section titled “Booleans”import { can, cannot, canAll, canAny } from "@warlock.js/access";
if (await can(user, "orders.update")) { /* … */ }
await canAll(user, ["orders.update", "orders.viewCost"]); // needs BOTHawait canAny(user, ["orders.update", "orders.updateStatus"]); // needs EITHERThrowers (services)
Section titled “Throwers (services)”import { authorize, authorizeAll, authorizeAny } from "@warlock.js/access";
await authorize(user, "orders.update"); // throws ForbiddenError (403, EC100) on denyawait authorizeAll(user, ["orders.update", "orders.viewCost"]);Class-level vs instance-level
Section titled “Class-level vs instance-level”Pass a resource and the permission’s policy runs on top of the grant:
await authorize(user, "orders.update"); // class-level — grant onlyawait authorize(user, "orders.update", { resource: order }); // instance-level — grant AND policygate is class-level only (it runs before the controller, with no record to inspect). Instance checks belong in the service, after you load the row.
Wildcards
Section titled “Wildcards”* grants everything; orders.* covers orders.update and any nested orders.update.status — but not the bare orders (the prefix needs a trailing ., so granting orders.* and checking orders is silently denied).
Good to know
Section titled “Good to know”can*never throws;authorize*throwsForbiddenError. Gate controllers with middleware, assert in services withauthorize.- Everything fails closed — an error resolving the decision denies; a user with no roles is denied, not allowed. The one exception: a misconfig (
AccessConfigError, e.g. no resolver) is re-thrown loudly instead of denied.