import { randUserName, randRecentDate, randIp, randPassword, randUuid, randNumber } from '@ngneat/falso'; import { r } from 'react-router/dist/development/fog-of-war-Cm1iXIp7'; export interface LoginAttempt { readonly date: string; readonly remoteIP: string; readonly username: string; readonly password: string; readonly sshClientVersion: string; readonly connectionUUID: string; readonly country: string; } export interface TotalStats { readonly password: number; readonly username: number; readonly ip: number; readonly country: number; readonly attempts: number; } export interface StatsResult { name: string; count: number; } export type StatsType = 'password' | 'username' | 'ip' | 'country' | 'attempts'; export interface ApiaryAPI { live(fn: (a: LoginAttempt) => void): void; stats(statsType: StatsType, limit: number): Promise; query(queryType: string, query: string): Promise; totals(): Promise; } function fakeLoginAttempt(): LoginAttempt { return { date: randRecentDate({ days: 2 }).toISOString(), remoteIP: randIp().toString(), username: randUserName().toString(), password: randPassword().toString(), sshClientVersion: '1.0', connectionUUID: randUuid().toString(), country: 'NO' } } export class DummyApiaryAPIClient implements ApiaryAPI { live(fn: (a: LoginAttempt) => void): () => void { const interval = setInterval(() => { let a = fakeLoginAttempt(); fn(a); }, 1000); return () => { clearInterval(interval) } } async stats(_type: StatsType, limit: number): Promise { const stats = Array.from({ length: limit }, () => { switch (_type) { case 'password': return { name: randPassword().toString(), count: randNumber().valueOf() } case 'username': return { name: randUserName().toString(), count: randNumber().valueOf() } case 'ip': return { name: randIp().toString(), count: randNumber().valueOf() } case 'country': return { name: 'NO', count: randNumber().valueOf() } } return { name: randUserName().toString(), count: randNumber().valueOf() } }); const sorted = stats.sort((a, b) => b.count - a.count) return Promise.resolve(sorted); } async query(_type: string, _query: string): Promise { const attempts = Array.from({ length: 10 }, () => { return fakeLoginAttempt(); }) return Promise.resolve(attempts); } async totals(): Promise { return Promise.resolve({ password: 1, username: 1, ip: 1, country: 1, attempts: 1 }) } } export class ApiaryAPIClient implements ApiaryAPI { live(fn: (a: LoginAttempt) => void): () => void { const es = new EventSource('/api/stream'); const updateFn = (ev: MessageEvent) => { const attempt: LoginAttempt = JSON.parse(ev.data); fn(attempt); }; es.addEventListener('message', updateFn) return () => { es.removeEventListener('message', updateFn); es.close(); } } async stats(statsType: StatsType, limit: number): Promise { const resp = await fetch(`/api/stats?type=${statsType}&limit=${limit}`) if (!resp.ok) { throw new Error('Failed to fetch query') } const data: StatsResult[] | null = await resp.json() if (!data) { return [] } return data.sort((a, b) => b.count - a.count) } async query(queryType: string, query: string): Promise { const resp = await fetch(`/api/query?type=${queryType}&query=${query}`) if (!resp.ok) { throw new Error('Failed to fetch query') } const data: LoginAttempt[] = await resp.json() return data } async totals(): Promise { let password: number = -1 let username: number = -1 let ip: number = -1 let attempts: number = -1 let country: number = -1 const resp = await fetch('/api/stats?type=total') const data: Array = await resp.json() for (const stat of data) { switch (stat.name) { case 'UniquePasswords': password = stat.count break case 'UniqueUsernames': username = stat.count break case 'UniqueIPs': ip = stat.count break case 'TotalLoginAttempts': attempts = stat.count break case 'UniqueCountries': country = stat.count break } } return { password, username, ip, attempts, country } } }