JSPM

  • Created
  • Published
  • Downloads 38
  • Score
    100M100P100Q91018F
  • License MIT

ExGuard backend SDK for user role and permission validation

Package Exports

  • exguard-backend

Readme

ExGuard Backend SDK

Simple RBAC permission guard for NestJS.

Installation

npm install exguard-backend

Quick Setup (Auto)

Run this command in your NestJS project root:

npx exguard-backend setup

This creates:

  • src/exguard/exguard.guard.ts - The permission guard
  • src/exguard/exguard.module.ts - The module

Manual Setup

1. Create Guard File

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 permMeta = this.reflector.get(EXGUARD_PERMISSIONS_KEY, context.getHandler());
    if (permMeta) {
      const userPermissions = authResult.user.modules?.flatMap(m => m.permissions) || [];
      const hasPermission = permMeta.requireAll
        ? permMeta.permissions.every(p => userPermissions.includes(p))
        : permMeta.permissions.some(p => userPermissions.includes(p));
      if (!hasPermission) 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) : null;
  }
}

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

2. Create Module File

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'],
    };
  }
}

3. 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,
    }),
  ],
})
export class AppModule {}

4. 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 {
  
  // User needs ANY of these permissions
  @Get()
  @RequirePermissions(['item:read'])
  findAll() { }

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

  // Multiple permissions - needs ALL
  @Delete(':id')
  @RequirePermissions(['item:delete', 'admin'], true)
  delete() { }
}

API Reference

Decorator Description
@RequirePermissions(['perm1']) User needs ANY of the permissions
@RequirePermissions(['perm1', 'perm2'], true) User needs ALL permissions

Token

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 (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,
});

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

License

MIT