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

@agility-luhn/iam

v1.5.1

Published

IAM (Identity and Access Management) para Luhn: autenticação, autorização, MFA, controle de rotas e papéis.

Readme

@agility-luhn/iam

IAM (Identity and Access Management) para aplicações LUHN. Distribuído como plugin do runtime: ao registrar o plugin em createLuhnServer({ plugins: [createIamPlugin(...)] }), o app hospedeiro ganha as entidades de identidade, services de autenticação/senha/menu, e o enforcement automático de Authorization: Bearer <jwt> em todas as rotas (exceto allowlist pública).

Pacote público do monorepo LUHN, publicado como @agility-luhn/iam no npm.


1. O que o pacote entrega

| Camada | Conteúdo | |---------------------------|----------| | Modelagem .luhn | luhn/iam.luhn — application; declarações organizadas nas pastas de cada módulo | | Artefatos gerados | src/luhn/ — interfaces/Zod/services/controllers/handles + DDL Postgres + OpenAPI | | Plugin para o runtime | src/plugin.ts — createIamPlugin(options) | | Hashing de senha | src/passwords.ts — argon2id com parâmetros OWASP 2024 | | Assinatura JWT | src/jwt.ts — IamJwtSigner (HS256 default, RS256 opcional) | | Tokens opacos | src/tokens.ts — geração, hash SHA-256 e verificação em tempo constante | | Implementação dos services | src/services/ — auth, password, menu (handlers reais) | | Compilador embarcado | src/compile-iam.ts — regera os artefatos sob src/luhn | | Entrypoint público | src/index.ts |


2. Modelo de domínio (luhn/)

A aplicação iam declara quatro módulos. Cada módulo mantém suas declarações na pasta de mesmo nome:

application iam (v0.1.0)
├── module identity (owner true) — records e businesses de identidade
├── module auth             — login, refresh, logout e me
├── module password         — define, change, requestRecovery e completeRecovery
└── module access           — myRoutes

O arquivo luhn/iam.luhn contém somente a declaração da application. As demais declarações ficam em luhn/<module>/, fazendo do diretório a fonte de verdade para o módulo de cada símbolo.

2.1 Tabelas Postgres geradas

Schemas: iam. Tabelas:

  • iam.user, iam.user_roles, iam.user_units.
  • iam.role, iam.role_permissions.
  • iam.app.
  • iam.app_module, iam.app_module_routes.

Cada registro de iam.app_module pertence obrigatoriamente a uma aplicação por meio de app_id.

Tipos físicos relevantes:

  • Enums (user_status, mfa_type, http_method, etc.) são enum nativo Postgres — armazenam o nome ('ACTIVE', 'GET', 'POST', ...), não o ordinal.
  • passwordHash e totpSecret mapeiam para TEXT (encriptografia em repouso é responsabilidade do banco).
  • registrationToken e recoveryToken armazenam apenas hash SHA-256 (veja src/tokens.ts).

O IAM não tem multitenancy próprio. Todas as suas businesses usam @scope global. O app hospedeiro decide se acopla as entidades de IAM ao seu conceito de conta/unidade (via mixin, FK extra, ou políticas RLS).


3. Plugin do Runtime

import { createLuhnServer } from '@agility-luhn/runtime';
import { createIamPlugin, IamJwtSigner, PasswordHasher } from '@agility-luhn/iam';

const iamPlugin = createIamPlugin({
  jwt: new IamJwtSigner({
    signingKey: process.env.JWT_SECRET!,        // HS256: segredo simétrico
    issuer: 'meu-app',
    audience: 'meu-app-api',
    accessTtlSeconds: 15 * 60,                   // default 15 min
    refreshTtlSeconds: 14 * 24 * 60 * 60         // default 14 dias
  }),
  hasher: new PasswordHasher(),                  // argon2id (64 MiB / 3 it / 4 lanes)
  mailer: async ({ to, subject, body }) => { /* opcional */ }
});

