@chrono-os/sso-client
v0.2.0
Published
Cliente do Identity Provider central (login único): validar sessão, sign-in server-to-server e autorização por AppRole. Zero dependências de runtime.
Maintainers
Readme
@chrono-os/sso-client
Cliente do Identity Provider central (login único). Valida sessão do IdP, faz sign-in server-to-server e decide se o usuário tem papel neste app. Zero dependências de runtime (Node ≥ 18.17, fetch nativo).
Substitui o idp.ts que estava copiado em 11 backends, com três formas da mesma regra de autorização e duas assinaturas de idpSignIn.
import { createSsoClient } from '@chrono-os/sso-client'
export const sso = createSsoClient({
idpUrl: process.env.IDP_URL, // vazia = desligado (fork de parceiro)
appKeys: process.env.IDP_APP_KEY ?? 'legaris', // CSV ou array
publicOrigin: process.env.APP_PUBLIC_URL, // Origin confiável no sign-in
})
sso.enabled // IDP_URL presente?
await sso.verifySession(token) // { email, appRoles, user } | null
sso.isAuthorized(sessao.appRoles) // admin (ou *) neste app?
await sso.signIn(email, senha) // token | null
await sso.signInAndAuthorize(email, senha) // sessão autorizada | null
sso.profile(sessao.appRoles) // { role, permissions, expiresAt, wildcard } | nullPerfil de permissões por app (0.2.0)
O IdP guarda, por usuário × app, um permissions (JSON cujo formato é do app) e um expiresAt. Papel vencido some da sessão e não autoriza.
type PerfilPainel = { abas: string[]; dadosPessoais: 'ocultos' | 'mascarados' | 'completos'; capivara: boolean }
const perfil = sso.profile<PerfilPainel>(sessao.appRoles) // ou getAppProfile(appRoles, 'dashboard-nairio')
if (!perfil) negar()
else if (perfil.wildcard || perfil.role === 'admin') acessoTotal()
else aplicar(perfil.permissions) // viewer: abas + nível de dado pessoalprofile prefere o papel do próprio app ao wildcard * (o dono). O tipo P é anotação: valide permissions no app antes de confiar nele.
- Nunca lança: rede fora, IdP fora, 401 e corpo estranho viram
null, para o app cair no login local em vez de devolver 500. - Log sem credencial: só status e tipo do erro; senha errada (401) não loga.
idpUrldefinida semappKeysfalha no boot, em vez de recusar todo login em silêncio.roles(default['admin']) define os papéis aceitos além do wildcard*.
Contrato do IdP: GET /api/v1/identity/session (Bearer; cada AppRole com permissions e expiresAt desde 2026-09-24) e POST /api/v1/auth/sign-in/email.
