328 lines
7.3 KiB
Go
328 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"flag"
|
|
"fmt"
|
|
"html/template"
|
|
"io/fs"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
//go:embed templates/*
|
|
var templatesFS embed.FS
|
|
|
|
//go:embed static/*
|
|
var staticFS embed.FS
|
|
|
|
// User struktura za uporabniške podatke
|
|
type User struct {
|
|
ID int
|
|
Username string
|
|
Password string
|
|
Email string
|
|
FullName string
|
|
}
|
|
|
|
// In-memory shranjevanje uporabnikov
|
|
var (
|
|
users = make(map[string]*User)
|
|
usersMux = &sync.RWMutex{}
|
|
lastID = 0
|
|
)
|
|
|
|
// Template cache
|
|
var templates *template.Template
|
|
|
|
func init() {
|
|
// Privzeti uporabniki
|
|
addUser("jnovak", "secure123", "janez.novak@securebank.si", "Janez Novak")
|
|
addUser("mpetric", "banking456", "maja.petric@securebank.si", "Maja Petrič")
|
|
}
|
|
|
|
func addUser(username, password, email, fullName string) {
|
|
usersMux.Lock()
|
|
defer usersMux.Unlock()
|
|
lastID++
|
|
users[username] = &User{
|
|
ID: lastID,
|
|
Username: username,
|
|
Password: password,
|
|
Email: email,
|
|
FullName: fullName,
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
// Nastavimo port preko command line argumenta
|
|
var port = flag.String("port", "8080", "Port na katerem se bo zagnal server")
|
|
var rateLimit = flag.Int("rate-limit", 3000, "Maksimalno število zahtev na sekundo")
|
|
flag.Parse()
|
|
|
|
// Naložimo HTML predloge iz embedded FS
|
|
var err error
|
|
templates, err = template.ParseFS(templatesFS, "templates/*.html")
|
|
if err != nil {
|
|
log.Fatal("Napaka pri nalaganju embedded predlog:", err)
|
|
}
|
|
|
|
// Create rate killer first
|
|
killer := newRateKiller(*rateLimit)
|
|
|
|
// Start network attack monitors with direct access to killer
|
|
go monitorTCP(killer)
|
|
go monitorUDP(killer)
|
|
|
|
// Statične datoteke iz embedded FS
|
|
staticSubFS, err := fs.Sub(staticFS, "static")
|
|
if err != nil {
|
|
log.Fatal("Napaka pri ustvarjanju static subFS:", err)
|
|
}
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticSubFS))))
|
|
|
|
// Routes
|
|
mux.HandleFunc("/", homeHandler)
|
|
mux.HandleFunc("/register", registerHandler)
|
|
mux.HandleFunc("/login", loginHandler)
|
|
mux.HandleFunc("/dashboard", dashboardHandler)
|
|
mux.HandleFunc("/logout", logoutHandler)
|
|
|
|
// Zagon strežnika
|
|
fmt.Printf("🏦 SecureBank aplikacija (embedded build)\n")
|
|
fmt.Printf("Port: %s\n", *port)
|
|
fmt.Printf("HTTP: http://localhost:%s\n", *port)
|
|
fmt.Printf("Rate Limit: %d req/sec\n", *rateLimit)
|
|
fmt.Printf("📦 Vsi fajli so vključeni v binary!\n")
|
|
fmt.Println()
|
|
|
|
serverAddr := ":" + *port
|
|
log.Fatal(http.ListenAndServe(serverAddr, killer.wrap(mux)))
|
|
}
|
|
|
|
type rateKiller struct {
|
|
limit int
|
|
mu sync.Mutex
|
|
window time.Time
|
|
count int
|
|
}
|
|
|
|
func newRateKiller(limit int) *rateKiller {
|
|
rk := &rateKiller{
|
|
limit: limit,
|
|
window: time.Now().Truncate(time.Second),
|
|
}
|
|
// Reset counter every second
|
|
go rk.resetCounter()
|
|
return rk
|
|
}
|
|
|
|
func (rk *rateKiller) wrap(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
rk.mu.Lock()
|
|
nowWindow := time.Now().Truncate(time.Second)
|
|
if nowWindow.After(rk.window) {
|
|
rk.window = nowWindow
|
|
rk.count = 0
|
|
}
|
|
rk.count++
|
|
exceeded := rk.count > rk.limit
|
|
rk.mu.Unlock()
|
|
|
|
if exceeded {
|
|
log.Printf("CRITICAL: request rate exceeded %d req/s - shutting down", rk.limit)
|
|
os.Exit(1)
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func (rk *rateKiller) recordPacket() {
|
|
rk.mu.Lock()
|
|
rk.count++
|
|
current := rk.count
|
|
rk.mu.Unlock()
|
|
|
|
if current > rk.limit {
|
|
log.Printf("CRITICAL: attack detected - %d packets/sec exceeded limit of %d - SHUTTING DOWN", current, rk.limit)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func (rk *rateKiller) resetCounter() {
|
|
ticker := time.NewTicker(time.Second)
|
|
defer ticker.Stop()
|
|
for range ticker.C {
|
|
rk.mu.Lock()
|
|
if rk.count > 0 {
|
|
log.Printf("📊 Rate: %d packets/sec (limit: %d)", rk.count, rk.limit)
|
|
}
|
|
rk.count = 0
|
|
rk.window = time.Now().Truncate(time.Second)
|
|
rk.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
func monitorTCP(killer *rateKiller) {
|
|
listener, err := net.Listen("tcp", ":80")
|
|
if err != nil {
|
|
log.Printf("Warning: TCP monitor on :80 failed: %v", err)
|
|
return
|
|
}
|
|
defer listener.Close()
|
|
log.Println("🔍 TCP attack monitor active on :80")
|
|
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
killer.recordPacket()
|
|
conn.Close()
|
|
}
|
|
}
|
|
|
|
func monitorUDP(killer *rateKiller) {
|
|
conn, err := net.ListenPacket("udp", ":53")
|
|
if err != nil {
|
|
log.Printf("Warning: UDP monitor on :53 failed: %v", err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
log.Println("🔍 UDP attack monitor active on :53")
|
|
|
|
buf := make([]byte, 1024)
|
|
for {
|
|
_, _, err := conn.ReadFrom(buf)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
killer.recordPacket()
|
|
}
|
|
}
|
|
|
|
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
|
templates.ExecuteTemplate(w, "home.html", nil)
|
|
}
|
|
|
|
func registerHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == "GET" {
|
|
templates.ExecuteTemplate(w, "register.html", nil)
|
|
return
|
|
}
|
|
|
|
if r.Method == "POST" {
|
|
username := r.FormValue("username")
|
|
password := r.FormValue("password")
|
|
email := r.FormValue("email")
|
|
fullName := r.FormValue("fullName")
|
|
|
|
//Validacija
|
|
if username == "" || password == "" || email == "" || fullName == "" {
|
|
templates.ExecuteTemplate(w, "register.html", map[string]string{
|
|
"Error": "Vsa polja so obvezna!",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Preverimo če uporabnik že obstaja
|
|
usersMux.RLock()
|
|
_, exists := users[username]
|
|
usersMux.RUnlock()
|
|
|
|
if exists {
|
|
templates.ExecuteTemplate(w, "register.html", map[string]string{
|
|
"Error": "Uporabniško ime že obstaja!",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Dodamo novega uporabnika
|
|
addUser(username, password, email, fullName)
|
|
|
|
// Preusmeri na prijavo
|
|
http.Redirect(w, r, "/login?registered=1", http.StatusSeeOther)
|
|
return
|
|
}
|
|
}
|
|
|
|
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == "GET" {
|
|
data := map[string]string{}
|
|
if r.URL.Query().Get("registered") == "1" {
|
|
data["Success"] = "Registracija je bila uspešna! Sedaj se lahko prijavite."
|
|
}
|
|
templates.ExecuteTemplate(w, "login.html", data)
|
|
return
|
|
}
|
|
|
|
if r.Method == "POST" {
|
|
username := r.FormValue("username")
|
|
password := r.FormValue("password")
|
|
|
|
// Preverimo podatke
|
|
usersMux.RLock()
|
|
user, exists := users[username]
|
|
usersMux.RUnlock()
|
|
|
|
if !exists || user.Password != password {
|
|
templates.ExecuteTemplate(w, "login.html", map[string]string{
|
|
"Error": "Napačno uporabniško ime ali geslo!",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Uspešna prijava - preusmeri na dashboard
|
|
// POZOR: Brez session management za demo!
|
|
http.Redirect(w, r, "/dashboard?user="+username, http.StatusSeeOther)
|
|
return
|
|
}
|
|
}
|
|
|
|
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
|
|
username := r.URL.Query().Get("user")
|
|
if username == "" {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
usersMux.RLock()
|
|
user, exists := users[username]
|
|
usersMux.RUnlock()
|
|
|
|
if !exists {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Simuliramo "bančne" podatke
|
|
accounts := []map[string]interface{}{
|
|
{
|
|
"Name": "Glavni račun",
|
|
"Number": "SI56 1234 5678 9012 345",
|
|
"Balance": "€2,456.78",
|
|
},
|
|
{
|
|
"Name": "Varčevalni račun",
|
|
"Number": "SI56 1234 5678 9012 999",
|
|
"Balance": "€15,230.45",
|
|
},
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"User": user,
|
|
"Accounts": accounts,
|
|
}
|
|
|
|
templates.ExecuteTemplate(w, "dashboard.html", data)
|
|
}
|
|
|
|
func logoutHandler(w http.ResponseWriter, r *http.Request) {
|
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
}
|