fix execution validation, verify that clientID own playerID to prevent spoofing

This commit is contained in:
Evan
2025-02-18 17:13:08 -08:00
parent 5e6f8f5d91
commit f894276ef0
8 changed files with 106 additions and 31 deletions
+10 -10
View File
@@ -308,7 +308,7 @@ export class Transport {
this.sendIntent({
type: "allianceRequest",
clientID: this.lobbyConfig.clientID,
requestor: event.requestor.id(),
playerID: event.requestor.id(),
recipient: event.recipient.id(),
});
}
@@ -318,7 +318,7 @@ export class Transport {
type: "allianceRequestReply",
clientID: this.lobbyConfig.clientID,
requestor: event.requestor.id(),
recipient: event.recipient.id(),
playerID: event.recipient.id(),
accept: event.accepted,
});
}
@@ -327,7 +327,7 @@ export class Transport {
this.sendIntent({
type: "breakAlliance",
clientID: this.lobbyConfig.clientID,
requestor: event.requestor.id(),
playerID: event.requestor.id(),
recipient: event.recipient.id(),
});
}
@@ -349,7 +349,7 @@ export class Transport {
this.sendIntent({
type: "attack",
clientID: this.lobbyConfig.clientID,
attackerID: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
targetID: event.targetID,
troops: event.troops,
});
@@ -359,7 +359,7 @@ export class Transport {
this.sendIntent({
type: "boat",
clientID: this.lobbyConfig.clientID,
attackerID: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
targetID: event.targetID,
troops: event.troops,
x: event.cell.x,
@@ -371,7 +371,7 @@ export class Transport {
this.sendIntent({
type: "targetPlayer",
clientID: this.lobbyConfig.clientID,
requestor: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
target: event.targetID,
});
}
@@ -380,7 +380,7 @@ export class Transport {
this.sendIntent({
type: "emoji",
clientID: this.lobbyConfig.clientID,
sender: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
recipient:
event.recipient == AllPlayers ? AllPlayers : event.recipient.id(),
emoji: event.emoji,
@@ -391,7 +391,7 @@ export class Transport {
this.sendIntent({
type: "donate",
clientID: this.lobbyConfig.clientID,
sender: event.sender.id(),
playerID: event.sender.id(),
recipient: event.recipient.id(),
troops: event.troops,
});
@@ -401,7 +401,7 @@ export class Transport {
this.sendIntent({
type: "troop_ratio",
clientID: this.lobbyConfig.clientID,
player: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
ratio: event.ratio,
});
}
@@ -410,7 +410,7 @@ export class Transport {
this.sendIntent({
type: "build_unit",
clientID: this.lobbyConfig.clientID,
player: this.lobbyConfig.playerID,
playerID: this.lobbyConfig.playerID,
unit: event.unit,
x: event.cell.x,
y: event.cell.y,
+11 -10
View File
@@ -133,11 +133,12 @@ const BaseIntentSchema = z.object({
"build_unit",
]),
clientID: ID,
playerID: ID,
});
export const AttackIntentSchema = BaseIntentSchema.extend({
type: z.literal("attack"),
attackerID: ID,
playerID: ID,
targetID: ID.nullable(),
troops: z.number().nullable(),
});
@@ -154,7 +155,7 @@ export const SpawnIntentSchema = BaseIntentSchema.extend({
export const BoatAttackIntentSchema = BaseIntentSchema.extend({
type: z.literal("boat"),
attackerID: ID,
playerID: ID,
targetID: ID.nullable(),
troops: z.number().nullable(),
x: z.number(),
@@ -163,52 +164,52 @@ export const BoatAttackIntentSchema = BaseIntentSchema.extend({
export const AllianceRequestIntentSchema = BaseIntentSchema.extend({
type: z.literal("allianceRequest"),
requestor: ID,
playerID: ID,
recipient: ID,
});
export const AllianceRequestReplyIntentSchema = BaseIntentSchema.extend({
type: z.literal("allianceRequestReply"),
requestor: ID, // The one who made the original alliance request
recipient: ID,
playerID: ID,
accept: z.boolean(),
});
export const BreakAllianceIntentSchema = BaseIntentSchema.extend({
type: z.literal("breakAlliance"),
requestor: ID, // The one who made the original alliance request
playerID: ID,
recipient: ID,
});
export const TargetPlayerIntentSchema = BaseIntentSchema.extend({
type: z.literal("targetPlayer"),
requestor: ID,
playerID: ID,
target: ID,
});
export const EmojiIntentSchema = BaseIntentSchema.extend({
type: z.literal("emoji"),
sender: ID,
playerID: ID,
recipient: z.union([ID, z.literal(AllPlayers)]),
emoji: EmojiSchema,
});
export const DonateIntentSchema = BaseIntentSchema.extend({
type: z.literal("donate"),
sender: ID,
playerID: ID,
recipient: ID,
troops: z.number().nullable(),
});
export const TargetTroopRatioIntentSchema = BaseIntentSchema.extend({
type: z.literal("troop_ratio"),
player: ID,
playerID: ID,
ratio: z.number().min(0).max(1),
});
export const BuildUnitIntentSchema = BaseIntentSchema.extend({
type: z.literal("build_unit"),
player: ID,
playerID: ID,
unit: z.nativeEnum(UnitType),
x: z.number(),
y: z.number(),
+26 -10
View File
@@ -33,6 +33,7 @@ import { DonateExecution } from "./DonateExecution";
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
import { ConstructionExecution } from "./ConstructionExecution";
import { fixProfaneUsername, isProfaneUsername } from "../validations/username";
import { NoOpExecution } from "./NoOpExecution";
export class Executor {
// private random = new PseudoRandom(999)
@@ -52,11 +53,26 @@ export class Executor {
}
createExec(intent: Intent): Execution {
if (intent.type != "spawn") {
if (!this.mg.hasPlayer(intent.playerID)) {
console.warn(
`player ${intent.playerID} not found on intent ${intent.type}`,
);
return new NoOpExecution();
}
const player = this.mg.player(intent.playerID);
if (player.clientID() != intent.clientID) {
console.warn(
`intent ${intent.type} has incorrect clientID ${intent.clientID} for player ${player.name()} with clientID ${player.clientID()}`,
);
return new NoOpExecution();
}
}
switch (intent.type) {
case "attack": {
return new AttackExecution(
intent.troops,
intent.attackerID,
intent.playerID,
intent.targetID,
null,
);
@@ -77,40 +93,40 @@ export class Executor {
);
case "boat":
return new TransportShipExecution(
intent.attackerID,
intent.playerID,
intent.targetID,
this.mg.ref(intent.x, intent.y),
intent.troops,
);
case "allianceRequest":
return new AllianceRequestExecution(intent.requestor, intent.recipient);
return new AllianceRequestExecution(intent.playerID, intent.recipient);
case "allianceRequestReply":
return new AllianceRequestReplyExecution(
intent.requestor,
intent.recipient,
intent.playerID,
intent.accept,
);
case "breakAlliance":
return new BreakAllianceExecution(intent.requestor, intent.recipient);
return new BreakAllianceExecution(intent.playerID, intent.recipient);
case "targetPlayer":
return new TargetPlayerExecution(intent.requestor, intent.target);
return new TargetPlayerExecution(intent.playerID, intent.target);
case "emoji":
return new EmojiExecution(
intent.sender,
intent.playerID,
intent.recipient,
intent.emoji,
);
case "donate":
return new DonateExecution(
intent.sender,
intent.playerID,
intent.recipient,
intent.troops,
);
case "troop_ratio":
return new SetTargetTroopRatioExecution(intent.player, intent.ratio);
return new SetTargetTroopRatioExecution(intent.playerID, intent.ratio);
case "build_unit":
return new ConstructionExecution(
intent.player,
intent.playerID,
this.mg.ref(intent.x, intent.y),
intent.unit,
);
+1 -1
View File
@@ -43,7 +43,7 @@ export class MirvExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!this.mg.hasPlayer(this.senderID)) {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`MIRVExecution: player ${this.senderID} not found`);
this.active = false;
return;
+15
View File
@@ -0,0 +1,15 @@
import { Execution, Game, Player } from "../game/Game";
export class NoOpExecution implements Execution {
isActive(): boolean {
return false;
}
activeDuringSpawnPhase(): boolean {
return false;
}
init(mg: Game, ticks: number): void {}
tick(ticks: number): void {}
owner(): Player {
return null;
}
}
@@ -19,6 +19,21 @@ export class AllianceRequestExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`AllianceRequestExecution requester ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`AllianceRequestExecution recipient ${this.recipientID} not found`,
);
this.active = false;
return;
}
this.mg = mg;
this.requestor = mg.player(this.requestorID);
this.recipient = mg.player(this.recipientID);
@@ -20,6 +20,20 @@ export class AllianceRequestReplyExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`AllianceRequestReplyExecution requester ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`AllianceRequestReplyExecution recipient ${this.recipientID} not found`,
);
this.active = false;
return;
}
this.mg = mg;
this.requestor = mg.player(this.requestorID);
this.recipient = mg.player(this.recipientID);
@@ -19,6 +19,20 @@ export class BreakAllianceExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`BreakAllianceExecution requester ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`BreakAllianceExecution: recipient ${this.recipientID} not found`,
);
this.active = false;
return;
}
this.requestor = mg.player(this.requestorID);
this.recipient = mg.player(this.recipientID);
this.mg = mg;