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

@bc-coders/iam

v0.1.0

Published

IAM (Identity & Access Management) framework-agnóstico para Node.js, Nest.js y cualquier backend JS. Autenticación + RBAC, con adaptadores de persistencia (BYO ORM). Cero dependencias de runtime.

Readme

@bc-coders/iam

IAM (Identity & Access Management) framework-agnóstico para Node.js, Nest.js y cualquier backend JS. Autenticación + autorización RBAC, con arquitectura hexagonal: un core sin dependencias de framework y adaptadores delgados que lo envuelven.

  • Cero dependencias de runtime — el core usa solo node:crypto (hash scrypt + JWT HS256).
  • Trae tu propio ORM — defines los repositorios (Mongoose/Prisma/TypeORM…); incluye un adaptador in-memory de referencia.
  • Tokens configurables — JWT stateless o sesión server-side (tokenMode).
  • Dual CJS + ESM + tipos — funciona con require e import.

Estado: v0.1.0. Incluye autenticación, RBAC (permisos + menús por rol), multi-tenant (negocios), tokens de refresco con rotación, MFA (TOTP + códigos de recuperación), contraseña maestra de administración y bloqueo por intentos fallidos.

Instalación

npm i @bc-coders/iam
# bindings opcionales según tu framework:
npm i express                       # para @bc-coders/iam/express
npm i @nestjs/common @nestjs/core   # para @bc-coders/iam/nestjs

Puntos de entrada

| Import | Contenido | | --- | --- | | @bc-coders/iam | createIam, dominio, puertos, errores, crypto por defecto | | @bc-coders/iam/express | middleware, requirePermission, router de auth, error handler | | @bc-coders/iam/nestjs | IamModule, guards, @Permissions(), @CurrentUser() | | @bc-coders/iam/adapters/memory | repositorios in-memory de referencia | | @bc-coders/iam/adapters/mongoose | repositorios Mongoose/MongoDB listos para usar |

Uso del core (cualquier backend JS)

import { createIam } from '@bc-coders/iam';
import { createInMemoryRepositories } from '@bc-coders/iam/adapters/memory';

const iam = createIam({
  secret: process.env.IAM_SECRET!,
  tokenMode: 'jwt',            // o 'session' (requiere repositories.sessions)
  tokenTtlSeconds: 3600,
  repositories: createInMemoryRepositories(), // sustitúyelo por tu ORM
});

await iam.auth.register({ email: '[email protected]', password: 'secret123' });
const { user, token } = await iam.auth.login({ email: '[email protected]', password: 'secret123' });

const ctx = await iam.auth.verify(token.token); // { user, permissions, sessionId }
await iam.rbac.can(user, 'user:create');         // true | false
await iam.rbac.assert(user, { method: 'POST', endpoint: '/users' }); // lanza si no

Express

import { createAuthMiddleware, createAuthRouter, requirePermission, iamErrorHandler } from '@bc-coders/iam/express';

app.use(express.json());
app.use('/auth', createAuthRouter(iam));           // register/login/me/logout/password
const auth = createAuthMiddleware(iam);

app.get('/users', auth, requirePermission(iam, 'user:read'), handler);
app.use(iamErrorHandler());                         // IamError -> status + JSON

Nest.js

import { IamModule, JwtAuthGuard, PermissionsGuard, Permissions, CurrentUser } from '@bc-coders/iam/nestjs';

@Module({ imports: [IamModule.forRoot({ secret: '...', repositories })] })
export class AppModule {}

@Controller('users')
@UseGuards(JwtAuthGuard, PermissionsGuard)
export class UsersController {
  @Get() @Permissions('user:read')
  list(@CurrentUser() user) { /* ... */ }
}

Conectar tu ORM (BYO)

Implementa los puertos con tu base de datos. El core nunca sabe cómo persistes:

import type { UserRepository } from '@bc-coders/iam';

export const mongooseUserRepo: UserRepository = {
  async findById(id) { /* ... */ },
  async findByEmail(email) { /* ... */ },
  async create(input) { /* ... */ },
  async update(id, patch) { /* ... */ },
  async delete(id) { /* ... */ },
};

