Custom Guards

Role, permission, and clearance checks

Each guard below checks one kind of rule. Copy the one closest to what you need and adapt it.

Basic Guard

The simplest guard allows a call only when a guard earlier in the chain has identified the caller.

import { Guard, ExecutionContext, Injectable } from '@nitrostack/core';

@Injectable({ deps: [] })
export class AuthGuard implements Guard {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    // Check if user is authenticated
    if (!context.auth?.subject) {
      return false;  // Deny access
    }
    return true;  // Allow access
  }
}

Role-Based Guard

Allows a call only when the caller has the admin role.

@Injectable({ deps: [] })
export class AdminGuard implements Guard {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const roles = context.auth?.roles as string[] | undefined;
    
    if (!roles || !roles.includes('admin')) {
      context.logger.warn('Admin access denied', {
        userId: context.auth?.subject,
        roles
      });
      return false;
    }
    
    return true;
  }
}

Permission-Based Guard

Takes the permission it checks as a constructor argument, so one class covers many permissions.

@Injectable({ deps: [] })
export class PermissionGuard implements Guard {
  constructor(private requiredPermission: string) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const permissions = context.auth?.permissions as string[] | undefined;
    return permissions?.includes(this.requiredPermission) ?? false;
  }
}

// Factory function for different permissions
export function RequirePermission(permission: string) {
  return class extends PermissionGuard {
    constructor() {
      super(permission);
    }
  };
}

// Usage
@UseGuards(RequirePermission('products:write'))
async createProduct(input: any, ctx: ExecutionContext) {
  // ...
}

Clearance Level Guard

Ranks callers by level and allows a call only at or above the level the tool needs.

type ClearanceLevel = 'public' | 'crew' | 'officer' | 'commander' | 'captain';

const CLEARANCE_HIERARCHY: Record<ClearanceLevel, number> = {
  public: 0,
  crew: 1,
  officer: 2,
  commander: 3,
  captain: 4
};

@Injectable({ deps: [] })
export class ClearanceGuard implements Guard {
  constructor(private requiredLevel: ClearanceLevel) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const userLevel = (context.auth?.clearance as ClearanceLevel) || 'public';
    const userRank = CLEARANCE_HIERARCHY[userLevel] ?? 0;
    const requiredRank = CLEARANCE_HIERARCHY[this.requiredLevel];

    if (userRank < requiredRank) {
      context.logger.warn('Clearance check failed', {
        userId: context.auth?.subject,
        userLevel,
        requiredLevel: this.requiredLevel
      });
      return false;
    }

    context.logger.info('Clearance check passed', {
      userLevel,
      requiredLevel: this.requiredLevel
    });
    return true;
  }
}

// Factory for specific clearance levels
export class OfficerClearanceGuard extends ClearanceGuard {
  constructor() { super('officer'); }
}

export class CommanderClearanceGuard extends ClearanceGuard {
  constructor() { super('commander'); }
}

More guard patterns

On this page