Skip to main content

ABAC Permissions

Efesto integrates CASL for Attribute-Based Access Control. The framework's whole contribution is small and precise:

  1. You declare a permission: [action, model] tuple on an endpoint.
  2. You build a CASL ability and put it on the request/context.
  3. Before running the handler, Efesto checks the ability against the tuple and rejects the request if it can't.

That's it. There are no permission decorators, no role hierarchies, no database loaders built into Efesto, those are patterns you build on top with CASL.

1. Build the ability (your code)

The ability is yours to create, usually in the auth middleware, from the authenticated user. Both stacks expect it on a context field named ability by default.

Attach the ability to req in a middleware that runs before Efesto:

import { defineAbility } from "@casl/ability";

app.use((req, res, next) => {
req.ability = defineAbility((can) => {
can("readAll", "User");
can("readOne", "User");
can("create", "User");
});
next();
});

app.use("/api/v1", efesto({ /* ... */ }));

2. Declare the permission on the endpoint

The tuple is [action, model] on both stacks. Efesto checks it with ability.can(action, model) before the handler runs.

Declare permission inside the method's _<method>Swagger object. It is written to the generated Swagger and enforced at runtime:

import { BaseApiService, SwaggerOptions } from "efesto";

class Users extends BaseApiService {
constructor() {
super(__filename);
}

_getSwagger: SwaggerOptions = {
operationId: "getUsers",
permission: ["readAll", "User"],
responses: { 200: { content: { "application/json": { schema: { type: "array", items: "@User" } } } } },
};

_get(req, res) {
return res.json(listUsers());
}
}
export default Users;

3. What happens on failure

If the ability can't perform the action, Efesto raises a CASL ForbiddenError before the method runs. Handle it in your errorMiddleware to shape the response.

No ability, no check

On both stacks the check is only enforced when an ability is present on the context. If you never attach one, permission is documented but not enforced. The Elysia guard mirrors the Express behavior here intentionally.

Configuration

Tune enforcement through configuration. Defaults match the descriptions above.

In the Efesto config object:

config: {
abacPermissions: {
actions: ["readAll", "readOne", "create", "update", "delete"], // documented actions
models: ["User", "Product"], // documented models
checkPermissionBeforeResolver: true, // enforce before the handler (default true)
reqAbilityField: "ability", // where Efesto reads the ability (default "ability")
},
}
PropertyTypeDescriptionDefault
actionsstring[]Available actions (typing/help for the tuple)
modelsstring[]Available models
checkPermissionBeforeResolverbooleanEnforce before the method runstrue
reqAbilityFieldstringreq field holding the ability"ability"

Beyond the basics

Roles, ownership rules, time windows, and database-driven permissions are all possible, but they are CASL features and your application logic, not Efesto APIs. You express them when you defineAbility (step 1); Efesto only checks the resulting ability against the declared tuple. See the CASL documentation for conditions and field-level rules.

The ability is usually built in your authentication layer.