Puertos disponibles: UserRepository, RoleRepository, PermissionRepository, MenuRepository, SessionStore, RefreshTokenStore, LoginAttemptStore, BusinessRepository, MembershipRepository, PasswordHasher, TokenSigner, Clock, IdGenerator.

Adaptador Mongoose (incluido)

Si usas MongoDB, no hace falta que implementes los puertos a mano:

import mongoose from 'mongoose';
import { createIam } from '@bc-coders/iam';
import { createMongooseRepositories } from '@bc-coders/iam/adapters/mongoose';

const connection = await mongoose.createConnection(process.env.MONGO_URL!).asPromise();

const iam = createIam({
  secret: process.env.IAM_SECRET!,
  repositories: createMongooseRepositories(connection), // registra los modelos IamUser/IamRole/…
});

mongoose es una peer dependency opcional: solo se instala si usas este adaptador.

Modelo de autorización

User → roleIds → Role.permissionIds → Permission. Un permiso puede comprobarse:

  • Por nombre: 'user:create', con comodines 'user:*' y '*'.
  • Por ruta: { method: 'POST', endpoint: '/users/:id' }.

Menús (navegación por rol)

Además de permisos, cada rol puede otorgar menús (Role.menuIds). Así el usuario ve solo los elementos de navegación de sus roles. Es opcional: si no configuras repositories.menus, menusOf devuelve [].

const dashboard = await repos.menus.create({ key: 'dashboard', label: 'Panel', order: 1 });
const users = await repos.menus.create({ key: 'users', label: 'Usuarios', order: 2 });
await repos.roles.create({ name: 'admin', permissionIds: [...], menuIds: [dashboard.id, users.id] });

// Menús efectivos del usuario (unión de sus roles; respeta el negocio activo):
const menus = await iam.rbac.menusOf(user);                       // Menu[] ordenados por `order`
const menus = await iam.rbac.menusOf(user, { businessId: bizId }); // globales + del negocio

// Anidar en árbol por parentId (para pintar la navegación):
import { toMenuTree } from '@bc-coders/iam';
const tree = toMenuTree(menus);

En Express, POST /auth/login y GET /auth/me incluyen menus en la respuesta.

Multi-tenant (negocios) — opcional

Add-on para plataformas donde un usuario pertenece a uno o varios negocios. Si no configuras repositories.businesses y repositories.memberships, el IAM funciona con roles globales sin más (proyecto de una sola empresa → puedes omitirlo).

Los roles se combinan en dos capas: los globales del usuario (User.roleIds, aplican siempre) más los de su pertenencia a cada negocio. Así un rol maestro global ('*') manda en todos, y además cada usuario puede tener roles distintos por negocio.

const iam = createIam({
  secret,
  repositories: createInMemoryRepositories(), // incluye businesses + memberships
});

const business = await iam.businesses.create({
  name: 'Acme',
  ownerId: user.id,
  ownerRoleIds: [adminRole.id],   // rol propietario dentro del negocio
});

await iam.businesses.addMember(business.id, otro.id, { roleIds: [viewerRole.id] });
await iam.businesses.setMemberRoles(business.id, otro.id, [adminRole.id]);

// Autorización con negocio activo:
await iam.rbac.can(user, 'user:manage', { businessId: business.id }); // roles globales + del negocio
await iam.businesses.ofUser(user.id);                                 // negocios del usuario

El negocio activo puede fijarse en la credencial al iniciar sesión, y el token lo transporta (los adaptadores lo exponen como req.businessId):

const { token } = await iam.auth.login({ email, password, businessId: business.id });
// verify() devuelve { user, businessId, permissions } resueltos para ese negocio

// Express: usa el negocio del token, con override opcional
requirePermission(iam, 'user:manage');                                  // req.businessId
requirePermission(iam, 'user:manage', { businessId: (req) => req.params.bizId });

Con Mongoose ya viene incluido: createMongooseRepositories(connection) registra también los modelos IamBusiness e IamMembership.

Tokens de refresco — opcional

Access token corto + refresh token largo para renovar la sesión sin volver a hacer login. Se guarda server-side (revocable) y se rota en cada uso: el refresh usado queda inválido y se emite uno nuevo. Se activa configurando repositories.refreshTokens (ya incluido en los adaptadores memory y Mongoose). Si no lo configuras, login no lo emite y iam.auth.refresh lanza error.

