longpoll api update, token auth

This commit is contained in:
Michael Wain 2024-02-19 15:05:20 +03:00
parent bafd220a4c
commit 89fde1afc9
14 changed files with 199 additions and 36 deletions

View File

@ -0,0 +1,30 @@
package com.alterdekim.game.component;
import com.alterdekim.game.dto.LongPollResult;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@Component
@Getter
@Slf4j
public class LongPoll {
private final BlockingQueue<LongPollingSession> longPollingQueue = new ArrayBlockingQueue<>(100);
@Scheduled(fixedRate = 5000)
private void longPoll() {
getLongPollingQueue().forEach(longPollingSession -> {
try {
longPollingSession.getDeferredResult().setResult(new LongPollResult());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
});
getLongPollingQueue().removeIf(e -> e.getDeferredResult().isSetOrExpired());
}
}

View File

@ -0,0 +1,14 @@
package com.alterdekim.game.component;
import com.alterdekim.game.dto.LongPollResult;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.web.context.request.async.DeferredResult;
@AllArgsConstructor
@Getter
public class LongPollingSession {
private Long last_chat_id;
private DeferredResult<LongPollResult> deferredResult;
}

View File

@ -1,20 +1,21 @@
package com.alterdekim.game.controller;
import com.alterdekim.game.component.LongPoll;
import com.alterdekim.game.component.LongPollingSession;
import com.alterdekim.game.dto.*;
import com.alterdekim.game.entities.Chat;
import com.alterdekim.game.entities.Room;
import com.alterdekim.game.entities.User;
import com.alterdekim.game.service.*;
import com.alterdekim.game.util.Hash;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import java.io.UnsupportedEncodingException;
@ -47,11 +48,16 @@ public class APIController {
@Autowired
private UserServiceImpl userService;
@Autowired
private LongPoll longPoll;
@GetMapping("/api/v1/games/list")
public ResponseEntity<List<RoomResult>> gamesList() {
List<RoomResult> results = roomService.getAll()
.stream()
.map(room -> new RoomResult(room, roomPlayerService.findByRoomId(room.getId())))
.map(room -> new RoomResult(room.getId(), room.getPlayerCount(), roomPlayerService.findByRoomId(room.getId()).stream()
.map(p -> userService.findById(p.getId()))
.map(p -> new UserResult(p.getId(), p.getUsername())).collect(Collectors.toList())))
.collect(Collectors.toList());
return ResponseEntity.ok(results);
}
@ -126,10 +132,22 @@ public class APIController {
return ResponseEntity.accepted().build();
}
@GetMapping("/api/v1/notify/get/{last_chat_id}/")
public ResponseEntity<LongPollResult> getNotify(@PathVariable Long last_chat_id ) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
@GetMapping("/async/notify/get/")
@ResponseBody
public DeferredResult<LongPollResult> getNotify(@RequestParam("last_chat_id") Long last_chat_id,
@RequestParam("accessToken") String accessToken,
@RequestParam("uid") Long userId) {
try {
User u = userService.findById(userId);
if (u == null) return null;
if (!Hash.sha256((u.getId() + u.getUsername() + u.getPassword()).getBytes()).equals(accessToken))
return null;
} catch ( Exception e ) {
log.error(e.getMessage(), e);
}
final DeferredResult<LongPollResult> deferredResult = new DeferredResult<>();
longPoll.getLongPollingQueue().add(new LongPollingSession(last_chat_id, deferredResult));
/*Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Long userId = userService.findByUsername(((org.springframework.security.core.userdetails.User) authentication.getPrincipal()).getUsername()).getId();
userService.updateOnline(userId);
@ -160,6 +178,15 @@ public class APIController {
c.setMessage(message);
return c;
}).collect(Collectors.toList());
return ResponseEntity.ok(new LongPollResult(onlineCount, results, users));
// Room stuff
List<Room> rooms = roomService.getAllActive();
List<RoomResult> roomResults = rooms.stream()
.map( r -> new RoomResult(r.getId(), r.getPlayerCount(), roomPlayerService.findByRoomId(r.getId()).stream()
.map(p -> userService.findById(p.getId()))
.map(p -> new UserResult(p.getId(), p.getUsername())).collect(Collectors.toList())))
.collect(Collectors.toList());
return ResponseEntity.ok(new LongPollResult(onlineCount, results, users, roomResults));*/
return deferredResult;
}
}

View File

@ -5,8 +5,11 @@ import com.alterdekim.game.entities.Invite;
import com.alterdekim.game.entities.User;
import com.alterdekim.game.service.InviteService;
import com.alterdekim.game.service.UserService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
@ -33,33 +36,12 @@ public class AuthController {
return "game";
}
@GetMapping("/rules")
public String rulesPage(Model model) {
return "rules";
}
@GetMapping("/login")
public String loginPage(Model model) {
model.addAttribute("title", "Login" + base_title);
return "login";
}
@GetMapping("/games")
public String gamesPage(Model model) {
return "games";
}
@GetMapping("/")
public String homePage(Model model) {
return "index";
}
@GetMapping("/access-denied")
public String accessDenied(Model model) {
model.addAttribute("title", "Access denied");
return "access-denied";
}
@GetMapping("/signup")
public String showRegistrationForm(Model model) {
UserDTO userDto = new UserDTO();

View File

@ -0,0 +1,51 @@
package com.alterdekim.game.controller;
import com.alterdekim.game.dto.AuthApiObject;
import com.alterdekim.game.entities.User;
import com.alterdekim.game.service.UserServiceImpl;
import com.alterdekim.game.util.Hash;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Slf4j
@Controller
public class StaticController {
@Autowired
private UserServiceImpl userService;
@GetMapping("/rules")
public String rulesPage(Model model) {
return "rules";
}
@GetMapping("/games")
public String gamesPage(Model model) {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User u = userService.findByUsername(((org.springframework.security.core.userdetails.User) authentication.getPrincipal()).getUsername());
Long userId = u.getId();
String apiKey = Hash.sha256((u.getId() + u.getUsername() + u.getPassword()).getBytes());
model.addAttribute("auth_obj", new AuthApiObject(apiKey, userId));
} catch ( Exception e ) {
log.error(e.getMessage(), e);
}
return "games";
}
@GetMapping("/")
public String homePage(Model model) {
return "index";
}
@GetMapping("/access-denied")
public String accessDenied(Model model) {
model.addAttribute("title", "Access denied");
return "access-denied";
}
}

View File

@ -0,0 +1,13 @@
package com.alterdekim.game.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class AuthApiObject {
private String accessToken;
private Long uid;
}

View File

@ -14,4 +14,5 @@ public class LongPollResult {
private Integer onlineCount;
private List<Chat> messages;
private List<UserResult> users;
private List<RoomResult> rooms;
}

View File

@ -2,6 +2,7 @@ package com.alterdekim.game.dto;
import com.alterdekim.game.entities.Room;
import com.alterdekim.game.entities.RoomPlayer;
import com.alterdekim.game.entities.User;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@ -12,6 +13,7 @@ import java.util.List;
@NoArgsConstructor
@Getter
public class RoomResult {
private Room room;
private List<RoomPlayer> players;
private Long id;
private Integer playerCount;
private List<UserResult> players;
}

View File

@ -3,8 +3,14 @@ package com.alterdekim.game.repository;
import com.alterdekim.game.entities.Room;
import com.alterdekim.game.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface RoomRepository extends JpaRepository<Room, Long> {
@Query(value = "SELECT r FROM Room r WHERE r.isPrivate = false")
List<Room> findAllByActiveTrue();
}

View File

@ -1,6 +1,7 @@
package com.alterdekim.game.security;
import com.alterdekim.game.handler.CustomAccessDeniedHandler;
import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -13,6 +14,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@ -31,6 +33,7 @@ public class SpringSecurity {
http.csrf().disable()
.authorizeHttpRequests((authorize) ->
authorize
.requestMatchers("/async/**").permitAll()
.requestMatchers("/game").hasAnyAuthority("ROLE_ADMIN")
.requestMatchers("/games").hasAnyAuthority("ROLE_ADMIN")
.requestMatchers("/profile/**").hasAnyAuthority("ROLE_ADMIN")

View File

@ -18,4 +18,8 @@ public class RoomServiceImpl implements RoomService {
public List<Room> getAll() {
return roomRepository.findAll();
}
public List<Room> getAllActive() {
return roomRepository.findAllByActiveTrue();
}
}

View File

@ -0,0 +1,22 @@
package com.alterdekim.game.util;
import java.security.MessageDigest;
public class Hash {
public static String sha256( byte[] b ) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return bytesToHex(digest.digest(b));
}
private static String bytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder(2 * hash.length);
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}

View File

@ -23,9 +23,9 @@ function findUser(users, id) {
function parseTime(unix_timestamp) {
var date = new Date(unix_timestamp * 1000);
var hours = date.getHours();
var hours = "0" + date.getHours();
var minutes = "0" + date.getMinutes();
return hours + ':' + minutes.substr(-2);
return hours.substr(-2) + ':' + minutes.substr(-2);
}
function replyButtonClicked(obj) {
@ -35,8 +35,15 @@ function replyButtonClicked(obj) {
}
setInterval(function() {
let accessToken = $("api-tag").attr("data-access-token");
let uid = $("api-tag").attr("data-uid");
$.ajax({
url: "/api/v1/notify/get/" + last_chat_id + "/",
url: "/async/notify/get/",
data: {
last_chat_id: last_chat_id,
accessToken: accessToken,
uid: uid
},
method: "GET"
}).done(function(data) {
console.log(data);

View File

@ -401,6 +401,7 @@
cursor: pointer;
}
</style>
<api-tag th:data-access-token="${auth_obj.accessToken}" th:data-uid="${auth_obj.uid}"></api-tag>
</head>
<body>
<th:block th:insert="~{fragments/navbar}"></th:block>