What Is CASL and How It Powers Authorization in Akash Console
CASL is a declarative JavaScript authorization library that Akash Console uses to enforce role-based access control through compile-time rules, runtime permission checks, and automatic SQL query filtering.
CASL (Can Access Some Library) is an isomorphic authorization library that lets developers define what actions users can perform on domain objects through simple, declarative rules. The akash-network/console repository implements CASL to enforce granular, role-based access control across its API layer and data access tier. This approach unifies business logic authorization with database query filtering, ensuring consistent permission checks throughout the application stack.
Core Concepts of CASL Authorization
CASL operates on the concept of abilities—rules that specify which actions (e.g., create, read, delete) a user can perform on which subjects (domain objects like Deployment or UserWallet) under specific conditions (e.g., userId: "${user.id}"). These rules are compiled into a MongoAbility instance that can be queried at runtime or transformed into database predicates.
Five Layers of CASL Implementation in Akash Console
Akash Console integrates CASL through five distinct architectural layers, each handling a specific phase of the authorization lifecycle.
1. Centralized Rule Definitions in AbilityService
Static role-based rules are declared as raw CASL rules and compiled using a lodash template. In apps/api/src/auth/services/ability/ability.service.ts, the AbilityService class maintains a RULES map for three distinct roles: REGULAR_USER, REGULAR_PAYING_USER, and SUPER_USER. These raw rules define permissible actions and subject-specific conditions, then get compiled into a Mongo-style ability via createMongoAbility.
2. Dynamic Ability Compilation Per Request
For every incoming request, AbilityService#getAbilityFor (lines 54-57) compiles the static rule template with the current user’s dynamic data—such as their specific userId—and returns a ready-to-use Ability instance tailored to that user’s session.
3. Execution Context Storage via AuthInterceptor
The generated ability must persist throughout the request lifecycle. In apps/api/src/auth/services/auth.interceptor.ts (lines 90-97), the AuthInterceptor authenticates the user via JWT or API key, obtains the UserOutput, determines the appropriate role, and injects the compiled ability into AuthService for downstream consumption.
4. Runtime Enforcement with throwUnlessCan and the Protected Decorator
Business logic enforces permissions through AuthService#throwUnlessCan (lines 60-63), which invokes CASL’s ability.can method and throws a 403 ForbiddenError when checks fail. For declarative route protection, the Protected decorator (defined in apps/api/src/auth/services/auth.service.ts, lines 66-79) extracts the injected ability and runs throwUnlessCan before the controller method executes.
5. SQL Query Filtering with DrizzleAbility
To prevent unauthorized data exposure at the database layer, DrizzleAbility in apps/api/src/lib/drizzle-ability/drizzle-ability.ts transforms CASL’s abstract syntax tree (AST) into SQL WHERE clauses. The whereAccessibleBy method (lines 44-47) generates drizzle-orm conditions that automatically filter queries to return only rows the user is permitted to access.
End-to-End Authorization Flow
The complete authorization lifecycle in Akash Console follows this sequence:
- An incoming request triggers the
AuthInterceptor, which validates the user’s JWT or API key. - The interceptor calls
AbilityService.getAbilityFor(role, user)to compile role-specific rules with the user’s unique identifiers, producing a MongoAbility instance. - The ability is stored in
AuthService(this.authService.ability), making it available to all downstream services. - Controllers use the
@Protected([...])decorator or services callauthService.throwUnlessCan(action, subject, conditions)to validate permissions; CASL evaluates the rule tree and throws a 403 if access is denied. - When repositories query the database, they instantiate
DrizzleAbilitywith the current ability and callwhereAccessibleByto append CASL-derivedWHEREclauses, ensuring only accessible rows are returned.
Practical Code Examples from the Source
Defining Role-Based Rules
The following excerpt from apps/api/src/auth/services/ability/ability.service.ts shows how static rules are declared for different user tiers:
private readonly RULES: Record<Role, Array<RawRule & { enabledIf?: FeatureFlagValue }>> = {
REGULAR_USER: [
{ action: ["create", "read", "sign"], subject: "UserWallet", conditions: { userId: "${user.id}" } },
{ action: "manage", subject: "WalletSetting", conditions: { userId: "${user.id}" } },
// …additional rules
],
REGULAR_PAYING_USER: [ /* same as REGULAR_USER plus extra privileges */ ],
SUPER_USER: [{ action: "manage", subject: "all" }]
};
Creating a Request-Specific Ability
Inside apps/api/src/auth/services/auth.interceptor.ts, the interceptor binds the compiled ability to the request context:
if (user) {
const role = this.getUserRole(user);
this.authService.ability = this.abilityService.getAbilityFor(role, user);
}
Enforcing Permissions in Services
Services validate operations before execution by calling throwUnlessCan:
this.authService.throwUnlessCan('delete', 'Alert', { userId: currentUser.id });
Protecting Controller Routes
The @Protected decorator provides declarative guards on API endpoints:
@Protected([{ action: 'manage', subject: 'DeploymentSetting' }])
async updateSetting(@Body() dto: UpdateDto) {
// Handler executes only if the user can manage DeploymentSettings
}
Filtering Database Queries
Repositories use DrizzleAbility to apply authorization logic directly to SQL generation:
const ability = this.authService.ability;
const drizzle = new DrizzleAbility(deploymentsTable, ability, 'read', 'Deployment');
const where = drizzle.whereAccessibleBy();
return db.select().from(deploymentsTable).where(where);
Summary
- CASL provides a single source of truth for both business logic checks and database filtering in Akash Console, eliminating authorization drift between layers.
- Declarative rules defined in
AbilityServiceuse lodash templates to inject user-specific conditions into static role definitions. - Per-request abilities are compiled, stored in execution context via
AuthInterceptor, and enforced throughthrowUnlessCanor the@Protecteddecorator. - DrizzleAbility bridges CASL’s declarative rules with SQL generation, automatically filtering queries to return only permitted rows.
- The architecture supports three distinct roles—
REGULAR_USER,REGULAR_PAYING_USER, andSUPER_USER—with escalating privileges managed through a unified rule set.
Frequently Asked Questions
What does CASL stand for and why did Akash Console choose it?
CASL stands for Can Access Some Library. Akash Console selected CASL because its isomorphic, declarative API allows the same authorization rules to be defined once and applied consistently across both runtime permission checks and database query construction, reducing code duplication and security vulnerabilities.
How does Akash Console convert CASL rules into SQL WHERE clauses?
The conversion happens in apps/api/src/lib/drizzle-ability/drizzle-ability.ts. The DrizzleAbility class parses the CASL ability’s abstract syntax tree and translates conditions into drizzle-orm predicates via the whereAccessibleBy method. This allows the ORM to generate SQL that filters out rows the user cannot access before they ever leave the database.
What are the three user roles defined in the Akash Console CASL configuration?
The AbilityService defines rules for REGULAR_USER, REGULAR_PAYING_USER, and SUPER_USER. Regular users can manage their own wallets and settings, paying users receive additional privileges, and super users possess manage permissions over all subjects without restriction.
How does the @Protected decorator work with CASL abilities?
The @Protected decorator, implemented in apps/api/src/auth/services/auth.service.ts, extracts the CASL ability stored in AuthService by the interceptor. Before the decorated controller method runs, it validates the user’s permissions against the declared rules using throwUnlessCan. If the check fails, it immediately returns an HTTP 403 Forbidden response, preventing unauthorized business logic execution.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →