Init and optimizations

This commit is contained in:
2025-07-12 23:09:54 +02:00
parent 1a0b081e96
commit 77e4a2b6d1
11 changed files with 1061 additions and 0 deletions

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module git.send.nrw/sendnrw/raccws
go 1.24.4
require github.com/google/uuid v1.6.0

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

221
main.go Normal file
View File

@@ -0,0 +1,221 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"git.send.nrw/sendnrw/raccws/racc_common"
"git.send.nrw/sendnrw/raccws/racc_filebrowser"
"git.send.nrw/sendnrw/raccws/racc_httpstatus"
"git.send.nrw/sendnrw/raccws/racc_logging"
"git.send.nrw/sendnrw/raccws/racc_middleware"
"git.send.nrw/sendnrw/raccws/racc_session"
"git.send.nrw/sendnrw/raccws/racc_sysauth"
"git.send.nrw/sendnrw/raccws/racc_template"
)
var DefaultAppPath string = "/"
var DefaultLogNumber int = 500
var CORE_SESSIONFILE string = "serversessions.json"
var CORE_SERVERLOG string = "serverlog.json"
var CORE_RELPATH string = "./"
func DefaultRoute(w http.ResponseWriter, r *http.Request) {
//fmt.Println(r.URL.Path)
if r.URL.Path == "/" {
http.Redirect(w, r, DefaultAppPath, http.StatusMovedPermanently)
}
fileInfo, err := os.Stat(CORE_RELPATH + r.URL.Path)
if os.IsNotExist(err) {
racc_httpstatus.Default404(w, r)
return
}
if !fileInfo.IsDir() {
http.ServeFile(w, r, CORE_RELPATH+r.URL.Path)
return
} else {
racc_httpstatus.Default404(w, r)
return
}
}
func prepareExit() {
fmt.Println("Running exit tasks...")
// Hier können Sie Ihre Aufräumarbeiten ausführen
SaveFile(CORE_SESSIONFILE, &racc_session.SessionStore)
SaveFile(CORE_SERVERLOG, racc_logging.GetLastXitems(DefaultLogNumber))
fmt.Println("Exit completed.")
}
func main() {
/* */
//app_adinfo.MakeDefault()
/* */
/*server_port := flag.String("port", "8080", "Port to be used for http server")
server_mode := flag.String("mode", "http", "Security mode the server should run (http/https)")
server_tls_key := flag.String("key", "private.key", "Private key file")
server_tls_cert := flag.String("cert", "public.crt", "Public cert file")
flag.Parse()*/
HTTP_PORT := racc_common.GetENV("HTTP_PORT", "8080")
HTTP_TLS := racc_common.GetENV("HTTP_TLS", "0")
HTTP_TLS_PRIVATEKEY := racc_common.GetENV("HTTP_TLS_PRIVATEKEY", "")
HTTP_TLS_CERTIFICATE := racc_common.GetENV("HTTP_TLS_CERTIFICATE", "")
CORE_SESSIONFILE = racc_common.GetENV("CORE_SESSIONFILE", "serversessions.json")
CORE_SERVERLOG = racc_common.GetENV("CORE_SERVERLOG", "serverlog.json")
CORE_RELPATH = racc_common.GetENV("CORE_RELPATH", "./")
/* Print actual settings */
fmt.Println("----------")
fmt.Println("Service configured with settings:")
fmt.Println("----------")
fmt.Println("HTTP-PORT: "+HTTP_PORT, "HTTP_TLS: "+HTTP_TLS, "HTTP_TLS_PRIVATEKEY: "+HTTP_TLS_PRIVATEKEY, "HTTP_TLS_CERTIFICATE: "+HTTP_TLS_CERTIFICATE)
fmt.Println("CORE_SESSIONFILE: "+CORE_SESSIONFILE, "CORE_SERVERLOG: "+CORE_SERVERLOG, "CORE_RELPATH: "+CORE_RELPATH)
fmt.Println("----------")
// Signal-Kanal einrichten
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
// Goroutine, die auf Signale wartet
go func() {
<-stop
fmt.Println("Received stop signal")
prepareExit()
os.Exit(0)
}()
// Hauptprogramm
fmt.Println("Program is running. Press Ctrl+C to exit.")
/* Load Infrastructure */
/* Load Server-Log */
LoadFile(CORE_SERVERLOG, &racc_logging.LogBook)
/* Load Session-Data */
LoadFile(CORE_SESSIONFILE, &racc_session.SessionStore)
/* Router-Definition */
mainRouter := http.NewServeMux()
defaultRouter := http.NewServeMux()
apiRouter := http.NewServeMux()
portalRouter := http.NewServeMux()
//adminRouter := http.NewServeMux()
userRouter := http.NewServeMux()
/* Main-Router-Middleware */
mainMiddlewareStack := racc_middleware.CreateStack(
racc_session.SessionMiddleware,
)
/* Admin-Router-Middleware */
defaultMiddlewareStack := racc_middleware.CreateStack(racc_logging.Logging)
apiMiddlewareStack := racc_middleware.CreateStack(racc_logging.Logging, racc_sysauth.BearerAuthMiddleware("auth_bearer.json"))
portalMiddlewareStack := racc_middleware.CreateStack(racc_logging.Logging, racc_template.RenderApplicationTemplate("html-templates/application_1.html", nil))
//adminMiddlewareStack := racc_middleware.CreateStack(racc_logging.Logging, racc_sysauth.BasicAuthMiddleware("auth_basic_admin.json"))
userMiddlewareStack := racc_middleware.CreateStack(racc_logging.Logging)
//appDemoMiddlewareStack := racc_middleware.CreateStack(racc_logging.Logging)
appFileMiddlewareStack := racc_middleware.CreateStack(racc_logging.Logging)
//appDynDNSMiddlewareStack := racc_middleware.CreateStack(racc_logging.Logging)
/* Main-Router-Routen */
mainRouter.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
mainRouter.Handle("/api/", apiMiddlewareStack(http.StripPrefix("/api", apiRouter)))
mainRouter.Handle("/portal/", portalMiddlewareStack(http.StripPrefix("/portal", portalRouter)))
//mainRouter.Handle("/admin/", adminMiddlewareStack(http.StripPrefix("/admin", app_admin.Main())))
mainRouter.Handle("/user/", userMiddlewareStack(http.StripPrefix("/user", userRouter)))
//mainRouter.Handle("/demo/", appDemoMiddlewareStack(http.StripPrefix("/demo", app_demo.Main())))
mainRouter.Handle("/files/", appFileMiddlewareStack(http.StripPrefix("/files", racc_filebrowser.Router())))
//mainRouter.Handle("/dns/", appDynDNSMiddlewareStack(http.StripPrefix("/dns", app_dyndns.Main())))
/* Default-Route (Should stay here forever) */
mainRouter.Handle("/", defaultMiddlewareStack(http.StripPrefix("", defaultRouter)))
defaultRouter.HandleFunc("/", DefaultRoute)
/* Sub-Router-Routen */
//adminRouter.HandleFunc("/sessions", app_admin.adminSessionHandler)
apiRouter.HandleFunc("/", HttpSaveFile(CORE_SERVERLOG, &racc_logging.LogBook))
/* */
server := http.Server{
Addr: ":" + HTTP_PORT,
Handler: mainMiddlewareStack(mainRouter),
}
/* */
fmt.Println("Server listening on port :" + HTTP_PORT)
if HTTP_TLS == "0" {
fmt.Println("Protocol is http (insecure)")
StopServer(server.ListenAndServe())
}
if HTTP_TLS == "1" {
fmt.Println("Protocol is https (secure)")
StopServer(server.ListenAndServeTLS(HTTP_TLS_CERTIFICATE, HTTP_TLS_PRIVATEKEY))
}
}
func StopServer(e error) {
fmt.Println("Stopping server...")
prepareExit()
fmt.Println("Server stopped!")
}
func HttpSaveFile(xfile string, a any) racc_httpstatus.App {
return func(next_w http.ResponseWriter, next_r *http.Request) {
file, _ := os.Create(xfile)
encoder := json.NewEncoder(file)
err := encoder.Encode(a)
if err != nil {
fmt.Println("Error encoding JSON:", err)
}
file.Close()
}
}
func SaveFile(xfile string, a any) {
file, _ := os.Create(xfile)
encoder := json.NewEncoder(file)
err := encoder.Encode(a)
if err != nil {
fmt.Println("Error encoding JSON:", err)
}
file.Close()
fmt.Println("Saved content to: ", xfile)
}
func HttpLoadFile(xfile string, a any) racc_httpstatus.App {
return func(next_w http.ResponseWriter, next_r *http.Request) {
if _, err := os.Stat(xfile); os.IsNotExist(err) {
fmt.Println("Speicherdatei nicht gefunden, neue Map wird erstellt")
} else {
file, _ := os.Open(xfile)
decoder := json.NewDecoder(file)
err := decoder.Decode(a)
if err != nil {
fmt.Println("Fehler in JSON-Datei:", err)
}
file.Close()
}
}
}
func LoadFile(xfile string, a any) {
if _, err := os.Stat(xfile); os.IsNotExist(err) {
fmt.Println("Speicherdatei nicht gefunden, neue Map wird erstellt")
} else {
file, _ := os.Open(xfile)
decoder := json.NewDecoder(file)
err := decoder.Decode(a)
if err != nil {
fmt.Println("Fehler in JSON-Datei:", err)
}
file.Close()
}
fmt.Println("Loaded content from: ", xfile)
}

View File

@@ -0,0 +1,22 @@
package racc_common
import (
"os"
"strconv"
"strings"
)
func GetENV(k, d string) string {
if v := os.Getenv(k); v != "" {
return v
}
return d
}
func Enabled(k string, def bool) bool {
b, err := strconv.ParseBool(strings.ToLower(os.Getenv(k)))
if err != nil {
return def
}
return b
}

View File

@@ -0,0 +1,150 @@
package racc_filebrowser
import (
"errors"
"html/template"
"net/http"
"os"
"path"
"path/filepath"
"sort"
"strings"
)
const (
appID = "files"
baseURI = "/files" // URL-Prefix (immer Slash)
root = "./" + appID // lokaler Wurzel­ordner
)
// ---------- Helper-Typen ----------
type fileInfo struct {
Name string // nur Dateiname
WebPath string // URL-Pfad (Slash-Separators)
LocalPath string // voller lokaler Pfad (OS-Separators)
IsDir bool
}
type pageData struct {
Path string // angezeigter Ordner (URL-Pfad ab /files)
Query string // Suchbegriff
Dirs []fileInfo // nur Ordner
Files []fileInfo // nur Dateien
}
// ---------- Öffentliche API ----------
// Router liefert eine fertige Sub-Mux, die du im Haupt­server mounten kannst.
func Router() *http.ServeMux {
m := http.NewServeMux()
m.HandleFunc("/", handle)
return m
}
// ---------- HTTP-Handler ----------
func handle(w http.ResponseWriter, r *http.Request) {
// Zielpfad aus URL zusammensetzen und absichern
reqPath := strings.TrimPrefix(r.URL.Path, baseURI) // »/foo« → »foo«
local, err := safeJoin(root, reqPath) // verhindert ..-Traversal
if err != nil {
http.Error(w, "invalid path", http.StatusBadRequest)
return
}
query := r.URL.Query().Get("q")
// Datei-Info ermitteln
info, err := os.Stat(local)
if errors.Is(err, os.ErrNotExist) {
http.NotFound(w, r)
return
} else if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Datei direkt ausliefern
if !info.IsDir() && query == "" {
http.ServeFile(w, r, local)
return
}
// Verzeichnis auflisten
dirs, files, err := listDir(local, query)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
pd := pageData{
Path: path.Clean("/" + reqPath), // immer URL-Slash
Query: query,
Dirs: dirs,
Files: files,
}
renderTemplate(w, pd)
}
// ---------- Pfad-Utilities ----------
// safeJoin verknüpft root und req so, dass das Ergebnis IMMER unter root bleibt.
func safeJoin(root, req string) (string, error) {
clean := filepath.Clean("/" + req) // Normieren
local := filepath.Join(root, clean) // OS-Pfad
if !strings.HasPrefix(local, filepath.Clean(root)) {
return "", errors.New("path escape")
}
return local, nil
}
// buildWebPath konvertiert einen lokalen Pfad zurück in einen URL-Pfad.
func buildWebPath(local string) string {
rel, _ := filepath.Rel(root, local) // garantiert unter root
return path.Join(baseURI, filepath.ToSlash(rel))
}
// ---------- Verzeichnis lesen ----------
func listDir(folder, query string) (dirs, files []fileInfo, err error) {
entries, err := os.ReadDir(folder)
if err != nil {
return nil, nil, err
}
for _, e := range entries {
name := e.Name()
if query != "" && !strings.Contains(strings.ToLower(name), strings.ToLower(query)) {
continue
}
local := filepath.Join(folder, name)
item := fileInfo{
Name: name,
LocalPath: local,
WebPath: buildWebPath(local),
IsDir: e.IsDir(),
}
if item.IsDir {
dirs = append(dirs, item)
} else {
files = append(files, item)
}
}
// alphabetisch sortieren
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name < dirs[j].Name })
sort.Slice(files, func(i, j int) bool { return files[i].Name < files[j].Name })
return
}
// ---------- Template-Renderer ----------
var tmpl = template.Must(template.ParseFiles("./html-templates/filebrowser.html"))
func renderTemplate(w http.ResponseWriter, pd pageData) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := tmpl.Execute(w, pd); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

View File

@@ -0,0 +1,117 @@
package racc_httpstatus
import (
"net/http"
"strconv"
"git.send.nrw/sendnrw/raccws/racc_logging"
"git.send.nrw/sendnrw/raccws/racc_template"
)
type App func(http.ResponseWriter, *http.Request)
var Default400 = CreateStatusPage("defaultErrors.html", "./html-templates", 400, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "400", ErrorName: "Bad Request", ErrorMSG: "The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing)."})
var Default401 = CreateStatusPage("defaultErrors.html", "./html-templates", 401, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "401", ErrorName: "Unauthorized", ErrorMSG: "The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource. The server generating a 401 response MUST send a WWW-Authenticate header field containing at least one challenge applicable to the target resource."})
var Default402 = CreateStatusPage("defaultErrors.html", "./html-templates", 402, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "402", ErrorName: "Payment Required", ErrorMSG: "The 402 (Payment Required) status code is reserved for future use."})
var Default403 = CreateStatusPage("defaultErrors.html", "./html-templates", 403, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "403", ErrorName: "Forbidden", ErrorMSG: "The 403 (Forbidden) status code indicates that the server understood the request but refuses to fulfill it. A server that wishes to make public why the request has been forbidden can describe that reason in the response content (if any)."})
var Default404 = CreateStatusPage("defaultErrors.html", "./html-templates", 404, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "404", ErrorName: "Not Found", ErrorMSG: "The 404 (Not Found) status code indicates that the origin server did not find a current representation for the target resource or is not willing to disclose that one exists. A 404 status code does not indicate whether this lack of representation is temporary or permanent; the 410 (Gone) status code is preferred over 404 if the origin server knows, presumably through some configurable means, that the condition is likely to be permanent."})
var Default405 = CreateStatusPage("defaultErrors.html", "./html-templates", 405, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "405", ErrorName: "Method Not Allowed", ErrorMSG: "The 405 (Method Not Allowed) status code indicates that the method received in the request-line is known by the origin server but not supported by the target resource. The origin server MUST generate an Allow header field in a 405 response containing a list of the target resource's currently supported methods."})
var Default406 = CreateStatusPage("defaultErrors.html", "./html-templates", 406, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "406", ErrorName: "Not Acceptable", ErrorMSG: "The 406 (Not Acceptable) status code indicates that the target resource does not have a current representation that would be acceptable to the user agent, according to the proactive negotiation header fields received in the request, and the server is unwilling to supply a default representation."})
var Default407 = CreateStatusPage("defaultErrors.html", "./html-templates", 407, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "407", ErrorName: "Proxy Authentication Required", ErrorMSG: "The 407 (Proxy Authentication Required) status code is similar to 401 (Unauthorized), but it indicates that the client needs to authenticate itself in order to use a proxy for this request. The proxy MUST send a Proxy-Authenticate header field containing a challenge applicable to that proxy for the request. The client MAY repeat the request with a new or replaced Proxy-Authorization header field."})
var Default408 = CreateStatusPage("defaultErrors.html", "./html-templates", 408, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "408", ErrorName: "Request Timeout", ErrorMSG: "The 408 (Request Timeout) status code indicates that the server did not receive a complete request message within the time that it was prepared to wait."})
var Default409 = CreateStatusPage("defaultErrors.html", "./html-templates", 409, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "409", ErrorName: "Conflict", ErrorMSG: "The 409 (Conflict) status code indicates that the request could not be completed due to a conflict with the current state of the target resource. This code is used in situations where the user might be able to resolve the conflict and resubmit the request. The server SHOULD generate content that includes enough information for a user to recognize the source of the conflict. Conflicts are most likely to occur in response to a PUT request. For example, if versioning were being used and the representation being PUT included changes to a resource that conflict with those made by an earlier (third-party) request, the origin server might use a 409 response to indicate that it can't complete the request. In this case, the response representation would likely contain information useful for merging the differences based on the revision history."})
var Default500 = CreateStatusPage("defaultErrors.html", "./html-templates", 500, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "500", ErrorName: "Internal Server Error", ErrorMSG: "The 500 (Internal Server Error) status code indicates that the server encountered an unexpected condition that prevented it from fulfilling the request."})
var Default501 = CreateStatusPage("defaultErrors.html", "./html-templates", 501, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "501", ErrorName: "Not Implemented", ErrorMSG: "The 501 (Not Implemented) status code indicates that the server does not support the functionality required to fulfill the request. This is the appropriate response when the server does not recognize the request method and is not capable of supporting it for any resource."})
var Default502 = CreateStatusPage("defaultErrors.html", "./html-templates", 502, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "502", ErrorName: "Bad Gateway", ErrorMSG: "The 502 (Bad Gateway) status code indicates that the server, while acting as a gateway or proxy, received an invalid response from an inbound server it accessed while attempting to fulfill the request."})
var Default503 = CreateStatusPage("defaultErrors.html", "./html-templates", 503, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "503", ErrorName: "Service Unavailable", ErrorMSG: "The 503 (Service Unavailable) status code indicates that the server is currently unable to handle the request due to a temporary overload or scheduled maintenance, which will likely be alleviated after some delay. The server MAY send a Retry-After header field to suggest an appropriate amount of time for the client to wait before retrying the request."})
var Default504 = CreateStatusPage("defaultErrors.html", "./html-templates", 504, struct {
ErrorCode string
ErrorName string
ErrorMSG string
}{ErrorCode: "504", ErrorName: "Gateway Timeout", ErrorMSG: "The 504 (Gateway Timeout) status code indicates that the server, while acting as a gateway or proxy, did not receive a timely response from an upstream server it needed to access in order to complete the request."})
func CreateStatusPage(tplname, tpldir string, statuscode int, data interface{}) App {
return func(next_w http.ResponseWriter, next_r *http.Request) {
sc := strconv.Itoa(statuscode)
racc_logging.AppendToLogMainFunction(next_w, next_r, sc)
racc_template.ProcessTemplate(next_w, tplname, tpldir, statuscode, data)
}
}
func CreateAppResponse(statuscode int, statusmsg string) App {
return func(next_w http.ResponseWriter, next_r *http.Request) {
next_w.WriteHeader(statuscode)
next_w.Write([]byte(statusmsg))
}
}

