UNPKG

codalware-auth

Version:

Complete authentication system with enterprise security, attack protection, team workspaces, waitlist, billing, UI components, 2FA, and account recovery - production-ready in 5 minutes. Enhanced CLI with verification, rollback, and App Router scaffolding.

104 lines (100 loc) 5.76 kB
import { Adapter, User, Session, MagicToken } from './types'; /** * createDrizzleAdapter * Accepts a Drizzle client or any database client exposing `query(sql, params)` returning rows. * This keeps the adapter compatible with Drizzle's client or plain `pg` pools. It avoids * adding hard dependency on Drizzle so consumers can pass their own client. */ export function createDrizzleAdapter(opts: { db: any }): Adapter { if (!opts?.db) throw new Error('db client required'); const db = opts.db; async function execute(sql: string, params: any[] = []) { // Drizzle clients expose different APIs; try common shapes if (typeof db.query === 'function') { // pg-style or drizzle raw client const res = await db.query(sql, params); return res.rows ?? res; } if (typeof db.execute === 'function') { // some drizzle clients use execute const res = await db.execute(sql, params); return res.rows ?? res; } throw new Error('db client does not support query/execute'); } return { async createUser({ email, name, metadata }) { const rows = await execute( `INSERT INTO users(email, name, metadata) VALUES($1,$2,$3) RETURNING id, email, name, metadata, created_at, updated_at`, [email, name ?? null, metadata ? JSON.stringify(metadata) : null] ); const r = rows[0]; return { id: r.id, email: r.email, name: r.name, metadata: r.metadata, createdAt: r.created_at, updatedAt: r.updated_at } as User; }, async getUserById(id) { const rows = await execute(`SELECT id, email, name, metadata, created_at, updated_at FROM users WHERE id=$1`, [id]); const r = rows[0]; if (!r) return null; return { id: r.id, email: r.email, name: r.name, metadata: r.metadata, createdAt: r.created_at, updatedAt: r.updated_at } as User; }, async getUserByEmail(email) { const rows = await execute(`SELECT id, email, name, metadata, created_at, updated_at FROM users WHERE email=$1`, [email]); const r = rows[0]; if (!r) return null; return { id: r.id, email: r.email, name: r.name, metadata: r.metadata, createdAt: r.created_at, updatedAt: r.updated_at } as User; }, async updateUser(id, patch) { const fields: string[] = []; const vals: any[] = []; let i = 1; if (patch.email !== undefined) { fields.push(`email=$${i++}`); vals.push(patch.email); } if (patch.name !== undefined) { fields.push(`name=$${i++}`); vals.push(patch.name); } if (patch.metadata !== undefined) { fields.push(`metadata=$${i++}`); vals.push(JSON.stringify(patch.metadata)); } if (fields.length === 0) throw new Error('nothing to update'); const sql = `UPDATE users SET ${fields.join(',')}, updated_at=now() WHERE id=$${i} RETURNING id, email, name, metadata, created_at, updated_at`; vals.push(id); const rows = await execute(sql, vals); const r = rows[0]; return { id: r.id, email: r.email, name: r.name, metadata: r.metadata, createdAt: r.created_at, updatedAt: r.updated_at } as User; }, async createSession(session) { const rows = await execute( `INSERT INTO sessions(user_id, handle, expires_at, metadata) VALUES($1,$2,$3,$4) RETURNING id, user_id, handle, created_at, expires_at, metadata`, [session.userId, session.handle ?? null, session.expiresAt, session.metadata ? JSON.stringify(session.metadata) : null] ); const r = rows[0]; return { id: r.id, userId: r.user_id, handle: r.handle, createdAt: r.created_at, expiresAt: r.expires_at, metadata: r.metadata } as Session; }, async getSessionById(id) { const rows = await execute(`SELECT id, user_id, handle, created_at, expires_at, metadata FROM sessions WHERE id=$1`, [id]); const r = rows[0]; if (!r) return null; return { id: r.id, userId: r.user_id, handle: r.handle, createdAt: r.created_at, expiresAt: r.expires_at, metadata: r.metadata } as Session; }, async deleteSession(id) { await execute(`DELETE FROM sessions WHERE id=$1`, [id]); }, async deleteSessionsByUserId(userId) { await execute(`DELETE FROM sessions WHERE user_id=$1`, [userId]); }, async storeMagicToken({ tokenHash, userId = null, expiresAt, ip = null, userAgent = null }) { const rows = await execute( `INSERT INTO magic_tokens(token_hash, user_id, expires_at, ip, user_agent) VALUES($1,$2,$3,$4,$5) RETURNING id, token_hash, user_id, created_at, expires_at, consumed_at, ip, user_agent`, [tokenHash, userId, expiresAt, ip, userAgent] ); const r = rows[0]; return { id: r.id, tokenHash: r.token_hash, userId: r.user_id, createdAt: r.created_at, expiresAt: r.expires_at, consumedAt: r.consumed_at, ip: r.ip, userAgent: r.user_agent } as MagicToken; }, async findValidMagicToken(tokenHash) { const rows = await execute(`SELECT id, token_hash, user_id, created_at, expires_at, consumed_at, ip, user_agent FROM magic_tokens WHERE token_hash=$1 AND consumed_at IS NULL AND expires_at > now()`, [tokenHash]); const r = rows[0]; if (!r) return null; return { id: r.id, tokenHash: r.token_hash, userId: r.user_id, createdAt: r.created_at, expiresAt: r.expires_at, consumedAt: r.consumed_at, ip: r.ip, userAgent: r.user_agent } as MagicToken; }, async consumeMagicToken(id) { const { rowCount } = await execute(`UPDATE magic_tokens SET consumed_at=now() WHERE id=$1 AND consumed_at IS NULL`, [id]); // execute may return array; try to detect rowCount if (typeof rowCount === 'number' && rowCount === 0) return; }, }; }