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
text/typescript
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;
},
};
}