View File

@@ -0,0 +1,58 @@
package racc_logging
import (
"fmt"
"net/http"
"strings"
"time"
"git.send.nrw/sendnrw/raccws/racc_session"
)
type LogItem struct {
Module string
URL string
Time string
Session string
RemoteIP string
RemotePort string
Method string
Status string
}
var LogBook []LogItem
func GetLastXitems(amount int) []LogItem {
var temp []LogItem
startIndex := len(LogBook) - amount
// Sicherstellen, dass startIndex nicht negativ wird
if startIndex < 0 {
startIndex = 0
}
for i := startIndex; i < len(LogBook); i++ {
temp = append(temp, LogBook[i])
}
return temp
}
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
AppendToLogMainFunction(w, r, "")
next.ServeHTTP(w, r)
})
}
func AppendToLogMainFunction(w http.ResponseWriter, r *http.Request, status string) {
start := time.Now()
sessionID := r.Context().Value(racc_session.SessionKey).(string)
meth := r.Method
IP_ONLY := strings.Split(r.RemoteAddr, ":")[0]
PORT_ONLY := strings.Split(r.RemoteAddr, ":")[1]
li := LogItem{Module: "http", URL: r.URL.Path, Time: start.Format(time.DateTime), Session: sessionID, RemoteIP: IP_ONLY, RemotePort: PORT_ONLY, Status: status, Method: meth}
WriteLog(li)
}
func WriteLog(logitem LogItem) {
LogBook = append(LogBook, logitem)
fmt.Println(logitem)
}

