JSPM

  • Created
  • Published
  • Downloads 51
  • Score
    100M100P100Q91521F
  • License MIT

ExGuard backend SDK for user role and permission validation

Package Exports

  • exguard-backend

Readme

ExGuard Backend SDK

Simple RBAC/ABAC permission guard for NestJS.

Installation

npm install exguard-backend

Quick Setup

1. Copy Guard Files

Create src/exguard/exguard.guard.ts:

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException, Inject, Optional } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ExGuardBackend } from 'exguard-backend';

export const EXGUARD_PERMISSIONS_KEY = 'exguard_permissions';
export const EXGUARD_ROLES_KEY = 'exguard_roles';

@Injectable()
export class ExGuardPermissionGuard implements CanActivate {
  constructor(
    @Optional() @Inject('EXGUARD_INSTANCE') private exGuard: ExGuardBackend,
    private reflector: Reflector,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = this.extractToken(request);

    if (!token) throw new UnauthorizedException('No token provided');
    if (!this.exGuard) return true;

    const authResult = await this.exGuard.authenticate({ token, request });

    if (!authResult.allowed) throw new ForbiddenException(authResult.error || 'Access denied');
    if (!authResult.user) throw new ForbiddenException('User not found');

    const handler = context.getHandler();
    const permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, handler);

    if (permMeta) {
      const { permissions, requireAll } = permMeta;
      const userPermissions = authResult.user.modules?.flatMap(m => m.permissions) || [];

      if (requireAll) {
        if (!permissions.every(p => userPermissions.includes(p))) {
          throw new ForbiddenException('Insufficient permissions');
        }
      } else {
        if (!permissions.some(p => userPermissions.includes(p))) {
          throw new ForbiddenException('Insufficient permissions');
        }
      }
    }

    request.user = authResult.user;
    return true;
  }

  private extractToken(request: any): string | null {
    const auth = request.headers?.authorization;
    return auth?.startsWith('Bearer ') ? auth.substring(7) : request.headers?.['x-access-token'] || null;
  }
}

export function RequirePermissions(permissions: string[], requireAll = false) {
  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    Reflect.defineMetadata(EXGUARD_PERMISSIONS_KEY, { permissions, requireAll }, descriptor.value);
  };
}

Create src/exguard/exguard.module.ts:

import { Module, Global, DynamicModule } from '@nestjs/common';
import { ExGuardBackend } from 'exguard-backend';

@Global()
@Module({})
export class ExGuardModule {
  static forRoot(options: { baseUrl: string; apiKey: string; cache?: { enabled?: boolean; ttl?: number } }): DynamicModule {
    const exGuard = new ExGuardBackend({
      baseUrl: options.baseUrl,
      apiKey: options.apiKey,
      cache: options.cache || { enabled: true, ttl: 300000 },
    });

    return {
      module: ExGuardModule,
      providers: [
        { provide: 'EXGUARD_INSTANCE', useValue: exGuard },
      ],
      exports: ['EXGUARD_INSTANCE'],
    };
  }
}

2. Configure AppModule

import { Module } from '@nestjs/common';
import { ExGuardModule } from './exguard/exguard.module';

@Module({
  imports: [
    ExGuardModule.forRoot({
      baseUrl: 'https://api.exguard.com',
      apiKey: process.env.EXGUARD_API_KEY,
      cache: { enabled: true, ttl: 300000 },
    }),
  ],
})
export class AppModule {}

3. Use in Controllers

import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import { ExGuardPermissionGuard, RequirePermissions } from '@/exguard/exguard.guard';

@Controller('items')
@UseGuards(ExGuardPermissionGuard)
export class ItemsController {
  
  @Get()
  @RequirePermissions(['item:read'])
  findAll() { }

  @Post()
  @RequirePermissions(['item:create'])
  create() { }

  @Get('drafts')
  @RequirePermissions(['item:read_draft', 'item:admin']) // ANY of these
  findDrafts() { }

  @Delete(':id')
  @RequirePermissions(['item:delete', 'admin'], true) // ALL of these
  delete() { }
}

Token Format

The guard extracts token from:

  • Authorization: Bearer <token> header
  • x-access-token header

Configuration

Option Type Default Description
baseUrl string required ExGuard API URL
apiKey string required Your API key
cache.enabled boolean true Enable caching
cache.ttl number 300000 Cache TTL in ms (5 min)

Express/Fastify (Non-NestJS)

import { createExGuardExpress } from 'exguard-backend';

const guard = createExGuardExpress({
  baseUrl: 'https://api.exguard.com',
  apiKey: process.env.EXGUARD_API_KEY,
});

// Use as middleware
app.use('/api', guard.requirePermissions(['item:read']));

License

MIT