@chrono-os/auth-better
v0.6.1
Published
BetterAuth + Prisma adapter pré-configurado para apps Chrono — User/Session/Account/Verification + enum Role (MEMBER|ADMIN), helpers servidor (buildAuth) e cliente (createAuthClient). Promoção do @svadulto/auth-config
Maintainers
Readme
@chrono-os/auth-better
O módulo de login da casa: BetterAuth + Prisma configurado com os defaults da casa no servidor, client React tipado e as telas prontas (login, cadastro, 2FA, recuperação de senha, dispositivos conectados).
Install
yarn add @chrono-os/auth-better better-authModels Prisma necessários no schema do app: User, Session, Account, Verification. Com 2FA, também TwoFactor e User.twoFactorEnabled. O default do buildAuth grava User.role ('MEMBER'): app sem essa coluna passa userAdditionalFields: {}.
Entradas
| Import | O que tem | Quem usa |
|---|---|---|
| @chrono-os/auth-better | = /core (servidor, sem framework) | backend com moduleResolution: "node" |
| @chrono-os/auth-better/core | buildAuth, buildAuthOptions, readSession, toWebHeaders | backend Nest/Express/Next |
| @chrono-os/auth-better/server | core + adaptador Fastify (fastifyAuthPlugin, getSessionFromRequest) | backend Fastify |
| @chrono-os/auth-better/client | createAuthClient (React), genérico nos plugins | frontend |
| @chrono-os/auth-better/ui + /ui.css | as telas prontas | frontend |
Servidor
import { buildAuth, readSession } from '@chrono-os/auth-better/core'
export const auth = buildAuth({
prisma,
secret: env.BETTER_AUTH_SECRET,
baseURL: `${env.API_URL}/api/v1/auth`,
trustedOrigins: [env.FRONTEND_URL],
google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, prompt: 'select_account' },
// Atalhos de login confiável — cada um liga um plugin do próprio better-auth
twoFactor: { issuer: 'Meu App' }, // TOTP + 10 códigos de backup + confiar no dispositivo (30d)
checkBreachedPasswords: true, // recusa senha vazada (Have I Been Pwned, k-anonimato)
// Sobreposições são MESCLADAS no default, não substituem
emailAndPassword: { sendResetPassword: async ({ user, url }) => mandarEmail(user.email, url) },
session: { expiresIn: 60 * 60 * 24 * 30 },
})
const sessao = await readSession(auth, req.headers) // null em cookie inválido, nunca lançaDefaults: e-mail+senha (mínimo 8, entra direto após cadastro), sessão de 7 dias renovada a cada dia com cache de cookie de 5 min, sameSite: 'lax', bearer() para mobile, campo role. Rate limit é o do better-auth, ligado em produção: 3 tentativas a cada 10 s no login e 3 por minuto no reset. extend(options) recebe a config final para qualquer opção que o pacote não previu.
Frontend
import { createAuthClient } from '@chrono-os/auth-better/client'
import { twoFactorClient } from 'better-auth/client/plugins'
export const authClient = createAuthClient({ baseURL: `${API_URL}/api/v1/auth`, plugins: [twoFactorClient()] })Telas prontas (/ui)
import { LoginForm } from '@chrono-os/auth-better/ui'
import '@chrono-os/auth-better/ui.css'
<LoginForm
client={authClient}
onSuccess={() => router.push(destinoSeguro(params.get('from'), '/painel'))}
google={{ callbackURL: `${location.origin}/painel` }}
forgotPasswordHref="/esqueci-senha"
signUpHref="/cadastro"
renderLink={(href, children, className) => <Link href={href} className={className}>{children}</Link>}
/>| Componente | O que faz |
|---|---|
| LoginForm | e-mail e senha, olho, aviso de Caps Lock, manter conectado, Google, esqueci a senha, reenvio de confirmação de e-mail, desafio 2FA no lugar (ou onTwoFactorRequired para outra rota), selo "último usado" |
| TwoFactorChallenge | código do app ou de backup + confiar neste dispositivo por 30 dias |
| SignUpForm | nome, e-mail, senha com medidor de força, aceite opcional (obrigatório quando passado), honeypot anti-robô, "confirme seu e-mail" quando o servidor exige verificação |
| ForgotPasswordForm | mesma resposta exista ou não a conta |
| ResetPasswordForm | senha nova + confirmação; token inválido ou vencido leva a "pedir link novo" |
| ChangePasswordForm | senha atual + nova, "sair dos outros dispositivos" marcado |
| TwoFactorSettings | ativar (QR gerado no navegador + chave manual + confirmação), desativar, novos códigos de backup |
| SessionsList | dispositivos conectados, encerrar um, sair de todos os outros |
| PasswordInput, PasswordStrength, QrCode, destinoSeguro | peças soltas |
Regras que os componentes seguem: erro de login sempre genérico ("e-mail ou senha incorretos"); rede fora não trava o botão; 429 aparece como "muitas tentativas"; autocomplete certo em todo campo (username, current-password, new-password, one-time-code); rótulo, aria-invalid, aria-describedby e role="alert" em todo formulário; alvo de toque de 44px; input com 16px (sem zoom no iOS). O QR do 2FA é montado localmente: o otpauth:// carrega o segredo TOTP e não pode ir para serviço de QR de terceiros.
Estilo. CSS real, sem Tailwind: funciona em app Tailwind 3 ou 4 sem mexer em content/@source. Para a cara da marca, aponte os tokens para os do app no :root — o tema do app passa a valer aqui também:
:root { --ab-primary: var(--brand-600); --ab-bg: var(--surface); --ab-fg: var(--text); --ab-radius: 12px; }Tokens: --ab-bg --ab-fg --ab-muted --ab-border --ab-input-bg --ab-primary --ab-primary-fg --ab-danger --ab-danger-bg --ab-success --ab-success-bg --ab-focus --ab-radius --ab-font. Os fallbacks cobrem claro, escuro (.dark, data-theme="dark" ou data-mode="dark") e black OLED (.oled) num ancestral. prefers-color-scheme não é seguido sozinho, de propósito: num app sem tema escuro o formulário ficaria escuro numa página clara.
Textos. pt-BR em tom neutro. Cada componente aceita textos={{ entrar: 'Bora' }} para trocar só o que precisar (TEXTOS_PADRAO tem a lista).
peerDeps
better-auth ^1.0.0@prisma/client ^5 || ^6 || ^7react ^18 || ^19(opcional — só para/cliente/ui)fastify ^5(opcional — só para/server)
Versionamento
SemVer. Tag v* → npmjs.org. Em 0.x, minor pode quebrar: veja o CHANGELOG antes de subir.
