Files
apiary/frontend/src/js/api.ts

155 lines
5.0 KiB
TypeScript

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<StatsResult[]>;
query(queryType: string, query: string): Promise<LoginAttempt[]>;
totals(): Promise<TotalStats>;
}
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<StatsResult[]> {
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<LoginAttempt[]> {
const attempts = Array.from({ length: 10 }, () => {
return fakeLoginAttempt();
})
return Promise.resolve(attempts);
}
async totals(): Promise<TotalStats> {
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<string>) => {
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<StatsResult[]> {
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<LoginAttempt[]> {
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<TotalStats> {
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<StatsResult> = 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 }
}
}