UNPKG

@kanadi/core

Version:

Multi-Layer CAPTCHA Framework with customizable validators and challenge bundles

187 lines (165 loc) 4.45 kB
import { sql } from "bun"; export interface KanadiUser { id: string; active_device_id?: string; created_at: Date; updated_at: Date; } export interface KanadiDevice { id: string; user_id: string; session_id: string; ip: string; user_agent?: string; referrer?: string; fingerprint?: string; client_fingerprint?: string; server_fingerprint?: string; created_at: Date; updated_at: Date; } export class UserRepository { async findOrCreateUser(deviceInfo: { sessionId: string; ip: string; userAgent?: string; referrer?: string; fingerprint?: string; clientFingerprint?: string; serverFingerprint?: string; }): Promise<{ user: KanadiUser; device: KanadiDevice; isNew: boolean }> { if (deviceInfo.fingerprint) { const existingDeviceByFingerprint = await sql` SELECT * FROM kanadi_devices WHERE fingerprint = ${deviceInfo.fingerprint} ORDER BY updated_at DESC LIMIT 1 `; if (existingDeviceByFingerprint.length > 0) { const device = existingDeviceByFingerprint[0] as KanadiDevice; await sql` UPDATE kanadi_devices SET session_id = ${deviceInfo.sessionId}, ip = ${deviceInfo.ip}, user_agent = ${deviceInfo.userAgent || null}, referrer = ${deviceInfo.referrer || null}, updated_at = CURRENT_TIMESTAMP WHERE id = ${device.id} `; const user = await this.findById(device.user_id); return { user: user!, device, isNew: false }; } } const existingDeviceBySession = await sql` SELECT * FROM kanadi_devices WHERE session_id = ${deviceInfo.sessionId} LIMIT 1 `; if (existingDeviceBySession.length > 0) { const device = existingDeviceBySession[0] as KanadiDevice; if (deviceInfo.fingerprint) { await sql` UPDATE kanadi_devices SET fingerprint = ${deviceInfo.fingerprint}, client_fingerprint = ${deviceInfo.clientFingerprint || null}, server_fingerprint = ${deviceInfo.serverFingerprint || null}, updated_at = CURRENT_TIMESTAMP WHERE id = ${device.id} `; } const user = await this.findById(device.user_id); return { user: user!, device, isNew: false }; } const newUser = await this.createUser(); const newDevice = await this.createDevice({ userId: newUser.id, ...deviceInfo, }); await sql` UPDATE kanadi_users SET active_device_id = ${newDevice.id} WHERE id = ${newUser.id} `; return { user: newUser, device: newDevice, isNew: true }; } async createUser(): Promise<KanadiUser> { const result = await sql` INSERT INTO kanadi_users DEFAULT VALUES RETURNING * `; return result[0] as KanadiUser; } async findById(id: string): Promise<KanadiUser | null> { const result = await sql` SELECT * FROM kanadi_users WHERE id = ${id} LIMIT 1 `; return result.length > 0 ? (result[0] as KanadiUser) : null; } async createDevice(data: { userId: string; sessionId: string; ip: string; userAgent?: string; referrer?: string; fingerprint?: string; clientFingerprint?: string; serverFingerprint?: string; }): Promise<KanadiDevice> { const result = await sql` INSERT INTO kanadi_devices ( user_id, session_id, ip, user_agent, referrer, fingerprint, client_fingerprint, server_fingerprint ) VALUES ( ${data.userId}, ${data.sessionId}, ${data.ip}, ${data.userAgent || null}, ${data.referrer || null}, ${data.fingerprint || null}, ${data.clientFingerprint || null}, ${data.serverFingerprint || null} ) RETURNING * `; return result[0] as KanadiDevice; } async findDeviceBySessionId(sessionId: string): Promise<KanadiDevice | null> { const result = await sql` SELECT * FROM kanadi_devices WHERE session_id = ${sessionId} LIMIT 1 `; return result.length > 0 ? (result[0] as KanadiDevice) : null; } async getStats(): Promise<{ totalUsers: number; totalDevices: number; activeToday: number; }> { const userResult = await sql` SELECT COUNT(*) as count FROM kanadi_users `; const deviceResult = await sql` SELECT COUNT(*) as count FROM kanadi_devices `; const activeTodayResult = await sql` SELECT COUNT(DISTINCT user_id) as count FROM kanadi_devices WHERE created_at >= CURRENT_DATE `; return { totalUsers: Number(userResult[0]?.count || 0), totalDevices: Number(deviceResult[0]?.count || 0), activeToday: Number(activeTodayResult[0]?.count || 0), }; } }