machinelock-manager/main.go
2024-12-27 23:47:52 +01:00

223 lines
6.1 KiB
Go

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",
})
}
}