mirror of
https://github.com/Dvorinka/PPve.git
synced 2026-07-29 06:23:47 +00:00
test
This commit is contained in:
+5
-224
@@ -1004,12 +1004,11 @@
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
<h1>Admin Dashboard</h1>
|
||||
<div class="admin-nav">
|
||||
<a href="#aplikace" class="nav-link active" data-section="aplikace">Aplikace</a>
|
||||
<a href="#banner" class="nav-link" data-section="banner">Správa banneru</a>
|
||||
<a href="#rezervace" class="nav-link" data-section="rezervace">Správa rezervací</a>
|
||||
<a href="#nastaveni" class="nav-link" data-section="nastaveni">Nastavení</a>
|
||||
</div>
|
||||
<nav class="admin-nav">
|
||||
<a href="#aplikace" class="nav-link active">Aplikace</a>
|
||||
<a href="#banner" class="nav-link">Správa banneru</a>
|
||||
<a href="#rezervace" class="nav-link">Správa rezervací</a>
|
||||
</nav>
|
||||
</div>
|
||||
<button class="logout-btn" id="logoutBtn">Odhlásit se</button>
|
||||
</div>
|
||||
@@ -1065,61 +1064,6 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Credentials Management Section -->
|
||||
<div class="card" style="margin: 2rem auto; max-width: 800px; display: none;" id="credentials">
|
||||
<h3>Nastavení přihlašování</h3>
|
||||
|
||||
<div id="credentialsAlert" class="hidden p-4 mb-4 rounded"></div>
|
||||
|
||||
<form id="credentialsForm" class="space-y-4">
|
||||
<div>
|
||||
<label for="currentUsername" class="block text-sm font-medium text-gray-700">Aktuální uživatelské jméno</label>
|
||||
<input type="text" id="currentUsername" name="currentUsername" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="currentPassword" class="block text-sm font-medium text-gray-700">Aktuální heslo</label>
|
||||
<input type="password" id="currentPassword" name="currentPassword" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div class="border-t border-gray-200 pt-4">
|
||||
<h4 class="text-md font-medium text-gray-900 mb-3">Nové přihlašovací údaje</h4>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="newUsername" class="block text-sm font-medium text-gray-700">Nové uživatelské jméno</label>
|
||||
<input type="text" id="newUsername" name="newUsername" required
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="newPassword" class="block text-sm font-medium text-gray-700">Nové heslo</label>
|
||||
<input type="password" id="newPassword" name="newPassword" required minlength="8"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
<p class="mt-1 text-sm text-gray-500">Heslo musí mít alespoň 8 znaků</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="confirmPassword" class="block text-sm font-medium text-gray-700">Potvrďte nové heslo</label>
|
||||
<input type="password" id="confirmPassword" name="confirmPassword" required minlength="8"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
<button type="button" onclick="document.getElementById('credentials').style.display = 'none'"
|
||||
class="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Zrušit
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Uložit změny
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h2>Vítejte v administraci</h2>
|
||||
|
||||
@@ -3102,163 +3046,6 @@ async function loadBanner() {
|
||||
// Add submission flag at the top of the script
|
||||
let isSubmitting = false;
|
||||
|
||||
// Setup credentials form
|
||||
function setupCredentialsForm() {
|
||||
const credentialsForm = document.getElementById('credentialsForm');
|
||||
if (!credentialsForm) return;
|
||||
|
||||
credentialsForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Prevent multiple submissions
|
||||
if (isSubmitting) return;
|
||||
isSubmitting = true;
|
||||
|
||||
const formData = new FormData(credentialsForm);
|
||||
const currentUsername = formData.get('currentUsername');
|
||||
const currentPassword = formData.get('currentPassword');
|
||||
const newUsername = formData.get('newUsername');
|
||||
const newPassword = formData.get('newPassword');
|
||||
const confirmPassword = formData.get('confirmPassword');
|
||||
|
||||
// Reset previous errors
|
||||
document.querySelectorAll('.form-control').forEach(el => el.classList.remove('border-red-500'));
|
||||
const alertEl = document.getElementById('credentialsAlert');
|
||||
alertEl.classList.add('hidden');
|
||||
|
||||
// Client-side validation
|
||||
let isValid = true;
|
||||
|
||||
if (!currentUsername || !currentPassword) {
|
||||
showError('Vyplňte prosím aktuální přihlašovací údaje.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (newPassword && newPassword.length < 8) {
|
||||
document.getElementById('newPassword').classList.add('border-red-500');
|
||||
showError('Nové heslo musí mít alespoň 8 znaků.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
document.getElementById('confirmPassword').classList.add('border-red-500');
|
||||
showError('Nová hesla se neshodují.');
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
isSubmitting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare request data
|
||||
const requestData = {
|
||||
currentUsername,
|
||||
currentPassword,
|
||||
newUsername: newUsername || undefined,
|
||||
newPassword: newPassword || undefined
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/update-credentials', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Nepodařilo se aktualizovat přihlašovací údaje');
|
||||
}
|
||||
|
||||
// Show success message
|
||||
showSuccess('Přihlašovací údaje byly úspěšně aktualizovány. Budete odhlášeni za 3 sekundy...');
|
||||
|
||||
// Logout after a delay
|
||||
setTimeout(() => {
|
||||
localStorage.removeItem('token');
|
||||
window.location.href = '/login.html';
|
||||
}, 3000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Chyba při aktualizaci přihlašovacích údajů:', error);
|
||||
showError(error.message || 'Nastala chyba při aktualizaci přihlašovacích údajů');
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
}
|
||||
});
|
||||
|
||||
function showError(message) {
|
||||
const alertEl = document.getElementById('credentialsAlert');
|
||||
alertEl.textContent = message;
|
||||
alertEl.className = 'bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4';
|
||||
alertEl.classList.remove('hidden');
|
||||
alertEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
const alertEl = document.getElementById('credentialsAlert');
|
||||
alertEl.textContent = message;
|
||||
alertEl.className = 'bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4';
|
||||
alertEl.classList.remove('hidden');
|
||||
alertEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
// Setup navigation
|
||||
function setupNavigation() {
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
const sections = document.querySelectorAll('.card[id]');
|
||||
|
||||
// Show section based on hash or default to first section
|
||||
function showSection(sectionId) {
|
||||
// Hide all sections
|
||||
sections.forEach(section => {
|
||||
section.style.display = 'none';
|
||||
});
|
||||
|
||||
// Show selected section
|
||||
const targetSection = document.getElementById(sectionId);
|
||||
if (targetSection) {
|
||||
targetSection.style.display = 'block';
|
||||
} else if (sections.length > 0) {
|
||||
// Default to first section if target not found
|
||||
sections[0].style.display = 'block';
|
||||
}
|
||||
|
||||
// Update active nav link
|
||||
navLinks.forEach(link => {
|
||||
link.classList.toggle('active', link.getAttribute('data-section') === sectionId);
|
||||
});
|
||||
|
||||
// Update URL hash
|
||||
window.location.hash = `#${sectionId}`;
|
||||
}
|
||||
|
||||
// Handle nav link clicks
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const sectionId = link.getAttribute('data-section');
|
||||
showSection(sectionId);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle initial load
|
||||
const initialSection = window.location.hash ? window.location.hash.substring(1) : 'aplikace';
|
||||
showSection(initialSection);
|
||||
|
||||
// Handle browser back/forward
|
||||
window.addEventListener('popstate', () => {
|
||||
const sectionId = window.location.hash ? window.location.hash.substring(1) : 'aplikace';
|
||||
showSection(sectionId);
|
||||
});
|
||||
}
|
||||
|
||||
async function saveBanner(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -4508,12 +4295,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Initialize banner image upload functionality
|
||||
const dragDropArea = document.getElementById('dragDropArea');
|
||||
|
||||
// Initialize credentials form
|
||||
setupCredentialsForm();
|
||||
|
||||
// Navigation handling
|
||||
setupNavigation();
|
||||
const uploadImageBtn = document.getElementById('uploadImageBtn');
|
||||
const bannerImageInput = document.getElementById('bannerImage');
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
@@ -19,17 +18,6 @@ type Credentials struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type UpdateCredentialsRequest struct {
|
||||
CurrentUsername string `json:"currentUsername"`
|
||||
CurrentPassword string `json:"currentPassword"`
|
||||
NewUsername string `json:"newUsername"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
type CredentialsResponse struct {
|
||||
IsDefaultCredentials bool `json:"isDefaultCredentials"`
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
Username string `json:"username"`
|
||||
jwt.RegisteredClaims
|
||||
@@ -39,17 +27,9 @@ var (
|
||||
// In production, use environment variable for JWT key
|
||||
jwtKey = getJWTKey()
|
||||
|
||||
// Default credentials
|
||||
defaultUsername = "admin"
|
||||
defaultPassword = "admin"
|
||||
defaultPasswordHash = mustHashPassword(defaultPassword)
|
||||
|
||||
// Current credentials (in-memory, would be from DB in production)
|
||||
adminUsername = defaultUsername
|
||||
adminPasswordHash = defaultPasswordHash
|
||||
|
||||
// Mutex for thread-safe credential updates
|
||||
credentialsMutex sync.RWMutex
|
||||
adminUsername = "admin"
|
||||
// In a real app, store hashed password and retrieve from a secure storage
|
||||
adminPasswordHash = mustHashPassword("admin") // Default password, should be changed after first login
|
||||
)
|
||||
|
||||
func getJWTKey() []byte {
|
||||
@@ -68,36 +48,32 @@ func mustHashPassword(password string) string {
|
||||
return string(hash)
|
||||
}
|
||||
|
||||
func authenticateUser(creds Credentials) (string, bool, error) {
|
||||
credentialsMutex.RLock()
|
||||
defer credentialsMutex.RUnlock()
|
||||
|
||||
func authenticateUser(creds Credentials) (string, error) {
|
||||
// In a real app, verify against a database
|
||||
if creds.Username != adminUsername {
|
||||
return "", false, errors.New("invalid credentials")
|
||||
return "", errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(adminPasswordHash), []byte(creds.Password)); err != nil {
|
||||
return "", false, errors.New("invalid credentials")
|
||||
return "", errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
// Check if using default credentials
|
||||
isDefault := creds.Username == defaultUsername && bcrypt.CompareHashAndPassword(
|
||||
[]byte(defaultPasswordHash), []byte(creds.Password)) == nil
|
||||
|
||||
tokenString, err := createToken(creds.Username)
|
||||
return tokenString, isDefault, err
|
||||
}
|
||||
|
||||
func createToken(username string) (string, error) {
|
||||
// Create JWT token
|
||||
expirationTime := time.Now().Add(24 * time.Hour)
|
||||
claims := &Claims{
|
||||
Username: username,
|
||||
Username: creds.Username,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(expirationTime),
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString(jwtKey)
|
||||
tokenString, err := token.SignedString(jwtKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
func verifyToken(tokenString string) (*Claims, error) {
|
||||
@@ -150,63 +126,27 @@ func AuthMiddleware(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// LoginHandler handles user login
|
||||
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
http.Error(w, `{"error":"Method not allowed"}`, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var creds Credentials
|
||||
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
tokenString, isDefault, err := authenticateUser(creds)
|
||||
err := json.NewDecoder(r.Body).Decode(&creds)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
||||
http.Error(w, `{"error":"Invalid request body"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := authenticateUser(creds)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"Invalid credentials"}`, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"token": tokenString,
|
||||
"isDefaultCredentials": isDefault,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCredentialsHandler handles credential updates
|
||||
func UpdateCredentialsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var updateCreds UpdateCredentialsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&updateCreds); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Authenticate current credentials
|
||||
if _, _, err := authenticateUser(Credentials{
|
||||
Username: updateCreds.CurrentUsername,
|
||||
Password: updateCreds.CurrentPassword,
|
||||
}); err != nil {
|
||||
http.Error(w, "Invalid current credentials", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Update credentials
|
||||
credentialsMutex.Lock()
|
||||
defer credentialsMutex.Unlock()
|
||||
|
||||
adminUsername = updateCreds.NewUsername
|
||||
adminPasswordHash = mustHashPassword(updateCreds.NewPassword)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]bool{
|
||||
"success": true,
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"token": token,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -141,7 +141,6 @@ func main() {
|
||||
|
||||
// Authentication routes
|
||||
r.HandleFunc("/api/login", LoginHandler).Methods("POST", "OPTIONS")
|
||||
r.HandleFunc("/api/update-credentials", UpdateCredentialsHandler).Methods("POST", "OPTIONS")
|
||||
|
||||
// Public endpoints (must be defined before protected ones)
|
||||
r.HandleFunc("/api/banner", GetBannerHandler).Methods("GET", "OPTIONS")
|
||||
|
||||
Reference in New Issue
Block a user