View File

@@ -0,0 +1,15 @@
package racc_middleware
import "net/http"
type Middleware func(http.Handler) http.Handler
func CreateStack(xs ...Middleware) Middleware {
return func(next http.Handler) http.Handler {
for i := len(xs) - 1; i >= 0; i-- {
x := xs[i]
next = x(next)
}
return next
}
}

View File

@@ -0,0 +1,101 @@
package racc_session
import (
"context"
"net/http"
"time"
"github.com/google/uuid"
)
type contextKey string
const SessionKey contextKey = "session_id"
/* Default Session Expiration */
var SessionDuration time.Duration = 24 * 60 * time.Minute
// SessionMiddleware überprüft, ob ein Session-Cookie vorhanden ist,
// und erstellt bei Bedarf eine neue SessionID.
func SessionMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_id")
if err != nil || cookie.Value == "" {
// Erstelle eine neue SessionID
sessionID := uuid.New().String()
// Erstelle Time to Expire
///now := time.Now()
// Ablaufzeit in 60 Minuten
///expiration := now.Local().Add(SessionDuration)
// Speichere die SessionID in einem Cookie
http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: sessionID,
Path: "/",
///Expires: expiration,
// Optional: Setze weitere Cookie-Attribute wie Secure, HttpOnly, etc.
})
// Setze die SessionID im Kontext des Requests
r = r.WithContext(context.WithValue(r.Context(), SessionKey, sessionID))
} else {
// Setze die vorhandene SessionID im Kontext des Requests
r = r.WithContext(context.WithValue(r.Context(), SessionKey, cookie.Value))
}
next.ServeHTTP(w, r)
})
}
type SessionItem struct {
Key string
Value string
}
var SessionStore = map[string][]SessionItem{}
func AddOrUpdateSessionItem(sessionID string, item SessionItem) {
if items, exists := SessionStore[sessionID]; exists {
for i, existingItem := range items {
if existingItem.Key == item.Key {
SessionStore[sessionID][i].Value = item.Value
return
}
}
SessionStore[sessionID] = append(items, item)
} else {
SessionStore[sessionID] = []SessionItem{item}
}
/*jsonstore, err := json.Marshal(SessionStore)
if err != nil {
panic("LOL")
}
fmt.Println(string(jsonstore))*/
}
func RemoveSessionItem(sessionID string, keyToRemove string) {
if items, exists := SessionStore[sessionID]; exists {
updatedItems := []SessionItem{}
for _, item := range items {
if item.Key != keyToRemove {
updatedItems = append(updatedItems, item)
}
}
if len(updatedItems) == 0 {
delete(SessionStore, sessionID)
} else {
SessionStore[sessionID] = updatedItems
}
}
}
func GetSessionItem(sessionID string, keyToInspect string) string {
if items, exists := SessionStore[sessionID]; exists {
for i, existingItem := range items {
if existingItem.Key == keyToInspect {
return SessionStore[sessionID][i].Value
}
}
return ""
} else {
return ""
}
}