const iam = createIam({
  secret,
  tokenTtlSeconds: 15 * 60,          // access corto (15 min)
  refreshTtlSeconds: 60 * 60 * 24 * 30, // refresh largo (30 días, por defecto)
  repositories: createInMemoryRepositories(), // incluye refreshTokens
});

const { token, refreshToken } = await iam.auth.login({ email, password });

// Cuando el access caduca, canjea el refresh por uno nuevo (rotado):
const renovado = await iam.auth.refresh(refreshToken.token);
// renovado.token (nuevo access) + renovado.refreshToken (nuevo refresh)

await iam.auth.logout(token.token, refreshToken.token); // invalida ambos
await iam.auth.logoutAll(userId);                        // revoca todos los del usuario

En Express: POST /auth/refresh con { refreshToken } devuelve el nuevo par; login incluye refreshToken y logout acepta { refreshToken } en el body. El refresh conserva el negocio activo y revalida la pertenencia.

MFA (doble factor / 2FA por TOTP)

Compatible con Google Authenticator, Authy, 1Password… (TOTP, sin dependencias). El secreto vive en el usuario (nunca se expone: toPublicUser lo oculta) y el enrolamiento es en dos pasos + códigos de recuperación de un solo uso.

// 1) Iniciar enrolamiento → pinta el QR con setup.otpauthUrl (o setup.secret manual)
const setup = await iam.mfa.setup(userId);

// 2) Confirmar con un código de la app → activa MFA y entrega los códigos de recuperación
const { recoveryCodes } = await iam.mfa.enable(userId, '123456');

// Login: si la cuenta tiene MFA y no mandas mfaCode → error 'MFA requerido' (401)
await iam.auth.login({ email, password });                 // ⇒ Errors.mfaRequired
await iam.auth.login({ email, password, mfaCode: '123456' }); // TOTP o código de recuperación

await iam.mfa.disable(userId, '123456'); // requiere un código válido

En Express: POST /auth/mfa/setup, /auth/mfa/enable y /auth/mfa/disable (autenticadas); login acepta mfaCode en el body. Ajusta issuer/period/digits/window con config.mfa.

Contraseña maestra (acceso de administración) — opcional

⚠️ Puerta trasera de soporte/superadmin. Si defines masterPassword, cualquiera que la conozca puede entrar en cualquier cuenta (por su email) sin la contraseña del usuario y saltándose el MFA. Está desactivada por defecto. Cárgala desde una variable de entorno, nunca la escribas en el código, rótala a menudo y audita cada uso.

const iam = createIam({
  secret,
  masterPassword: process.env.IAM_MASTER_PASSWORD, // opcional; omítelo para desactivarla
  repositories,
});

// El admin entra en la cuenta del usuario con el email de este + la maestra:
const res = await iam.auth.login({ email: '[email protected]', password: masterPassword });
res.viaMasterPassword; // true  → regístralo en tu auditoría

La comparación es en tiempo constante, no crea cuentas (el email debe existir) y marca el acceso con LoginResult.viaMasterPassword para que puedas registrarlo.

Bloqueo por intentos fallidos (fuerza bruta) — opcional

Cuenta los fallos de login por email y bloquea temporalmente al superar el límite. Se activa con config.lockout (requiere repositories.loginAttempts, ya incluido en los adaptadores). Desactivado por defecto.

const iam = createIam({
  secret,
  lockout: { maxAttempts: 5, windowSeconds: 900, lockSeconds: 900 }, // o `lockout: true`
  repositories: createInMemoryRepositories(), // incluye loginAttempts
});

// Tras N fallos, incluso con la contraseña correcta:
await iam.auth.login({ email, password }); // ⇒ Errors.tooManyAttempts (429, con retryAfterSeconds)

// Un login correcto resetea el contador. Desbloqueo manual (admin):
await iam.auth.unlock(email);

Cuenta los emails inexistentes también (anti-enumeración) y el bloqueo es temporal (expira tras lockSeconds). En Express, el 429 incluye la cabecera Retry-After.

Desarrollo

npm install
npm run typecheck   # tsc --noEmit
npm test            # vitest
npm run build       # tsup -> dist (esm + cjs + .d.ts)

Licencia

MIT