Login seguro con Better Auth + Drizzle: email/contraseña, GitHub/Google, 2FA, límites y CSRF
Código6 archivosContexto~812 tokensMCP better-authescaneo superado
Instalar
$
genpm add @yohangel/authQué obtienes
- Código en src/lib/auth/, 6 archivos. (15,5 kB)
- Reglas de IA en src/lib/auth/AGENTS.md, más archivos de reglas para tu IDE.
- Variables añadidas a .env.example: BETTER_AUTH_SECRET, BETTER_AUTH_URL, DATABASE_URL.
README
Este paquete no tiene README.
~812 tokens→ src/lib/auth/AGENTS.md→ .cursor/rules/genpm-yohangel-auth.mdc
Esto es exactamente lo que lee tu IA cuando trabaja en src/lib/auth. No se añade nada más a su contexto.
@yohangel/auth — rules for AI agents
Purpose
Server-side authentication core built on Better Auth + Drizzle (Postgres): email/password, GitHub/Google OAuth, TOTP 2FA, database-backed rate limiting and CSRF protection. Framework-agnostic: pair it with @yohangel/auth-next, auth-express or auth-nest (server) and @yohangel/auth-react (UI). It does not send emails by itself and has no UI.
Module map
index.ts— public API. Import only from here.auth.ts—createAuth(opts)(secure defaults),getAuth(),getSession(headers),socialProviders().schema.ts— Drizzle tables:users,sessions,accounts,verifications,two_factors,rate_limits.db.ts—setAuthDb(db)/getAuthDb(); defaults to postgres-js fromDATABASE_URL.
Integration (do this after installing)
- Set env vars (add them to
.env.example, never commit real values):BETTER_AUTH_SECRET— 32+ random chars (openssl rand -base64 32). Startup throws in production if shorter.BETTER_AUTH_URL— public base URL of the server, e.g.https://app.example.com.DATABASE_URL— Postgres. If the app already has a Drizzle client, pass it:createAuth({ db }).- Optional:
GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET,GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET(a provider is enabled only when both are set; callback URL is<BETTER_AUTH_URL>/api/auth/callback/<provider>),APP_NAME(2FA issuer),AUTH_TRUSTED_ORIGINS(comma-separated extra origins, e.g. a separate SPA),AUTH_IP_HEADER(e.g.cf-connecting-ipbehind Cloudflare, so rate limits see the real client IP).
- Add the tables to the app's migrations: include
src/lib/auth/schema.tsindrizzle.config.tsschema, thennpx drizzle-kit generate && npx drizzle-kit migrate. - Wire emails:
createAuth({ sendEmail: async ({ kind, to, url, subject, text }) => … })using the app's mailer. WithsendEmailset, email verification is required to sign in and password reset is enabled. Without it, links are printed to the console in development only. - Create the instance through the framework adapter package (it calls
createAuthwith the right plugins). UsegetSession(headers)anywhere else on the server.
Conventions
- One instance per app. Extra Better Auth plugins go in
createAuth({ plugins: [...] })— e.g.organization(),admin(),passkey()— then regenerate/extendschema.tswith the tables those plugins document. - Always authorize on the server with
getSession(); client-side checks are only for UX. - Prefer
overridesonly for options not covered here, and keep the security defaults below.
Don't
- Don't lower
minPasswordLength(12), disablerateLimitin production, or setadvanced.disableCSRFCheck/disableOriginChecktotrue. - Don't log, return or store passwords, session tokens, OAuth tokens or
BETTER_AUTH_SECRET. - Don't add origins to
AUTH_TRUSTED_ORIGINSwith wildcards you do not control. - Don't read
accounts.passwordor compare passwords yourself; use Better Auth's API. - Don't build redirects from user input without checking they are internal paths.
Docs: the Better Auth MCP server (better-auth) is configured by GenPM; ask it about plugins and options.
# @yohangel/auth — rules for AI agents
## Purpose
Server-side authentication core built on Better Auth + Drizzle (Postgres): email/password, GitHub/Google OAuth, TOTP 2FA, database-backed rate limiting and CSRF protection. Framework-agnostic: pair it with `@yohangel/auth-next`, `auth-express` or `auth-nest` (server) and `@yohangel/auth-react` (UI). It does not send emails by itself and has no UI.
## Module map
- `index.ts` — public API. Import only from here.
- `auth.ts` — `createAuth(opts)` (secure defaults), `getAuth()`, `getSession(headers)`, `socialProviders()`.
- `schema.ts` — Drizzle tables: `users`, `sessions`, `accounts`, `verifications`, `two_factors`, `rate_limits`.
- `db.ts` — `setAuthDb(db)` / `getAuthDb()`; defaults to postgres-js from `DATABASE_URL`.
## Integration (do this after installing)
1. Set env vars (add them to `.env.example`, never commit real values):
- `BETTER_AUTH_SECRET` — 32+ random chars (`openssl rand -base64 32`). Startup throws in production if shorter.
- `BETTER_AUTH_URL` — public base URL of the server, e.g. `https://app.example.com`.
- `DATABASE_URL` — Postgres. If the app already has a Drizzle client, pass it: `createAuth({ db })`.
- Optional: `GITHUB_CLIENT_ID`/`GITHUB_CLIENT_SECRET`, `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET` (a provider is enabled only when both are set; callback URL is `<BETTER_AUTH_URL>/api/auth/callback/<provider>`), `APP_NAME` (2FA issuer), `AUTH_TRUSTED_ORIGINS` (comma-separated extra origins, e.g. a separate SPA), `AUTH_IP_HEADER` (e.g. `cf-connecting-ip` behind Cloudflare, so rate limits see the real client IP).
2. Add the tables to the app's migrations: include `src/lib/auth/schema.ts` in `drizzle.config.ts` `schema`, then `npx drizzle-kit generate && npx drizzle-kit migrate`.
3. Wire emails: `createAuth({ sendEmail: async ({ kind, to, url, subject, text }) => … })` using the app's mailer. With `sendEmail` set, email verification is required to sign in and password reset is enabled. Without it, links are printed to the console in development only.
4. Create the instance through the framework adapter package (it calls `createAuth` with the right plugins). Use `getSession(headers)` anywhere else on the server.
## Conventions
- One instance per app. Extra Better Auth plugins go in `createAuth({ plugins: [...] })` — e.g. `organization()`, `admin()`, `passkey()` — then regenerate/extend `schema.ts` with the tables those plugins document.
- Always authorize on the server with `getSession()`; client-side checks are only for UX.
- Prefer `overrides` only for options not covered here, and keep the security defaults below.
## Don't
- Don't lower `minPasswordLength` (12), disable `rateLimit` in production, or set `advanced.disableCSRFCheck` / `disableOriginCheck` to `true`.
- Don't log, return or store passwords, session tokens, OAuth tokens or `BETTER_AUTH_SECRET`.
- Don't add origins to `AUTH_TRUSTED_ORIGINS` with wildcards you do not control.
- Don't read `accounts.password` or compare passwords yourself; use Better Auth's API.
- Don't build redirects from user input without checking they are internal paths.
Docs: the Better Auth MCP server (`better-auth`) is configured by GenPM; ask it about plugins and options.
El árbol exacto que se inyectará, tras aplicar .genpmignore. Anclado a
// Tablas de @yohangel/auth (Better Auth 1.7: núcleo + twoFactor + rateLimit). Añádelas al `schema` de tu drizzle.config.ts.
// Nombres en plural y snake_case en SQL; las claves JS son las que espera Better Auth.
import { bigint, boolean, index, integer, pgTable, text, timestamp } from 'drizzle-orm/pg-core';
const ts = (name: string) => timestamp(name, { withTimezone: true, mode: 'date' });
const createdAt = ts('created_at').defaultNow().notNull();
const updatedAt = ts('updated_at')
.defaultNow()
.$onUpdate(() => new Date())
.notNull();
export const users = pgTable('users', {
id: text('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
emailVerified: boolean('email_verified').default(false).notNull(),
image: text('image'),
twoFactorEnabled: boolean('two_factor_enabled').default(false),
createdAt,
updatedAt,
});
export const sessions = pgTable(
'sessions',
{
id: text('id').primaryKey(),
// Better Auth guarda el token firmado de la cookie; la cookie además lleva HMAC con BETTER_AUTH_SECRET.
token: text('token').notNull().unique(),
expiresAt: ts('expires_at').notNull(),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt,
updatedAt,
},
(t) => [index('sessions_user_idx').on(t.userId)],
);
/** Una fila por forma de entrar: `credential` (email+contraseña, hash scrypt en `password`) o un proveedor OAuth. */
export const accounts = pgTable(
'accounts',
{
id: text('id').primaryKey(),
accountId: text('account_id').notNull(),
providerId: text('provider_id').notNull(),
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
accessToken: text('access_token'),
refreshToken: text('refresh_token'),
idToken: text('id_token'),
accessTokenExpiresAt: ts('access_token_expires_at'),
refreshTokenExpiresAt: ts('refresh_token_expires_at'),
scope: text('scope'),
password: text('password'),
createdAt,
updatedAt,
},
(t) => [index('accounts_user_idx').on(t.userId)],
);
/** Tokens de un solo uso: verificación de email, restablecer contraseña, estado OAuth. */
export const verifications = pgTable(
'verifications',
{
id: text('id').primaryKey(),
identifier: text('identifier').notNull(),
value: text('value').notNull(),
expiresAt: ts('expires_at').notNull(),
createdAt,
updatedAt,
},
(t) => [index('verifications_identifier_idx').on(t.identifier)],
);
export const twoFactors = pgTable(
'two_factors',
{
id: text('id').primaryKey(),
secret: text('secret').notNull(),
backupCodes: text('backup_codes').notNull(),
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
verified: boolean('verified').default(true),
failedVerificationCount: integer('failed_verification_count').default(0),
lockedUntil: ts('locked_until'),
},
(t) => [index('two_factors_user_idx').on(t.userId)],
);
/** Límite de peticiones compartido entre instancias (serverless): el de memoria no sirve con varias réplicas. */
export const rateLimits = pgTable('rate_limits', {
id: text('id').primaryKey(),
key: text('key').notNull().unique(),
count: integer('count').notNull(),
lastRequest: bigint('last_request', { mode: 'number' }).notNull(),
});
/** Modelo de Better Auth → tabla Drizzle (se pasa al adaptador). */
export const authSchema = {
user: users,
session: sessions,
account: accounts,
verification: verifications,
twoFactor: twoFactors,
rateLimit: rateLimits,
};
export type User = typeof users.$inferSelect;
export type Session = typeof sessions.$inferSelect;
- servidor
- better-auth
- comando
- https://mcp.better-auth.com/mcp
| Versión | Commit | Publicado | Escaneo |
|---|---|---|---|
| 1.0.0 | 7145b9d | hace 3 horas | ✔ escaneo superado |
- genpm
- ninguno
- propuesta
- GenPM propone el comando npm y solo lo ejecuta si dices que sí.
- escaneo
- escaneo superado · 0 hallazgos
- commit
- auth@1.0.0 → 7145b9d58055b5145085782c958b7577f1d65276 · verificado tras la descarga
- scripts
- Ninguno. GenPM nunca ejecuta código del paquete.
- licencia
- MIT
- reporte
- ¿Ves algo raro?