Compare commits

...

10 commits
v0.0.1 ... main

Author SHA1 Message Date
henne
402f755497 fix: delete associations first when deleting token or machine
All checks were successful
continuous-integration/drone/tag Build is passing
2025-03-08 20:30:12 +01:00
henne
95e9ae41ef feat: prettier index view
All checks were successful
continuous-integration/drone/tag Build is passing
2024-12-30 19:34:23 +01:00
henne
282c3bbc0e chore: build docker image in ci 2024-12-30 19:33:52 +01:00
henne
942fa88394 feat: added dockerfile
All checks were successful
continuous-integration/drone/tag Build is passing
2024-12-27 23:51:56 +01:00
henne
9b05ca26f3 feat: included server 2024-12-27 23:47:52 +01:00
henne
907b7a5b34 chore: drone test #2
All checks were successful
continuous-integration/drone/tag Build is passing
2024-12-16 11:58:45 +01:00
henne
445236538b chore: ci test
Some checks failed
continuous-integration/drone/tag Build is failing
2024-12-16 11:31:48 +01:00
henne
e38d98a326 chore: ci test
Some checks reported errors
continuous-integration/drone/tag Build was killed
2024-12-16 11:29:21 +01:00
henne
7e2b821ce6 chore: add ds store to gitignore 2024-12-16 11:19:43 +01:00
henne
84958f7fbe added readme 2024-12-14 22:36:35 +01:00
22 changed files with 808 additions and 233 deletions

41
.drone.yml Normal file
View file

@ -0,0 +1,41 @@
---
kind: pipeline
type: docker
name: default
trigger:
event:
- tag
steps:
- name: go get
image: golang
commands:
- go get
- mkdir dist
- name: Building for Linux
image: golang
environment:
GOOS: linux
GOARCH: amd64
commands:
- go build -v -o dist/machinelock-manager-linux-amd64
- name: gitea release
image: plugins/gitea-release
settings:
api_key:
from_secret: token
base_url: https://git.ctdo.de
files: dist/*
checksum:
- md5
- sha1
- sha256
- name: build docker image
image: plugins/docker
settings:
username: ctdo
password:
from_secret: dockerhub_password
repo: ctdo/machinelock-manager
tags: latest

3
.gitignore vendored
View file

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

12
Dockerfile Normal file
View file

@ -0,0 +1,12 @@
FROM golang:1.23 as build-env
WORKDIR /go/src/machinelock
ADD . /go/src/machinelock
RUN go get -d -v ./...
RUN CGO_ENABLED=0 go build -v -o /go/bin/machinelock
FROM gcr.io/distroless/base
COPY --from=build-env /go/bin/machinelock /
CMD ["/machinelock"]

19
README.md Normal file
View file

@ -0,0 +1,19 @@
# Machine Lock Manager
Server für die Maschinen Locks. Aktuell wird im CTDO die gruppe admin benötigt um zugriff auf die laufende software zu erhalten.
## Flags
Entweder als CLI parameter anzugeben, oder aber auch als environment variable, dann in CAPS und mit \_ anstatt -.
```bash
--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
```

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=

279
main.go
View file

@ -1,84 +1,235 @@
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) {
var machine db.Machine
if err := db.DB.Where("id = ?", c.Param("id")).First(&machine).Error; err != nil {
returnInternalError(c, err)
return
}
db.DB.Model(&machine).Association("Tokens").Clear()
if err := db.DB.Delete(machine).Error; err != nil {
returnInternalError(c, err)
return
}
c.Redirect(http.StatusFound, "/machines")
}
func deleteToken(c *gin.Context) {
var token db.Token
if err := db.DB.Where("id = ?", c.Param("id")).First(&token).Error; err != nil {
returnInternalError(c, err)
return
}
db.DB.Model(&token).Association("Machines").Clear()
if err := db.DB.Delete(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,43 +7,50 @@
<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">
<thead>
<tr>
<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>
<th></th>
<th class="sticky-top">User</th>
<th style="min-width:120px" class="sticky-top">RFID ID</th>
{{range .Machines}}
<th style="writing-mode:vertical-rl" class="sticky-top">{{.Name}}</th>
{{end}}
<th class="sticky-top"></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}} title="{{.Name}}"/></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
}