From 11d3b8721f1cb479c39ceda242c96e97f9321b67 Mon Sep 17 00:00:00 2001 From: Snoweuph Date: Wed, 12 Feb 2025 11:51:05 +0100 Subject: [PATCH] TD-69: Create Login Endpoint and Store Login Session --- api/api.yml | 52 ++++++++++++++ .../player/session/PlayerLoginSessions.java | 67 +++++++++++++++++++ .../server/server/ServerApiController.java | 25 +++++++ 3 files changed, 144 insertions(+) create mode 100644 src/main/java/de/towerdefence/server/player/session/PlayerLoginSessions.java diff --git a/api/api.yml b/api/api.yml index 17a79e2..83ffca4 100644 --- a/api/api.yml +++ b/api/api.yml @@ -39,6 +39,34 @@ components: - username - password ############################################# + # PlayerLoginData # + ############################################# + PlayerLoginData: + description: Data needed to log a Player in + type: object + properties: + username: + type: string + password: + type: string + required: + - username + - password + ############################################# + # PlayerLoginSession # + ############################################# + PlayerLoginSession: + description: Data needed to log a Player in + type: object + properties: + username: + type: string + token: + type: string + required: + - username + - token + ############################################# # AdminAuthInfo # ############################################# ServerHealth: @@ -61,6 +89,8 @@ components: responses: 201PlayerCreated: description: "201 - Player Created" + 401PlayerNameOrPasswordWrong: + description: "401 - Player Name or Password is Wrong" 401Unauthorized: description: "401 - Unauthorized" 404NotFound: @@ -112,6 +142,28 @@ paths: $ref: "#/components/responses/409UsernameTaken" 500: $ref: "#/components/responses/500InternalError" + /player/login: + post: + operationId: "PlayerLogin" + tags: + - server + description: "Endpoint for logging a Player in" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/PlayerLoginData" + responses: + 200: + description: "A Login Session, which can be used in the Webhook" + content: + application/json: + schema: + $ref: "#/components/schemas/PlayerLoginSession" + 401: + $ref: "#/components/responses/401PlayerNameOrPasswordWrong" + 500: + $ref: "#/components/responses/500InternalError" /server/health: get: operationId: "ServerGetHealthcheck" diff --git a/src/main/java/de/towerdefence/server/player/session/PlayerLoginSessions.java b/src/main/java/de/towerdefence/server/player/session/PlayerLoginSessions.java new file mode 100644 index 0000000..33b56d8 --- /dev/null +++ b/src/main/java/de/towerdefence/server/player/session/PlayerLoginSessions.java @@ -0,0 +1,67 @@ +package de.towerdefence.server.player.session; + +import de.towerdefence.server.player.Player; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@Component +public class PlayerLoginSessions { + public static final int PLAYER_LOGIN_SESSION_TOKEN_BYTE_LENGTH = 64; + private final SecureRandom random; + private final Map playerLoginSessionTokens = new HashMap<>(); + private final Map playerLoginSessionPlayers = new HashMap<>(); + private final Map> playerLoginSessionSchedule = new HashMap<>(); + private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + public PlayerLoginSessions() { + random = new SecureRandom(); + } + + public String createPlayerLoginSession(Player player) { + byte[] token_data = new byte[PLAYER_LOGIN_SESSION_TOKEN_BYTE_LENGTH]; + this.random.nextBytes(token_data); + String token = new String(token_data, StandardCharsets.UTF_8); + String playerName = player.getUsername(); + this.playerLoginSessionTokens.put(playerName, token); + this.playerLoginSessionPlayers.put(playerName, player); + this.playerLoginSessionSchedule.put(playerName, scheduler.schedule( + () -> { + this.playerLoginSessionTokens.remove(playerName); + this.playerLoginSessionPlayers.remove(playerName); + this.playerLoginSessionSchedule.remove(playerName); + }, + 30, + TimeUnit.SECONDS + )); + return token; + } + + /** + * @return an Optional Player. If it is empty, that Player has no valid Login Session + */ + public Optional getPlayerFromLoginSession(String username, String token) { + if (!this.playerLoginSessionTokens.containsKey(username)) { + return Optional.empty(); + } + if (!this.playerLoginSessionTokens.get(username).equals(token)) { + return Optional.empty(); + } + this.playerLoginSessionTokens.remove(username); + Player player = this.playerLoginSessionPlayers.get(username); + ScheduledFuture task = this.playerLoginSessionSchedule.get(username); + if (task != null) { + task.cancel(true); + } + this.playerLoginSessionSchedule.remove(username); + return Optional.of(player); + } +} diff --git a/src/main/java/de/towerdefence/server/server/ServerApiController.java b/src/main/java/de/towerdefence/server/server/ServerApiController.java index 447660f..b366dab 100644 --- a/src/main/java/de/towerdefence/server/server/ServerApiController.java +++ b/src/main/java/de/towerdefence/server/server/ServerApiController.java @@ -2,11 +2,14 @@ package de.towerdefence.server.server; import com.fasterxml.jackson.databind.ObjectMapper; import de.towerdefence.server.oas.ServerApi; +import de.towerdefence.server.oas.models.PlayerLoginData; +import de.towerdefence.server.oas.models.PlayerLoginSession; import de.towerdefence.server.oas.models.PlayerRegistrationData; import de.towerdefence.server.oas.models.ServerHealth; import de.towerdefence.server.player.Player; import de.towerdefence.server.player.PlayerRepository; import de.towerdefence.server.player.PlayerService; +import de.towerdefence.server.player.session.PlayerLoginSessions; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -25,6 +28,8 @@ public class ServerApiController implements ServerApi { private PlayerRepository playerRepository; @Autowired private PlayerService playerService; + @Autowired + private PlayerLoginSessions playerLoginSessions; @Override public Optional getObjectMapper() { @@ -52,6 +57,26 @@ public class ServerApiController implements ServerApi { return new ResponseEntity<>(HttpStatus.CREATED); } + @Override + public ResponseEntity playerLogin(PlayerLoginData body) { + Player player = playerRepository.findByUsername(body.getUsername()); + if(player == null){ + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + try { + if(!playerService.checkPassword(player, body.getPassword())) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + } catch (NoSuchAlgorithmException e) { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + String token = playerLoginSessions.createPlayerLoginSession(player); + PlayerLoginSession session = new PlayerLoginSession(); + session.setUsername(player.getUsername()); + session.setToken(token); + return new ResponseEntity<>(session, HttpStatus.OK); + } + @Override public ResponseEntity serverGetHealthcheck() { ServerHealth health = new ServerHealth();