mirror of
https://github.com/RoastSlav/quickdrop.git
synced 2026-05-09 05:50:19 -05:00
the admin pages are now behind an admin password set up at start up
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
# -----------------
|
||||
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
# MVNW_USERNAME/MVNW_PASSWORD - user and password.html for downloading maven
|
||||
# MVNW_USERNAME/MVNW_PASSWORD - user and app-password.html for downloading maven
|
||||
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.rostislav.quickdrop.config;
|
||||
|
||||
import org.rostislav.quickdrop.interceptor.AdminPasswordInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final AdminPasswordInterceptor adminPasswordInterceptor;
|
||||
|
||||
@Autowired
|
||||
public WebConfig(AdminPasswordInterceptor adminPasswordInterceptor) {
|
||||
this.adminPasswordInterceptor = adminPasswordInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(adminPasswordInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns("/admin/setup", "/static/**", "/css/**", "/js/**", "/images/**");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.rostislav.quickdrop.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.rostislav.quickdrop.entity.ApplicationSettingsEntity;
|
||||
import org.rostislav.quickdrop.entity.FileEntity;
|
||||
import org.rostislav.quickdrop.model.AnalyticsDataView;
|
||||
@@ -8,11 +9,13 @@ import org.rostislav.quickdrop.model.FileEntityView;
|
||||
import org.rostislav.quickdrop.service.AnalyticsService;
|
||||
import org.rostislav.quickdrop.service.ApplicationSettingsService;
|
||||
import org.rostislav.quickdrop.service.FileService;
|
||||
import org.springframework.security.crypto.bcrypt.BCrypt;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -32,7 +35,11 @@ public class AdminViewController {
|
||||
}
|
||||
|
||||
@GetMapping("/dashboard")
|
||||
public String getDashboardPage(Model model) {
|
||||
public String getDashboardPage(Model model, HttpServletRequest request) {
|
||||
if (!checkForAdminPassword(request)) {
|
||||
return "redirect:/admin/password";
|
||||
}
|
||||
|
||||
List<FileEntity> files = fileService.getFiles();
|
||||
|
||||
model.addAttribute("files", files.stream().map(
|
||||
@@ -44,9 +51,26 @@ public class AdminViewController {
|
||||
return "admin/dashboard";
|
||||
}
|
||||
|
||||
@GetMapping("/setup")
|
||||
public String showSetupPage() {
|
||||
if (applicationSettingsService.isAdminPasswordSet()) {
|
||||
return "redirect:/admin/dashboard";
|
||||
}
|
||||
return "welcome";
|
||||
}
|
||||
|
||||
@PostMapping("/setup")
|
||||
public String setAdminPassword(String adminPassword) {
|
||||
applicationSettingsService.setAdminPassword(adminPassword);
|
||||
return "redirect:/admin/dashboard";
|
||||
}
|
||||
|
||||
@GetMapping("/settings")
|
||||
public String getSettingsPage(Model model) {
|
||||
public String getSettingsPage(Model model, HttpServletRequest request) {
|
||||
if (!checkForAdminPassword(request)) {
|
||||
return "redirect:/admin/password";
|
||||
}
|
||||
|
||||
ApplicationSettingsEntity settings = applicationSettingsService.getApplicationSettings();
|
||||
|
||||
ApplicationSettingsViewModel applicationSettingsViewModel = new ApplicationSettingsViewModel(settings);
|
||||
@@ -57,11 +81,36 @@ public class AdminViewController {
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
public String saveSettings(ApplicationSettingsViewModel settings) {
|
||||
public String saveSettings(ApplicationSettingsViewModel settings, HttpServletRequest request) {
|
||||
if (!checkForAdminPassword(request)) {
|
||||
return "redirect:/admin/password";
|
||||
}
|
||||
settings.setMaxFileSize(megabytesToBytes(settings.getMaxFileSize()));
|
||||
|
||||
|
||||
applicationSettingsService.updateApplicationSettings(settings, settings.getAppPassword());
|
||||
return "redirect:/admin/dashboard";
|
||||
}
|
||||
|
||||
@PostMapping("/password")
|
||||
public String checkAdminPassword(@RequestParam String password, HttpServletRequest request) {
|
||||
String adminPasswordHash = applicationSettingsService.getAdminPasswordHash();
|
||||
if (BCrypt.checkpw(password, adminPasswordHash)) {
|
||||
request.getSession().setAttribute("adminPassword", adminPasswordHash);
|
||||
return "redirect:/admin/dashboard";
|
||||
} else {
|
||||
return "redirect:/admin/password";
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/password")
|
||||
public String showAdminPasswordPage() {
|
||||
return "/admin/admin-password";
|
||||
}
|
||||
|
||||
private boolean checkForAdminPassword(HttpServletRequest request) {
|
||||
String password = (String) request.getSession().getAttribute("adminPassword");
|
||||
String adminPasswordHash = applicationSettingsService.getAdminPasswordHash();
|
||||
return password != null && password.equals(adminPasswordHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class FileViewController {
|
||||
if (fileEntity.passwordHash != null &&
|
||||
(password == null || !fileService.checkPassword(uuid, password))) {
|
||||
model.addAttribute("uuid", uuid);
|
||||
return "filePassword";
|
||||
return "file-password";
|
||||
}
|
||||
|
||||
populateModelAttributes(fileEntity, model, request);
|
||||
@@ -89,7 +89,7 @@ public class FileViewController {
|
||||
return "redirect:/file/" + uuid;
|
||||
} else {
|
||||
model.addAttribute("uuid", uuid);
|
||||
return "filePassword";
|
||||
return "file-password";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,11 @@ package org.rostislav.quickdrop.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
@Controller
|
||||
public class IndexViewController {
|
||||
|
||||
@GetMapping("/")
|
||||
public RedirectView index() {
|
||||
return new RedirectView("/file/upload");
|
||||
public String getIndexPage() {
|
||||
return "redirect:/file/upload";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
public class PasswordController {
|
||||
@GetMapping("/login")
|
||||
public String passwordPage() {
|
||||
return "password";
|
||||
return "app-password";
|
||||
}
|
||||
|
||||
@GetMapping("/admin")
|
||||
public String adminPasswordPage() {
|
||||
return "admin/admin-password";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.rostislav.quickdrop.interceptor;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.rostislav.quickdrop.service.ApplicationSettingsService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@Component
|
||||
public class AdminPasswordInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final ApplicationSettingsService applicationSettingsService;
|
||||
|
||||
public AdminPasswordInterceptor(ApplicationSettingsService applicationSettingsService) {
|
||||
this.applicationSettingsService = applicationSettingsService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
String requestURI = request.getRequestURI();
|
||||
if (!applicationSettingsService.isAdminPasswordSet()
|
||||
&& !requestURI.startsWith("/admin/setup")
|
||||
&& !requestURI.startsWith("/static/")
|
||||
&& !requestURI.startsWith("/css/")
|
||||
&& !requestURI.startsWith("/js/")
|
||||
&& !requestURI.startsWith("/images/")) {
|
||||
response.sendRedirect("/admin/setup");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -98,4 +98,15 @@ public class ApplicationSettingsService {
|
||||
public String getAdminPasswordHash() {
|
||||
return applicationSettings.getAdminPasswordHash();
|
||||
}
|
||||
|
||||
public boolean isAdminPasswordSet() {
|
||||
return !applicationSettings.getAdminPasswordHash().isEmpty();
|
||||
}
|
||||
|
||||
public void setAdminPassword(String adminPassword) {
|
||||
ApplicationSettingsEntity applicationSettingsEntity = applicationSettingsRepository.findById(1L).orElseThrow();
|
||||
applicationSettingsEntity.setAdminPasswordHash(BCrypt.hashpw(adminPassword, BCrypt.gensalt()));
|
||||
applicationSettingsRepository.save(applicationSettingsEntity);
|
||||
this.applicationSettings = applicationSettingsEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>
|
||||
Enter Admin Password</title>
|
||||
<meta content="width=device-width, initial-scale=1"
|
||||
name="viewport">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet">
|
||||
<link href="/images/favicon.png"
|
||||
rel="icon"
|
||||
type="image/png">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Main Content -->
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<h2 class="text-center mb-4">
|
||||
Admin Password Required</h2>
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<form id="adminPasswordForm"
|
||||
method="POST"
|
||||
th:action="@{/admin/password}">
|
||||
<!-- CSRF Token -->
|
||||
<input th:name="${_csrf.parameterName}"
|
||||
th:value="${_csrf.token}"
|
||||
type="hidden"/>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label"
|
||||
for="adminPassword">Admin Password:</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="adminPassword"
|
||||
name="password"
|
||||
required
|
||||
type="password"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button class="btn btn-primary w-100"
|
||||
type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div th:if="${error}">
|
||||
<p class="text-danger mt-3"
|
||||
th:text="${error}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -17,43 +17,38 @@
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center"
|
||||
href="/">
|
||||
<img alt="Website Logo"
|
||||
class="me-2"
|
||||
height="40"
|
||||
src="/images/favicon.png">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img alt="Website Logo" class="me-2" height="40" src="/images/favicon.png">
|
||||
QuickDrop
|
||||
</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
aria-controls="navbarNav"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
class="navbar-toggler"
|
||||
data-bs-target="#navbarNav"
|
||||
data-bs-toggle="collapse"
|
||||
type="button"
|
||||
>
|
||||
data-bs-toggle="collapse">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse"
|
||||
id="navbarNav">
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
href="/file/list">View
|
||||
Files</a>
|
||||
<a class="nav-link" href="/file/list">View Files</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
href="/file/upload">Upload
|
||||
File</a>
|
||||
<a class="nav-link" href="/file/upload">Upload File</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<!-- Admin Dashboard Button -->
|
||||
<a class="nav-link" href="/admin/dashboard" onclick="requestAdminPassword()">Admin Dashboard</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center mb-4">
|
||||
|
||||
@@ -17,38 +17,35 @@
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center"
|
||||
href="/">
|
||||
<img alt="Website Logo"
|
||||
class="me-2"
|
||||
height="40"
|
||||
src="/images/favicon.png">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img alt="Website Logo" class="me-2" height="40" src="/images/favicon.png">
|
||||
QuickDrop
|
||||
</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
aria-controls="navbarNav"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
class="navbar-toggler"
|
||||
data-bs-target="#navbarNav"
|
||||
data-bs-toggle="collapse"
|
||||
type="button"
|
||||
>
|
||||
data-bs-toggle="collapse">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse"
|
||||
id="navbarNav">
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
href="/file/upload">Upload
|
||||
File</a>
|
||||
<a class="nav-link" href="/file/upload">Upload File</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<!-- Admin Dashboard Button -->
|
||||
<a class="nav-link" href="/admin/dashboard" onclick="requestAdminPassword()">Admin Dashboard</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center mb-4">
|
||||
|
||||
@@ -17,38 +17,35 @@
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center"
|
||||
href="/">
|
||||
<img alt="Website Logo"
|
||||
class="me-2"
|
||||
height="40"
|
||||
src="/images/favicon.png">
|
||||
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||
<img alt="Website Logo" class="me-2" height="40" src="/images/favicon.png">
|
||||
QuickDrop
|
||||
</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
aria-controls="navbarNav"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
class="navbar-toggler"
|
||||
data-bs-target="#navbarNav"
|
||||
data-bs-toggle="collapse"
|
||||
type="button"
|
||||
>
|
||||
data-bs-toggle="collapse">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse"
|
||||
id="navbarNav">
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link"
|
||||
href="/file/list">View
|
||||
Files</a>
|
||||
<a class="nav-link" href="/file/list">View Files</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<!-- Admin Dashboard Button -->
|
||||
<a class="nav-link" href="/admin/dashboard" onclick="requestAdminPassword()">Admin Dashboard</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container">
|
||||
<h1 class="text-center mb-4">
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Welcome to QuickDrop</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card welcome-card shadow">
|
||||
<div class="card-header bg-primary text-white text-center">
|
||||
<h1>Welcome to QuickDrop</h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-center">
|
||||
Thank you for setting up QuickDrop! Before you get started, please set an admin password for the dashboard.
|
||||
</p>
|
||||
<form id="setupForm" method="post" th:action="@{/admin/setup}">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="adminPassword">Admin Password</label>
|
||||
<input class="form-control" id="adminPassword" minlength="8" name="adminPassword" required
|
||||
type="password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="confirmPassword">Confirm Password</label>
|
||||
<input class="form-control" id="confirmPassword" minlength="8" name="confirmPassword" required
|
||||
type="password">
|
||||
</div>
|
||||
<div class="text-danger mb-3" id="error-message" style="display: none;">Passwords do not match.</div>
|
||||
<button class="btn btn-primary w-100" type="submit">Set Admin Password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('setupForm').addEventListener('submit', function (e) {
|
||||
const password = document.getElementById('adminPassword').value;
|
||||
const confirmPassword = document.getElementById('confirmPassword').value;
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
e.preventDefault();
|
||||
document.getElementById('error-message').style.display = 'block';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user