@integrattion/auth
v1.4.1
Published
Cliente de autenticação do ecossistema Integrattion. Implementa AUTH_CONTRACT.md §5 contra o Partners.
Readme
@integrattion/auth
Cliente de autenticação do ecossistema Integrattion. Implementa o
AUTH_CONTRACT.md
§5 inteira contra o Partners, que é o diretório de identidade e autorização (ADR-001).
Nenhum sistema do ecossistema implementa autenticação própria.
Por que este pacote existe assim
- Zero dependência de runtime. RS256 é verificado com o
cryptonativo do Node. Um pacote de JWT aqui só somaria superfície de supply chain a algo que ~11 sistemas vão importar. - Zero build no consumidor.
dist/é commitado; o app instala via npm e usa direto. - Zero ORM.
app_usersé DDL + helpers sobrepg. Dos 11 repos ativos, 9 usampgcru e 2 usam Prisma — um schema de ORM serviria a um repo só.
Instalação
Distribuição via npm público (registry.npmjs.org), no escopo @integrattion.
npm i @integrattion/authPor que npm público, e não dependência git
A primeira versão distribuía por dependência git (github:integrattion-os-projects/auth#vX.Y.Z).
Quebrou no build em container: node:*-alpine não tem o binário git, e o repo é privado — sem
credencial disponível no container, npm ci não resolve a dependência.
GitHub Packages foi descartado pelo mesmo motivo do ADR-001: obrigaria token de registry em ~11 Dockerfiles e workflows de CI, para resolver um problema que o ecossistema, sendo de um dono só, não tem.
O pacote não contém segredo. O client ID do Google é público por contrato
(AUTH_CONTRACT.md §6) —
nada aqui depende de o repositório ou o registry serem privados.
# .env do app
AUTH_ISSUER_URL=https://partners.integrattion.com.br
AUTH_SYSTEM_SLUG=campaign # slug da entity no Integrattion OS
GOOGLE_OAUTH_CLIENT_ID=965501017365-bkbucvhr3lbrl9ch4q4ngcuchff6e919.apps.googleusercontent.comNenhum segredo de auth no app. A validação é por chave pública.
Servidor (Express)
import { createAuthHandlers, requireRole, appUsers, DEFAULT_ROLES } from '@integrattion/auth';
const authConfig = {
issuer: process.env.AUTH_ISSUER_URL!,
systemSlug: process.env.AUTH_SYSTEM_SLUG!,
roles: DEFAULT_ROLES, // do mais forte para o mais fraco
defaultRole: 'VIEWER',
pool, // pg.Pool do app
googleClientId: process.env.GOOGLE_OAUTH_CLIENT_ID,
};
await appUsers(pool).ensureSchema(); // ou rode APP_USERS_DDL como migration
app.use(express.json());
app.use(createAuthHandlers(authConfig));
app.use('/api', requireRole(authConfig)); // exige sessão
app.delete('/api/campanhas/:id', requireRole(authConfig, 'ADMIN'), handler);Rotas montadas: POST /api/auth/callback · POST /api/auth/refresh · POST /api/auth/logout ·
GET /api/auth/me · GET /api/auth/config · GET|PUT /api/auth/admin/users.
Bootstrap de OWNER
/api/auth/admin/users exige roles[0] (OWNER). Sem bootstrapOwners, o primeiro usuário de um
sistema novo entra como defaultRole (ex.: VIEWER) e ninguém consegue abrir a tela de acessos — só
com UPDATE manual no banco. bootstrapOwners resolve isso: e-mails nesta lista entram com
roles[0] só na criação JIT da linha em app_users (primeiro login). Uma linha já existente
nunca é alterada por causa da lista — se um OWNER foi rebaixado, o próximo login não o promove de
volta.
const authConfig = {
issuer: process.env.AUTH_ISSUER_URL!,
systemSlug: process.env.AUTH_SYSTEM_SLUG!,
roles: DEFAULT_ROLES,
defaultRole: 'VIEWER',
// O pacote NÃO lê process.env — quem lê é o app.
bootstrapOwners: (process.env.AUTH_BOOTSTRAP_OWNERS ?? '').split(',').map(s => s.trim()).filter(Boolean),
pool,
googleClientId: process.env.GOOGLE_OAUTH_CLIENT_ID,
};Comparação por e-mail é case-insensitive e com trim. Um valor que não parece e-mail em
bootstrapOwners derruba o boot com erro claro, em vez de falhar silenciosamente.
Front (React)
import { AuthProvider, LoginGate, useSession, AccessAdminPage, UserMenu } from '@integrattion/auth/react';
<AuthProvider>
<LoginGate appName="Campaign Manager">
<header>
<UserMenu acessosHref="/settings/access" />
</header>
<App />
</LoginGate>
</AuthProvider>useSession() devolve { user, roles, status, logout }. <AccessAdminPage /> é a tela de papéis —
não escreva a sua.
<UserMenu /> é o menu de conta — avatar com iniciais, primeiro nome no botão; abre com nome
completo, e-mail e papel atual, mais os itens "Acessos" (só com OWNER) e "Sair". Não escreva o
seu: é exatamente o tipo de tela que cada app reimplementava diferente antes do ADR-001.
<UserMenu
acessosHref="/settings/access" // rota do app pra tela de papéis; null remove o item. Default: '/acessos'
align="right" // lado em que o menu abre. Default: 'right'
/>Renderiza null enquanto a sessão carrega (status === 'loading') ou sem sessão
(status === 'anonymous') — não precisa de gate próprio em volta, só ficar dentro do
<AuthProvider>.
Tela de login
Desde a v1.4.0, quando <LoginGate> não recebe renderLogin, ele usa <LoginScreen> — a tela
padrão do ecossistema, com um layout de duas colunas (imagem + frase à direita, formulário à
esquerda). Quem já usa renderLogin continua funcionando exatamente como antes; nada muda pra
quem tem tela própria.
<LoginGate
appName="Campaign Manager"
logoUrl="/logo-campaign.svg"
quotes={[
'O melhor jeito de prever o futuro é criá-lo.',
'Feito é melhor que perfeito.',
]}
quoteSignature="Time Campaign Manager"
theme="system" // 'system' (padrão) | 'light' | 'dark'
>
<App />
</LoginGate>quotes— sorteia 1 frase por montagem da tela (não re-sorteia em re-render). Semquotes(ou array vazio), o bloco da frase some inteiro — a imagem continua.quoteSignature— assinatura abaixo da frase. Padrão: usaappName.logoUrl— sem logo, mostra um monograma com a inicial maiúscula deappName.tagline— padrão: "Entre com sua conta Google autorizada."imagesManifestUrl— manifest público[{src, source, sourceUrl, author}]sorteado para o fundo da coluna direita. Padrão:https://components.integrattion.com.br/login-assets/manifest.json. Qualquer falha (fetch, JSON, manifest vazio) cai num gradiente CSS de fallback — a tela nunca quebra por causa da imagem, e o botão do Google nunca espera por ela.theme—'system'(padrão) segueprefers-color-schemede verdade, inclusive trocando o tema do botão do Google sozinho se o SO mudar de esquema com a tela aberta.'light'/'dark'força o tema.
Responsivo: duas colunas a partir de 1024px. Abaixo disso, decisão de design registrada aqui: a imagem vira uma faixa fina no topo (não desaparece) e o painel de frase some — não cabe bem em tela estreita.
<LoginScreen> também é exportado como componente puro — sem useSession, recebe tudo via
props (slot, error, appName, quotes etc.). Serve pra quem quer montar o próprio wrapper de
sessão em volta, ou pra preview/storybook. O dono do GSI (google.accounts.id.initialize /
renderButton) continua sendo <LoginGate> — <LoginScreen> só recebe o slot já pronto.
import { LoginScreen } from '@integrattion/auth/react';
<LoginScreen
appName="Campaign Manager"
slot={<button>Entrar com Google</button>}
quotes={['Frase de exemplo.']}
/>O que este pacote NÃO faz
- Não cria pessoa nem e-mail de login (isso é só no Partners).
- Não decide se alguém entra — só o que faz depois de entrar.
- Não pede escopo sensível do Google. Só
openid,email,profile.
Papéis
roles é ordenado do mais forte para o mais fraco; requireRole compara por posição. O padrão é
OWNER > ADMIN > EDITOR > VIEWER, mas cada app define os seus.
