mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-06 09:12:02 +00:00
add session UI for Users
This commit is contained in:
parent
95f289b9a3
commit
1343e41149
@ -46,10 +46,12 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
||||
Principal principal = request.getUserPrincipal();
|
||||
|
||||
// allowlist for public or static routes
|
||||
if ("/".equals(requestURI)
|
||||
|| "/login".equals(requestURI)
|
||||
|| "/home".equals(requestURI)
|
||||
|| "/home-legacy".equals(requestURI)
|
||||
if ("/login".equals(requestURI)
|
||||
// || "/".equals(requestURI)
|
||||
// || "/home".equals(requestURI)
|
||||
// || "/home-legacy".equals(requestURI)
|
||||
|| "/userSession".equals(requestURI)
|
||||
|| requestURI.contains("/userSession/invalidate/")
|
||||
|| requestURI.contains("/js/")
|
||||
|| requestURI.contains("/css/")
|
||||
|| requestURI.contains("/fonts/")
|
||||
@ -76,10 +78,22 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
||||
.filter(s -> s.getSessionId().equals(finalSession.getId()))
|
||||
.anyMatch(s -> s.isExpired());
|
||||
|
||||
if (isExpiredByAdmin) {
|
||||
if (isExpiredByAdmin
|
||||
&& !"/".equals(requestURI)
|
||||
&& !"/home".equals(requestURI)
|
||||
&& !"/home-legacy".equals(requestURI)) {
|
||||
response.sendRedirect("/logout");
|
||||
log.info("Session expired. Logging out user {}", principal.getName());
|
||||
return false;
|
||||
} else if (isExpiredByAdmin
|
||||
&& ("/".equals(requestURI)
|
||||
|| "/home".equals(requestURI)
|
||||
|| "/home-legacy".equals(requestURI))) {
|
||||
log.info(
|
||||
"Max sessions reached for this user. To continue on this device, please"
|
||||
+ " close your session in another browser. ");
|
||||
response.sendError(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return false;
|
||||
}
|
||||
|
||||
int maxApplicationSessions = sessionsInterface.getMaxApplicationSessions();
|
||||
@ -101,7 +115,15 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
||||
.toList();
|
||||
|
||||
boolean hasUserActiveSession =
|
||||
activeSessions.stream().anyMatch(s -> s.getSessionId().equals(sessionId));
|
||||
// activeSessions.stream().anyMatch(s ->
|
||||
// s.getSessionId().equals(sessionId));
|
||||
activeSessions.stream()
|
||||
.anyMatch(
|
||||
s ->
|
||||
s.getSessionId().equals(sessionId)
|
||||
// && !s.isExpired()
|
||||
&& s.getPrincipalName()
|
||||
.equals(principal.getName()));
|
||||
|
||||
final String currentPrincipal = principal.getName();
|
||||
|
||||
|
@ -83,9 +83,6 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
||||
|
||||
int sessionUserCount = getAllSessions(principalName, false).size();
|
||||
|
||||
if (sessionUserCount >= getMaxUserSessions()) {
|
||||
return;
|
||||
}
|
||||
SessionEntity sessionEntity = sessionRepository.findBySessionId(sessionId);
|
||||
if (sessionEntity == null) {
|
||||
sessionEntity = new SessionEntity();
|
||||
@ -94,7 +91,12 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
||||
}
|
||||
sessionEntity.setPrincipalName(principalName);
|
||||
sessionEntity.setLastRequest(new Date()); // Set lastRequest to the current date
|
||||
sessionEntity.setExpired(false);
|
||||
|
||||
if (sessionUserCount >= getMaxUserSessions()) {
|
||||
sessionEntity.setExpired(true);
|
||||
} else {
|
||||
sessionEntity.setExpired(false);
|
||||
}
|
||||
sessionRepository.save(sessionEntity);
|
||||
sessionRepository.flush();
|
||||
}
|
||||
|
@ -10,9 +10,11 @@ import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
@ -43,6 +45,72 @@ public class SessionStatusController {
|
||||
}
|
||||
}
|
||||
|
||||
// list all sessions from authentication user, return String redirect userSession.html
|
||||
@GetMapping("/userSession")
|
||||
public String getUserSessions(
|
||||
HttpServletRequest request, Model model, Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return "redirect:/login";
|
||||
}
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
Object principal = authentication.getPrincipal();
|
||||
String principalName = UserUtils.getUsernameFromPrincipal(principal);
|
||||
if (principalName == null) {
|
||||
return "redirect:/login";
|
||||
}
|
||||
|
||||
boolean isSessionValid =
|
||||
sessionPersistentRegistry.getAllSessions(principalName, false).stream()
|
||||
.allMatch(
|
||||
sessionEntity ->
|
||||
sessionEntity.getSessionId().equals(session.getId()));
|
||||
|
||||
if (isSessionValid) {
|
||||
return "redirect:/";
|
||||
}
|
||||
// Get all sessions for the user
|
||||
List<SessionInformation> sessionList =
|
||||
sessionPersistentRegistry.getAllSessions(principalName, false).stream()
|
||||
.filter(
|
||||
sessionEntity ->
|
||||
!sessionEntity.getSessionId().equals(session.getId()))
|
||||
.toList();
|
||||
|
||||
model.addAttribute("sessionList", sessionList);
|
||||
return "userSession";
|
||||
}
|
||||
return "redirect:/login";
|
||||
}
|
||||
|
||||
@GetMapping("/userSession/invalidate/{sessionId}")
|
||||
public String invalidateUserSession(
|
||||
HttpServletRequest request,
|
||||
Authentication authentication,
|
||||
@PathVariable String sessionId)
|
||||
throws ServletException {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return "redirect:/login";
|
||||
}
|
||||
Object principal = authentication.getPrincipal();
|
||||
String principalName = UserUtils.getUsernameFromPrincipal(principal);
|
||||
if (principalName == null) {
|
||||
return "redirect:/login";
|
||||
}
|
||||
boolean isOwner =
|
||||
sessionPersistentRegistry.getAllSessions(principalName, false).stream()
|
||||
.anyMatch(session -> session.getSessionId().equals(sessionId));
|
||||
if (isOwner) {
|
||||
customHttpSessionListener.expireSession(sessionId, false);
|
||||
sessionPersistentRegistry.registerNewSession(
|
||||
request.getRequestedSessionId().split(".node0")[0], principal);
|
||||
// return "redirect:/userSession?messageType=sessionInvalidated"
|
||||
return "redirect:/userSession";
|
||||
} else {
|
||||
return "redirect:/login";
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/session/invalidate/{sessionId}")
|
||||
public String invalidateSession(
|
||||
HttpServletRequest request,
|
||||
|
@ -14,6 +14,10 @@
|
||||
<h1 class="display-2" th:text="#{oops}"></h1>
|
||||
<p class="lead" th:if="${param.status == '404'}" th:text="#{error.404.1}"></p>
|
||||
<p class="lead" th:unless="${param.status == '404'}" th:text="#{error.404.2}"></p>
|
||||
<p class="lead" th:if="${status == 417}">
|
||||
<a th:href="@{'/userSession'}" th:text="#{session.maxUserSession}">Max sessions reached for this user. To continue on this device, please close your
|
||||
session in another browser.</a>
|
||||
</p>
|
||||
<br>
|
||||
<h2 th:text="#{error.needHelp}"></h2>
|
||||
<p th:text="#{error.contactTip}"></p>
|
||||
@ -21,7 +25,7 @@
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" class="btn btn-primary" target="_blank" th:text="#{error.github}"></a>
|
||||
<a href="https://discord.gg/HYmhKj45pU" id="discord-button" class="btn btn-primary" target="_blank" th:text="#{joinDiscord}"></a>
|
||||
</div>
|
||||
<a th:href="@{'/'}" id="home-button" class="home-button btn btn-primary" th:text="#{goHomepage}"></a>
|
||||
<a th:href="@{'/'}" id="home-button" class="home-button btn btn-primary" th:text="#{goHomepage}"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -259,6 +259,8 @@
|
||||
<div class="modal-footer">
|
||||
<a th:if="${@loginEnabled and @activeSecurity}" class="btn btn-danger" role="button"
|
||||
th:text="#{settings.signOut}" th:href="@{'/logout'}">Sign Out</a>
|
||||
<a class="btn btn-danger" role="button"
|
||||
th:text="#{settings.userSessions}" th:href="@{'/userSession'}">Sign Out</a>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
52
src/main/resources/templates/userSession.html
Normal file
52
src/main/resources/templates/userSession.html
Normal file
@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
|
||||
xmlns:th="https://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{session.title}, header=#{session.header})}"></th:block>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
<br><br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-9 bg-card">
|
||||
<div class="tool-header">
|
||||
<span class="material-symbols-rounded tool-header-icon organize">key</span>
|
||||
<span class="tool-header-text" th:text="#{session.user}">User Session</span>
|
||||
</div>
|
||||
<div class="bg-card mt-3 mb-3">
|
||||
<span th:text="#{session.maxUserSession}">Max sessions reached for this user. To continue on this device, please close your session in another browser.</span>
|
||||
<table class="table table-striped table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" th:text="#{session.sessionId}">Session ID</th>
|
||||
<th scope="col" th:text="#{session.lastRequest}">last Request</th>
|
||||
<th scope="col" th:text="#{session.invalidate}">Logout</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="userSession : ${sessionList}">
|
||||
<td th:text="${userSession.getSessionId}"></td>
|
||||
<td th:text="${userSession.getLastRequest}"></td>
|
||||
<td><a th:href="@{/userSession/invalidate/{id}(id=${userSession.getSessionId})}"
|
||||
class="btn btn-sm btn-danger">
|
||||
<span class="material-symbols-rounded">logout</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user