|
Some checks failed
Build and Publish / build-and-publish (push) Failing after 37s
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| coverage | ||
| src | ||
| test | ||
| .gitignore | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsup.config.ts | ||
| vitest.config.ts | ||
@lilith/nestjs-guards
Production-ready NestJS authorization guards for role-based and resource-based access control.
Features
- Authentication Guard:
AuthGuard- Validates user authentication - Role-based Access Control (RBAC):
RoleGuard+@Roles()decorator - Permission-based Access Control:
PermissionGuard+@Permissions()decorator - Resource Ownership:
ResourceGuard+@RequireResource()decorator - Utility Functions:
requireAuth(),extractResourceId() - 100% Test Coverage: Comprehensive test suite with 91 tests
- Tree-shakeable ESM: Optimized bundle size
Installation
pnpm add @lilith/nestjs-guards
Peer Dependencies:
@lilith/nestjs-auth ^1.0.0@nestjs/common ^10.0.0 || ^11.0.0@nestjs/core ^10.0.0 || ^11.0.0
Quick Start
1. Authentication Guard
Validates that a user is authenticated (after JWT validation).
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '@lilith/nestjs-auth';
import { AuthGuard } from '@lilith/nestjs-guards';
@Controller('profile')
@UseGuards(JwtAuthGuard, AuthGuard)
export class ProfileController {
@Get()
getProfile(@CurrentUser() user: GuardUser) {
return this.profileService.findByUserId(user.id);
}
}
2. Role-based Access Control
Restrict access based on user roles (OR logic - user needs at least one role).
import { Controller, Get, Delete, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '@lilith/nestjs-auth';
import { RoleGuard, Roles } from '@lilith/nestjs-guards';
@Controller('admin')
@UseGuards(JwtAuthGuard, RoleGuard)
export class AdminController {
@Get('users')
@Roles('admin', 'moderator')
getUsers() {
// Accessible by users with 'admin' OR 'moderator' role
return this.userService.findAll();
}
@Delete('users/:id')
@Roles('admin')
deleteUser() {
// Only accessible by users with 'admin' role
return this.userService.delete(id);
}
}
3. Permission-based Access Control
Fine-grained access control with permissions (AND logic - user needs all permissions).
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '@lilith/nestjs-auth';
import { PermissionGuard, Permissions } from '@lilith/nestjs-guards';
@Controller('content')
@UseGuards(JwtAuthGuard, PermissionGuard)
export class ContentController {
@Get()
@Permissions('content:read')
getContent() {
// Requires 'content:read' permission
return this.contentService.findAll();
}
@Post()
@Permissions('content:write', 'content:publish')
createContent() {
// Requires BOTH 'content:write' AND 'content:publish' permissions
return this.contentService.create(data);
}
}
4. Resource Ownership
Validate that users can only access their own resources.
Basic Usage (ID Matching):
import { Controller, Get, Put, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '@lilith/nestjs-auth';
import { ResourceGuard, RequireResource } from '@lilith/nestjs-guards';
@Controller('posts')
@UseGuards(JwtAuthGuard, ResourceGuard)
export class PostsController {
@Get(':id')
@RequireResource()
getPost() {
// Only allows access if post.id === user.id
return this.postsService.findOne(id);
}
@Put(':postId')
@RequireResource({ paramName: 'postId' })
updatePost() {
// Custom param name
return this.postsService.update(postId, data);
}
@Delete()
@RequireResource({ extractionOrder: ['body', 'query'] })
deletePost() {
// Extract ID from request body or query params
return this.postsService.delete(id);
}
}
Advanced Usage (Database Lookup):
For complex ownership checks (e.g., checking post.authorId), extend ResourceGuard:
import { Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ResourceGuard, GuardUser } from '@lilith/nestjs-guards';
import { PostsService } from './posts.service';
@Injectable()
export class PostOwnershipGuard extends ResourceGuard {
constructor(
reflector: Reflector,
private readonly postsService: PostsService,
) {
super(reflector);
}
protected override async checkOwnership(
user: GuardUser,
resourceId: string,
): Promise<boolean> {
const post = await this.postsService.findOne(resourceId);
if (!post) return false;
// Check if user owns the post
return post.authorId === user.id;
}
}
Then use it in your controller:
@Controller('posts')
@UseGuards(JwtAuthGuard, PostOwnershipGuard)
export class PostsController {
@Get(':id')
@RequireResource()
getPost(@Param('id') id: string) {
// Only accessible if user.id === post.authorId
return this.postsService.findOne(id);
}
}
API Reference
Guards
AuthGuard
Validates that request.user exists. Use after authentication guards.
@UseGuards(JwtAuthGuard, AuthGuard)
RoleGuard
Checks if user has at least one of the required roles (OR logic).
@UseGuards(JwtAuthGuard, RoleGuard)
@Roles('admin', 'moderator')
PermissionGuard
Checks if user has ALL required permissions (AND logic).
@UseGuards(JwtAuthGuard, PermissionGuard)
@Permissions('content:read', 'content:write')
ResourceGuard
Validates resource ownership. Default: resourceId === userId. Override checkOwnership() for custom logic.
@UseGuards(JwtAuthGuard, ResourceGuard)
@RequireResource({ paramName: 'id' })
Decorators
@Roles(...roles: string[])
Specifies required roles for a route or controller.
@Roles('admin', 'moderator') // User needs 'admin' OR 'moderator'
@Permissions(...permissions: string[])
Specifies required permissions for a route or controller.
@Permissions('content:read', 'content:write') // User needs BOTH permissions
@RequireResource(options?: ResourceExtractionOptions)
Configures resource ownership checking.
Options:
paramName?: string- Route parameter name (default:'id')bodyField?: string- Body field name (default:'id')queryField?: string- Query parameter name (default:'id')extractionOrder?: Array<'params' | 'body' | 'query'>- Priority order (default:['params', 'body', 'query'])
@RequireResource({ paramName: 'userId', extractionOrder: ['params', 'query'] })
Utilities
requireAuth(user, message?)
Validates user is authenticated. Throws UnauthorizedException if not.
requireAuth(request.user, 'Custom error message');
extractResourceId(request, options?)
Extracts resource ID from request params, body, or query.
const id = extractResourceId(request, { paramName: 'postId' });
Types
GuardUser
Extended user interface with roles and permissions.
interface GuardUser extends AuthenticatedUser {
roles?: string[];
permissions?: string[];
organizationId?: string;
}
ResourceContext
Context for resource-based authorization.
interface ResourceContext {
user: GuardUser;
resourceId: string;
request: Request;
}
ResourceExtractionOptions
Configuration for resource ID extraction (see @RequireResource decorator).
Guard Combinations
Guards can be combined for layered security:
// Authentication + Role Check
@UseGuards(JwtAuthGuard, RoleGuard)
@Roles('admin')
// Authentication + Permission Check
@UseGuards(JwtAuthGuard, PermissionGuard)
@Permissions('content:delete')
// Authentication + Resource Ownership
@UseGuards(JwtAuthGuard, ResourceGuard)
@RequireResource()
// All layers
@UseGuards(JwtAuthGuard, RoleGuard, ResourceGuard)
@Roles('editor')
@RequireResource()
Error Handling
All guards throw standard NestJS exceptions:
UnauthorizedException- User not authenticated or resource ID not foundForbiddenException- User lacks required roles/permissions or doesn't own resource
Example error messages:
// AuthGuard
"Authentication required"
// RoleGuard
"User has no roles assigned"
"Access denied. Required role(s): admin or moderator"
// PermissionGuard
"User has no permissions assigned"
"Access denied. Missing permission(s): content:write, content:delete"
// ResourceGuard
"Resource ID not found in params, body, query"
"Access denied. You do not own this resource."
Testing
The package includes comprehensive tests with 100% coverage:
pnpm test # Run tests
pnpm test:watch # Watch mode
pnpm test:coverage # Coverage report
Test Statistics:
- 91 tests across 9 test suites
- 100% code coverage (statements, branches, functions, lines)
- All guards, decorators, and utilities tested
Development
pnpm build # Build package
pnpm typecheck # Type check
pnpm lint # Lint code
pnpm lint:check # Check linting
License
Proprietary - Lilith Platform