permissions-contractx
v1.0.2
Published
Enterprise-grade authentication and authorization package for NestJS microservices with role-based and permission-based access control
Maintainers
Readme
🔐 Permissions ContractX
A comprehensive NestJS authentication and authorization package designed for microservices in the ContractX ecosystem. This package provides JWT-based authentication, role-based access control (RBAC), and granular permission management.
✨ Features
- 🔑 JWT Authentication - Secure token-based authentication
- 👥 Role-Based Access Control - Hierarchical role system
- 🔐 Permission-Based Authorization - Granular permission control
- 🏢 Multi-Tenant Support - Client/Provider organizational structure
- 🚀 Easy Integration - Simple decorator-based API
- 🛡️ Security First - Built-in security best practices
- 📊 Request Context - User context service for easy access
- 🔧 TypeScript - Full TypeScript support with strict typing
- 📖 Comprehensive Documentation - Detailed examples and guides
📦 Installation
npm install permissions-contractxPeer Dependencies
Make sure you have the required peer dependencies installed:
npm install @nestjs/common @nestjs/core @nestjs/jwt @nestjs/config jsonwebtoken rxjs reflect-metadata🚀 Quick Start
1. Basic Setup
// app.module.ts
import { Module } from '@nestjs/common';
import { PermissionsContractXModule } from 'permissions-contractx';
@Module({
imports: [
PermissionsContractXModule.register({
jwt: {
secret: 'your-secret-key',
issuer: 'your-api',
audience: 'your-users',
expiresIn: '15m',
},
guards: {
enableGlobalAuth: true, // Apply authentication globally
},
}),
],
})
export class AppModule {}2. Using Decorators in Controllers
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import {
CurrentUser,
Roles,
RequirePermissions,
Public,
JwtAuthGuard,
RolesGuard,
PermissionsGuard,
JwtPayload,
} from 'permissions-contractx';
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
export class UsersController {
// Public endpoint (no authentication)
@Public()
@Get('public')
getPublicInfo() {
return { message: 'This is public' };
}
// Authenticated endpoint
@Get('profile')
getProfile(@CurrentUser() user: JwtPayload) {
return {
id: user.sub,
name: user.fullName,
roles: user.role,
permissions: user.permissions,
};
}
// Role-based access
@Roles('superadmin', 'client_contract_admin')
@Get('admin-data')
getAdminData() {
return { message: 'Admin only data' };
}
// Permission-based access
@RequirePermissions('users.create')
@Post()
createUser(@CurrentUser() user: JwtPayload) {
return { message: 'User created', createdBy: user.fullName };
}
// Combined role and permission
@Roles('client_contract_admin')
@RequirePermissions('users.delete')
@Delete(':id')
deleteUser(@Param('id') id: string) {
return { message: `User ${id} deleted` };
}
}3. Environment Variables Setup
Create a .env file:
JWT_SECRET=your-super-secret-jwt-key-change-in-production
JWT_ISSUER=contractx-api
JWT_AUDIENCE=contractx-users
JWT_EXPIRES_IN=15m
# Optional: Enable global guards
ENABLE_GLOBAL_AUTH=true
ENABLE_AUTH_LOGGING=true
# Development only
DISABLE_AUTH=false # Set to true to disable auth in development4. Auto-Configuration with Environment Variables
// app.module.ts - Easiest setup
@Module({
imports: [
PermissionsContractXModule.forRoot(), // Auto-configures from environment variables
],
})
export class AppModule {}🏗️ Advanced Configuration
Async Configuration
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
PermissionsContractXModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
jwt: {
secret: configService.get('JWT_SECRET'),
issuer: configService.get('JWT_ISSUER'),
audience: configService.get('JWT_AUDIENCE'),
expiresIn: configService.get('JWT_EXPIRES_IN', '15m'),
clockTolerance: 30, // seconds
},
guards: {
enableGlobalAuth: true,
enableGlobalRoles: false,
enableGlobalPermissions: false,
},
security: {
enableLogging: process.env.NODE_ENV === 'production',
},
development: {
disableAuth: process.env.NODE_ENV === 'development',
mockUser: {
sub: 'dev-user-123',
role: ['superadmin'],
permissions: ['*'],
fullName: 'Development User',
email: '[email protected]',
},
},
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}📋 JWT Token Structure
The JWT token must include the following payload structure:
{
"sub": "user_123456789", // User ID (required)
"fullName": "John Doe", // User's full name (required)
"email": "[email protected]", // User's email
"clientId": "client_001", // Organization/Client ID
"sessionId": "session_abc", // Session tracking
"role": [ // User roles array (required)
"admin",
"manager",
"user"
],
"permissions": [ // User permissions array (required)
"users:read",
"users:create",
"users:update",
"users:delete",
"settings:manage"
],
// Standard JWT fields
"iat": 1609459200, // Issued at
"exp": 1609545600, // Expires at
"iss": "contractx-api", // Issuer
"aud": "contractx-users" // Audience
}🛡️ Guards
1. JwtAuthGuard
Validates JWT tokens and extracts user information.
import { Controller, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from 'permissions-contractx';
@Controller('protected')
@UseGuards(JwtAuthGuard)
export class ProtectedController {
// All routes require valid JWT token
}2. RolesGuard
Checks if user has required roles.
import { Controller, UseGuards } from '@nestjs/common';
import { JwtAuthGuard, RolesGuard, RequireRoles } from 'permissions-contractx';
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
@Get('dashboard')
@RequireRoles('admin', 'manager') // User must have admin OR manager role
getDashboard() {
return { message: 'Admin dashboard' };
}
}3. PermissionsGuard
Checks if user has required permissions.
import { Controller, UseGuards } from '@nestjs/common';
import { JwtAuthGuard, PermissionsGuard, RequirePermissions } from 'permissions-contractx';
@Controller('users')
@UseGuards(JwtAuthGuard, PermissionsGuard)
export class UsersController {
@Post()
@RequirePermissions('users:create') // User must have this exact permission
createUser() {
return { message: 'User created' };
}
@Delete(':id')
@RequirePermissions('users:delete', 'admin:all') // User must have one of these
deleteUser() {
return { message: 'User deleted' };
}
}🎯 Decorators
The permissions-contractx package provides a comprehensive set of decorators for authentication and authorization in your NestJS applications.
🔐 Authentication Decorators
@Public()
Marks routes as public (no authentication required). Essential for login endpoints, health checks, and public APIs.
@Controller('auth')
export class AuthController {
@Public()
@Post('login')
login(@Body() credentials: LoginDto) {
return this.authService.login(credentials);
}
@Public()
@Get('health')
getHealth() {
return { status: 'OK', timestamp: new Date() };
}
}@CurrentUser(property?)
Injects the current authenticated user or specific user properties into route handlers.
@Controller('users')
export class UsersController {
// Inject full user object
@Get('profile')
getProfile(@CurrentUser() user: JwtPayload) {
return {
id: user.sub,
name: user.fullName,
email: user.email,
roles: user.role,
permissions: user.permissions
};
}
// Inject specific user property
@Post('actions')
performAction(
@CurrentUser('sub') userId: string,
@CurrentUser('fullName') userName: string,
@Body() actionData: any
) {
return {
action: 'completed',
performedBy: userName,
userId: userId,
data: actionData
};
}
}@UserId()
Convenience decorator to get the current user's ID directly.
@Controller('resources')
export class ResourcesController {
@Post()
createResource(
@UserId() userId: string,
@Body() resourceData: CreateResourceDto
) {
return this.resourcesService.create({
...resourceData,
createdBy: userId,
createdAt: new Date()
});
}
@Get('my-resources')
getMyResources(@UserId() userId: string) {
return this.resourcesService.findByUser(userId);
}
}@UserRoles()
Injects the current user's roles array into the route handler.
@Controller('dashboard')
export class DashboardController {
@Get('capabilities')
getUserCapabilities(@UserRoles() roles: string[]) {
const capabilities = {
canManageUsers: roles.includes('superadmin') || roles.includes('client_contract_admin'),
canViewReports: roles.some(role => role.includes('reports')),
canManageContracts: roles.some(role => role.includes('contract_admin')),
isClientSide: roles.some(role => role.startsWith('client_')),
isProviderSide: roles.some(role => role.startsWith('provider_'))
};
return { roles, capabilities };
}
}@UserPermissions()
Injects the current user's permissions array into the route handler.
@Controller('admin')
export class AdminController {
@Get('available-actions')
getAvailableActions(@UserPermissions() permissions: string[]) {
const modulePermissions = permissions.reduce((acc, permission) => {
const [module, action] = permission.split(':');
if (!acc[module]) acc[module] = [];
acc[module].push(action);
return acc;
}, {});
return {
totalPermissions: permissions.length,
moduleBreakdown: modulePermissions,
rawPermissions: permissions
};
}
}@UserClientId()
Injects the current user's client ID for multi-tenant applications.
@Controller('tenant')
export class TenantController {
@Get('data')
getTenantData(@UserClientId() clientId: string) {
return this.dataService.findByClient(clientId);
}
@Post('settings')
updateTenantSettings(
@UserClientId() clientId: string,
@Body() settings: TenantSettingsDto
) {
return this.settingsService.update(clientId, settings);
}
}👥 Role-Based Authorization Decorators
@Roles(...roles)
Requires user to have at least one of the specified roles (OR logic).
@Controller('admin')
export class AdminController {
// User needs superadmin OR any contract admin role
@Roles('superadmin', 'client_contract_admin', 'provider_contract_admin')
@Get('dashboard')
getAdminDashboard() {
return { message: 'Admin dashboard data' };
}
// Multiple specific roles
@Roles('client_finance_manager', 'provider_finance_manager')
@Get('financial-reports')
getFinancialReports() {
return this.reportsService.getFinancialData();
}
}@AdminOnly()
Shortcut for admin-level access (superadmin, client_contract_admin, provider_contract_admin).
@Controller('system')
export class SystemController {
@AdminOnly()
@Delete('cache')
clearCache() {
return this.cacheService.clear();
}
@AdminOnly()
@Post('maintenance-mode')
enableMaintenanceMode(@Body() config: MaintenanceConfigDto) {
return this.systemService.enableMaintenance(config);
}
}@ClientOnly()
Restricts access to client-side roles only.
@Controller('client')
export class ClientController {
@ClientOnly()
@Get('performance-metrics')
getClientPerformanceMetrics(@UserClientId() clientId: string) {
return this.metricsService.getClientMetrics(clientId);
}
@ClientOnly()
@Post('escalation')
createEscalation(@Body() escalationData: CreateEscalationDto) {
return this.escalationService.create(escalationData);
}
}@ProviderOnly()
Restricts access to provider-side roles only.
@Controller('provider')
export class ProviderController {
@ProviderOnly()
@Get('delivery-schedule')
getDeliverySchedule() {
return this.deliveryService.getSchedule();
}
@ProviderOnly()
@Put('deliverable/:id/status')
updateDeliverableStatus(
@Param('id') id: string,
@Body() statusUpdate: DeliverableStatusDto
) {
return this.deliverableService.updateStatus(id, statusUpdate);
}
}@SuperAdminOnly()
Restricts access to superadmin role only.
@Controller('system-admin')
export class SystemAdminController {
@SuperAdminOnly()
@Post('create-tenant')
createTenant(@Body() tenantData: CreateTenantDto) {
return this.tenantService.create(tenantData);
}
@SuperAdminOnly()
@Delete('user/:id')
deleteUser(@Param('id') userId: string) {
return this.userService.delete(userId);
}
}🔐 Permission-Based Authorization Decorators
@RequirePermissions(...permissions)
Requires user to have all specified permissions (AND logic).
@Controller('contracts')
export class ContractsController {
// User must have ALL specified permissions
@RequirePermissions('contracts.create', 'contracts.validate')
@Post()
createContract(@Body() contractData: CreateContractDto) {
return this.contractService.create(contractData);
}
// Single permission requirement
@RequirePermissions('contracts.delete')
@Delete(':id')
deleteContract(@Param('id') id: string) {
return this.contractService.delete(id);
}
}@RequireAnyPermission(...permissions)
Requires user to have at least one of the specified permissions (OR logic).
@Controller('documents')
export class DocumentsController {
// User needs ANY of these permissions
@RequireAnyPermission('documents.read', 'documents.show', 'documents.filter')
@Get()
getDocuments(@Query() filters: DocumentFiltersDto) {
return this.documentService.findAll(filters);
}
// Administrative permissions
@RequireAnyPermission('documents.admin', 'superadmin.all')
@Post('batch-process')
batchProcessDocuments(@Body() batchData: BatchProcessDto) {
return this.documentService.batchProcess(batchData);
}
}🎯 Module-Level Permission Decorators
These decorators provide convenient access patterns based on common CRUD operations:
@ReadAccess(module)
Grants read access to a module (read, show, or filter permissions).
@Controller('reports')
export class ReportsController {
@ReadAccess('reports')
@Get('financial')
getFinancialReports() {
// User needs reports.read, reports.show, OR reports.filter
return this.reportsService.getFinancialReports();
}
@ReadAccess('contracts')
@Get('contract-summary')
getContractSummary() {
// User needs contracts.read, contracts.show, OR contracts.filter
return this.contractService.getSummary();
}
}@WriteAccess(module)
Grants write access to a module (create or update permissions).
@Controller('users')
export class UsersController {
@WriteAccess('users')
@Post()
createUser(@Body() userData: CreateUserDto) {
// User needs users.create OR users.update
return this.userService.create(userData);
}
@WriteAccess('users')
@Put(':id')
updateUser(
@Param('id') id: string,
@Body() userData: UpdateUserDto
) {
// User needs users.create OR users.update
return this.userService.update(id, userData);
}
}@DeleteAccess(module)
Grants delete access to a module.
@Controller('deliverables')
export class DeliverablesController {
@DeleteAccess('deliverables')
@Delete(':id')
deleteDeliverable(@Param('id') id: string) {
// User needs deliverables.delete
return this.deliverableService.delete(id);
}
}@FullAccess(module)
Grants full CRUD access to a module (any module permission).
@Controller('admin/modules')
export class ModuleAdminController {
@FullAccess('users')
@Post('users/bulk-operation')
bulkUserOperation(@Body() operation: BulkOperationDto) {
// User needs ANY users module permission
return this.userService.bulkOperation(operation);
}
}🔄 Combining Decorators
Decorators can be combined for complex authorization logic:
@Controller('advanced')
export class AdvancedController {
// Combine role and permission requirements
@Roles('client_contract_admin', 'provider_contract_admin')
@RequirePermissions('contracts.approve')
@Put('contracts/:id/approve')
approveContract(
@Param('id') contractId: string,
@CurrentUser() user: JwtPayload
) {
return this.contractService.approve(contractId, user.sub);
}
// Complex permission logic
@ClientOnly()
@RequireAnyPermission('reports.generate', 'reports.admin')
@Post('custom-report')
generateCustomReport(
@Body() reportSpec: CustomReportDto,
@UserClientId() clientId: string
) {
return this.reportsService.generateCustom(reportSpec, clientId);
}
// Multiple permission sets
@RequirePermissions('documents.create')
@RequireAnyPermission('contracts.update', 'deliverables.update')
@Post('document-with-linking')
createLinkedDocument(
@Body() documentData: LinkedDocumentDto,
@UserId() userId: string
) {
// User must have documents.create AND (contracts.update OR deliverables.update)
return this.documentService.createWithLinking(documentData, userId);
}
}🏷️ Method vs Class Level Decorators
Decorators can be applied at both class and method levels:
// Class-level decorators apply to all methods
@Controller('secure')
@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
@ClientOnly() // All methods require client roles
export class SecureController {
@Get('data')
getData() {
// Inherits @ClientOnly from class
return this.dataService.getClientData();
}
@RequirePermissions('admin.override')
@Get('admin-data')
getAdminData() {
// Combines @ClientOnly + @RequirePermissions
return this.dataService.getAdminData();
}
@Public() // Overrides class-level @ClientOnly
@Get('public-info')
getPublicInfo() {
return { message: 'Public information' };
}
}🛠️ Services
UserContextService
Access user information and check permissions programmatically:
import { Injectable } from '@nestjs/common';
import { UserContextService } from 'permissions-contractx';
@Injectable()
export class MyService {
constructor(private userContext: UserContextService) {}
async doSomething() {
// Get user information
const user = this.userContext.getUser();
const userId = this.userContext.getUserId();
const roles = this.userContext.getUserRoles();
// Check permissions
if (this.userContext.hasRole('admin')) {
// Do admin stuff
}
if (this.userContext.hasPermission('users.create')) {
// Can create users
}
if (this.userContext.isClientUser()) {
// Client-side user logic
}
// Get user summary for logging
const summary = this.userContext.getUserSummary();
console.log('User accessing service:', summary);
}
}🚫 Error Handling
The package provides specific error messages for different authentication and authorization failures:
Common Error Responses
// 401 - Missing token
{
"statusCode": 401,
"message": "Access token is required",
"error": "Unauthorized"
}
// 401 - Invalid token
{
"statusCode": 401,
"message": "Invalid access token",
"error": "Unauthorized"
}
// 401 - Expired token
{
"statusCode": 401,
"message": "Access token has expired",
"error": "Unauthorized"
}
// 403 - Insufficient permissions
{
"statusCode": 403,
"message": "Insufficient permissions. Required: users:create",
"error": "Forbidden"
}
// 403 - Missing roles
{
"statusCode": 403,
"message": "Insufficient roles. Required: admin",
"error": "Forbidden"
}Custom Error Handling
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { UnauthorizedException, ForbiddenException } from '@nestjs/common';
@Catch(UnauthorizedException, ForbiddenException)
export class AuthExceptionFilter implements ExceptionFilter {
catch(exception: UnauthorizedException | ForbiddenException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
// Custom error handling logic
response.status(exception.getStatus()).json({
statusCode: exception.getStatus(),
message: exception.message,
timestamp: new Date().toISOString(),
});
}
}💾 Database Integration & ODS Seeder
ODS Roles and Permissions Seeder
The package includes a complete seeder for the ODS (Operational Data Store) roles and permissions matrix with 16 predefined roles and 23 modules:
import { ContractXRolePermissionSeeder } from '@your-org/permissions-contractx';
@Injectable()
export class DatabaseSeederService {
constructor(
private readonly seeder: ContractXRolePermissionSeeder,
// Your repository dependencies
@InjectRepository(Role) private roleRepo: Repository<Role>,
@InjectRepository(Permission) private permissionRepo: Repository<Permission>,
@InjectRepository(RolePermission) private rolePermissionRepo: Repository<RolePermission>
) {}
async seedRolesAndPermissions() {
const { roles, permissions, rolePermissions } = await this.seeder.seed();
// Save to database
const savedRoles = await this.roleRepo.save(roles);
const savedPermissions = await this.permissionRepo.save(permissions);
// Update IDs for role-permission mappings
const mappingsWithIds = rolePermissions.map(rp => ({
...rp,
roleId: savedRoles.find(r => r.name === roles.find(role => role.id === rp.roleId)?.name)?.id,
permissionId: savedPermissions.find(p => p.name === permissions.find(perm => perm.id === rp.permissionId)?.name)?.id
}));
await this.rolePermissionRepo.save(mappingsWithIds);
// Validate seeded data
const isValid = await this.seeder.validateSeed(savedRoles, savedPermissions, mappingsWithIds);
if (!isValid) {
throw new Error('Seeder validation failed');
}
// Get statistics
const stats = this.seeder.getSeederStats();
console.log('Seeding completed:', stats);
/*
Output:
{
totalRoles: 16,
totalPermissions: 161, // 23 modules × 7 actions each
totalModules: 23,
rolesByType: {
system: 4,
client: 6,
provider: 6
},
permissionsByCategory: {
contract_management: 98,
financial: 21,
system_admin: 21,
user_management: 7,
client_management: 7,
provider_management: 7
}
}
*/
}
}ODS Roles Matrix (16 Roles)
The seeder creates the following roles based on the complete ODS specification:
🔧 System Roles
- superadmin: Full system access to all modules
- support: Broad access excluding client management
- auditor: Read-only audit and compliance access
- guest: Limited read-only access
👥 Client-Side Roles
- client_contract_admin: Full contract management for client
- client_performance_manager: SLA and performance management
- client_finance_manager: Financial operations and invoicing
- client_reports_manager: Reporting and analytics
- client_relationship_manager: Relationship and meeting management
- client_risk_manager: Risk and security management
🏢 Provider-Side Roles
- provider_contract_admin: Provider contract administration
- provider_performance_manager: Provider performance management
- provider_finance_manager: Provider financial operations
- provider_relationship_manager: Provider relationship management
- provider_risk_manager: Provider risk management
- provider_operator: Operational support and deliverable management
📊 ODS Modules (23 Modules)
The seeder covers all ContractX modules with comprehensive CRUD permissions:
- Core Management: clients, contracts, users, providers, documents, clauses
- Deliverables: deliverables, subdeliverables, deliverable_history
- SLA Management: sla_services, measurement_windows, credit_service_levels
- Collaboration: meetings, meeting_participants, action_items
- Notifications: notification_escalations
- Financial: invoice_services, invoice_lines
- System: security_control, configuration, workflows
Database Entity Examples
For database integration with TypeORM, implement the seeder interfaces:
// Role Entity
@Entity('roles')
export class Role implements RoleEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
name: string;
@Column()
displayName: string;
@Column()
description: string;
@Column()
type: string;
@Column()
scope: string;
@Column()
level: number;
@Column({ default: true })
isActive: boolean;
@Column()
tenantAssociation: string;
@Column('jsonb')
metadata: Record<string, any>;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// Permission Entity
@Entity('permissions')
export class Permission implements PermissionEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
name: string;
@Column()
displayName: string;
@Column()
description: string;
@Column()
module: string;
@Column()
action: string;
@Column()
category: string;
@Column()
scope: string;
@Column({ default: true })
isActive: boolean;
@Column('jsonb')
metadata: Record<string, any>;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// Role-Permission Junction
@Entity('role_permissions')
export class RolePermission implements RolePermissionEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
roleId: string;
@Column()
permissionId: string;
@Column({ default: true })
isActive: boolean;
@Column('jsonb', { nullable: true })
metadata: Record<string, any>;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// User entity example with JWT payload
@Entity('users')
export class User implements JwtPayload {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
email: string;
@Column('simple-array')
roles: string[];
@Column('simple-array')
permissions: string[];
@Column({ nullable: true })
clientId?: string;
@Column({ nullable: true })
providerId?: string;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}Integration with Auth Service
// In your auth-service-contract
@Injectable()
export class AuthService {
constructor(
private readonly seeder: ContractXRolePermissionSeeder,
private readonly authorizationService: ContractXAuthorizationService,
private readonly validationService: ContractXValidationService,
) {}
async initializeSystem() {
// Seed ODS roles and permissions
await this.seeder.seed();
}
async validateUserAccess(user: JwtPayload, requiredPermission: string) {
return this.authorizationService.checkPermission(user, requiredPermission);
}
async getUserAccessMatrix(user: JwtPayload) {
return this.authorizationService.generateAccessMatrix(user);
}
}🌍 Environment Variables
| Variable | Description | Default | Required |
|----------|-------------|---------|----------|
| JWT_SECRET | JWT signing secret | - | ✅ Yes |
| JWT_ISSUER | Token issuer | contractx-api | No |
| JWT_AUDIENCE | Token audience | contractx-users | No |
| JWT_EXPIRES_IN | Token expiration | 15m | No |
| JWT_CLOCK_TOLERANCE | Clock tolerance (seconds) | 0 | No |
| ENABLE_GLOBAL_AUTH | Apply auth guard globally | false | No |
| ENABLE_GLOBAL_ROLES | Apply roles guard globally | false | No |
| ENABLE_GLOBAL_PERMISSIONS | Apply permissions guard globally | false | No |
| ENABLE_AUTH_LOGGING | Enable security logging | false | No |
| DISABLE_AUTH | Disable authentication | false | No |
🔒 Security Best Practices
1. JWT Token Structure
The package expects JWT tokens with this payload structure:
interface JwtPayload {
sub: string; // User ID
role: string[]; // User roles
permissions: string[]; // User permissions
fullName: string; // User's full name
email?: string; // User's email
clientId?: string; // Organization/client ID
sessionId?: string; // Session tracking
iat?: number; // Issued at
exp?: number; // Expires at
}2. Environment Variables
# Required
JWT_SECRET=your-256-bit-secret-key-here
JWT_ISSUER=your-api-name
JWT_AUDIENCE=your-api-users
# Optional but recommended
JWT_EXPIRES_IN=15m
JWT_CLOCK_TOLERANCE=30
# Security
ENABLE_AUTH_LOGGING=true3. Global Guards
// For maximum security, enable global authentication
PermissionsContractXModule.register({
guards: {
enableGlobalAuth: true, // All routes require auth by default
enableGlobalRoles: false, // Only specific routes check roles
enableGlobalPermissions: false, // Only specific routes check permissions
},
})🧪 Testing
Unit Testing
import { Test } from '@nestjs/testing';
import { JwtService } from '@nestjs/jwt';
import { JwtAuthGuard } from 'permissions-contractx';
describe('Authentication', () => {
let guard: JwtAuthGuard;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
JwtAuthGuard,
{
provide: JwtService,
useValue: { verifyAsync: jest.fn() },
},
// ... other providers
],
}).compile();
guard = module.get(JwtAuthGuard);
});
it('should authenticate valid token', async () => {
// Test implementation
});
});Integration Testing
import * as request from 'supertest';
describe('Protected Routes', () => {
it('should reject unauthenticated requests', () => {
return request(app.getHttpServer())
.get('/protected-route')
.expect(401);
});
it('should allow authenticated requests', () => {
return request(app.getHttpServer())
.get('/protected-route')
.set('Authorization', `Bearer ${validToken}`)
.expect(200);
});
});🚀 Production Deployment
Docker Support
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
ENV NODE_ENV=production
ENV JWT_SECRET=${JWT_SECRET}
ENV JWT_ISSUER=contractx-production
EXPOSE 3000
CMD ["npm", "run", "start:prod"]Environment Configuration
# docker-compose.yml
version: '3.8'
services:
app:
build: .
environment:
- NODE_ENV=production
- JWT_SECRET=${JWT_SECRET}
- JWT_ISSUER=contractx-api
- JWT_AUDIENCE=contractx-users
- ENABLE_AUTH_LOGGING=true📖 Examples
Complete Controller Example
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
UseGuards,
} from '@nestjs/common';
import {
JwtAuthGuard,
RolesGuard,
PermissionsGuard,
CurrentUser,
Roles,
RequirePermissions,
Public,
ClientOnly,
AdminOnly,
JwtPayload,
} from 'permissions-contractx';
@Controller('contracts')
@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
export class ContractsController {
@Public()
@Get('health')
getHealth() {
return { status: 'OK' };
}
@Get()
@RequirePermissions('contracts.read', 'contracts.filter')
async getContracts(@CurrentUser() user: JwtPayload) {
// User must have both read AND filter permissions
return { contracts: [], requestedBy: user.fullName };
}
@Post()
@ClientOnly()
@RequirePermissions('contracts.create')
async createContract(
@Body() contractData: any,
@CurrentUser() user: JwtPayload,
) {
// Only client-side users with create permission
return { id: '123', createdBy: user.sub };
}
@Put(':id/approve')
@AdminOnly()
@RequirePermissions('contracts.approve')
async approveContract(
@Param('id') id: string,
@CurrentUser() user: JwtPayload,
) {
// Only admins with approval permission
return { id, approvedBy: user.fullName, approvedAt: new Date() };
}
@Delete(':id')
@Roles('superadmin')
async deleteContract(@Param('id') id: string) {
// Only superadmin can delete
return { message: `Contract ${id} deleted` };
}
}🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🆘 Support
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 📚 Documentation: Full Documentation
📊 Package Stats
- ✅ Zero Dependencies (only peer dependencies)
- 🚀 TypeScript First - Built with TypeScript for TypeScript
- 📦 Tree Shakeable - Only import what you need
- 🔒 Security Focused - Built with security best practices
- 🧪 Well Tested - Comprehensive test coverage
Made with ❤️ by the ContractX Team