View File

@@ -0,0 +1,289 @@
package racc_sysauth
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"git.send.nrw/sendnrw/raccws/racc_session"
)
type Credential struct {
Username string `json:"username"`
Password string `json:"password"`
}
// BearerToken repräsentiert ein gültiges Token
type BearerToken string
// MultiAuthMiddleware ist eine Middleware, die Basic Auth, Bearer Auth und Active Directory Auth unterstützt.
func MultiAuthMiddleware(basicCredFilePath, bearerTokenFilePath, adServer, adBaseDN, adUserDN, adPassword string) func(http.Handler) http.Handler {
basicCredentials, err := loadBasicCredentials(basicCredFilePath)
if err != nil {
fmt.Println("Error loading basic credentials:", err)
return nil
}
bearerTokens, err := loadBearerTokens(bearerTokenFilePath)
if err != nil {
fmt.Println("Error loading bearer tokens:", err)
return nil
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
z0, _ := validateAuth(authHeader, basicCredentials, bearerTokens, adServer, adBaseDN, adUserDN, adPassword)
if !z0 {
w.Header().Set("WWW-Authenticate", `Basic realm="Please provide credentials"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}
// ADAuthMiddleware ist eine Middleware, die Active Directory-Authentifizierung durchführt.
/*func ADAuthMiddleware(adServer, adBaseDN, adUserDN, adPassword string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" || !strings.HasPrefix(authHeader, "Basic ") {
w.Header().Set("WWW-Authenticate", `Basic realm="Please provide credentials"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
encodedCredentials := strings.TrimPrefix(authHeader, "Basic ")
decodedCredentials, err := base64.StdEncoding.DecodeString(encodedCredentials)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
parts := strings.SplitN(string(decodedCredentials), ":", 2)
if len(parts) != 2 {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
username := parts[0]
password := parts[1]
if !validateADCredentials(adServer, adBaseDN, adUserDN, adPassword, username, password) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}*/
// BasicAuthMiddleware ist eine Middleware, die Basic-Authentifizierung erfordert.
func BasicAuthMiddleware(credFilePath string) func(http.Handler) http.Handler {
credentials, err := loadCredentials(credFilePath)
if err != nil {
fmt.Println("Error loading credentials:", err)
return nil
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
z0, z1 := validateBasicAuth(authHeader, credentials)
if !z0 {
w.Header().Set("WWW-Authenticate", `Basic realm="Please provide credentials"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
} else {
sessionID := r.Context().Value(racc_session.SessionKey).(string)
authMethod := racc_session.SessionItem{Key: "authmethod", Value: "Basic"}
racc_session.AddOrUpdateSessionItem(sessionID, authMethod)
authUser := racc_session.SessionItem{Key: "authuser", Value: z1}
racc_session.AddOrUpdateSessionItem(sessionID, authUser)
}
next.ServeHTTP(w, r)
})
}
}
// BearerAuthMiddleware ist eine Middleware, die Bearer-Authentifizierung erfordert.
func BearerAuthMiddleware(tokenFilePath string) func(http.Handler) http.Handler {
tokens, err := loadBearerTokens(tokenFilePath)
if err != nil {
fmt.Println("Error loading tokens:", err)
return nil
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if !validateBearerToken(authHeader, tokens) {
w.Header().Set("WWW-Authenticate", `Bearer realm="Please provide a valid token"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}
// loadBasicCredentials lädt die Anmeldeinformationen für Basic Auth aus einer JSON-Datei
func loadBasicCredentials(filePath string) ([]Credential, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
var credentials []Credential
decoder := json.NewDecoder(file)
if err := decoder.Decode(&credentials); err != nil {
return nil, err
}
return credentials, nil
}
// loadBearerTokens lädt die gültigen Bearer-Tokens aus einer JSON-Datei
func loadBearerTokens(filePath string) ([]BearerToken, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
var tokens []BearerToken
decoder := json.NewDecoder(file)
if err := decoder.Decode(&tokens); err != nil {
return nil, err
}
return tokens, nil
}
// validateAuth überprüft, ob der Authorization-Header entweder Basic Auth, Bearer Token oder Active Directory enthält
func validateAuth(authHeader string, basicCredentials []Credential, bearerTokens []BearerToken, adServer, adBaseDN, adUserDN, adPassword string) (bool, string) {
if authHeader == "" {
return false, ""
}
if strings.HasPrefix(authHeader, "Basic ") {
return validateBasicAuth(authHeader, basicCredentials)
} else if strings.HasPrefix(authHeader, "Bearer ") {
return validateBearerToken(authHeader, bearerTokens), ""
} else if strings.HasPrefix(authHeader, "AD ") {
/*return validateADAuth(authHeader, adServer, adBaseDN, adUserDN, adPassword), ""*/
}
return false, ""
}
// validateBasicAuth überprüft, ob der Authorization-Header gültige Basic-Authentifizierungsdaten enthält
func validateBasicAuth(authHeader string, credentials []Credential) (bool, string) {
encodedCredentials := strings.TrimPrefix(authHeader, "Basic ")
decodedCredentials, err := base64.StdEncoding.DecodeString(encodedCredentials)
if err != nil {
return false, ""
}
parts := strings.SplitN(string(decodedCredentials), ":", 2)
if len(parts) != 2 {
return false, ""
}
username := parts[0]
password := parts[1]
for _, cred := range credentials {
if username == cred.Username && password == cred.Password {
return true, cred.Username
}
}
return false, ""
}
// validateBearerToken überprüft, ob der Authorization-Header ein gültiges Bearer-Token enthält
func validateBearerToken(authHeader string, validTokens []BearerToken) bool {
token := strings.TrimPrefix(authHeader, "Bearer ")
for _, validToken := range validTokens {
if BearerToken(token) == validToken {
return true
}
}
return false
}
// validateADAuth überprüft die Benutzeranmeldeinformationen gegen Active Directory
/*func validateADAuth(authHeader, adServer, adBaseDN, adUserDN, adPassword string) bool {
if !strings.HasPrefix(authHeader, "AD ") {
return false
}
encodedCredentials := strings.TrimPrefix(authHeader, "AD ")
decodedCredentials, err := base64.StdEncoding.DecodeString(encodedCredentials)
if err != nil {
return false
}
parts := strings.SplitN(string(decodedCredentials), ":", 2)
if len(parts) != 2 {
return false
}
username := parts[0]
password := parts[1]
return validateADCredentials(adServer, adBaseDN, adUserDN, adPassword, username, password)
}*/
// validateADCredentials überprüft die Benutzeranmeldeinformationen gegen Active Directory
/*func validateADCredentials(adServer, adBaseDN, adUserDN, adPassword, username, password string) bool {
l, err := ldap.DialURL(adServer)
if err != nil {
fmt.Println("Failed to connect to AD server:", err)
return false
}
defer l.Close()
err = l.Bind(adUserDN, adPassword)
if err != nil {
fmt.Println("Failed to bind to AD server:", err)
return false
}
searchRequest := ldap.NewSearchRequest(
adBaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf("(sAMAccountName=%s)", username),
[]string{"dn"},
nil,
)
sr, err := l.Search(searchRequest)
if err != nil {
fmt.Println("Failed to search user in AD:", err)
return false
}
if len(sr.Entries) != 1 {
fmt.Println("User not found or multiple users found")
return false
}
userDN := sr.Entries[0].DN
err = l.Bind(userDN, password)
if err != nil {
fmt.Println("Failed to authenticate user:", err)
return false
}
return true
}*/
func loadCredentials(filePath string) ([]Credential, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
var credentials []Credential
decoder := json.NewDecoder(file)
if err := decoder.Decode(&credentials); err != nil {
return nil, err
}
return credentials, nil
}

View File

@@ -0,0 +1,81 @@
package racc_template
import (
"fmt"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
)
var templates = template.Must(template.ParseFiles("./html-templates/index.html"))
func WriteTemplate(title, description, templatedir string, status int) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
initTemplates(templatedir)
w.WriteHeader(status)
//fmt.Println("Status WriteTemplate ", status)
renderTemplate(w, "html-templates/application_mainframe.html", nil)
})
}
}
func RenderApplicationTemplate(tmpl string, data interface{}) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
a, b := template.ParseFiles("html-templates/application_mainframe.html", tmpl)
fmt.Println(b)
tmpls := template.Must(a, b)
c := tmpls.ExecuteTemplate(w, "application_mainframe.html", data)
fmt.Println(c)
})
}
}
func RenderApplicationTemplateHandler(tmpl string, data interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
a, b := template.ParseFiles("html-templates/application_mainframe.html", tmpl)
fmt.Println(b)
tmpls := template.Must(a, b)
c := tmpls.ExecuteTemplate(w, "application_mainframe.html", data)
fmt.Println(c)
})
}
func ProcessTemplate(w http.ResponseWriter, templatename, templatedir string, status int, data interface{}) {
initTemplates(templatedir)
w.WriteHeader(status)
//fmt.Println("Status ProcessTemplate ", status)
renderTemplate(w, templatename, data)
}
func initTemplates(directory string) {
files, err := os.ReadDir(directory)
if err != nil {
log.Fatal(err)
}
var filePaths []string
for _, file := range files {
if !file.IsDir() {
filePaths = append(filePaths, filepath.Join(directory, file.Name()))
}
}
fmt.Println(filePaths)
tmpl, err := template.ParseFiles(filePaths...)
if err != nil {
log.Fatal(err)
}
templates = template.Must(tmpl, err)
}
func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
err := templates.ExecuteTemplate(w, tmpl, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}