mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2024-11-04 23:10:11 +01:00
Login
This commit is contained in:
parent
cadc8e499d
commit
35a998b934
@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@ -23,10 +24,12 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import stirling.software.SPDF.model.Role;
|
||||||
@Component
|
@Component
|
||||||
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
|
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserDetailsService userDetailsService;
|
private UserDetailsService userDetailsService;
|
||||||
@ -39,7 +42,6 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
protected void doFilterInternal(HttpServletRequest request,
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
FilterChain filterChain) throws ServletException, IOException {
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
if (!rateLimit) {
|
if (!rateLimit) {
|
||||||
// If rateLimit is not enabled, just pass all requests without rate limiting
|
// If rateLimit is not enabled, just pass all requests without rate limiting
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
@ -47,7 +49,6 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String method = request.getMethod();
|
String method = request.getMethod();
|
||||||
|
|
||||||
if (!"POST".equalsIgnoreCase(method)) {
|
if (!"POST".equalsIgnoreCase(method)) {
|
||||||
// If the request is not a POST, just pass it through without rate limiting
|
// If the request is not a POST, just pass it through without rate limiting
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
@ -73,7 +74,34 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
identifier = request.getRemoteAddr();
|
identifier = request.getRemoteAddr();
|
||||||
}
|
}
|
||||||
|
|
||||||
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket());
|
Role userRole = getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
||||||
|
|
||||||
|
if (request.getHeader("X-API-Key") != null) {
|
||||||
|
// It's an API call
|
||||||
|
processRequest(userRole.getApiCallsPerDay(), identifier, apiBuckets, request, response, filterChain);
|
||||||
|
} else {
|
||||||
|
// It's a Web UI call
|
||||||
|
processRequest(userRole.getWebCallsPerDay(), identifier, webBuckets, request, response, filterChain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Role getRoleFromAuthentication(Authentication authentication) {
|
||||||
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
for (GrantedAuthority authority : authentication.getAuthorities()) {
|
||||||
|
try {
|
||||||
|
return Role.fromString(authority.getAuthority());
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
// Ignore and continue to next authority.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("User does not have a valid role.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRequest(int limitPerDay, String identifier, Map<String, Bucket> buckets,
|
||||||
|
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay));
|
||||||
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
|
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
|
||||||
|
|
||||||
if (probe.isConsumed()) {
|
if (probe.isConsumed()) {
|
||||||
@ -84,12 +112,11 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
|
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
|
||||||
response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill));
|
response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill));
|
||||||
response.getWriter().write("Rate limit exceeded for POST requests.");
|
response.getWriter().write("Rate limit exceeded for POST requests.");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bucket createUserBucket() {
|
private Bucket createUserBucket(int limitPerDay) {
|
||||||
Bandwidth limit = Bandwidth.classic(1000, Refill.intervally(1000, Duration.ofDays(1)));
|
Bandwidth limit = Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1)));
|
||||||
return Bucket.builder().addLimit(limit).build();
|
return Bucket.builder().addLimit(limit).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,10 @@ public class InitialSetup {
|
|||||||
String initialPassword = System.getenv("INITIAL_PASSWORD");
|
String initialPassword = System.getenv("INITIAL_PASSWORD");
|
||||||
if(initialUsername != null && initialPassword != null) {
|
if(initialUsername != null && initialPassword != null) {
|
||||||
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
|
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
|
||||||
} else {
|
|
||||||
userService.saveUser("admin", "password", Role.ADMIN.getRoleId());
|
|
||||||
}
|
}
|
||||||
|
// else {
|
||||||
|
// userService.saveUser("admin", "password", Role.ADMIN.getRoleId());
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,13 @@ public enum Role {
|
|||||||
return webCallsPerDay;
|
return webCallsPerDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Role fromString(String roleId) {
|
||||||
|
for (Role role : Role.values()) {
|
||||||
|
if (role.getRoleId().equalsIgnoreCase(roleId)) {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("No Role defined for id: " + roleId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
4
src/main/resources/static/images/clipboard.svg
Normal file
4
src/main/resources/static/images/clipboard.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard" viewBox="0 0 16 16">
|
||||||
|
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
|
||||||
|
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 496 B |
@ -68,21 +68,34 @@
|
|||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input type="password" class="form-control" id="apiKey" placeholder="Your API Key" readonly>
|
<input type="password" class="form-control" id="apiKey" placeholder="Your API Key" readonly>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-outline-secondary" id="copyBtn" type="button" onclick="copyToClipboard()">
|
||||||
|
<img src="images/clipboard.svg" alt="Copy" style="height:20px;">
|
||||||
|
</button>
|
||||||
<button class="btn btn-outline-secondary" id="showBtn" type="button" onclick="showApiKey()">👁️ Show</button>
|
<button class="btn btn-outline-secondary" id="showBtn" type="button" onclick="showApiKey()">👁️ Show</button>
|
||||||
<button class="btn btn-outline-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()">🔄 Refresh</button>
|
<button class="btn btn-outline-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()">🔄 Refresh</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
function copyToClipboard() {
|
||||||
|
const apiKeyElement = document.getElementById("apiKey");
|
||||||
|
apiKeyElement.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function showApiKey() {
|
function showApiKey() {
|
||||||
const apiKeyElement = document.getElementById("apiKey");
|
const apiKeyElement = document.getElementById("apiKey");
|
||||||
|
const copyBtn = document.getElementById("copyBtn");
|
||||||
if (apiKeyElement.type === "password") {
|
if (apiKeyElement.type === "password") {
|
||||||
apiKeyElement.type = "text";
|
apiKeyElement.type = "text";
|
||||||
|
copyBtn.disabled = false; // Enable copy button when API key is visible
|
||||||
} else {
|
} else {
|
||||||
apiKeyElement.type = "password";
|
apiKeyElement.type = "password";
|
||||||
|
copyBtn.disabled = true; // Disable copy button when API key is hidden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +120,7 @@
|
|||||||
let apiKey = await response.text();
|
let apiKey = await response.text();
|
||||||
manageUIState(apiKey);
|
manageUIState(apiKey);
|
||||||
document.getElementById("apiKey").type = 'text';
|
document.getElementById("apiKey").type = 'text';
|
||||||
|
document.getElementById("copyBtn").disabled = false;
|
||||||
} else {
|
} else {
|
||||||
alert('Error refreshing API key.');
|
alert('Error refreshing API key.');
|
||||||
}
|
}
|
||||||
@ -118,13 +132,16 @@
|
|||||||
function manageUIState(apiKey) {
|
function manageUIState(apiKey) {
|
||||||
const apiKeyElement = document.getElementById("apiKey");
|
const apiKeyElement = document.getElementById("apiKey");
|
||||||
const showBtn = document.getElementById("showBtn");
|
const showBtn = document.getElementById("showBtn");
|
||||||
|
const copyBtn = document.getElementById("copyBtn");
|
||||||
|
|
||||||
if (apiKey && apiKey.trim().length > 0) {
|
if (apiKey && apiKey.trim().length > 0) {
|
||||||
apiKeyElement.value = apiKey;
|
apiKeyElement.value = apiKey;
|
||||||
showBtn.disabled = false;
|
showBtn.disabled = false;
|
||||||
|
copyBtn.disabled = true;
|
||||||
} else {
|
} else {
|
||||||
apiKeyElement.value = "";
|
apiKeyElement.value = "";
|
||||||
showBtn.disabled = true;
|
showBtn.disabled = true;
|
||||||
|
copyBtn.disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<!-- Hi if you have been redirected here when using API then you might need to supply a X-API-KEY key in header to authenticate! -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html xmlns:th="http://www.thymeleaf.org">
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
<head>
|
<head>
|
||||||
|
Loading…
Reference in New Issue
Block a user