Files
bds/modules/web/web.go
Matheus Sampaio Queiroga ad65c0a9b0 Refactor user management and server templates
- Removed old SQLite user schema and replaced it with a new structure that includes password handling and cookie management.
- Updated user.go to implement password encryption and user creation logic.
- Modified web templates to reflect the new server management system, including the removal of outdated templates and the addition of new server creation and listing functionalities.
- Introduced error handling templates for better user feedback on bad requests and server errors.
- Added a makefile for easier database setup and management with Docker.

Signed-off-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
2025-05-09 22:03:06 -03:00

397 lines
11 KiB
Go

package web
import (
"context"
"fmt"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"sirherobrine23.com.br/go-bds/bds/modules/api"
"sirherobrine23.com.br/go-bds/bds/modules/datas"
"sirherobrine23.com.br/go-bds/bds/modules/datas/permission"
"sirherobrine23.com.br/go-bds/bds/modules/datas/server"
"sirherobrine23.com.br/go-bds/bds/modules/datas/user"
"sirherobrine23.com.br/go-bds/bds/modules/web/templates"
static "sirherobrine23.com.br/go-bds/bds/modules/web/web_src"
)
const (
ContextUser = "ctxUser"
CookieName = "bdsCookie"
)
func getUser(r *http.Request) *user.User {
switch v := r.Context().Value(ContextUser).(type) {
case *user.User:
return v
default:
return nil
}
}
type WebConfig struct {
*datas.DatabaseSchemas
}
// Mount router to /api
func MountRouter(config *WebConfig) (http.Handler, error) {
webTemplates, err := templates.Templates()
if err != nil {
return nil, err
}
api, err := api.MountRouter(&api.RouteConfig{DatabaseSchemas: config.DatabaseSchemas})
if err != nil {
return nil, err
}
// Start new handler
router := chi.NewMux()
router.Mount("/api", api)
// Serve static files to client
staticFiles := http.FileServerFS(static.StaticFiles)
router.Mount("/js", staticFiles)
router.Mount("/css", staticFiles)
router.Mount("/img", staticFiles)
router.Mount("/fonts", staticFiles)
router.Handle("GET /favicon.ico", http.RedirectHandler("/img/logo.ico", http.StatusMovedPermanently))
// Home server
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
webTemplates.Render("public/home.tmpl", w, &templates.RenderData{
Title: "Home",
Lang: "en-us",
PageIsInstall: false,
User: getUser(r),
External: map[string]any{},
})
})
// Auth page
router.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
if getUser(r) != nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
pageConfig := &templates.RenderData{External: map[string]any{}}
if r.Method == http.MethodPost {
var username, password string
switch r.Header.Get("Content-Type") {
case "application/x-www-form-urlencoded":
if err := r.ParseForm(); err != nil {
println(err.Error())
pageConfig.External["Error"] = fmt.Sprintf("cannot parse Form body: %s", err)
webTemplates.Render("users/auth/signin.tmpl", w, pageConfig)
return
}
// Get username and password
username, password = r.Form.Get("username"), r.Form.Get("password")
default:
webTemplates.Render("users/auth/signin.tmpl", w, pageConfig)
return
}
user, err := config.User.Username(username)
if err != nil {
pageConfig.External["Error"] = fmt.Sprintf("User not exist: %s", err)
webTemplates.Render("users/auth/signin.tmpl", w, pageConfig)
return
}
// Ignore if disabled user
if user.Permission == permission.Unknown {
pageConfig.External["Error"] = "Unauthorized login"
webTemplates.Render("users/auth/signin.tmpl", w, pageConfig)
return
}
ok, err := user.Password.Check(password)
switch {
case ok:
var newCookie string
if newCookie, err = config.Cookie.CreateCookie(user.ID); err == nil {
http.SetCookie(w, &http.Cookie{Name: CookieName, Value: newCookie})
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
fallthrough
default:
pageConfig.External["Error"] = "Cannot auth"
if err != nil {
pageConfig.External["Error"] = fmt.Sprintf("Cannot auth: %s", err)
}
}
}
webTemplates.Render("users/auth/signin.tmpl", w, pageConfig)
})
router.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
if getUser(r) != nil {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
pageConfig := &templates.RenderData{External: map[string]any{}}
if r.Method == http.MethodPost {
var name, username, email, password string
switch r.Header.Get("Content-Type") {
case "application/x-www-form-urlencoded":
if err := r.ParseForm(); err != nil {
println(err.Error())
pageConfig.External["Error"] = fmt.Sprintf("cannot parse Form body: %s", err)
webTemplates.Render("users/auth/register.tmpl", w, pageConfig)
return
}
name, username, email, password = r.Form.Get("name"), r.Form.Get("username"), r.Form.Get("email"), r.Form.Get("password")
default:
webTemplates.Render("users/auth/register.tmpl", w, pageConfig)
return
}
// Create user
user, err := config.User.Create(name, username, email, password)
if err != nil {
pageConfig.External["Error"] = fmt.Sprintf("Cannot create new user: %s", err)
webTemplates.Render("users/auth/register.tmpl", w, pageConfig)
return
}
// Set cookie
redirectTo := "/login"
if newCookie, err := config.Cookie.CreateCookie(user.ID); err == nil {
http.SetCookie(w, &http.Cookie{Name: CookieName, Value: newCookie})
redirectTo = "/"
}
http.Redirect(w, r, redirectTo, http.StatusSeeOther)
return
}
webTemplates.Render("users/auth/register.tmpl", w, pageConfig)
})
router.Get("/servers", func(w http.ResponseWriter, r *http.Request) {
user := getUser(r)
if user == nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
pageConfig := &templates.RenderData{
Lang: "en-us",
User: user,
Title: fmt.Sprintf("%s Servers", user.Username),
External: map[string]any{
"Servers": []*server.Server{},
},
}
servers, err := config.Servers.ByOwner(user.ID)
if err != nil {
pageConfig.External["Error"] = err.Error()
webTemplates.Render400(w, pageConfig)
return
}
pageConfig.External["Servers"] = servers
webTemplates.Render("server/server_list.tmpl", w, pageConfig)
})
router.Get("/servers/new", func(w http.ResponseWriter, r *http.Request) {
user := getUser(r)
if user == nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
pageConfig := &templates.RenderData{
Lang: "en-us",
User: user,
Title: fmt.Sprintf("%s new server", user.Username),
External: map[string]any{},
}
webTemplates.Render("server/new_server.tmpl", w, pageConfig)
})
router.Post("/servers/new", func(w http.ResponseWriter, r *http.Request) {
user := getUser(r)
if user == nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
if !user.Permission.IsRoot() && !user.Permission.Contains(permission.WebCreateServer) {
webTemplates.Render400(w, &templates.RenderData{
Title: "You not have permission to create server",
Lang: "en-us",
PageIsInstall: false,
User: user,
External: map[string]any{
"Error": "you not have permission to create server, contact admin",
},
})
return
}
pageConfig := &templates.RenderData{
Lang: "en-us",
User: user,
Title: fmt.Sprintf("%s new server", user.Username),
External: map[string]any{},
}
if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
pageConfig.External["Error"] = "invalid body"
webTemplates.Render("server/new_server.tmpl", w, pageConfig)
return
}
if err := r.ParseForm(); err != nil {
pageConfig.External["Error"] = err.Error()
webTemplates.Render("server/new_server.tmpl", w, pageConfig)
return
}
switch v := r.Form.Get("server"); v {
case "bedrock", "java", "pocketmine", "spigot", "purpur", "paper", "folia", "velocity":
default:
pageConfig.External["Error"] = fmt.Sprintf("Invalid server type input: %s", v)
webTemplates.Render("server/new_server.tmpl", w, pageConfig)
return
}
var serverType server.ServerType
switch r.Form.Get("server") {
case "bedrock":
serverType = server.Bedrock
case "java":
serverType = server.Java
case "pocketmine":
serverType = server.Pocketmine
case "spigot":
serverType = server.SpigotMC
case "purpur":
serverType = server.PurpurMC
case "paper":
serverType = server.PaperMC
case "folia":
serverType = server.FoliaMC
case "velocity":
serverType = server.VelocityMC
}
serverInfo, err := config.Servers.CreateServer(r.Form.Get("servername"), "latest", serverType, user)
if err != nil {
pageConfig.External["Error"] = fmt.Sprintf("Cannot make new server: %s", err)
webTemplates.Render("server/new_server.tmpl", w, pageConfig)
return
}
// Redirect client to admin page
http.Redirect(w, r, fmt.Sprintf("/servers/%d", serverInfo.ID), http.StatusSeeOther)
})
router.Get("/servers/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
user := getUser(r)
if user == nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
pageConfig := &templates.RenderData{User: user, External: map[string]any{}, Title: "Unknown Server", Lang: "en-us"}
serverID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil {
webTemplates.Render404(w, pageConfig)
return
}
server, err := config.Servers.ByID(serverID)
if err != nil {
pageConfig.External["Error"] = fmt.Sprintf("Server not exist or error: %s", err)
webTemplates.Render400(w, pageConfig)
return
}
// Check is avaible to edit
if !user.Permission.IsRoot() {
haveUser, ok := server.Owners.UserID(user.ID)
if !ok || (!haveUser.Permission.Contains(permission.ServerOwner) && !haveUser.Permission.Contains(permission.ServerEdit|permission.ServerView)) {
pageConfig.External["Error"] = "Not have permission to access this server"
webTemplates.Render404(w, pageConfig)
return
}
}
pageConfig.External["Server"] = server
webTemplates.Render("server/server.tmpl", w, pageConfig)
})
// Global 404 page error
router.NotFound(func(w http.ResponseWriter, r *http.Request) {
user := getUser(r)
webTemplates.Render404(w, &templates.RenderData{
Title: "Page not found",
Lang: "en-us",
User: user,
External: map[string]any{
"ErrorMsg": "Page request not found",
},
})
})
router.MethodNotAllowed(router.NotFoundHandler())
// Catch panic and set context with user info
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
user := getUser(r)
if err := recover(); err != nil {
webTemplates.Render5xx(w, &templates.RenderData{
Title: "Internal error",
Lang: "en-us",
User: user,
External: map[string]any{
"ErrorMsg": fmt.Errorf("backend error: %s", err).Error(),
},
})
}
}()
if cookie, err := r.Cookie(CookieName); err == nil {
exist, userID, err := config.Cookie.Cookie(cookie.Value)
if err != nil {
webTemplates.Render5xx(w, &templates.RenderData{
Title: "Internal error",
Lang: "en-us",
External: map[string]any{
"ErrorMsg": fmt.Errorf("backend error: %s", err).Error(),
},
})
return
} else if exist {
user, err := config.User.ByID(userID)
if err != nil {
webTemplates.Render5xx(w, &templates.RenderData{
Title: "Internal error",
Lang: "en-us",
External: map[string]any{
"ErrorMsg": fmt.Errorf("backend error: %s", err).Error(),
},
})
return
}
r = r.WithContext(context.WithValue(r.Context(), ContextUser, user))
}
}
// Caller api router handler
router.ServeHTTP(w, r)
}), nil
}