feat: included server

This commit is contained in:
henne 2024-12-27 23:47:52 +01:00
parent 907b7a5b34
commit 9b05ca26f3
20 changed files with 733 additions and 235 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
machinelock-*
.DS_Store
start.sh
db.sqlite

View file

@ -1,11 +1,19 @@
# Machine Lock Manager
Kleines Stück Software um die Machinelocks einfacher zu managen.
Server für die Maschinen Locks. Aktuell wird im CTDO die gruppe admin benötigt um zugriff auf die laufende software zu erhalten.
Nutzung:
## Flags
Entweder als CLI parameter anzugeben, oder aber auch als environment variable, dann in CAPS und mit \_ anstatt -.
```bash
./machinelock-darwin-arm --token="VAULTTOKEN"
--machine-token token den die maschinen nutzen um sich an der api zu authentifizieren
--port port für den webserver
--cookie-secret secret für den cookie store, wenn leer wird einer random generiert bei jedem start
--oauth-client-id oauth client id obviously
--oauth-client-secret und das secret..
--oauth-endpoint der endpunkt vom oauth
--base-url und die base url wo das tool erreichbar ist, wird benutzt um den richtigen pfad für die oauth return url zu generieren
--db db connect string (with sqlite it will be the path to the sqlite file, with psql it will be the dsn string)
--db-type type of db, currently supported is sqlite and psql
```
Anschließend dann auf http://localhost:3000 gehen.

View file

@ -1,21 +0,0 @@
package client
import (
"log"
"time"
"github.com/hashicorp/vault-client-go"
)
var Client *vault.Client
func Init(token string) {
var err error
Client, err = vault.New(vault.WithAddress("https://vault.ctdo.de"), vault.WithRequestTimeout(30*time.Second))
if err != nil {
log.Fatal(err)
}
if err := Client.SetToken(token); err != nil {
log.Fatal(err)
}
}

47
config/main.go Normal file
View file

@ -0,0 +1,47 @@
package config
import (
"strings"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
)
var (
Port int
DbType string
Db string
CookieSecret string
MachineToken string
OauthEndpoint string
OauthClientID string
OauthClientSecret string
BaseUrl string
)
func init() {
flag.String("machine-token", "", "Token with wich machines will authenticate")
flag.Int("port", 3000, "Port to listen on")
flag.String("db-type", "sqlite", "Database type: either sqlite or psql")
flag.String("db", "db.sqlite", "Connect string for db")
flag.String("cookie-secret", "", "Secret for the cookie store, leave empty for random secret")
flag.String("oauth-client-id", "", "Oauth2 Client ID")
flag.String("oauth-client-secret", "", "Oauth2 Client Secret")
flag.String("oauth-endpoint", "", "Authentik Endpoint URL")
flag.String("base-url", "http://localhost:3000", "Base URL where this will be hosted")
_ = viper.BindPFlags(flag.CommandLine)
flag.Parse()
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.AutomaticEnv()
Port = viper.GetInt("port")
CookieSecret = viper.GetString("cookie-secret")
MachineToken = viper.GetString("machine-token")
OauthClientID = viper.GetString("oauth-client-id")
OauthClientSecret = viper.GetString("oauth-client-secret")
OauthEndpoint = viper.GetString("oauth-endpoint")
BaseUrl = viper.GetString("base-url")
Db = viper.GetString("db")
DbType = viper.GetString("db-type")
}

61
cookies/main.go Normal file
View file

@ -0,0 +1,61 @@
package cookies
import (
"net/http"
"git.ctdo.de/ctdo/machinelock-manager/config"
"git.ctdo.de/ctdo/machinelock-manager/templates"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
uuid "github.com/satori/go.uuid"
)
func Init(r *gin.Engine) {
var secret []byte
if config.CookieSecret != "" {
secret = []byte(config.CookieSecret)
} else {
secret = uuid.NewV4().Bytes()
}
store := cookie.NewStore(secret)
session := sessions.Sessions("machinelock", store)
r.Use(session)
}
func SetAuth(c *gin.Context, status bool) {
session := sessions.Default(c)
if status {
session.Set("auth", "ok") // logged in and in correct group to have access for this
} else {
session.Set("auth", "nok") // logged in but not in correct group to access this aka forbidden
}
session.Options(sessions.Options{
MaxAge: 3600 * 24 * 7, // 7 tage
Path: "/",
})
session.Save()
}
func Logout(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
}
func CheckAuth(c *gin.Context) {
session := sessions.Default(c)
if session.Get("auth") == nil {
c.Redirect(http.StatusFound, "/auth") // redirect to login
c.Abort()
return
}
if session.Get("auth") == "nok" {
templates.Templates.ExecuteTemplate(c.Writer, "forbidden", gin.H{})
c.Abort()
return
}
if session.Get("auth") != "ok" {
c.Next()
return
}
}