const server = await createLuhnServer({
  luhnDir: './luhn',
  setup,
  plugins: [iamPlugin]
});

await server.listen(3300);

3.1 Contrato do plugin

createIamPlugin(options): LuhnPlugin devolve:

  • name: '@agility-luhn/iam'.
  • luhnDirs: [<pasta luhn do pacote>] — adiciona as entidades/services do IAM à compilação do app.
  • install(ctx) — registra os handlers TS dos services (auth.login, auth.refresh, auth.logout, auth.me, password.define, password.change, password.requestRecovery, password.completeRecovery, access.myRoutes) injetando dependências (db, jwt, hasher, mailer?).
  • authenticate(req) — lê Authorization: Bearer <jwt>, valida com jwt.verify, exige token_type='access', e devolve SecurityContext { userId, tenantId, roles, permissions }.
  • authorize(req) — política default:
    • libera apenas a allowlist pública abaixo;
    • exige req.user !== undefined em qualquer outra rota (incluindo CRUD /api/<mod>/<entidade>).
  • dispose() — no-op atual; previsto para fechar caches/Redis em versões futuras.

3.2 Allowlist pública (sem token)

| Método | Path | Por quê | |--------|-------------------------------------|---------| | POST | /api/auth/login | bootstrap do token | | POST | /api/auth/refresh | renovação de access via refresh token | | POST | /api/password/define | primeiro acesso (token de cadastro por e-mail) | | POST | /api/password/request-recovery | usuário esqueceu a senha | | POST | /api/password/complete-recovery | consome token de recuperação |

Qualquer outra rota (/api/auth/me, /api/auth/logout, /api/password/change, /api/access/my-routes, CRUDs do app inteiro) responde 401 UNAUTHORIZED sem token e 403 FORBIDDEN quando há token mas autorização nega.


4. Services Expostos

4.1 auth.login — POST /api/auth/login

// request
{ "login": "admin", "password": "...", "unitId": "...", "mfaCode": "123456" }
// response
{
  "accessToken": "...",   "refreshToken": "...",
  "expiresIn": 900,       "tokenType": "Bearer",
  "user": { /* identity.user */ },
  "unitId": "...",        "mfaRequired": false
}

Erros: INVALID_CREDENTIALS, USER_INACTIVE, USER_PENDING, PASSWORD_EXPIRED, MFA_REQUIRED, MFA_INVALID, INVALID_UNIT.

Internamente: busca o business identity.user, compara hash com hasher.verify, atualiza last_login_at, emite par access_token + refresh_token via IamJwtSigner.

4.2 auth.refresh — POST /api/auth/refresh

Consome refresh token, valida token_type='refresh', devolve novo access_token (rotacionando refresh quando configurado).

4.3 auth.logout — POST /api/auth/logout

Invalida o refresh token (idealmente via blacklist por jti).

4.4 auth.me — POST /api/auth/me

Retorna o usuário corrente derivado de req.user.userId (resolvido pelo authenticate).

4.5 password.define — POST /api/password/define

Consome registrationToken (hash conferido com verifyToken), grava passwordHash (argon2id), marca usuário como ACTIVE + emailVerified. Erros: INVALID_TOKEN, EXPIRED_TOKEN, WEAK_PASSWORD.

4.6 password.change — POST /api/password/change

Exige autenticação. Valida senha atual, aplica nova com hasher, limpa passwordExpired.

4.7 password.requestRecovery — POST /api/password/request-recovery

Gera recoveryToken opaco (32 bytes, base64url), persiste só o hash com expiração, dispara e-mail via mailer (quando configurado).

4.8 password.completeRecovery — POST /api/password/complete-recovery

Consome recoveryToken, redefine senha, limpa token.

4.9 access.myRoutes — POST /api/access/my-routes

Cruza identity.appModule (+ routes) com os papéis do usuário corrente para devolver a árvore de menu autorizada. Base para a UI montar a navegação após login.


5. Helpers Framework

