package main import ( "errors" "fmt" "log/slog" "net/http" "strconv" "git.ctdo.de/ctdo/machinelock-manager/config" "git.ctdo.de/ctdo/machinelock-manager/cookies" "git.ctdo.de/ctdo/machinelock-manager/db" "git.ctdo.de/ctdo/machinelock-manager/oidc" "git.ctdo.de/ctdo/machinelock-manager/templates" "github.com/gin-gonic/gin" "gorm.io/gorm" ) func main() { router := gin.New() cookies.Init(router) authGrp := router.Group("/") authGrp.Use(cookies.CheckAuth) { authGrp.GET("/", index) // overview machine / token connection authGrp.GET("/machines", machines) // machine listing authGrp.POST("/tokens", createToken) // create a new rfid token authGrp.POST("/machines", createMachine) // create a new machine authGrp.POST("/tokens/:id", editToken) // edit a token (only nick and allowed machines) authGrp.POST("/machines/:id", editMachine) // edit a machine authGrp.GET("/machines/:id/delete", deleteMachine) // deletes a machine authGrp.GET("/tokens/:id/delete", deleteToken) // deletes a token } router.GET("/bootstrap.min.css", bs) // static bootstrap file serving router.GET("/auth", oidc.HandleRedirect) // oidc redirect handler router.GET("/auth/success", oidc.HandleOauthCallback) // oauth flow callback router.GET("/machine/:slug", GetMachine) // machine api to get allowed tokens router.GET("/logout", logout) router.GET("/forbidden", forbidden) router.Run("[::]:" + strconv.Itoa(config.Port)) } func forbidden(c *gin.Context) { templates.Templates.ExecuteTemplate(c.Writer, "forbidden", gin.H{}) } func logout(c *gin.Context) { cookies.Logout(c) c.Redirect(http.StatusFound, "/") } func bs(c *gin.Context) { c.FileFromFS("templates/bootstrap.min.css", http.FS(templates.Res)) } func returnInternalError(c *gin.Context, err error) { slog.Error("an error occured", "error", err) c.String(http.StatusInternalServerError, "text", "Internal Error") } func index(c *gin.Context) { var tokens []db.Token if err := db.DB.Preload("Machines").Find(&tokens).Error; err != nil { returnInternalError(c, err) return } var machines []db.Machine if err := db.DB.Find(&machines).Error; err != nil { returnInternalError(c, err) return } templates.Templates.ExecuteTemplate(c.Writer, "index", gin.H{ "Tokens": tokens, "Machines": machines, }) } func machines(c *gin.Context) { var machines []db.Machine if err := db.DB.Find(&machines).Error; err != nil { returnInternalError(c, err) return } templates.Templates.ExecuteTemplate(c.Writer, "machines", machines) } func createMachine(c *gin.Context) { var form db.Machine if err := c.ShouldBind(&form); err != nil { returnInternalError(c, err) return } machine := db.Machine{ Name: form.Name, Slug: form.Slug, } if err := db.DB.Create(&machine).Error; err != nil { returnInternalError(c, err) return } c.Redirect(http.StatusFound, "/machines") } func editMachine(c *gin.Context) { var machine db.Machine if err := db.DB.Where("id = ?", c.Param("id")).First(&machine).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { c.String(http.StatusNotFound, "gibts nich") return } returnInternalError(c, err) return } var form db.Machine if err := c.ShouldBind(&form); err != nil { returnInternalError(c, err) return } machine.Name = form.Name machine.Slug = form.Slug db.DB.Save(&machine) c.Redirect(http.StatusFound, "/machines") } func deleteMachine(c *gin.Context) { if err := db.DB.Where("id = ?", c.Param("id")).Delete(db.Machine{}).Error; err != nil { returnInternalError(c, err) return } c.Redirect(http.StatusFound, "/machines") } func deleteToken(c *gin.Context) { if err := db.DB.Where("id = ?", c.Param("id")).Delete(db.Token{}).Error; err != nil { returnInternalError(c, err) return } c.Redirect(http.StatusFound, "/") } func createToken(c *gin.Context) { var form db.Token if err := c.ShouldBind(&form); err != nil { returnInternalError(c, err) return } token := db.Token{ Nick: form.Nick, ID: form.ID, } if err := db.DB.Create(&token).Error; err != nil { returnInternalError(c, err) return } c.Redirect(http.StatusFound, "/") } func editToken(c *gin.Context) { var token db.Token if err := db.DB.Where("id = ?", c.Param("id")).First(&token).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { c.String(http.StatusNotFound, "gibts nich") return } returnInternalError(c, err) return } var form struct { Nick string `form:"nick"` Machine map[string]string `form:"machine"` } if err := c.ShouldBind(&form); err != nil { returnInternalError(c, err) return } form.Machine = c.PostFormMap("machine") db.DB.Model(&token).Association("Machines").Clear() token.Nick = form.Nick token.Machines = make([]*db.Machine, 0) for id := range form.Machine { var m db.Machine if err := db.DB.Where("id = ?", id).First(&m).Error; err == nil { token.Machines = append(token.Machines, &m) } else { slog.Error(err.Error()) } } if err := db.DB.Omit("Machines.*").Save(&token).Error; err != nil { slog.Error(err.Error()) } c.Redirect(http.StatusFound, "/") } func GetMachine(c *gin.Context) { if config.MachineToken == "" { c.JSON(http.StatusServiceUnavailable, gin.H{ "error": "no machine auth token is set, skipping response for security reasons", }) return } if c.Request.Header.Get("Authorization") == fmt.Sprintf("Bearer %s", config.MachineToken) { var machine db.Machine if err := db.DB.Where("slug = ?", c.Param("slug")).Preload("Tokens").First(&machine).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusNotFound, gin.H{ "error": "not found", }) return } slog.Error(err.Error()) c.JSON(http.StatusInternalServerError, gin.H{ "error": "internal error", }) return } allowedTokens := make([]string, 0) for _, t := range machine.Tokens { allowedTokens = append(allowedTokens, t.ID) } c.JSON(http.StatusOK, allowedTokens) } else { c.JSON(http.StatusUnauthorized, gin.H{ "error": "unauthorized", }) } }