155 lines
5.0 KiB
TypeScript
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 }
|
|
}
|
|
}
|