Skip to content

Commit 1b0da31

Browse files
committed
feat(player-connection): implement player connection history to detect alt accounts
1 parent ff2868f commit 1b0da31

10 files changed

Lines changed: 83 additions & 2 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { Knex } from "knex";
2+
3+
4+
export async function up(knex: Knex): Promise<void> {
5+
await knex.schema.createTable("player_connection_history", (table) => {
6+
table.increments("id").primary();
7+
table.string("steam_id_3").notNullable();
8+
table.string("ip_address").notNullable();
9+
table.string("nickname").notNullable();
10+
table.timestamp("timestamp").notNullable().defaultTo(knex.fn.now());
11+
});
12+
}
13+
14+
15+
export async function down(knex: Knex): Promise<void> {
16+
await knex.schema.dropTableIfExists("player_connection_history");
17+
}
18+

packages/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export * from './src/repository/UserRepository';
1818
export * from './src/repository/UserBanRepository';
1919
export * from './src/repository/UserCreditsRepository';
2020
export * from './src/repository/ServerStatusMetricsRepository';
21+
export * from './src/repository/PlayerConnectionHistoryRepository';
2122

2223
// Services
2324
export * from './src/services/BackgroundTaskQueue';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export type PlayerConnectionHistory = {
2+
id?: number;
3+
steamId3: string;
4+
ipAddress: string;
5+
nickname: string;
6+
timestamp: Date;
7+
};

packages/core/src/domain/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ export * from "./GuildParameters";
1414
export * from "./Cost";
1515
export * from "./DateRange";
1616
export * from "./ServerActivity";
17+
export * from "./PlayerConnectionHistory";
1718
export { ServerStatusParser } from "./ServerStatus";
1819
export * from "./ServerStatusMetric";
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { PlayerConnectionHistory } from "../domain/PlayerConnectionHistory";
2+
3+
export interface PlayerConnectionHistoryRepository {
4+
save(params: { connectionHistory: Omit<PlayerConnectionHistory, "id"> }): Promise<PlayerConnectionHistory>;
5+
}

packages/entrypoints/src/discordBot.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { SQLiteServerRepository } from "@tf2qs/providers";
2929
import { SQliteUserCreditsRepository } from "@tf2qs/providers";
3030
import { SQliteUserRepository } from "@tf2qs/providers";
3131
import { SQliteServerStatusMetricsRepository } from "@tf2qs/providers";
32+
import { SQlitePlayerConnectionHistoryRepository } from "@tf2qs/providers";
3233
import { AdyenPaymentService } from "@tf2qs/providers";
3334
import { ChancePasswordGeneratorService } from "@tf2qs/providers";
3435
import { defaultAWSServiceFactory } from "@tf2qs/providers";
@@ -134,6 +135,10 @@ export async function startDiscordBot() {
134135
knex: KnexConnectionManager.client
135136
})
136137

138+
const playerConnectionHistoryRepository = new SQlitePlayerConnectionHistoryRepository({
139+
knex: KnexConnectionManager.client
140+
})
141+
137142
const backgroundTaskQueue = new InMemoryBackgroundTaskQueue(defaultGracefulShutdownManager);
138143
const deleteServerUseCase = new DeleteServerForUser({
139144
serverManagerFactory: serverManagerFactory,
@@ -372,7 +377,8 @@ export async function startDiscordBot() {
372377
serverRepository,
373378
userRepository,
374379
eventLogger,
375-
backgroundTaskQueue
380+
backgroundTaskQueue,
381+
playerConnectionHistoryRepository
376382
});
377383

378384
initializeExpress({})

packages/entrypoints/src/udp/srcdsCommands/ClientConnected.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const clientConnected: SRCDSCommandParser<ClientConnectedArgs> = (rawStri
2121
raw: rawString,
2222
args: { nickname, ipAddress, steamId3 },
2323
type: "clientConnected",
24-
handler: async ({ args }) => {
24+
handler: async ({ args, services }) => {
2525
const { nickname, ipAddress, steamId3 } = args;
2626

2727
logger.emit({
@@ -33,6 +33,16 @@ export const clientConnected: SRCDSCommandParser<ClientConnectedArgs> = (rawStri
3333
steamId3,
3434
},
3535
});
36+
37+
// Persist connection history
38+
await services.playerConnectionHistoryRepository.save({
39+
connectionHistory: {
40+
steamId3,
41+
ipAddress,
42+
nickname,
43+
timestamp: new Date(),
44+
},
45+
});
3646
},
3747
};
3848
};

packages/entrypoints/src/udp/srcdsCommands/UDPCommandServices.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { UserRepository } from "@tf2qs/core";
44
import { EventLogger } from "@tf2qs/core";
55
import { ServerCommander } from "@tf2qs/core";
66
import { BackgroundTaskQueue } from "@tf2qs/core";
7+
import { PlayerConnectionHistoryRepository } from "@tf2qs/core";
78

89
/**
910
* List of services available to UDP Command Handlers
@@ -15,4 +16,5 @@ export type UDPCommandsServices = {
1516
userRepository: UserRepository
1617
eventLogger: EventLogger;
1718
backgroundTaskQueue: BackgroundTaskQueue;
19+
playerConnectionHistoryRepository: PlayerConnectionHistoryRepository;
1820
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Knex } from "knex";
2+
import { PlayerConnectionHistory, PlayerConnectionHistoryRepository } from "@tf2qs/core";
3+
4+
type SQlitePlayerConnectionHistoryRepositoryDependencies = {
5+
knex: Knex;
6+
};
7+
8+
export class SQlitePlayerConnectionHistoryRepository implements PlayerConnectionHistoryRepository {
9+
constructor(private readonly dependencies: SQlitePlayerConnectionHistoryRepositoryDependencies) {}
10+
11+
async save(params: { connectionHistory: Omit<PlayerConnectionHistory, "id"> }): Promise<PlayerConnectionHistory> {
12+
const { connectionHistory } = params;
13+
const { knex } = this.dependencies;
14+
15+
const [id] = await knex("player_connection_history").insert({
16+
steam_id_3: connectionHistory.steamId3,
17+
ip_address: connectionHistory.ipAddress,
18+
nickname: connectionHistory.nickname,
19+
timestamp: connectionHistory.timestamp,
20+
});
21+
22+
return {
23+
id,
24+
steamId3: connectionHistory.steamId3,
25+
ipAddress: connectionHistory.ipAddress,
26+
nickname: connectionHistory.nickname,
27+
timestamp: connectionHistory.timestamp,
28+
};
29+
}
30+
}

packages/providers/src/repository/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './SQliteServerRepository';
88
export * from './SQliteUserCreditsRepository';
99
export * from './SQliteUserRepository';
1010
export * from './SQliteServerStatusMetricsRepository';
11+
export * from './SQlitePlayerConnectionHistoryRepository';

0 commit comments

Comments
 (0)