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

@hemia/iam

v0.0.2

Published

Gestión de identidad y acceso para sistemas Hemia

Readme

@hemia/iam

Un paquete robusto, flexible y escalable de autorización IAM (Identity and Access Management) basado en RBAC (Role-Based Access Control) y ABAC (Attribute-Based Access Control) inspirado en XACML. Ideal para aplicaciones Node.js y TypeScript que requieren un control granular sobre los accesos.

Características Principales

  • RBAC: Manejo claro de permisos basado en roles.
  • ⚖️ ABAC: Políticas dinámicas que permiten evaluaciones detalladas y granulares basadas en atributos.
  • 🚀 Integración sencilla con Express y Node.js.
  • ⚙️ Fácilmente extensible y personalizable.

Instalación

npm install @hemia/iam

Uso básico

Definición de roles y permisos

import { Role, Permission } from '@hemia/iam';

const roles: Role[] = [
  {
    name: 'admin',
    permissions: [
      { type: '*', action: '*' },
    ],
  },
  {
    name: 'user',
    permissions: [
      { type: 'user-profile', action: 'view' },
      { type: 'user-profile', action: 'edit' },
    ],
  },
];

Definición de políticas ABAC

import { IAMService, Policy } from '@hemia/iam';

const policies: Policy[] = [
  {
    name: 'OwnProfilePolicy',
    conditions: (subject, resource, action) =>
      resource.type === 'user-profile' && resource.id === subject.id && ['view', 'edit', 'delete'].includes(action),
  },
];

const iamService = new IAMService(policies);

Configuración del Contexto IAM

import { IAMContext, IAMService } from '@hemia/iam';
import { JwtManager } from '@hemia/jwt-manager';
import { CacheService } from '@hemia/cache-manager';

// Configurar servicios
const jwtService = new JwtManager();
const cacheService = new CacheService();
const iamService = new IAMService(policies);

// Crear contexto IAM
const iamContext = new IAMContext(
  iamService,
  jwtService,
  cacheService,
  'your-secret-key',
  { prefixKey: 'user:', fieldKey: 'userId' }
);

Middleware en Express

Ejemplo básico con validación de token y permisos

import express from 'express';
import { IAMMiddleware } from '@hemia/iam';
import { TokenType } from '@hemia/iam';

const router = express.Router();

// Ruta protegida que requiere autenticación y autorización
router.get('/profile', 
  IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'view',
    resource: { type: 'user-profile' },
    system: 'user-management'
  }),
  (req, res) => {
    res.json({ 
      message: 'Perfil del usuario obtenido exitosamente',
      user: req.user 
    });
  }
);

Ejemplo con múltiples tipos de token

// Acepta tanto tokens de credenciales como tokens de sesión
router.post('/admin/users', 
  IAMMiddleware(iamContext, [TokenType.CREDENTIALS, TokenType.TOKEN], {
    action: 'create',
    resource: { type: 'user' },
    system: 'admin-panel'
  }),
  (req, res) => {
    // Crear nuevo usuario
    res.json({ message: 'Usuario creado exitosamente' });
  }
);

Ejemplo con recurso dinámico basado en parámetros

// El ID del recurso se obtiene de los parámetros de la URL
router.put('/users/:id', 
  IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'edit',
    resource: { 
      type: 'user',
      getId: (req) => req.params.id
    },
    system: 'user-management'
  }),
  (req, res) => {
    const userId = req.params.id;
    // Actualizar usuario
    res.json({ message: `Usuario ${userId} actualizado exitosamente` });
  }
);

Ejemplo con sujeto personalizado

// Configurar sujeto personalizado para validaciones específicas
router.delete('/projects/:projectId/members/:memberId', 
  IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'remove',
    resource: { 
      type: 'project-member',
      getId: (req) => req.params.memberId
    },
    subject: {
      get: (req) => req.user,
      getId: (req) => req.user?.id
    },
    system: 'project-management'
  }),
  (req, res) => {
    // Remover miembro del proyecto
    res.json({ message: 'Miembro removido del proyecto' });
  }
);

Ejemplo con autenticación opcional

// Permite acceso sin token pero con funcionalidad limitada
router.get('/public/posts', 
  IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'view',
    resource: { type: 'post' },
    system: 'blog',
    auth: {
      allowNoToken: true,
      skipAuthorization: false
    }
  }),
  (req, res) => {
    if (req.user) {
      // Usuario autenticado - mostrar posts personalizados
      res.json({ posts: getPersonalizedPosts(req.user) });
    } else {
      // Usuario anónimo - mostrar posts públicos
      res.json({ posts: getPublicPosts() });
    }
  }
);

