diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts index 06d98b70..b8e0a204 100644 --- a/front/src/Connexion/LocalUser.ts +++ b/front/src/Connexion/LocalUser.ts @@ -1,3 +1,5 @@ +import {MAX_USERNAME_LENGTH} from "../Enum/EnvironmentVariable"; + export interface CharacterTexture { id: number, level: number, @@ -5,6 +7,23 @@ export interface CharacterTexture { rights: string } +export const maxUserNameLength: number = MAX_USERNAME_LENGTH; + +export function isUserNameValid(value: string): boolean { + const regexp = new RegExp('^[A-Za-z]{1,'+maxUserNameLength+'}$'); + return regexp.test(value); +} + +export function areCharacterLayersValid(value: string[]): boolean { + if (!value.length) return false; + for (let i = 0; i < value.length; i++) { + if (/^\w+$/.exec(value[i]) === null) { + return false; + } + } + return true; +} + export class LocalUser { constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) { } diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 702df561..48b5697b 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -1,4 +1,4 @@ -import {LocalUser} from "./LocalUser"; +import {areCharacterLayersValid, isUserNameValid, LocalUser} from "./LocalUser"; const playerNameKey = 'playerName'; const selectedPlayerKey = 'selectedPlayer'; @@ -22,8 +22,9 @@ class LocalUserStore { setName(name:string): void { localStorage.setItem(playerNameKey, name); } - getName(): string { - return localStorage.getItem(playerNameKey) || ''; + getName(): string|null { + const value = localStorage.getItem(playerNameKey) || ''; + return isUserNameValid(value) ? value : null; } setPlayerCharacterIndex(playerCharacterIndex: number): void { @@ -44,7 +45,8 @@ class LocalUserStore { localStorage.setItem(characterLayersKey, JSON.stringify(layers)); } getCharacterLayers(): string[]|null { - return JSON.parse(localStorage.getItem(characterLayersKey) || "null"); + const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null"); + return areCharacterLayersValid(value) ? value : null; } setGameQualityValue(value: number): void { diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index ea2434af..5040e59f 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -14,6 +14,7 @@ const RESOLUTION = 2; const ZOOM_LEVEL = 1/*3/4*/; const POSITION_DELAY = 200; // Wait 200ms between sending position events const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player +export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8; export { DEBUG_MODE, diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 8751796f..e5ed7bba 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -35,7 +35,7 @@ export class GameManager { if (!this.playerName) { return LoginSceneName; - } else if (!this.characterLayers) { + } else if (!this.characterLayers || !this.characterLayers.length) { return SelectCharacterSceneName; } else { return EnableCameraSceneName; diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 4f5b2860..6cf13a86 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -11,6 +11,7 @@ import {localUserStore} from "../../Connexion/LocalUserStore"; import {addLoader} from "../Components/Loader"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; import {AbstractCharacterScene} from "./AbstractCharacterScene"; +import {areCharacterLayersValid} from "../../Connexion/LocalUser"; export const CustomizeSceneName = "CustomizeScene"; @@ -111,6 +112,9 @@ export class CustomizeScene extends AbstractCharacterScene { } i++; } + if (!areCharacterLayersValid(layers)) { + return; + } gameManager.setCharacterLayers(layers); diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 9ca6dcd2..ffdf1367 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -4,6 +4,7 @@ import {TextInput} from "../Components/TextInput"; import Image = Phaser.GameObjects.Image; import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {ResizableScene} from "./ResizableScene"; +import {isUserNameValid, maxUserNameLength} from "../../Connexion/LocalUser"; //todo: put this constants in a dedicated file export const LoginSceneName = "LoginScene"; @@ -37,7 +38,7 @@ export class LoginScene extends ResizableScene { create() { this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:'); - this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => { + this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, maxUserNameLength, this.name,(text: string) => { this.name = text; }); @@ -50,10 +51,9 @@ export class LoginScene extends ResizableScene { this.infoTextField = new TextField(this, 10, this.game.renderer.height - 35, infoText, false); this.input.keyboard.on('keyup-ENTER', () => { - if (this.name === '') { - return + if (isUserNameValid(this.name)) { + this.login(this.name); } - this.login(this.name); }); } diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index e47cf38a..a1f3fd81 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -10,6 +10,7 @@ import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesL import {addLoader} from "../Components/Loader"; import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; import {AbstractCharacterScene} from "./AbstractCharacterScene"; +import {areCharacterLayersValid} from "../../Connexion/LocalUser"; //todo: put this constants in a dedicated file @@ -142,6 +143,9 @@ export class SelectCharacterScene extends AbstractCharacterScene { } private nextScene(): void { + if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) { + return; + } this.scene.stop(SelectCharacterSceneName); if (this.selectedPlayer !== null) { gameManager.setCharacterLayers([this.selectedPlayer.texture.key]); diff --git a/front/tests/Phaser/Connexion/LocalUserTest.ts b/front/tests/Phaser/Connexion/LocalUserTest.ts new file mode 100644 index 00000000..00d8f400 --- /dev/null +++ b/front/tests/Phaser/Connexion/LocalUserTest.ts @@ -0,0 +1,45 @@ +import "jasmine"; +import {areCharacterLayersValid, isUserNameValid, maxUserNameLength} from "../../../src/Connexion/LocalUser"; + +describe("isUserNameValid()", () => { + it("should validate name with letters", () => { + expect(isUserNameValid('toto')).toBe(true); + }); + + it("should not validate empty name", () => { + expect(isUserNameValid('')).toBe(false); + }); + it("should not validate string with too many letters", () => { + let testString = ''; + for (let i = 0; i < maxUserNameLength + 2; i++) { + testString += 'a'; + } + expect(isUserNameValid(testString)).toBe(false); + }); + it("should not validate spaces", () => { + expect(isUserNameValid(' ')).toBe(false); + }); + it("should not validate numbers", () => { + expect(isUserNameValid('a12')).toBe(false); + }); + it("should not validate special characters", () => { + expect(isUserNameValid('a&-')).toBe(false); + }); +}); + +describe("areCharacterLayersValid()", () => { + it("should validate default textures array", () => { + expect(areCharacterLayersValid(['male1', 'male2'])).toBe(true); + }); + + it("should not validate an empty array", () => { + expect(areCharacterLayersValid([])).toBe(false); + }); + it("should not validate space only strings", () => { + expect(areCharacterLayersValid([' ', 'male1'])).toBe(false); + }); + + it("should not validate empty strings", () => { + expect(areCharacterLayersValid(['', 'male1'])).toBe(false); + }); +}); \ No newline at end of file