20
db/base.go Normal file
View file

@ -0,0 +1,20 @@
package db
import (
"time"
uuid "github.com/satori/go.uuid"
"gorm.io/gorm"
)
type Base struct {
ID *uuid.UUID `gorm:"type:uuid;primary_key" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (b *Base) BeforeCreate(_ *gorm.DB) error {
id := uuid.NewV4()
b.ID = &id
return nil
}

19
db/machine.go Normal file
View file

@ -0,0 +1,19 @@
package db
import (
uuid "github.com/satori/go.uuid"
"gorm.io/gorm"
)
type Machine struct {
Base
Name string `json:"name" form:"name"`
Slug string `json:"slug" form:"slug"`
Tokens []*Token `json:"tokens" gorm:"many2many:token_machines;"`
}
func (m *Machine) BeforeCreate(_ *gorm.DB) error {
id := uuid.NewV4()
m.ID = &id
return nil
}

42
db/main.go Normal file
View file

@ -0,0 +1,42 @@
package db
import (
"log/slog"
"git.ctdo.de/ctdo/machinelock-manager/config"
"github.com/glebarez/sqlite"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func init() {
var err error
switch config.DbType {
case "sqlite":
DB, err = gorm.Open(sqlite.Open(config.Db), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
case "psql":
DB, err = gorm.Open(postgres.Open(config.Db), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
}
if err != nil {
slog.Error("failed to connect database", "error", err.Error())
panic(0)
}
migrate()
}
func migrate() {
err := DB.AutoMigrate(
&Token{},
&Machine{},
)
if err != nil {
slog.Error(err.Error())
panic(0)
}
}

7
db/token.go Normal file
View file

@ -0,0 +1,7 @@
package db
type Token struct {
ID string `gorm:"primary_key" json:"id" form:"id"`
Nick string `json:"nick" form:"nick"`
Machines []*Machine `json:"machines" gorm:"many2many:token_machines;"`
}

46
go.mod
View file

@ -1,22 +1,38 @@
module example.com/henne
module git.ctdo.de/ctdo/machinelock-manager
go 1.23.3
require github.com/hashicorp/vault-client-go v0.4.3
require (
github.com/coreos/go-oidc/v3 v3.11.0
golang.org/x/oauth2 v0.21.0
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
@ -26,6 +42,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
@ -37,25 +54,28 @@ require (
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
)
require (
github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.10.0
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/glebarez/sqlite v1.11.0
github.com/satori/go.uuid v1.2.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0
golang.org/x/sys v0.20.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/sys v0.22.0 // indirect
gorm.io/driver/postgres v1.5.11
gorm.io/gorm v1.25.12
)

119
go.sum
View file

@ -6,17 +6,34 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@ -25,36 +42,51 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault-client-go v0.4.3 h1:zG7STGVgn/VK6rnZc0k8PGbfv2x/sJExRKHSUg3ljWc=
github.com/hashicorp/vault-client-go v0.4.3/go.mod h1:4tDw7Uhq5XOxS1fO+oMtotHL7j4sB9cp0T7U6m4FzDY=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -64,14 +96,20 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
@ -86,14 +124,13 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
@ -108,31 +145,43 @@ go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTV
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

267
main.go
View file

@ -1,84 +1,223 @@
package main
import (
"context"
"log"
"errors"
"fmt"
"log/slog"
"net/http"
"strings"
"strconv"
"example.com/henne/client"
"example.com/henne/templates"
"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"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
"gorm.io/gorm"
)
func main() {
flag.String("token", "", "Vault Token to use")
_ = viper.BindPFlags(flag.CommandLine)
flag.Parse()
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.AutomaticEnv()
client.Init(viper.GetString("token"))
router := gin.New()
router.GET("/", index)
router.GET("/bootstrap.min.css", bs)
router.POST("/", save)
router.Run("[::]:3000")
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 getUsers() (Users, error) {
users := map[string]User{}
ctx := context.Background()
l, err := client.Client.List(ctx, "/maschinenlock")
if err != nil {
return nil, err
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
}
if keys, ok := l.Data["keys"]; ok {
if list, ok := keys.([]interface{}); ok {
for _, s := range list {
u := User{}
u.Get(s.(string))
users[s.(string)] = u
}
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())
}
}
return users, err
}
func bs(ctx *gin.Context) {
ctx.FileFromFS("templates/bootstrap.min.css", http.FS(templates.Res))
if err := db.DB.Omit("Machines.*").Save(&token).Error; err != nil {
slog.Error(err.Error())
}
c.Redirect(http.StatusFound, "/")
}
func index(ctx *gin.Context) {
users, err := getUsers()
if err != nil {
log.Printf("%v", err)
ctx.String(http.StatusInternalServerError, "text", "Internal Error")
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
}
templates.Templates.ExecuteTemplate(ctx.Writer, "index", users)
}
func save(ctx *gin.Context) {
var form User
if err := ctx.ShouldBind(&form); err != nil {
log.Print(err)
ctx.String(http.StatusInternalServerError, "could not bind data")
return
}
err := form.Save(form.ID)
if err != nil {
log.Printf("%v", err)
ctx.String(http.StatusInternalServerError, "text", "Internal Error")
return
}
users, err := getUsers()
if err != nil {
log.Printf("%v", err)
ctx.String(http.StatusInternalServerError, "text", "Internal Error")
return
}
templates.Templates.ExecuteTemplate(ctx.Writer, "index", users)
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",
})
}
}

83
oidc/main.go Normal file
View file

@ -0,0 +1,83 @@
package oidc
import (
"context"
"log/slog"
"net/http"
"os"
"slices"
"git.ctdo.de/ctdo/machinelock-manager/config"
"git.ctdo.de/ctdo/machinelock-manager/cookies"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin"
"golang.org/x/oauth2"
)
var provider *oidc.Provider
var oauth2Config oauth2.Config
func init() {
ctx := context.Background()
var err error
provider, err = oidc.NewProvider(ctx, config.OauthEndpoint)
if err != nil {
slog.Error(err.Error())
os.Exit(1)
}
oauth2Config = oauth2.Config{
ClientID: config.OauthClientID,
ClientSecret: config.OauthClientSecret,
RedirectURL: config.BaseUrl + "/auth/success",
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
}
func HandleRedirect(ctx *gin.Context) {
ctx.Redirect(http.StatusFound, oauth2Config.AuthCodeURL("machinelock"))
}
func HandleOauthCallback(ctx *gin.Context) {
verifier := provider.Verifier(&oidc.Config{ClientID: oauth2Config.ClientID})
oauth2Token, err := oauth2Config.Exchange(ctx, ctx.Query("code"))
if err != nil {
slog.Error(err.Error())
ctx.String(http.StatusInternalServerError, "internal error check logs")
return
}
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
ctx.String(http.StatusInternalServerError, "internal error check logs")
return
}
// Parse and verify ID Token payload.
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
slog.Error(err.Error())
ctx.String(http.StatusInternalServerError, "internal error check logs")
return
}
// Extract custom claims
var claims struct {
Groups []string `json:"groups"`
}
if err := idToken.Claims(&claims); err != nil {
slog.Error(err.Error())
ctx.String(http.StatusInternalServerError, "internal error check logs")
return
}
if slices.Contains(claims.Groups, "admin") {
cookies.SetAuth(ctx, true)
slog.Info("auth success")
ctx.Redirect(http.StatusFound, "/")
} else {
cookies.SetAuth(ctx, false)
slog.Info("auth failure")
ctx.Redirect(http.StatusFound, "/forbidden")
}
}

View file

@ -4,6 +4,9 @@ import (
"errors"
"strconv"
"time"
"git.ctdo.de/ctdo/machinelock-manager/db"
uuid "github.com/satori/go.uuid"
)
func For(start, end int) <-chan int {
@ -45,5 +48,16 @@ func Dict(values ...interface{}) (map[string]interface{}, error) {
}
return dict, nil
}
func IsAllowed(token db.Token, id *uuid.UUID) bool {
if id == nil {
return false
}
for _, m := range token.Machines {
if m.ID != nil && uuid.Equal(*m.ID, *id) {
return true
}
}
return false
}
var FunctionMap = map[string]interface{}{"For": For, "Price": Price, "Now": Now, "Add": Add, "Dict": Dict}
var FunctionMap = map[string]interface{}{"For": For, "Price": Price, "Now": Now, "Add": Add, "Dict": Dict, "IsAllowed": IsAllowed}

View file

@ -8,7 +8,7 @@ import (
"strings"
"text/template"
"example.com/henne/template_functions"
"git.ctdo.de/ctdo/machinelock-manager/template_functions"
)
var (

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Machine Lock Permissions</title>
<link href="/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<p>Du hast keine Berechtigung diese Seiten zu sehen. Bist du in der "admin" Gruppe?</p>
<a href="/logout">Logout</a>
</body>
</html>

View file

@ -0,0 +1,16 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Machine Locks</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">Start</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/machines">Maschinen</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
</ul>
</div>
</nav>

View file

@ -7,6 +7,7 @@
<link href="/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
{{template "header"}}
<div class="container">
<h1 class="mt-4 mb-3">Machine Lock Permissions</h1>
<table class="table table-striped align-middle">
@ -15,35 +16,41 @@
<th>User</th>
<th style="min-width:120px">RFID ID</th>
<th>Bandsäge</th>
<th>Lasercutter</th>
<th>3D Drucker</th>
<th>Shapeoko</th>
<th>Bohrmaschine</th>
<th>Drehbank</th>
<th>Folienplotter</th>
{{range .Machines}}
<th style="writing-mode:vertical-rl">{{.Name}}</th>
{{end}}
<th></th>
</tr>
</thead>
<tbody>
{{range $key, $element := .}}
<form action="/" method="post">
{{range .Tokens}}
{{$token := .}}
<form action="/tokens/{{.ID}}" method="post">
<tr>
<td><input type="text" class="form-control" value="{{$element.User}}" name="user"></td>
<td>{{$key}}</td>
<td><input type="checkbox" class="form-check-input" name="bandsaege" value="1" {{if eq $element.Bandsaege "1"}}checked{{end}}/></td>
<td><input type="checkbox" class="form-check-input" name="lasercutter" value="1" {{if eq $element.Lasercutter "1" }}checked{{end}} /></td>
<td><input type="checkbox" class="form-check-input" name="dreiddrucker" value="1" {{if eq $element.Dreiddrucker "1" }}checked{{end}} /></td>
<td><input type="checkbox" class="form-check-input" name="shapeoko" value="1" {{if eq $element.Shapeoko "1" }}checked{{end}} /></td>
<td><input type="checkbox" class="form-check-input" name="bohrmaschine" value="1" {{if eq $element.Bohrmaschine "1" }}checked{{end}} /></td>
<td><input type="checkbox" class="form-check-input" name="drehbank" value="1" {{if eq $element.Drehbank "1" }}checked{{end}} /></td>
<td><input type="checkbox" class="form-check-input" name="folienplotter" value="1" {{if eq $element.Folienplotter "1" }}checked{{end}} /></td>
<td><input type="hidden" name="id" value="{{$key}}"/> <input type="submit" class="btn btn-sm btn-success" value="Speichern" /></td>
<td><input type="text" class="form-control" value="{{.Nick}}" name="nick"></td>
<td>{{.ID}}</td>
{{range $.Machines}}
<td><input type="checkbox" class="form-check-input" name="machine[{{.ID}}]" value="1" {{if IsAllowed $token .ID}}checked{{end}}/></td>
{{end}}
<td><input type="submit" class="btn btn-sm btn-success" value="Speichern" /><a href="/tokens/{{.ID}}/delete" class="btn btn-sm btn-danger">Löschen</a></td>
</tr>
</form>
{{end}}
</tbody>
</table>
<h2>Neuer Token</h2>
<form action="/tokens" method="post">
<div class="mb-3">
<label class="form-label">Nick</label>
<input type="text" class="form-control" name="nick" required>
</div>
<div class="mb-3">
<label class="form-label">RFID ID</label>
<input type="text" class="form-control" name="id" pattern="^[A-Z0-9]{2}-[A-Z0-9]{2}-[A-Z0-9]{2}-[A-Z0-9]{2}$">
<div class="form-text">Nur A-Z 0-9 und - zugelassen</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</body>
</html>

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Machine Lock Permissions</title>
<link href="/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
{{template "header"}}
<div class="container">
<h1 class="mt-4 mb-3">Maschinen</h1>
<table class="table table-striped align-middle">
<thead>
<tr>
<th>Name</th>
<th>Slug</th>
<th style="width:180px"></th>
</tr>
</thead>
<tbody>
{{range .}}
<form action="/machines/{{.ID}}" method="post">
<tr>
<td><input type="text" class="form-control" value="{{.Name}}" name="name"></td>
<td><input type="text" class="form-control" value="{{.Slug}}" name="slug"></td>
<td><input type="hidden" name="id" value="{{.ID}}"/> <input type="submit" class="btn btn-sm btn-primary" value="Speichern" /><a href="/machines/{{.ID}}/delete" class="btn btn-sm btn-danger">Löschen</a></td>
</tr>
</form>
{{end}}
</tbody>
</table>
<h2>Neue Maschine</h2>
<form action="/machines" method="post">
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" class="form-control" name="name">
</div>
<div class="mb-3">
<label class="form-label">Slug</label>
<input type="text" class="form-control" name="slug" pattern="[a-z0-9\-]*">
<div class="form-text">Nur a-z 0-9 und - zugelassen</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</body>
</html>

76
type.go
View file

@ -1,76 +0,0 @@
package main
import (
"context"
"log"
"example.com/henne/client"
)
type Users = map[string]User
type User struct {
ID string `json:"id" form:"id"`
User string `json:"user" form:"user"`
Bandsaege string `json:"mlock-bandsaege" form:"bandsaege"`
Bohrmaschine string `json:"mlock-bohrmaschine" form:"bohrmaschine"`
Drehbank string `json:"mlock-drehbank" form:"drehbank"`
Dreiddrucker string `json:"mlock-dreiddrucker" form:"dreiddrucker"`
Folienplotter string `json:"mlock-folienplotter" form:"folienplotter"`
Lasercutter string `json:"mlock-lasercutter" form:"lasercutter"`
Shapeoko string `json:"mlock-shapeoko" form:"shapeoko"`
}
func (u *User) Get(id string) {
ctx := context.Background()
h, err := client.Client.Read(ctx, "/maschinenlock/"+id)
if err != nil {
log.Fatal(err)
}
for i, d := range h.Data {
data := d.(string)
switch i {
case "user":
u.User = data
case "mlock-bandsaege":
u.Bandsaege = parseBool(data)
case "mlock-bohrmaschine":
u.Bohrmaschine = parseBool(data)
case "mlock-drehbank":
u.Drehbank = parseBool(data)
case "mlock-dreiddrucker":
u.Dreiddrucker = parseBool(data)
case "mlock-folienplotter":
u.Folienplotter = parseBool(data)
case "mlock-lasercutter":
u.Lasercutter = parseBool(data)
case "mlock-shapeoko":
u.Shapeoko = parseBool(data)
}
}
}
func parseBool(data string) string {
if data == "1" {
return data
}
return ""
}
func (u *User) Save(id string) error {
ctx := context.Background()
_, err := client.Client.Write(ctx, "/maschinenlock/"+id, map[string]interface{}{
"user": u.User,
"mlock-bandsaege": u.Bandsaege,
"mlock-bohrmaschine": u.Bohrmaschine,
"mlock-drehbank": u.Drehbank,
"mlock-dreiddrucker": u.Dreiddrucker,
"mlock-folienplotter": u.Folienplotter,
"mlock-lasercutter": u.Lasercutter,
"mlock-shapeoko": u.Shapeoko,
})
if err != nil {
return err
}
return nil
}