Repository navigation
Releases
Release 0.1.3
Bug fixes
- fix: name the mcp primitives without a check in the startup log by @johnnyhuirilef in #45
Full Changelog: 0.1.2...0.1.3
Release 0.1.2
Release 0.1.1
Release 0.1.0
Breaking changes
Handlers that declare no check are denied
AuthorizationGuard used to let a handler without @Can() through, so a forgotten decorator left the route open to anyone who was signed in. It now denies it: 403 for a user, 401 for a guest. A handler runs only when it, or its class, declares a check:
| Declaration | The guard |
|---|---|
@Can(Policy, 'ability') |
Runs every requirement of the class, then of the method. All must pass. |
@Can.Anyone() (new) |
Lets through anyone authentication lets through. Use it for routes that need no permission, and for routes that check in their service with AuthorizationService. |
@Public() from @nestjs/authentication 0.1.0+ |
Counts as @Can.Anyone(). |
| Nothing | Denied. |
@Controller('orders')
export class OrdersController {
@Get(':id')
@Can.Anyone() // OrdersService.findOne() authorizes the order
findOne(@CurrentUser() user: User, @Param('id') id: string) {
return this.ordersService.findOne(user, id);
}
}- On a method,
@Can.Anyone()or@Public()lifts the class's@Can(), as@Public()lifts the class's authentication. A@Can()on the method itself still applies. - On a class, neither lifts a method's
@Can(). - A method that
@Authenticate()puts back under authentication inside a@Public()class isn't public, and needs a declaration of its own. - Where Nest runs no global guard, nothing changes. That means GraphQL field resolvers without
fieldResolverEnhancers: ['guards'], and message handlers in a hybrid app connected withoutinheritAppConfig: true. With the guards enhancer, every@ResolveField()needs a declaration, and a class-level one covers them.
Migrating: at startup, the module logs every handler it will deny:
[Authorization] AuthorizationGuard denies every call to OrdersController.findAll, OrdersController.findOne: they declare no check. Add @Can(), or @Can.Anyone() where anyone may call the handler (a @Public() route from @nestjs/authentication needs neither).
Add @Can() or @Can.Anyone() to each handler it lists. The log doesn't stop the app from starting.
policy and ability can be null
On AuthorizationError and on the denied event, policy and ability are now string | null. They're null when the guard denied a handler that declares no check. Code that reads them, such as an audit logger, needs to handle null.
Features
@Can() reads ability arguments from the call (#17)
An ability that takes arguments after the user, such as an id from the URL, used to need an authorize() call in the handler, and a later edit could drop that call without anyone noticing. @Can() now takes a third argument that reads those arguments from the ExecutionContext:
@Policy()
export class OrderPolicy {
viewOrdersOf(user: User | null, customerId: string) {
return !!user && (user.id === customerId || user.roles.includes('staff'));
}
}
@Get()
@Can(OrderPolicy, 'viewOrdersOf', (context) => [context.switchToHttp().getRequest().params.customerId])
findAll(@Param('customerId') customerId: string) {}- Typed by the ability's own parameters. The third argument is required when the ability has required parameters after the user. A function declared on its own needs
as constto return a tuple. - Runs in the guard, before pipes. Values arrive as the transport delivered them, so route params are strings. An id the user may not see gets a 403, not a 400.
- Runs once per requirement, in order. After one requirement denies, later ones don't run. The values go to
before(), to the ability, and to thedeniedevent'sargs. - Errors pass through. An error the function throws, such as a
BadRequestException, reaches the client unchanged. - Works on every transport. Use
GqlExecutionContext.create(context).getArgs(),context.switchToWs().getData()orcontext.switchToRpc().getData().
Pass identifiers and scopes, not records. Loading a record in the guard loads it twice, and answers 403 before the handler can answer 404. Record checks stay in AuthorizationService.
Clearer @Can() type errors
@Can() now accepts any ability whose user parameter accepts null. When an ability name doesn't fit, the error names the problem:
@Can(ProductPolicy, 'craete') // '"craete"' is not assignable to '"create" | "update" | "view" | "viewDrafts"'
@Can(OrderPolicy, 'viewOrdersOf') // Expected 3 arguments, but got 2.
@Can(OrderPolicy, 'viewAll') // '"viewAll"' is not assignable to '"viewOrdersOf"' (viewAll takes User, not User | null)
Compatibility
@nestjs/authenticationis now an optional peer dependency,>=0.1.0. Authentication 0.1.0 is the first version that tells authorization a route is@Public(). With 0.0.1, every@Public()route is denied, and the startup log lists them all.npm lsreports that pairing as invalid.- Nest
^12.0.0, as before. Other dependency updates in this release are development-only.