203 lines
4.8 KiB
Go
203 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"path/filepath"
|
|
"hyperia/config"
|
|
"hyperia/db"
|
|
"hyperia/handlers"
|
|
"hyperia/logger"
|
|
// "runtime/debug"
|
|
"strings"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/alexedwards/argon2id"
|
|
)
|
|
|
|
func isWebSocketRequest(r *http.Request) bool {
|
|
connHeader := strings.ToLower(r.Header.Get("Connection"))
|
|
upgradeHeader := strings.ToLower(r.Header.Get("Upgrade"))
|
|
return strings.Contains(connHeader, "upgrade") && upgradeHeader == "websocket"
|
|
}
|
|
|
|
func HashPassword() {
|
|
hash, err := argon2id.CreateHash("banktest", argon2id.DefaultParams)
|
|
if err != nil {
|
|
log.Fatal().Msgf("failed to hash password: %v", err)
|
|
}
|
|
|
|
fmt.Println("Argon2id hash: ")
|
|
fmt.Println(hash)
|
|
}
|
|
|
|
func main() {
|
|
config.SetConfiguration()
|
|
logger.ConfigureLogger()
|
|
|
|
err := db.InitDB()
|
|
if err != nil {
|
|
log.Fatal().Msgf("failed to connect to database: %v", err)
|
|
} else {
|
|
log.Info().Msg("successfully connected to PostgreSQL")
|
|
}
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
if(loggedIn(w, r)) {
|
|
if isWebSocketRequest(r) {
|
|
handleWebSocket(w, r)
|
|
return
|
|
}
|
|
handleSite(w, r)
|
|
} else {
|
|
handlePublic(w, r)
|
|
}
|
|
})
|
|
|
|
log.Info().Msgf("Server starting on http://localhost:%s", config.PORT)
|
|
err = http.ListenAndServe(":"+config.PORT, nil)
|
|
if err != nil {
|
|
log.Fatal().Msgf("failed to start server: %v", err)
|
|
}
|
|
}
|
|
|
|
func handlePublic(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/api/signup" {
|
|
handlers.HandleSignup(w, r)
|
|
return
|
|
}
|
|
if r.URL.Path == "/api/login" {
|
|
handlers.HandleLogin(w, r)
|
|
return
|
|
}
|
|
if strings.HasPrefix(r.URL.Path, "/_") {
|
|
handleAsset(w, r)
|
|
return
|
|
}
|
|
|
|
servePublicFile(w, r)
|
|
}
|
|
|
|
func servePublicFile(w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
|
|
if path == "/" {
|
|
|
|
// Required for sign in / sign out redirect to work properly
|
|
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate")
|
|
w.Header().Set("Pragma", "no-cache")
|
|
w.Header().Set("Expires", "0")
|
|
w.Header().Set("Surrogate-Control", "no-store")
|
|
|
|
path = "/index.html"
|
|
} else if !strings.Contains(path, ".") {
|
|
path = filepath.Join("/pages", path) + ".html"
|
|
}
|
|
|
|
filePath := filepath.Join("../ui/public", path)
|
|
log.Debug().Msgf("serving: %s", filePath)
|
|
http.ServeFile(w, r, filePath)
|
|
}
|
|
|
|
func handleSite(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/signout" {
|
|
handlers.HandleLogout(w, r)
|
|
return
|
|
}
|
|
if strings.Contains(r.URL.Path, "/_") {
|
|
handleAsset(w, r)
|
|
return
|
|
}
|
|
|
|
serveSiteFiles(w, r)
|
|
}
|
|
|
|
func serveSiteFiles(w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
|
|
if(strings.Contains(path, "75820185")) { // necessary because, if we are at a url path 2 layers or more, the browser will insert that path at the beginning of the url
|
|
_, after, _ := strings.Cut(path, "75820185")
|
|
path = after
|
|
} else {
|
|
// Required for sign in / sign out redirect to work properly
|
|
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate")
|
|
w.Header().Set("Pragma", "no-cache")
|
|
w.Header().Set("Expires", "0")
|
|
w.Header().Set("Surrogate-Control", "no-store")
|
|
|
|
path = "/index.html"
|
|
}
|
|
|
|
filePath := filepath.Join("../ui/site", path)
|
|
log.Debug().Msgf("serving: %s", filePath)
|
|
http.ServeFile(w, r, filePath)
|
|
}
|
|
|
|
func handleAsset(w http.ResponseWriter, r *http.Request) {
|
|
_, after, _ := strings.Cut(r.URL.Path, "/_")
|
|
filePath := filepath.Join("../ui", "/_" + after)
|
|
log.Debug().Msgf("serving asset: %s", filePath)
|
|
http.ServeFile(w, r, filePath)
|
|
}
|
|
|
|
func loggedIn(w http.ResponseWriter, r *http.Request) bool {
|
|
cookie, err := r.Cookie("auth_token")
|
|
if err != nil {
|
|
log.Warn().Msg("Unauthorized - missing auth token")
|
|
return false
|
|
}
|
|
jwtToken := cookie.Value
|
|
|
|
token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) {
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
}
|
|
return []byte(config.JWT_SECRET), nil
|
|
})
|
|
|
|
if err != nil {
|
|
log.Err(err).Msg("error authenticating jwt")
|
|
return false
|
|
}
|
|
if err != nil || !token.Valid {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
|
|
var upgrader = websocket.Upgrader{
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
return true // In production, validate the origin!
|
|
},
|
|
}
|
|
|
|
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
fmt.Println("WebSocket upgrade failed:", err)
|
|
http.Error(w, "WebSocket upgrade failed", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
fmt.Println("WebSocket connection established")
|
|
|
|
for {
|
|
msgType, msg, err := conn.ReadMessage()
|
|
if err != nil {
|
|
fmt.Println("Read error:", err)
|
|
break
|
|
}
|
|
fmt.Printf("Received: %s\n", msg)
|
|
|
|
if err := conn.WriteMessage(msgType, msg); err != nil {
|
|
fmt.Println("Write error:", err)
|
|
break
|
|
}
|
|
}
|
|
} |