mirror of
https://github.com/Dvorinka/PPve.git
synced 2026-06-03 20:12:59 +00:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
# Admin Login System
|
||||||
|
|
||||||
|
This document provides information about the admin login system for the PP Kunovice web application.
|
||||||
|
|
||||||
|
## Default Admin Credentials
|
||||||
|
|
||||||
|
- **Username**: `admin`
|
||||||
|
- **Password**: `admin123`
|
||||||
|
|
||||||
|
**Important**: Change the default password after the first login in a production environment.
|
||||||
|
|
||||||
|
## Accessing the Admin Panel
|
||||||
|
|
||||||
|
1. Navigate to `/admin` in your web browser
|
||||||
|
2. Enter the admin credentials
|
||||||
|
3. After successful login, you'll be redirected to the admin dashboard
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Login
|
||||||
|
- **URL**: `/api/login`
|
||||||
|
- **Method**: `POST`
|
||||||
|
- **Content-Type**: `application/json`
|
||||||
|
- **Request Body**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Success Response**:
|
||||||
|
- **Code**: 200 OK
|
||||||
|
- **Content**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "jwt.token.here"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Error Response**:
|
||||||
|
- **Code**: 401 Unauthorized
|
||||||
|
- **Content**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Invalid credentials"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Protected Endpoints
|
||||||
|
|
||||||
|
All protected endpoints require a valid JWT token in the `Authorization` header:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
- `JWT_SECRET`: Secret key used to sign JWT tokens (default: auto-generated)
|
||||||
|
- `PORT`: Port the server listens on (default: 80)
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
1. Always use HTTPS in production
|
||||||
|
2. Change the default admin password
|
||||||
|
3. Set a strong `JWT_SECRET` environment variable in production
|
||||||
|
4. Consider implementing rate limiting for login attempts
|
||||||
|
5. Keep the server and dependencies up to date
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To run the server in development mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run .
|
||||||
|
```
|
||||||
|
|
||||||
|
The admin interface will be available at `http://localhost/admin`
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="cs">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Admin Dashboard - PP Kunovice</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
.logout-btn {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid white;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.logout-btn:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
.dashboard-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.card p {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>Admin Dashboard</h1>
|
||||||
|
<button class="logout-btn" id="logoutBtn">Odhlásit se</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>Vítejte v administraci</h2>
|
||||||
|
|
||||||
|
<div class="dashboard-cards">
|
||||||
|
<div class="card">
|
||||||
|
<h3>Uživatelé</h3>
|
||||||
|
<p>Správa uživatelských účtů</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Nastavení</h3>
|
||||||
|
<p>Konfigurace systému</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Statistiky</h3>
|
||||||
|
<p>Přehled aktivit</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Check if user is authenticated
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
window.location.href = '/admin.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout functionality
|
||||||
|
document.getElementById('logoutBtn').addEventListener('click', function() {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
window.location.href = '/';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add token to all fetch requests
|
||||||
|
const originalFetch = window.fetch;
|
||||||
|
window.fetch = async function(resource, init = {}) {
|
||||||
|
// Set up the headers
|
||||||
|
const headers = new Headers(init.headers || {});
|
||||||
|
if (token) {
|
||||||
|
headers.set('Authorization', `Bearer ${token}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the request
|
||||||
|
const response = await originalFetch(resource, {
|
||||||
|
...init,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
|
||||||
|
// If unauthorized, redirect to login
|
||||||
|
if (response.status === 401) {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
window.location.href = '/admin.html';
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="cs">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Admin Login - PP Kunovice</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.login-container {
|
||||||
|
background-color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
.login-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.login-header h1 {
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.login-button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
.login-button:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
.error-message {
|
||||||
|
color: #f44336;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-header">
|
||||||
|
<h1>Admin Login</h1>
|
||||||
|
</div>
|
||||||
|
<form id="loginForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Uživatelské jméno</label>
|
||||||
|
<input type="text" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Heslo</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="login-button">Přihlásit se</button>
|
||||||
|
<div id="errorMessage" class="error-message">
|
||||||
|
Chybné přihlašovací údaje
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('loginForm').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const username = document.getElementById('username').value;
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
const errorMessage = document.getElementById('errorMessage');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Login failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Save the token to localStorage
|
||||||
|
localStorage.setItem('token', data.token);
|
||||||
|
|
||||||
|
// Redirect to admin dashboard or home page
|
||||||
|
window.location.href = '/';
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
|
errorMessage.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Credentials struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Claims struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// In production, use environment variable for JWT key
|
||||||
|
jwtKey = getJWTKey()
|
||||||
|
|
||||||
|
adminUsername = "admin"
|
||||||
|
// In a real app, store hashed password and retrieve from a secure storage
|
||||||
|
adminPasswordHash = mustHashPassword("admin123") // Default password, should be changed after first login
|
||||||
|
)
|
||||||
|
|
||||||
|
func getJWTKey() []byte {
|
||||||
|
key := os.Getenv("JWT_SECRET")
|
||||||
|
if key == "" {
|
||||||
|
return []byte("default-secret-key-change-in-production")
|
||||||
|
}
|
||||||
|
return []byte(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustHashPassword(password string) string {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to hash password:", err)
|
||||||
|
}
|
||||||
|
return string(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticateUser(creds Credentials) (string, error) {
|
||||||
|
// In a real app, verify against a database
|
||||||
|
if creds.Username != adminUsername {
|
||||||
|
return "", errors.New("invalid credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(adminPasswordHash), []byte(creds.Password)); err != nil {
|
||||||
|
return "", errors.New("invalid credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create JWT token
|
||||||
|
expirationTime := time.Now().Add(24 * time.Hour)
|
||||||
|
claims := &Claims{
|
||||||
|
Username: creds.Username,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expirationTime),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenString, err := token.SignedString(jwtKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyToken(tokenString string) (*Claims, error) {
|
||||||
|
claims := &Claims{}
|
||||||
|
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, jwt.ErrSignatureInvalid
|
||||||
|
}
|
||||||
|
return jwtKey, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.Valid {
|
||||||
|
return nil, errors.New("invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthMiddleware verifies the JWT token in the Authorization header
|
||||||
|
func authMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Get token from Authorization header
|
||||||
|
authHeader := r.Header.Get("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
http.Error(w, `{"error":"Authorization header required"}`, http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format: Bearer <token>
|
||||||
|
tokenString := ""
|
||||||
|
if len(authHeader) > 7 && strings.ToUpper(authHeader[0:7]) == "BEARER " {
|
||||||
|
tokenString = authHeader[7:]
|
||||||
|
} else {
|
||||||
|
http.Error(w, `{"error":"Authorization header format must be Bearer <token>"}`, http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := verifyToken(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, `{"error":"Invalid or expired token`+err.Error()+`"}`, http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token is valid, proceed with the request
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, `{"error":"Method not allowed"}`, http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var creds Credentials
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&creds)
|
||||||
|
if err != nil {
|
||||||
|
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]string{
|
||||||
|
"token": token,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||||
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||||
github.com/tiendc/go-deepcopy v1.6.0 // indirect
|
github.com/tiendc/go-deepcopy v1.6.0 // indirect
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"gopkg.in/gomail.v2"
|
"gopkg.in/gomail.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,27 +40,53 @@ type GeoCoords struct {
|
|||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
// Set up reverse proxy to kontakt service
|
// Set up reverse proxy to kontakt service
|
||||||
kontaktURL, _ := url.Parse("http://webportal:8080")
|
kontaktURL, _ := url.Parse("http://webportal:8080")
|
||||||
kontaktProxy := httputil.NewSingleHostReverseProxy(kontaktURL)
|
kontaktProxy := httputil.NewSingleHostReverseProxy(kontaktURL)
|
||||||
|
|
||||||
http.Handle("/kontakt/", http.StripPrefix("/kontakt", kontaktProxy))
|
// Public routes
|
||||||
|
r.PathPrefix("/kontakt/").Handler(http.StripPrefix("/kontakt", kontaktProxy))
|
||||||
http.HandleFunc("/submit", enableCORS(handleSubmit))
|
r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.HandleFunc("/health", enableCORS(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write([]byte(`{"status":"ok"}`))
|
w.Write([]byte(`{"status":"ok"}`))
|
||||||
}))
|
}).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
http.HandleFunc("/", enableCORS(func(w http.ResponseWriter, r *http.Request) {
|
// Authentication routes
|
||||||
|
r.HandleFunc("/api/login", loginHandler).Methods("POST", "OPTIONS")
|
||||||
|
|
||||||
|
// Protected API routes
|
||||||
|
api := r.PathPrefix("/api").Subrouter()
|
||||||
|
api.Use(authMiddleware)
|
||||||
|
api.HandleFunc("/submit", handleSubmit).Methods("POST")
|
||||||
|
|
||||||
|
// Admin routes
|
||||||
|
r.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.ServeFile(w, r, "admin.html")
|
||||||
|
}).Methods("GET")
|
||||||
|
|
||||||
|
r.HandleFunc("/admin/dashboard", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.ServeFile(w, r, "admin-dashboard.html")
|
||||||
|
}).Methods("GET")
|
||||||
|
|
||||||
|
// Static file server for public files
|
||||||
|
fs := http.FileServer(http.Dir("."))
|
||||||
|
r.PathPrefix("/").Handler(fs)
|
||||||
|
|
||||||
|
// Redirect root to index.html
|
||||||
|
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/" {
|
||||||
http.ServeFile(w, r, "index.html")
|
http.ServeFile(w, r, "index.html")
|
||||||
}))
|
}
|
||||||
|
}).Methods("GET")
|
||||||
|
|
||||||
http.HandleFunc("/evidence-aut", enableCORS(func(w http.ResponseWriter, r *http.Request) {
|
// Public route for evidence-aut.html
|
||||||
|
r.HandleFunc("/evidence-aut", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.ServeFile(w, r, "evidence-aut.html")
|
http.ServeFile(w, r, "evidence-aut.html")
|
||||||
}))
|
}).Methods("GET")
|
||||||
|
|
||||||
http.HandleFunc("/kontakt", enableCORS(func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/kontakt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Check if kontakt service is already running
|
// Check if kontakt service is already running
|
||||||
resp, err := http.Get("http://webportal:8080/health")
|
resp, err := http.Get("http://webportal:8080/health")
|
||||||
if err == nil && resp.StatusCode == 200 {
|
if err == nil && resp.StatusCode == 200 {
|
||||||
@@ -79,7 +106,10 @@ func main() {
|
|||||||
// Wait briefly for service to start
|
// Wait briefly for service to start
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
http.Redirect(w, r, "http://webportal:8080/", http.StatusFound)
|
http.Redirect(w, r, "http://webportal:8080/", http.StatusFound)
|
||||||
}))
|
}).Methods("GET")
|
||||||
|
|
||||||
|
// Apply CORS middleware to all routes
|
||||||
|
handler := enableCORS(r)
|
||||||
|
|
||||||
port := os.Getenv("PORT")
|
port := os.Getenv("PORT")
|
||||||
if port == "" {
|
if port == "" {
|
||||||
@@ -87,14 +117,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Server běží na portu %s", port)
|
log.Printf("Server běží na portu %s", port)
|
||||||
err := http.ListenAndServe(":"+port, nil)
|
err := http.ListenAndServe(":"+port, handler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Chyba při spuštění serveru: %v", err)
|
log.Fatalf("Chyba při spuštění serveru: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func enableCORS(next http.HandlerFunc) http.HandlerFunc {
|
func enableCORS(next http.Handler) http.Handler {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||||
@@ -104,10 +134,8 @@ func enableCORS(next http.HandlerFunc) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if next != nil {
|
next.ServeHTTP(w, r)
|
||||||
next(w, r)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSubmit(w http.ResponseWriter, r *http.Request) {
|
func handleSubmit(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
Reference in New Issue
Block a user