Ejemplo saltando autorización (solo autenticación)

// Solo valida el token pero no verifica permisos
router.get('/dashboard', 
  IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'view',
    resource: { type: 'dashboard' },
    system: 'main-app',
    auth: {
      skipAuthorization: true
    }
  }),
  (req, res) => {
    // Cualquier usuario autenticado puede acceder
    res.json({ 
      message: 'Dashboard cargado',
      user: req.user 
    });
  }
);

Ejemplo con router de API completo

// Router completo para gestión de usuarios
const userRouter = express.Router();

// Listar usuarios (solo administradores)
userRouter.get('/', 
  IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'list',
    resource: { type: 'user' },
    system: 'admin-panel'
  }),
  getUsersController
);

// Obtener usuario específico
userRouter.get('/:id', 
  IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'view',
    resource: { 
      type: 'user',
      getId: (req) => req.params.id
    },
    system: 'user-management'
  }),
  getUserController
);

// Crear nuevo usuario
userRouter.post('/', 
  IAMMiddleware(iamContext, [TokenType.CREDENTIALS], {
    action: 'create',
    resource: { type: 'user' },
    system: 'admin-panel'
  }),
  createUserController
);

// Actualizar usuario
userRouter.put('/:id', 
  IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'edit',
    resource: { 
      type: 'user',
      getId: (req) => req.params.id
    },
    system: 'user-management'
  }),
  updateUserController
);

// Eliminar usuario
userRouter.delete('/:id', 
  IAMMiddleware(iamContext, [TokenType.CREDENTIALS], {
    action: 'delete',
    resource: { 
      type: 'user',
      getId: (req) => req.params.id
    },
    system: 'admin-panel'
  }),
  deleteUserController
);

// Montar el router
app.use('/api/users', userRouter);

Ejemplo con manejo de errores personalizado

// Middleware con manejo de errores específico
router.get('/sensitive-data', 
  IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'access',
    resource: { type: 'sensitive-data' },
    system: 'security'
  }),
  (req, res) => {
    try {
      const data = getSensitiveData(req.user.id);
      res.json({ data });
    } catch (error) {
      res.status(500).json({ 
        message: 'Error al obtener datos sensibles',
        error: error.message 
      });
    }
  }
);

// Middleware de manejo de errores global
app.use((err, req, res, next) => {
  if (err.code === 'FORBIDDEN') {
    return res.status(403).json({
      message: 'Acceso denegado',
      required: err.required,
      system: err.system
    });
  }
  
  if (err.status === 401) {
    return res.status(401).json({
      message: 'Token de autorización requerido o inválido'
    });
  }
  
  next(err);
});

Estructura del Modelo

Subject (Usuario autenticado)

export interface Subject {
  id: string;
  roles: Role[];
  attributes?: Record<string, any>;
}

Resource (Recurso protegido)

export interface Resource {
  type: string;
  id?: string;
  attributes?: Record<string, any>;
}

Casos de Uso Avanzados

Políticas ABAC Complejas

const advancedPolicies: Policy[] = [
  {
    name: 'OwnerOrAdminPolicy',
    conditions: (subject, resource, action) => {
      // El propietario puede hacer cualquier cosa con sus recursos
      if (resource.id === subject.id) return true;
      
      // Los administradores pueden hacer cualquier cosa
      return subject.roles.some(role => role.name === 'admin');
    }
  },
  {
    name: 'BusinessHoursPolicy',
    conditions: (subject, resource, action) => {
      const now = new Date();
      const hour = now.getHours();
      
      // Operaciones sensibles solo en horario laboral (9-17)
      if (['delete', 'admin'].includes(action)) {
        return hour >= 9 && hour <= 17;
      }
      
      return true;
    }
  },
  {
    name: 'DepartmentAccessPolicy',
    conditions: (subject, resource, action) => {
      // Acceso basado en departamento
      if (resource.attributes?.department) {
        return subject.attributes?.department === resource.attributes.department;
      }
      return true;
    }
  }
];

Configuración de Múltiples Sistemas

// Diferentes configuraciones para diferentes módulos
const createSystemMiddleware = (system: string) => {
  return (action: string, resourceType: string, options = {}) => {
    return IAMMiddleware(iamContext, [TokenType.TOKEN], {
      action,
      resource: { type: resourceType },
      system,
      ...options
    });
  };
};

