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

@riosaude/sso-kit

v0.4.4

Published

Migra aplicações existentes para o SSO RioSaúde: gera a integração da API NestJS e do front (Vite + React ou Next.js), inclusive webhook, verifica cada etapa de verdade e acompanha a evolução numa página

Readme

@riosaude/sso-kit

Migra uma aplicação já existente para o SSO RioSaúde. Para começar um sistema novo do zero, use @riosaude/create-sso-app — o sso-kit é para quem já tem uma aplicação NestJS rodando — com ou sem autenticação própria — e precisa passar a entrar pelo SSO.

Comece aqui

Pré-requisitos: API em NestJS; front, se houver, em Vite + React ou Next.js App Router; Node 20+; projeto versionado no git (comece numa branch própria, ex.: integracao-sso).

  1. Escolha o modelo de permissão. Se o sistema atende várias unidades (hospitais, unidades de saúde) e o acesso ou as permissões dependem da unidade, use scoped; senão, flat. Na dúvida, pergunte ao administrador do SSO — com o modelo errado a sessão chega sem permissões. Ver flat vs scoped.
  2. Gere a integração, na pasta da API:
    npx @riosaude/sso-kit add
    O comando pergunta o slug do sistema no SSO, o modelo, se há front, a pasta do front e o framework do front (Vite ou Next.js). Instala as dependências, cria o .env e mostra o que falta.
  3. Ajuste as URLs para o endereço que o navegador usa — localhost só serve se o navegador estiver na mesma máquina:
    • no .env da API: APP_URL, SSO_REDIRECT_URI e WEB_URL;
    • no .env.local do front: VITE_SSO_API_URL (Vite) ou NEXT_PUBLIC_SSO_API_URL (Next.js), com o prefixo da API (ex.: http://10.0.0.5:3100/api/v1). Se o projeto já tem a sua própria (VITE_API_URL / NEXT_PUBLIC_API_URL), o kit reaproveita e o código gerado lê as duas — não fique com duas variáveis para a mesma API. Se o seu front descobre a URL da API em tempo de execução (ex.: um config.json por ambiente), faça src/sso/api usar a mesma origem: variável de build e configuração de runtime divergem no dia em que uma muda.
  4. Peça o cadastro ao administrador do SSO. Rode npx @riosaude/sso-kit status --abrir: a seção Dados para o cadastro no SSO traz slug, redirect_uris, post_logout_redirect_uris e webhook_url prontos, com botão para copiar e enviar. Você recebe de volta SSO_CLIENT_SECRET e SSO_WEBHOOK_SECRET.
  5. Preencha os dois secrets no .env, marque com @Public() as rotas da API que precisam continuar abertas e ajuste o front — a sessão passa a ser um cookie, não um token: ver Ajustes no front.
  6. Suba a API e o front e prove a integração:
    npx @riosaude/sso-kit status --live
    Corrija o que a página apontar até só restarem as etapas manuais.
  7. Teste com um usuário real — entrar no sistema e, com a tela aberta, ter o acesso revogado no painel (a sessão deve cair sozinha) — e registre:
    npx @riosaude/sso-kit confirmar login-real
    npx @riosaude/sso-kit confirmar revogacao-real
    A integração está concluída quando a página mostra Integração concluída. Commite o .sso-kit/estado.json para o time acompanhar.

Conforme o seu caso

Só o que se aplicar ao seu sistema — num app sem login nem usuários próprios, nenhum destes itens é necessário para concluir a integração.

  • Quer restringir telas ou rotas por permissão? Sem isso, qualquer pessoa com acesso ao sistema no SSO usa tudo (quem não tem acesso já é barrado pelo próprio SSO). Para restringir, decore as rotas com @RequirePermission('recurso.acao'); no front, ssoSession.hasPermission('recurso.acao') (Vite) ou useSsoSession((s) => s.hasPermission)('recurso.acao') (Next.js) esconde o que o usuário não pode usar. Para cadastrar permissões e perfis no SSO sem digitar no painel, veja sso-kit rbac.
  • O app já tinha login próprio? Remova a tela de login antiga, a guarda JWT/Passport e os endpoints de autenticação que o SSO substituiu. O status aponta rastros encontrados (@nestjs/passport, JwtStrategy…) numa etapa opcional.
  • Tabelas do domínio referenciam usuários (auditoria, "criado por")? Espelhe o usuário no banco local no login — ver Ponto de extensão.
  • Ao levar para produção:
    • peça um cadastro novo no SSO de produção (https://sso2.riosaude.rio.br), com as URLs públicas do sistema — os secrets são outros;
    • sirva API e front em HTTPS: com NODE_ENV=production o cookie de sessão sai com Secure e não trafega em HTTP;
    • com mais de uma instância da API, troque o armazenamento de sessões em memória por Redis ou tabela (ver Sessão, cookies e tela de login).

Ajustes no front

O add gera a sessão, a tela de login com a identidade do SSO e o gate que protege o app. O que ele não reescreve é o código do seu app — estes ajustes são seus, e a página do status aponta os que faltam.

Vite + React

Gerado em src/sso/, sem dependência nova. O add envolve o <App /> do src/main.tsx:

<SsoSessionGate>
  <App />
</SsoSessionGate>

O gate fica fora do router e exibe sozinho a tela de login em /login — não é preciso criar rota. Por padrão todo o app exige login; para deixar páginas abertas, passe rotasPublicas={['/', '/sobre']}.

  1. Chamadas à API mandam o cookie. Com fetch, use o ssoFetch de src/sso/api ou credentials: 'include'. Com axios, crie a instância com withCredentials: true e remova o header Authorization e o token do localStorage:
    export const api = axios.create({ baseURL: import.meta.env.VITE_SSO_API_URL, withCredentials: true });
  2. Troque a sua tela/modal de login por um link para /login e o botão Sair por ssoSession.logout() (logout único no SSO). Remova cadastro, troca de senha e gestão de usuários — isso passa a ser no painel do SSO.
  3. Quem é o usuário: useSsoSession((s) => s.user) em componentes; ssoSession.hasPermission('recurso.acao') para esconder o que ele não pode usar. Rotas que liberavam acesso por um campo do usuário (ex.: tipo) passam a olhar os perfis/permissões da sessão.
  4. Só no modelo scoped (multiunidade): se o usuário tem mais de uma unidade, o gate pede a escolha antes de abrir o app. Ponha <SsoSeletorUnidade /> (de src/sso/SsoSeletorUnidade) no cabeçalho para trocar de unidade. O ssoFetch já envia x-unidade-id; no axios, acrescente:
    api.interceptors.request.use((config) => {
      const unidade = ssoSession.get().activeUnitId;
      if (unidade) config.headers.set('x-unidade-id', unidade);
      return config;
    });
    Quando a unidade ativa muda, recarregue os dados da tela (ex.: invalidar as queries do React Query).
  5. Rotas e menus por setor (opcional, scoped): se o sistema organiza o trabalho por setor, ssoSession.setores() devolve os setores do usuário na unidade ativa, cada um com slug. Monte as rotas com ele (ex.: /:setor/admin) em vez de uma lista fixa: setor novo cadastrado no painel do SSO (Estrutura → unidade → Setores → Novo setor) já aparece no app, e o acesso vem do setor concedido ao usuário.

Next.js (App Router)

Gerado em lib/sso/, components/sso/ e app/login/; instala zustand. O add envolve o {children} do layout raiz com <SsoSessionGate>.

  1. Chamadas à API mandam o cookie: ssoApiFetch de lib/sso/api, fetch com credentials: 'include' ou axios com withCredentials: true — sem header Authorization.
  2. Login e logout: link para /login; sair com useSsoSession((s) => s.logout)().
  3. Quem é o usuário: useSsoSession((s) => s.user) e useSsoSession((s) => s.hasPermission)('recurso.acao').
  4. Só no modelo scoped: unidade ativa em useSsoSession((s) => s.activeUnitId), troca com useSsoSession((s) => s.setActiveUnit)(id); ssoApiFetch envia x-unidade-id na leitura da sessão — nas suas chamadas, mande o mesmo header. Setores com slug para rotas e menus: useSsoSession((s) => s.setores)().

Como funciona

No espírito do shadcn/ui: o add copia código de verdade para dentro do seu projeto, e o status prova — contra o código, o SSO e o app rodando — o que já está pronto, guardando a evolução até a conclusão.

npx @riosaude/sso-kit add            # gera o módulo de autenticação + webhook
npx @riosaude/sso-kit status --live  # verifica tudo e atualiza a página de acompanhamento
npx @riosaude/sso-kit confirmar login-real
npx @riosaude/sso-kit doctor         # mesmas verificações, só leitura (CI)

Acompanhamento: sso-kit status

Cada execução verifica as etapas da integração, grava o resultado em .sso-kit/estado.json e gera .sso-kit/integracao.html, uma página que funciona offline:

  • Resumo — etapas obrigatórias concluídas, falhas e próximos passos com a evidência e o como resolver
  • Etapas por fase — status de cada uma, desde quando, quando foi verificada
  • Evolução — gráfico do percentual concluído por verificação e histórico com quem rodou, em qual commit e o que mudou
  • Conclusão — o veredito (concluída / em andamento / com falhas) e o status final de cada etapa com a evidência

Commite o estado.json: é ele que carrega a evolução para o time. A página é derivada dele e fica no .gitignore — quem quiser vê-la roda sso-kit status --abrir. Se o código roda numa máquina e o navegador está em outra, use sso-kit status --servir (porta 8099 por padrão) e abra http://<endereço-da-máquina>:8099/.

De onde vem a conclusão

Nada é marcado como concluído por declaração quando dá para provar:

| Fase | Como é verificada | |---|---| | Dependências, código, .env, frontend | lendo o projeto, a cada execução | | Cadastro no SSO | --live: sonda o SSO sem criar sessão — /oauth/authorize sem seguir o redirect distingue client inexistente, redirect_uri não cadastrada e cadastro correto; /oauth/token com code inválido prova o client_secret (secret errado = invalid_client, certo = invalid_grant) | | Aplicação em execução | --live: chama o login do app e confere o redirect; envia ao webhook um evento com assinatura inválida (tem que recusar) e um assinado com o SSO_WEBHOOK_SECRET para um usuário fictício (tem que aceitar) | | Usuários com acesso | --live com SSO_API_TOKEN: consulta a API v1; sem token, confirmação manual | | Login real e revogação real | só uma pessoa atesta: sso-kit confirmar login-real / confirmar revogacao-real — registra quem, quando e o commit |

Sem --live, etapas de rede mantêm o último resultado real com a data em que foram verificadas; o que nunca rodou fica não verificada (não conta como concluída nem como falha). Etapas confirmadas por pessoa aparecem sempre como "confirmada manualmente", separadas das provadas automaticamente.

npx @riosaude/sso-kit status --live --exigir-conclusao   # sai com 1 se não estiver concluída
npx @riosaude/sso-kit confirmar revogacao-real --nota "sessão caiu em ~2s"
npx @riosaude/sso-kit confirmar login-real --desfazer

sso-kit rbac

npx @riosaude/sso-kit rbac [--dir <api>] [--slug <slug>]

Gera scripts/sync-rbac.ts e os comandos rbac:sync / rbac:push. O script sobe o contexto do Nest, varre os controllers e monta o catálogo a partir das rotas com @RequirePermission — cada permissão sai com as rotas que ela libera, e as rotas sem permissão aparecem à parte, para nada passar despercebido. Depois ele publica tudo no SSO (PUT /api/v1/rbac/sync).

npm run rbac:sync             # só lista (dry-run)
npm run rbac:push             # lista e publica
npm run rbac:push -- --prune  # publica e remove permissões órfãs no SSO
  • Publicar exige SSO_ISSUER e um token de API do sistema, criado no painel em Sistemas → Tokens (não é o client_secret: esse serve só para o login). A variável do token é nomeada a partir do slug, ex.: MEU_SISTEMA_SSO_API_TOKEN.
  • O que é automático: permissões e as rotas de cada uma. O que não é: quais permissões cada perfil tem — isso é decisão de negócio e fica na constante ROLES, no topo do script, versionada com o código.
  • rbac:push substitui o conjunto de permissões de cada perfil declarado, e a mudança propaga na hora para quem tem o perfil (revogação + webhook).
  • Se o acesso do seu sistema é por unidade/setor, não crie um perfil por setor: o setor chega na claim com o slug, e setor novo passa a funcionar sem mexer no script.

sso-kit doctor

As mesmas verificações do status, sem gravar estado nem página. Sai com código 1 se alguma etapa obrigatória estiver em falha — dá para usar como gate de pipeline.

npx @riosaude/sso-kit doctor --dir ../minha-api --live
npx @riosaude/sso-kit doctor --json

sso-kit add

Gera src/sso/ no projeto NestJS: login OIDC+PKCE, sessão local (cookie httpOnly), logout único, webhook (user.revoked e user.permissions_updated) e guardas de autenticação/autorização. A própria SsoModule se registra como APP_GUARD.

npx @riosaude/sso-kit add
npx @riosaude/sso-kit add --dir ../minha-api --web ../minha-web --front vite --slug meu-sistema --model scoped --yes

Pergunta só o que não dá para inferir: slug do sistema no SSO, modelo de permissão (flat ou scoped, ver abaixo), se há front e o framework do front — Vite ou Next.js, já sugerindo o que detectou (--front vite|next para rodar sem perguntas). Depois faz sozinho, sem sobrescrever nada seu:

| Onde | O que | |---|---| | app.module.ts | importa SsoModule.register({}) | | main.ts | import 'dotenv/config' (se nada carregava o .env), cookie-parser, rawBody: true, credentials: true no enableCors({...}) quando há front | | .env | cria ou completa as chaves — APP_SECRET já gerado, valores existentes nunca trocados — e garante .env no .gitignore | | dependências | instala só as que faltam, com o npm/pnpm/yarn do projeto (--sem-instalar pula) | | front Vite | src/sso/ (sessão, gate, login, escolha de unidade no scoped), envolve o <App /> do main.tsx e grava VITE_SSO_API_URL no .env.local | | front Next.js | lib/sso/, components/sso/, app/login, envolve o {children} do layout raiz e grava NEXT_PUBLIC_SSO_API_URL no .env.local | | visual | logos e fonte do painel do SSO em public/sso-kit/ |

Arquivo que já existe sai ao lado como .sso-kit-novo; patch que não reconhece o formato do arquivo não é aplicado e vira etapa pendente na página. Ao final roda o primeiro status.

O que continua sendo do dev (e a página aponta cada um):

  • SSO_CLIENT_SECRET e SSO_WEBHOOK_SECRET — só o painel do SSO os mostra;
  • APP_URL/WEB_URL com o endereço que o navegador enxerga;
  • os ajustes no front — a etapa lista arquivo e quantidade de fetch/axios sem cookie;
  • toda rota da API passa a exigir sessão: marque com @Public() o que precisa continuar aberto (health check, por exemplo).

Sessão, cookies e tela de login

  • Sessão no servidor. O cookie leva só um id assinado; as claims ficam na API. A revogação (user.revoked) apaga a sessão e a tela aberta cai sozinha pelo canal SSE. O armazenamento é em memória: vale para uma instância, e reiniciar a API encerra as sessões (o próximo acesso refaz o login no SSO em silêncio). Com réplicas, troque o Map do SessionService por Redis ou tabela.
  • Cookies com nome da aplicação (sk_<slug>_sessao, sk_<slug>_pkce…; SSO_COOKIE_PREFIX para trocar). Cookie não separa por porta: com nome genérico, dois sistemas no mesmo host derrubam a sessão um do outro — e se o outro for o próprio SSO (que usa sso_session com Secure), o navegador bloqueia o cookie do app e o login entra em laço.
  • Tela de login e de "verificando sessão" com a identidade do painel do SSO (logos e Inter em public/sso-kit/, CSS Module isolado — não interfere no CSS do app).
  • Trava de laço: se o login conclui no SSO mas a sessão não chega à API, a tela para após 3 idas e explica as causas prováveis, em vez de girar.

Ponto de extensão: espelhar o usuário localmente

O add não tenta adivinhar o schema do seu banco. SsoModule.register() aceita um onLogin, chamado logo após o token ser validado e antes de criar a sessão. Use o id do SSO (claims.sub) como id do usuário local: as FKs do domínio continuam válidas sem de-para. O moduleRef dá acesso aos providers do app:

// src/app.module.ts — TypeORM
import { DataSource } from 'typeorm';

SsoModule.register({
  onLogin: async (claims, { moduleRef }) => {
    const usuarios = moduleRef.get(DataSource, { strict: false }).getRepository(Usuario);
    await usuarios.upsert({ id: claims.sub, nome: claims.nome ?? '' }, ['id']);
  },
})
// src/app.module.ts — Prisma
SsoModule.register({
  onLogin: async (claims, { moduleRef }) => {
    const prisma = moduleRef.get(PrismaService, { strict: false });
    await prisma.usuario.upsert({
      where: { id: claims.sub },
      update: { nome: claims.nome ?? '' },
      create: { id: claims.sub, nome: claims.nome ?? '' },
    });
  },
})

Se onLogin lançar erro, o login falha e o usuário volta para /login?erro=sso (o motivo fica no log da API).

Por que dois comandos, e por que add não edita tudo sozinho

Verificar é seguro de automatizar por completo — é só leitura. Editar app.module.ts/main.ts de um projeto que cada dev estruturou à sua maneira não é: um regex "esperto" que adivinha errado quebra build em silêncio. Por isso o add só edita automaticamente o que reconhece com alta confiança (import do módulo, cookie-parser, rawBody) e, para o resto, deixa a etapa pendente na página — nunca finge ter terminado algo que não fez.

Modelo flat vs scoped — qual escolher

| | flat | scoped | |---|---|---| | Permissões | lista única por usuário | uma lista por unidade/entidade | | Claim do token usada | roles/permissions | access (E13) | | Header x-unidade-id | não usado | seletor de unidade, validado contra o token | | Quando usar | a maioria dos sistemas | sistema multi-unidade com permissão variando por unidade (ex.: hospitais) |

Se o sistema no SSO distribui permissões por unidade e o app usa flat, a sessão chega com permissions vazio — qualquer rota com @RequirePermission responde 403. Nesse caso o modelo certo é scoped.

No scoped, a lista de unidades do usuário vem do SSO (é o acesso concedido no painel), não de uma tabela local — o front escolhe a unidade ativa e a API confere x-unidade-id contra o token em toda requisição.

Dá para migrar de flat para scoped depois — são módulos independentes, troque src/sso/ pelo outro modelo e ajuste quem lê session.permissions (flat) para session.scopes (scoped).