package server import ( "MiauInv/auth" "MiauInv/config" "MiauInv/frontend" "MiauInv/handlers" "MiauInv/models" utils "MiauInv/util" "log" "net/http" "os" "time" ) type Server struct { Port string JWTSecret []byte DatabasePath string CertificatePath string PrivateKeyPath string AllowRegistration bool } func InitServer() *Server { err := config.CheckIfExists() if err != nil { log.Fatal(err) return nil } cfg, err := config.LoadConfig() if err != nil { log.Fatal(err) return nil } jwtSecret := os.Getenv("JWT_SECRET") if jwtSecret == "" { log.Fatal("JWT_SECRET environment variable not set.") return nil } if len(jwtSecret) < 32 { log.Fatal("JWT_SECRET must be at least 32 characters long.") return nil } models.JWTSecret = []byte(jwtSecret) return &Server{ Port: cfg.Port, JWTSecret: []byte(jwtSecret), DatabasePath: cfg.DatabasePath, CertificatePath: cfg.CertificatePath, PrivateKeyPath: cfg.PrivateKeyPath, AllowRegistration: cfg.AllowRegistration, } } func (this *Server) Run() { log.Println("Starting server...") mux := http.NewServeMux() // // FRONTEND // mux.HandleFunc("/", frontend.Home) mux.HandleFunc("/login", utils.RenderFile("frontend/htmx/login.html")) mux.Handle("/dashboard", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Dashboard))) mux.Handle("/inventory", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Inventory))) mux.Handle("/items", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Items))) mux.Handle("/locations", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Locations))) mux.Handle("/projects", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Projects))) mux.Handle("/profile/settings", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.AccountSettings))) mux.Handle("/profile/activity", auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(frontend.Activity))) mux.HandleFunc("/profile/", utils.RenderFile("frontend/htmx/under-construction.html")) if this.AllowRegistration { mux.HandleFunc("/register", utils.RenderFile("frontend/htmx/register.html")) } else { mux.HandleFunc("/register", utils.RenderFile("frontend/htmx/register-blocked.html")) } // // API // loginLimiter := newRateLimiter(10, time.Minute, 5*time.Minute) accountLimiter := newRateLimiter(20, time.Minute, 2*time.Minute) activityLimiter := newRateLimiter(60, time.Minute, 2*time.Minute) authed := func(handler http.HandlerFunc) http.Handler { return auth.AuthMiddleware(this.JWTSecret)(http.HandlerFunc(handler)) } audited := func(entityType string, handler http.HandlerFunc) http.Handler { return auth.AuthMiddleware(this.JWTSecret)(handlers.ActivityMiddleware(entityType, false)(http.HandlerFunc(handler))) } mux.Handle("/api/login", loginLimiter.Middleware(http.HandlerFunc(handlers.APILogin))) mux.Handle("/api/login/2fa", loginLimiter.Middleware(http.HandlerFunc(handlers.APILoginTwoFactor))) mux.Handle("/api/refresh", loginLimiter.Middleware(http.HandlerFunc(handlers.RefreshToken))) mux.Handle("/api/logout", auth.AuthMiddleware(this.JWTSecret)(handlers.ActivityMiddleware("auth", true)(http.HandlerFunc(handlers.Logout)))) mux.Handle("/api/profile", authed(handlers.UserInfo)) mux.Handle("/api/activity", activityLimiter.Middleware(authed(handlers.ActivityLog))) mux.Handle("/api/2fa/setup", accountLimiter.Middleware(audited("security", handlers.TwoFactorSetup))) mux.Handle("/api/2fa/enable", loginLimiter.Middleware(audited("security", handlers.TwoFactorEnable))) mux.Handle("/api/2fa/disable", loginLimiter.Middleware(audited("security", handlers.TwoFactorDisable))) mux.Handle("/api/2fa/recovery-codes/regenerate", loginLimiter.Middleware(audited("security", handlers.TwoFactorRegenerateRecoveryCodes))) mux.Handle("/api/userinfo", authed(handlers.UserInfo)) mux.Handle("/api/account/username", accountLimiter.Middleware(audited("account", handlers.AccountUpdateUsername))) mux.Handle("/api/account/password", loginLimiter.Middleware(audited("account", handlers.AccountUpdatePassword))) mux.Handle("/api/passkeys", accountLimiter.Middleware(audited("security", handlers.Passkeys))) mux.Handle("/api/passkeys/register/options", accountLimiter.Middleware(audited("security", handlers.PasskeyRegisterOptions))) mux.Handle("/api/passkeys/register/finish", accountLimiter.Middleware(audited("security", handlers.PasskeyRegisterFinish))) mux.Handle("/api/passkeys/disable", loginLimiter.Middleware(audited("security", handlers.PasskeyDisable))) mux.Handle("/api/passkeys/login/options", loginLimiter.Middleware(http.HandlerFunc(handlers.PasskeyLoginOptions))) mux.Handle("/api/passkeys/login/finish", loginLimiter.Middleware(http.HandlerFunc(handlers.PasskeyLoginFinish))) if this.AllowRegistration { mux.Handle("/api/register", loginLimiter.Middleware(http.HandlerFunc(handlers.APIRegister))) } mux.Handle("/api/item", audited("item", handlers.Item)) mux.Handle("/api/location", audited("location", handlers.Location)) mux.Handle("/api/project", audited("project", handlers.Project)) mux.Handle("/api/stock", audited("stock", handlers.Stock)) mux.Handle("/api/association", audited("association", handlers.Associations)) // Assets mux.HandleFunc("/assets/", frontend.Assets) log.Printf("Listening on port %s", this.Port) log.Fatal(http.ListenAndServeTLS(":"+this.Port, this.CertificatePath, this.PrivateKeyPath, mux)) }