5.1 PasswordHasher (src/passwords.ts)

const hasher = new PasswordHasher({ memoryCost: 65536, timeCost: 3, parallelism: 4 });
const hash = await hasher.hash(plain);
const ok   = await hasher.verify(hash, plain);
const stale = hasher.needsRehash(hash); // true quando parâmetros mudaram

Padrões alinhados ao OWASP 2024+: argon2id, 64 MiB de memória, 3 iterações, 4 lanes. Construtor aceita override para CI/testes.

5.2 IamJwtSigner (src/jwt.ts)

const signer = new IamJwtSigner({
  algorithm: 'HS256',                  // default; suporta 'RS256' com par de chaves
  signingKey: process.env.JWT_SECRET!,
  verificationKey: undefined,          // RS256: chave pública PEM
  issuer: 'meu-app',
  audience: 'meu-app-api',
  accessTtlSeconds: 900,
  refreshTtlSeconds: 1209600
});

const access  = signer.signAccessToken({ sub, conta_id, login, roles, mfa: true });
const refresh = signer.signRefreshToken({ sub, conta_id, jti });
const claims  = signer.verify(token, 'access');

Claims: sub, conta_id, unidade_id?, login, roles[], mfa?, token_type: 'access' | 'refresh'.

5.3 Tokens opacos (src/tokens.ts)

const { raw, hash } = generateOpaqueToken(32);   // entrega `raw` por e-mail; grava `hash` no banco
const ok    = verifyToken(rawRecebido, hashArmazenado); // comparação timing-safe
const valid = tokenIsValid(expiraEmIso);

Tokens em claro nunca são persistidos. O banco recebe apenas o SHA-256 hex; a verificação usa crypto.timingSafeEqual.


6. Compilação e Regeneração

Sempre que luhn/iam.luhn mudar, regere os artefatos:

pnpm --filter @agility-luhn/iam compile    # luhn compile -o ./src/luhn
pnpm --filter @agility-luhn/iam build      # tsc -p tsconfig.json

O compile-iam.ts:

  • usa iamSetup (src/compile-iam.ts) — driver postgres, runtime importPath @agility-luhn/runtime/src/services.
  • escreve em src/luhn/ (artefatos TS + DDL database.json + openapi.json + objects-map.json).
  • imprime contagem de artefatos, erros e avisos.

tsconfig.json exclui compile-iam.ts do build do pacote (é script, não código de runtime).


7. Bootstrap: usuário admin

O playground inclui seed-admin.js (template) para gerar o hash argon2id do primeiro usuário e inseri-lo em iam.user com status='ACTIVE'. Em produção, prefira usar o fluxo registrationToken → password.define.


8. Integração com Banco (migração de rotas)

O catálogo identity.appModule (+ routes) é a base do access.myRoutes. O app hospedeiro deve popular esse catálogo a partir das rotas compiladas da aplicação.


9. Limitações Atuais

  • MFA: campos modelados (mfaEnabled, mfaType, mfaSecret), mas o fluxo TOTP/SMS/email ainda é placeholder em auth.login.
  • Refresh blacklist: depende da app hospedeira definir storage (Redis/coluna refresh_tokens).
  • Audit log: ainda não emitido pelo plugin.
  • Policy beyond roles: authorize atual é all-or-nothing por presença de usuário; políticas finas (route_actions, scopes) ficam a cargo do app ou de evoluções da authorize chain.
  • Multi-tenant: nenhum filtro automático por conta_id em queries CRUD — o SecurityContext é exposto, mas a propagação para handleCrudRoute está no roadmap.

10. Convenções

  • Comunicação no projeto: pt-BR. Comentários do código: EN.
  • exactOptionalPropertyTypes: true em todo o monorepo → opcionais saem com | undefined explícito.
  • Cross-package imports usam o nome do pacote (@agility-luhn/runtime), nunca caminhos relativos para outras packages/.
  • Após alterar generators do compiler, regere IAM e playground para validar.