// Middleware específico por sistema
const adminMiddleware = createSystemMiddleware('admin-panel');
const userMiddleware = createSystemMiddleware('user-management');
const reportMiddleware = createSystemMiddleware('reporting');

// Uso en routers
router.get('/admin/stats', adminMiddleware('view', 'statistics'), getStatsController);
router.get('/users/profile', userMiddleware('view', 'user-profile'), getProfileController);
router.get('/reports/sales', reportMiddleware('generate', 'sales-report'), getSalesReportController);

Middleware Condicional

// Aplicar diferentes niveles de seguridad según el entorno
const conditionalSecurity = (req: Request, res: Response, next: NextFunction) => {
  const isProduction = process.env.NODE_ENV === 'production';
  const isHighSecurity = req.path.includes('/admin') || req.path.includes('/financial');
  
  if (isProduction && isHighSecurity) {
    // En producción, rutas de alta seguridad requieren credenciales
    return IAMMiddleware(iamContext, [TokenType.CREDENTIALS], {
      action: 'access',
      resource: { type: 'high-security' },
      system: 'security'
    })(req, res, next);
  } else {
    // Otras rutas solo requieren token de sesión
    return IAMMiddleware(iamContext, [TokenType.TOKEN], {
      action: 'access',
      resource: { type: 'standard' },
      system: 'general'
    })(req, res, next);
  }
};

router.use('/admin/*', conditionalSecurity);
router.use('/financial/*', conditionalSecurity);

Mejores Prácticas

1. Organización de Middlewares

// middlewares/auth.ts
export const authMiddlewares = {
  // Autenticación básica
  basic: IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'access',
    resource: { type: 'basic' },
    system: 'general',
    auth: { skipAuthorization: true }
  }),
  
  // Solo administradores
  adminOnly: IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'admin',
    resource: { type: 'admin-resource' },
    system: 'admin-panel'
  }),
  
  // Propietario del recurso
  ownerOnly: (resourceType: string) => IAMMiddleware(iamContext, [TokenType.TOKEN], {
    action: 'owner-access',
    resource: { 
      type: resourceType,
      getId: (req) => req.params.id
    },
    system: 'ownership'
  })
};

2. Manejo de Errores Centralizados

// middlewares/errorHandler.ts
export const iamErrorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
  // Log de errores de seguridad
  if (err.status === 403 || err.code === 'FORBIDDEN') {
    console.warn(`🚫 Access denied: ${req.method} ${req.path}`, {
      user: req.user?.id,
      required: err.required,
      system: err.system,
      ip: req.ip
    });
  }
  
  // Respuestas consistentes
  if (err.status === 401) {
    return res.status(401).json({
      code: 'UNAUTHORIZED',
      message: 'Authentication required'
    });
  }
  
  if (err.status === 403 || err.code === 'FORBIDDEN') {
    return res.status(403).json({
      code: 'FORBIDDEN',
      message: 'Insufficient permissions',
      required: err.required
    });
  }
  
  next(err);
};

3. Testing del Middleware

// tests/middleware.test.ts
import request from 'supertest';
import { app } from '../app';

describe('IAM Middleware', () => {
  test('should deny access without token', async () => {
    const response = await request(app)
      .get('/api/users')
      .expect(401);
      
    expect(response.body.code).toBe('UNAUTHORIZED');
  });
  
  test('should allow access with valid token and permissions', async () => {
    const token = generateTestToken({ roles: ['admin'] });
    
    const response = await request(app)
      .get('/api/users')
      .set('Authorization', `Bearer ${token}`)
      .expect(200);
  });
  
  test('should deny access with insufficient permissions', async () => {
    const token = generateTestToken({ roles: ['user'] });
    
    const response = await request(app)
      .delete('/api/users/123')
      .set('Authorization', `Bearer ${token}`)
      .expect(403);
      
    expect(response.body.code).toBe('FORBIDDEN');
  });
});

Ejemplo de verificación de autorización

const subject: Subject = {
  id: 'user123',
  roles: [roles[1]],
  attributes: { department: 'marketing' },
};

const resource: Resource = { type: 'user-profile', id: 'user123' };

const allowed = await iamService.can(subject, resource, 'view');
console.log(allowed ? 'Acceso concedido' : 'Acceso denegado');

Licencia

MIT — © Hemia Technologies