npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

permissions-contractx

v1.0.2

Published

Enterprise-grade authentication and authorization package for NestJS microservices with role-based and permission-based access control

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.

TypeScript NestJS JWT License: MIT

✨ 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-contractx

Peer 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 development

4. 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=true

3. 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

📊 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