TD-69: Create Login Endpoint and Store Login Session
All checks were successful
Quality Check / Validate OAS (push) Successful in 31s
Quality Check / Linting (push) Successful in 58s
Quality Check / Testing (push) Successful in 57s
Quality Check / Static Analysis (push) Successful in 1m0s
Quality Check / Validate OAS (pull_request) Successful in 30s
Quality Check / Linting (pull_request) Successful in 59s
Quality Check / Testing (pull_request) Successful in 59s
Quality Check / Static Analysis (pull_request) Successful in 1m17s

This commit is contained in:
Snoweuph 2025-02-12 11:51:05 +01:00
parent 601d814b97
commit 11d3b8721f
Signed by: snoweuph
GPG key ID: BEFC41DA223CEC55
3 changed files with 144 additions and 0 deletions

View file

@ -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"

View file

@ -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<String, String> playerLoginSessionTokens = new HashMap<>();
private final Map<String, Player> playerLoginSessionPlayers = new HashMap<>();
private final Map<String, ScheduledFuture<?>> 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<Player> 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);
}
}

View file

@ -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<ObjectMapper> getObjectMapper() {
@ -52,6 +57,26 @@ public class ServerApiController implements ServerApi {
return new ResponseEntity<>(HttpStatus.CREATED);
}
@Override
public ResponseEntity<PlayerLoginSession> 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<ServerHealth> serverGetHealthcheck() {
ServerHealth health = new ServerHealth();