So many updates I can't keep up

This commit is contained in:
Melon 2022-10-17 00:13:18 +01:00
parent 739fea417e
commit 2a75a2089b
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
34 changed files with 444 additions and 237 deletions

View File

@ -1,16 +1,16 @@
#!/bin/bash
url='http://localhost:7073/auth/mfa-disable'
url='https://ap.summer.test:8443/v1/marigold/auth/mfa-disable'
accjson='Accept: application/json'
ctjson='Content-Type: application/json'
bear='Authorization: Bearer'
a=$(curl -sS -X POST "$url" -H "$accjson")
a=$(curl -ksS -X POST "$url" -H "$accjson")
access=$(cat "acc.json" | jq '.access_token' -r)
refresh=$(cat "acc.json" | jq '.refresh_token' -r)
read -p "MFA: " mfa
echo
a=$(echo "{\"mfa\":\"$mfa\"}" | curl -vsS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $access" --data @-)
a=$(echo "{\"mfa\":\"$mfa\"}" | curl -kvsS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $access" --data @-)
unset -v mfa
echo "Output: $a"

View File

@ -1,16 +1,16 @@
#!/bin/bash
url='http://localhost:7073/auth/mfa'
url='https://api.summer.test:8443/v1/marigold/auth/mfa'
accjson='Accept: application/json'
ctjson='Content-Type: application/json'
bear='Authorization: Bearer'
a=$(curl -sS -X POST "$url" -H "$accjson")
a=$(curl -ksS -X POST "$url" -H "$accjson")
access=$(cat "acc.json" | jq '.access_token' -r)
refresh=$(cat "acc.json" | jq '.refresh_token' -r)
read -p "Digits: " digits
echo
a=$(jo digits=$digits | curl -sS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $access" --data @-)
a=$(jo digits=$digits | curl -ksS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $access" --data @-)
unset -v digits
qr=$(echo "$a" | jq '.qr' -r)
token=$(echo "$a" | jq '.token' -r)
@ -21,7 +21,7 @@ echo "Saved 'qr.html' with an embedded image"
read -p "MFA: " mfa
echo
a=$(echo "{\"mfa\":\"$mfa\"}" | curl -vsS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $token" --data @-)
a=$(echo "{\"mfa\":\"$mfa\"}" | curl -kvsS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $token" --data @-)
unset -v mfa
echo "Output: $a"

View File

@ -1,9 +1,9 @@
#!/bin/bash
url='http://localhost:7073/auth/login'
url='https://api.summer.test:8443/v1/marigold/auth/login'
accjson='Accept: application/json'
ctjson='Content-Type: application/json'
bear='Authorization: Bearer'
a=$(curl -sS -X POST "$url" -H "$accjson")
a=$(curl -ksS -X POST "$url" -H "$accjson")
next=$(echo "$a" | jq '.next' -r)
token=$(echo "$a" | jq '.token' -r)
@ -13,7 +13,7 @@ if [[ "$next" = "login" ]]; then
echo
unset -v a
a=$(jo email="$em" password="$pw" | curl -sS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $token" --data @-)
a=$(jo email="$em" password="$pw" | curl -ksS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $token" --data @-)
unset -v pw
next=$(echo "$a" | jq '.next' -r)
token=$(echo "$a" | jq '.token' -r)
@ -23,7 +23,7 @@ if [[ "$next" = "login" ]]; then
echo
unset -v a
a=$(echo "{\"code\":\"$ecode\"}" | curl -sS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $token" --data @-)
a=$(echo "{\"code\":\"$ecode\"}" | curl -ksS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $token" --data @-)
unset -v ecode
next=$(echo "$a" | jq '.next' -r)
token=$(echo "$a" | jq '.token' -r)
@ -34,7 +34,7 @@ if [[ "$next" = "login" ]]; then
echo
unset -v a
a=$(echo "{\"mfa\":\"$mfa\"}" | curl -sS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $token" --data @-)
a=$(echo "{\"mfa\":\"$mfa\"}" | curl -ksS -X POST "$url" -H "$accjson" -H "$ctjson" -H "$bear $token" --data @-)
unset -v mfa
next=$(echo "$a" | jq '.next' -r)
token=$(echo "$a" | jq '.token' -r)

View File

@ -1,14 +1,15 @@
package main
import (
"code.mrmelon54.com/melon/summer/cmd/azalea/servers"
"code.mrmelon54.com/melon/summer/pkg/api"
)
type AzaleaConfig struct {
Database string `yaml:"database"`
Listen ListenConfig `yaml:"listen"`
Auth api.AuthConfig `yaml:"apiAuth"`
ApiDomain string `yaml:"apiDomain"`
Database string `yaml:"database"`
Listen ListenConfig `yaml:"listen"`
Auth api.AuthConfig[servers.PermBaseConfig] `yaml:"apiAuth"`
ApiDomain string `yaml:"apiDomain"`
}
type ListenConfig struct {

View File

@ -1,6 +1,8 @@
package quick_cert
import (
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
"code.mrmelon54.com/melon/summer/pkg/utils"
"github.com/stretchr/testify/assert"
"testing"
"xorm.io/xorm"
@ -10,4 +12,38 @@ func TestQuickCertDebug(t *testing.T) {
db, err := xorm.NewEngine("sqlite3", ":memory")
assert.NoError(t, err)
QuickCert(db)
var c []certificate.Certificate
var c2 []certificate.CertificateData
var c3 []certificate.CertificateDomain
assert.NoError(t, db.Find(&c))
assert.NoError(t, db.Find(&c2))
assert.NoError(t, db.Find(&c3))
assert.Equal(t, 1, len(c))
assert.Equal(t, 1, len(c2))
assert.Equal(t, 2, len(c3))
c[0].Id = 0
assert.Equal(t, certificate.Certificate{
Owner: 1,
LetsEncrypt: 1,
Namesilo: 1,
AutoRenew: utils.PBool(false),
Active: utils.PBool(true),
Renewing: utils.PBool(false),
RenewFailed: utils.PBool(false),
}, c[0])
c3[0].DomainId = 0
c3[0].CertId = 0
c3[1].DomainId = 0
c3[1].CertId = 0
assert.Equal(t, certificate.CertificateDomain{
Domain: "summer.test",
Wildcard: utils.PBool(false),
}, c3[0])
assert.Equal(t, certificate.CertificateDomain{
Domain: "summer.test",
Wildcard: utils.PBool(true),
}, c3[1])
}

View File

@ -12,11 +12,17 @@ import (
"xorm.io/xorm"
)
func NewApiServer(listen string, db *xorm.Engine, verify mjwt.Provider, perm api.AuthConfig) *http.Server {
type PermBaseConfig struct {
HttpService string `yaml:"httpService"`
HttpRedirect string `yaml:"httpRedirect"`
ApiRoute string `yaml:"apiRoute"`
}
func NewApiServer(listen string, db *xorm.Engine, verify mjwt.Provider, perm api.AuthConfig[PermBaseConfig]) *http.Server {
router := mux.NewRouter()
api.HandleCrudDatabase[web.HttpService](router, db, nil, api.AuthPermCheck(verify, perm.Perm["httpService"], perm.SudoPerm), "/http-services", "/http-service/{id}")
api.HandleCrudDatabase[web.HttpRedirect](router, db, nil, api.AuthPermCheck(verify, perm.Perm["httpRedirect"], perm.SudoPerm), "/http-redirects", "/http-redirect/{id}")
api.HandleCrudDatabase[web.ApiRoute](router, db, nil, api.AuthPermCheck(verify, perm.Perm["apiRoute"], perm.SudoPerm), "/api-routes", "/api-route/{id}")
api.HandleCrudDatabase[web.HttpService](router, db, nil, nil, api.AuthPermCheck(verify, perm.PermBase.HttpService, perm.SudoPerm), "/http-services", "/http-service/{id}")
api.HandleCrudDatabase[web.HttpRedirect](router, db, nil, nil, api.AuthPermCheck(verify, perm.PermBase.HttpRedirect, perm.SudoPerm), "/http-redirects", "/http-redirect/{id}")
api.HandleCrudDatabase[web.ApiRoute](router, db, nil, nil, api.AuthPermCheck(verify, perm.PermBase.ApiRoute, perm.SudoPerm), "/api-routes", "/api-route/{id}")
s := &http.Server{
Addr: listen,

View File

@ -8,6 +8,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/mrmelon54/favicon"
"github.com/sethvargo/go-limiter/httplimit"
@ -71,6 +72,23 @@ func NewHttpsServer(listen string, db *xorm.Engine, reverseProxy *httputil.Rever
func setupHttpsRouter(router *mux.Router, db *xorm.Engine, reverseProxy *httputil.ReverseProxy, apiDomain string) {
faviconColor := favicon.NewColor()
cors := handlers.CORS(
handlers.AllowCredentials(),
handlers.AllowedOrigins([]string{"*"}),
handlers.AllowedHeaders([]string{"Content-Type", "Authorization"}),
handlers.AllowedMethods([]string{
http.MethodGet,
http.MethodHead,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
http.MethodConnect,
http.MethodOptions,
http.MethodTrace,
}),
)
router.Use(setupRateLimiter())
router.HandleFunc("/favicon.{ext:(?:ico|png|svg)}", utils.CachedPage("1", func(rw http.ResponseWriter, req *http.Request) {
if !web.CheckDomainInDb(db, req.Host) {
@ -107,7 +125,7 @@ func setupHttpsRouter(router *mux.Router, db *xorm.Engine, reverseProxy *httputi
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write(b)
}))
router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
router.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if !web.CheckDomainInDb(db, req.Host) {
http.Error(rw, http.StatusText(http.StatusTeapot), http.StatusTeapot)
return
@ -115,7 +133,9 @@ func setupHttpsRouter(router *mux.Router, db *xorm.Engine, reverseProxy *httputi
if hostDomain, hostPort, ok := utils.SplitDomainPort(req.Host, 443); ok {
if hostDomain == apiDomain {
handleApiRoute(rw, req, db, reverseProxy)
cors(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
handleApiRoute(rw, req, db, reverseProxy)
})).ServeHTTP(rw, req)
return
}
if handleHttpService(rw, req, db, hostDomain, reverseProxy) {
@ -160,9 +180,6 @@ func handleApiRoute(rw http.ResponseWriter, req *http.Request, db *xorm.Engine,
req.Close = true
req.RequestURI = "/" + pPath
req.URL.Path = "/" + pPath
// Allow all origins to access the API
rw.Header().Set("Access-Control-Allow-Origin", "*")
reverseProxy.ServeHTTP(rw, req)
return
}

View File

@ -151,3 +151,7 @@ func (c CertDbProvider) Delete(id uint64) error {
_, err := c.db.Where("id = ?", id).Update(&certificate.Certificate{Active: utils.PBool(false)})
return err
}
func (c CertDbProvider) Id(id string) (uint64, error) {
return api.DefaultIdMap(id)
}

View File

@ -1,7 +0,0 @@
package api
type AuthConfig struct {
Public string `yaml:"public"`
Perm map[string]uint32 `yaml:"perm"`
SudoPerm []uint32 `yaml:"sudoPerm"`
}

View File

@ -9,11 +9,16 @@ import (
"xorm.io/xorm"
)
func NewApiServer(listen string, db *xorm.Engine, renewal *renewal.Renewal, verify mjwt.Provider, perm AuthConfig) *http.Server {
type PermBaseConfig struct {
Certificate string `yaml:"certificate"`
CertificatePair string `yaml:"certificatePair"`
}
func NewApiServer(listen string, db *xorm.Engine, renewal *renewal.Renewal, verify mjwt.Provider, perm api.AuthConfig[PermBaseConfig]) *http.Server {
router := mux.NewRouter()
handleGetPublic(router, db, "/domain-certificate/{domain}")
api.HandleCrud[CertDbItem](router, NewCertDbProvider(db, renewal), api.AuthPermCheck(verify, perm.Perm["certificate"], perm.SudoPerm), "/certificates", "/certificate/{id}")
handleGetPrivate(router, db, api.AuthPermCheck(verify, perm.Perm["certificatePair"], perm.SudoPerm), "/certificate-pair/{domain}")
api.HandleCrud[CertDbItem](router, NewCertDbProvider(db, renewal), "/certificates", "/certificate/{id}")
handleGetPrivate(router, db, api.AuthPermCheck(verify, perm.PermBase.CertificatePair, perm.SudoPerm), "/certificate-pair/{domain}")
return api.RunApiServer(listen, router)
}

View File

@ -1,13 +1,14 @@
package main
import (
"code.mrmelon54.com/melon/summer/cmd/buttercup/api"
api2 "code.mrmelon54.com/melon/summer/cmd/buttercup/api"
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/renewal"
)
type ButtercupConfig struct {
Database string `yaml:"database"`
Listen string `yaml:"listen"`
Renewal renewal.Config `yaml:"renewal"`
ApiAuth api.AuthConfig `yaml:"apiAuth"`
Database string `yaml:"database"`
Listen string `yaml:"listen"`
Renewal renewal.Config `yaml:"renewal"`
ApiAuth api.AuthConfig[api2.PermBaseConfig] `yaml:"apiAuth"`
}

View File

@ -1,13 +1,21 @@
package main
import "code.mrmelon54.com/melon/summer/pkg/mjwt"
type MarigoldConfig struct {
Database string `yaml:"database"`
Listen string `yaml:"listen"`
Auth AuthConfig `yaml:"auth"`
Database string `yaml:"database"`
Listen string `yaml:"listen"`
ApiAuth ApiAuthConfig `yaml:"apiAuth"`
}
type AuthConfig struct {
Issuer string `yaml:"issuer"`
Key string `yaml:"key"`
Public string `yaml:"public"`
type ApiAuthConfig struct {
Issuer string `yaml:"issuer"`
Key string `yaml:"key"`
Public string `yaml:"public"`
PermBase PermBaseConfig `yaml:"permBase"`
SudoPerm mjwt.PermStorage `yaml:"sudoPerm"`
}
type PermBaseConfig struct {
User string `yaml:"user"`
}

View File

@ -1,11 +1,13 @@
package main
import (
quickUser "code.mrmelon54.com/melon/summer/cmd/marigold/quick-user"
accountServer "code.mrmelon54.com/melon/summer/pkg/account-server"
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/cli"
loginChecker "code.mrmelon54.com/melon/summer/pkg/login-checker"
"code.mrmelon54.com/melon/summer/pkg/mjwt"
"code.mrmelon54.com/melon/summer/pkg/mjwt/auth"
oauthServer "code.mrmelon54.com/melon/summer/pkg/oauth-server"
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
"code.mrmelon54.com/melon/summer/pkg/tables/user"
@ -51,11 +53,12 @@ type Marigold struct {
func (marigold *Marigold) Init(runner *cli.Runner) {
marigold.runner = runner
utils.Check("[Marigold.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
quickUser.QuickUser(runner.Database)
var privKey *rsa.PrivateKey
file, err := os.ReadFile(marigold.conf.Auth.Key)
file, err := os.ReadFile(marigold.conf.ApiAuth.Key)
if os.IsNotExist(err) {
privKey = generateNewKeys(marigold.conf.Auth)
privKey = generateNewKeys(marigold.conf.ApiAuth)
file = pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privKey),
@ -66,12 +69,21 @@ func (marigold *Marigold) Init(runner *cli.Runner) {
privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
utils.Check("[Marigold.Init()] Failed to parse token signing key", err)
}
marigold.signer = mjwt.NewMJwtSigner(marigold.conf.Auth.Issuer, privKey)
marigold.signer = mjwt.NewMJwtSigner(marigold.conf.ApiAuth.Issuer, privKey)
marigold.checkLogin = loginChecker.NewLoginChecker(runner.Database, marigold.signer)
router := mux.NewRouter()
marigold.auth = accountServer.NewAccountServer(router.PathPrefix("/auth").Subrouter(), runner.Database, marigold.signer)
marigold.oauth = oauthServer.NewOAuthServer(router.PathPrefix("/oauth").Subrouter(), runner.Database, marigold.signer)
api.HandleCrudDatabase[user.User](router, runner.Database, nil, func(id string) (uint64, error) {
if id == "@me" {
// Verify as mfa flow token
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](marigold.signer, token)
if err != nil {
return err
}
}
}, api.AuthPermCheck(marigold.signer, marigold.conf.ApiAuth.PermBase.User, marigold.conf.ApiAuth.SudoPerm), "/users", "/user/{id}")
marigold.server = api.RunApiServer(marigold.conf.Listen, router)
}
@ -80,7 +92,7 @@ func (marigold *Marigold) Destroy() {
_ = marigold.server.Close()
}
func generateNewKeys(auth AuthConfig) *rsa.PrivateKey {
func generateNewKeys(auth ApiAuthConfig) *rsa.PrivateKey {
fmt.Println("[Marigold.Init()] Generating new RSA private key")
key, err := rsa.GenerateKey(rand.New(rand.NewSource(time.Now().UnixNano())), 4096)
utils.Check("[Marigold.Init()] Failed to generate new RSA private key", err)

View File

@ -0,0 +1,42 @@
//go:build DEBUG
package quick_user
import (
accountServer "code.mrmelon54.com/melon/summer/pkg/account-server"
"code.mrmelon54.com/melon/summer/pkg/tables/user"
"code.mrmelon54.com/melon/summer/pkg/utils"
"log"
"time"
"xorm.io/xorm"
)
func QuickUser(db *xorm.Engine) {
log.Println("[QuickUser()] Generating development admin user for testing marigold")
count, err := db.Count(&user.User{})
utils.Check("[QuickUser()] Failed to count users:", err)
if count != 0 {
return
}
// This password is meant to be visible for debugging purposes
pw, err := accountServer.HashPassword("Password123!")
utils.Check("[QuickUser()] Failed to hash password:", err)
_, err = db.Insert(&user.User{
Email: "john@example.com",
Username: "john",
Password: pw,
Visibility: utils.PByte(1),
Gender: utils.PString("male"),
Pronouns: utils.PByte(1),
Birthday: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
DisplayName: "John Doe",
LastOnline: time.Now(),
MfaActive: utils.PBool(false),
EmailVerified: utils.PBool(true),
EnableRefresh: utils.PBool(true),
})
utils.Check("[QuickUser()] Failed to add user:", err)
}

View File

@ -0,0 +1,7 @@
//go:build !DEBUG
package quick_user
import "xorm.io/xorm"
func QuickUser(_ *xorm.Engine) {}

View File

@ -3,7 +3,12 @@ package main
import "code.mrmelon54.com/melon/summer/pkg/api"
type RoseConfig struct {
Database string `yaml:"database"`
Listen string `yaml:"listen"`
Auth api.AuthConfig `yaml:"apiAuth"`
Database string `yaml:"database"`
Listen string `yaml:"listen"`
ApiAuth api.AuthConfig[PermBaseConfig] `yaml:"apiAuth"`
}
type PermBaseConfig struct {
TcpManager string `yaml:"tcpManager"`
UdpManager string `yaml:"udpManager"`
}

View File

@ -35,12 +35,14 @@ func (rose *Rose) Init(runner *cli.Runner) {
rose.runner = runner
utils.Check("[Rose.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
var err error
rose.verify, err = mjwt.NewMJwtVerifierFromFile(rose.conf.Auth.Public)
rose.verify, err = mjwt.NewMJwtVerifierFromFile(rose.conf.ApiAuth.Public)
utils.Check("[Rose.Init()] Failed to load verifier public key", err)
router := mux.NewRouter()
rose.tcpApi = tcp.NewTcpManager(router.PathPrefix("/tcp").Subrouter(), runner.Database, rose.verify, rose.conf.Auth)
rose.udpApi = udp.NewUdpManager(router.PathPrefix("/udp").Subrouter(), runner.Database, rose.verify, rose.conf.Auth)
rose.tcpApi = tcp.NewTcpManager(runner.Database)
rose.udpApi = udp.NewUdpManager(runner.Database)
api.HandleCrudDatabase[web.TcpRedirect](router, runner.Database, rose.tcpApi, nil, api.AuthPermCheck(rose.verify, rose.conf.ApiAuth.PermBase.TcpManager, rose.conf.ApiAuth.SudoPerm), "/tcp", "/tcp/{id}")
api.HandleCrudDatabase[web.UdpRedirect](router, runner.Database, rose.udpApi, nil, api.AuthPermCheck(rose.verify, rose.conf.ApiAuth.PermBase.UdpManager, rose.conf.ApiAuth.SudoPerm), "/udp", "/udp/{id}")
rose.server = api.RunApiServer(rose.conf.Listen, router)
}

9
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/mrmelon54/favicon v0.0.0-20220830075604-72b3eafe69b9
github.com/pkg/errors v0.9.1
@ -32,6 +33,7 @@ require (
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 // indirect
github.com/goccy/go-json v0.8.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
@ -52,14 +54,7 @@ require (
github.com/tdewolff/canvas v0.0.0-20220224205349-b6bbc6a34308 // indirect
github.com/tdewolff/minify/v2 v2.10.0 // indirect
github.com/tdewolff/parse/v2 v2.5.27 // indirect
github.com/tidwall/btree v1.4.2 // indirect
github.com/tidwall/buntdb v1.2.10 // indirect
github.com/tidwall/gjson v1.14.3 // indirect
github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/wcharczuk/go-chart/v2 v2.1.0 // indirect
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect

30
go.sum
View File

@ -34,6 +34,7 @@ github.com/adrg/xdg v0.3.0/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/ajstarks/svgo v0.0.0-20210923152817-c3b6e2f0c527/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
@ -43,6 +44,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -102,7 +104,10 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
@ -113,6 +118,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego/v4 v4.8.0 h1:XienkuT6ZKHe0DE/LXeGP4ZY+ft+7ZMlqtiJ7XJs2pI=
@ -210,13 +216,17 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@ -243,6 +253,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@ -302,6 +313,7 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
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/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
@ -314,6 +326,7 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -368,6 +381,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/mrmelon54/favicon v0.0.0-20220830075604-72b3eafe69b9 h1:37A6XlY1rNqbnD2ksPtHSHIUobNaFsBI1zDbuH/saFw=
github.com/mrmelon54/favicon v0.0.0-20220830075604-72b3eafe69b9/go.mod h1:11VXnD2+bF1vuLcw4K/2vCTz3atY0kf0kD5iA3Hop9s=
@ -473,6 +487,7 @@ github.com/sec51/qrcode v0.0.0-20160126144534-b7779abbcaf1 h1:CI9zS8HvMiibvXM/F3
github.com/sec51/qrcode v0.0.0-20160126144534-b7779abbcaf1/go.mod h1:uPm44Rj3uXSSOvmKmoeRuAUNUgwH2JHW5KIzqFFS/j4=
github.com/sec51/twofactor v1.0.0 h1:1BTbzPhyMyB0YvcWxgNxEkI7WDNsBLvR+z699YWGMC8=
github.com/sec51/twofactor v1.0.0/go.mod h1:CjtKwpvQSs9SYzLUsRH7gML+TgKeIofT8uxoy7RTLQI=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sethvargo/go-limiter v0.7.2 h1:FgC4N7RMpV5gMrUdda15FaFTkQ/L4fEqM7seXMs4oO8=
github.com/sethvargo/go-limiter v0.7.2/go.mod h1:C0kbSFbiriE5k2FFOe18M1YZbAR2Fiwf72uGu0CXCcU=
@ -485,6 +500,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
@ -522,17 +539,14 @@ github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/btree v1.4.2 h1:PpkaieETJMUxYNADsjgtNRcERX7mGc/GP2zp/r5FM3g=
github.com/tidwall/btree v1.4.2/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
github.com/tidwall/buntdb v1.2.10 h1:U/ebfkmYPBnyiNZIirUiWFcxA/mgzjbKlyPynFsPtyM=
github.com/tidwall/buntdb v1.2.10/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
@ -540,27 +554,33 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=
github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

View File

@ -6,7 +6,7 @@ import (
)
var (
passwordPattern = regexp.MustCompile("^{8,32}$")
passwordPattern = regexp.MustCompile("^.{8,32}$")
passwordHasDigit = regexp.MustCompile("\\d")
passwordHasSymbol = regexp.MustCompile("\\W")

View File

@ -44,12 +44,12 @@ func NewAccountServer(router *mux.Router, db *xorm.Engine, signer mjwt.Provider)
flows: make(map[string]*LoginFlow),
mFlow: &sync.RWMutex{},
}
router.HandleFunc("/login", s.handlePostLogin)
router.HandleFunc("/refresh", s.handlePostRefresh)
router.HandleFunc("/password-reset", s.handlePostPasswordReset)
router.HandleFunc("/password-email", s.handlePostPasswordEmail)
router.HandleFunc("/mfa", s.handleMfaCreate)
router.HandleFunc("/mfa-disable", s.handleMfaDisable)
router.HandleFunc("/login", s.handlePostLogin).Methods(http.MethodPost)
router.HandleFunc("/refresh", s.handlePostRefresh).Methods(http.MethodPost)
router.HandleFunc("/password-reset", s.handlePostPasswordReset).Methods(http.MethodPost)
router.HandleFunc("/password-email", s.handlePostPasswordEmail).Methods(http.MethodPost)
router.HandleFunc("/mfa", s.handleMfaCreate).Methods(http.MethodPost)
router.HandleFunc("/mfa-disable", s.handleMfaDisable).Methods(http.MethodPost)
return s
}
@ -64,15 +64,15 @@ func (a *AccountServer) LoadMfaTotp(u *user.User) (*twofactor.Totp, error) {
func (a *AccountServer) outputUserTokens(rw http.ResponseWriter, u *user.User) {
// Get perms list
var perms []user.LinkUserPerm
err := a.db.Where("user_id = ?", u.Id).Find(&perms)
var perms []user.ExtendUserPerm
err := a.db.Where("user_id = ?", u.Id).Join("INNER", &user.Perm{}, "link_user_perm.perm_id = perm.id").Find(&perms)
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Internal Server Error") {
return
}
storage := mjwt.EmptyPermStorage()
for i := range perms {
storage.Set(uint32(perms[i].PermId))
storage := mjwt.NewPermStorage()
for _, i := range perms {
storage.Set(i.Perm.Name)
}
// Generate access and refresh tokens
@ -118,7 +118,15 @@ func (a *AccountServer) handlePostLogin(rw http.ResponseWriter, req *http.Reques
// Verify mjwt
_, b, err := mjwt.ExtractClaims[flow.LoginFlowClaims](a.signer, token)
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusInternalServerError, "Token Not Valid") {
if err != nil {
// Verify as access token
_, z, err := mjwt.ExtractClaims[auth.AccessTokenClaims](a.signer, token)
if api.GenericErrorMsg[AccountServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
return
}
// The request was sent to verify an existing access token and was successful
a.outputFlowStep(rw, z.ID, "done", z.Claims.UserId)
return
}
@ -145,12 +153,12 @@ func (a *AccountServer) handlePostLogin(rw http.ResponseWriter, req *http.Reques
case "login":
// Check email matches pattern
if !emailPattern.MatchString(in.Email) {
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid input")
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid email input")
return
}
// Check password matches pattern
if !ValidatePassword(in.Password) {
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid input")
api.GenericErrorMsg[AccountServer](rw, api.ErrParamWrongType, http.StatusBadRequest, "Invalid password input")
return
}
@ -444,7 +452,7 @@ func (a *AccountServer) handleGetUserInfo(rw http.ResponseWriter, req *http.Requ
return
}
z := user.CheckSpecificPerms(a.db, b.Claims.Perms, []string{"super:user", "user:info", "user:email", "user:profile"})
z := user.CheckSpecificPerms(b.Claims.Perms, []string{"super:user", "user:info", "user:email", "user:profile"})
if z[0] {
z[1] = true
z[2] = true

View File

@ -1,8 +1,6 @@
package api
import (
"code.mrmelon54.com/melon/summer/pkg/mjwt"
"code.mrmelon54.com/melon/summer/pkg/mjwt/auth"
"code.mrmelon54.com/melon/summer/pkg/utils"
"encoding/json"
"errors"
@ -68,33 +66,6 @@ func GenericErrorMsg[T any](rw http.ResponseWriter, err error, code int, msg str
return true
}
func AuthPermCheck(verify mjwt.Provider, perm uint32, sudoPerm []uint32) func(req *http.Request) error {
return func(req *http.Request) error {
token := utils.GetBearerToken(req)
if token == "" {
return jwt.ErrTokenMalformed
}
// Verify as mfa flow token
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](verify, token)
if err != nil {
return err
}
// Check perms
perms := b.Claims.Perms
if perms.Get(perm) {
return nil
}
for _, j := range sudoPerm {
if perms.Get(j) {
return nil
}
}
return jwt.ErrTokenInvalidClaims
}
}
func RunApiServer(listen string, router *mux.Router) *http.Server {
s := &http.Server{
Addr: listen,

View File

@ -1,7 +1,9 @@
package api
type AuthConfig struct {
Public string `yaml:"public"`
Perm map[string]uint32 `yaml:"perm"`
SudoPerm []uint32 `yaml:"sudoPerm"`
import "code.mrmelon54.com/melon/summer/pkg/mjwt"
type AuthConfig[T any] struct {
Public string `yaml:"public"`
SudoPerm mjwt.PermStorage `yaml:"sudoPerm"`
PermBase T `yaml:"permBase"`
}

View File

@ -1,8 +1,9 @@
package api
import (
"code.mrmelon54.com/melon/summer/pkg/mjwt"
"github.com/gorilla/mux"
"net/http"
"strconv"
"xorm.io/xorm"
)
@ -23,6 +24,7 @@ type crudDatabaseMapper[T CrudProviderDatabase[T]] struct {
db *xorm.Engine
mapped T
detect CrudDetector[T]
idMap func(id string) (uint64, error)
}
func (c crudDatabaseMapper[T]) Find() ([]T, error) {
@ -72,7 +74,18 @@ func (c crudDatabaseMapper[T]) Delete(id uint64) error {
return err
}
func HandleCrudDatabase[T CrudProviderDatabase[T]](router *mux.Router, engine *xorm.Engine, detector CrudDetector[T], checkAuth func(req *http.Request) error, findPath, itemPath string) {
m := crudDatabaseMapper[T]{db: engine, detect: detector}
HandleCrud[T](router, m, checkAuth, findPath, itemPath)
func (c crudDatabaseMapper[T]) Id(id string) (uint64, error) {
if c.idMap != nil {
return c.idMap(id)
}
return DefaultIdMap(id)
}
func DefaultIdMap(id string) (uint64, error) {
return strconv.ParseUint(id, 10, 64)
}
func HandleCrudDatabase[T CrudProviderDatabase[T]](router *mux.Router, engine *xorm.Engine, detector CrudDetector[T], idMap func(id string) (uint64, error), verify mjwt.Provider, findPath, itemPath string) {
m := crudDatabaseMapper[T]{db: engine, detect: detector, idMap: idMap}
HandleCrud[T](router, m, findPath, itemPath)
}

View File

@ -1,11 +1,14 @@
package api
import (
"code.mrmelon54.com/melon/summer/pkg/mjwt"
"code.mrmelon54.com/melon/summer/pkg/mjwt/auth"
"code.mrmelon54.com/melon/summer/pkg/utils"
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt/v4"
"github.com/gorilla/mux"
"net/http"
"strconv"
)
type CrudProvider[T any] interface {
@ -15,34 +18,52 @@ type CrudProvider[T any] interface {
Put(id uint64, item T) error
Patch(id uint64, item T) error
Delete(id uint64) error
Id(id string, req *http.Request, claims auth.AccessTokenClaims) (uint64, error)
}
type crudHandler[T any] struct {
provider CrudProvider[T]
verify mjwt.Provider
permBase string
sudoPerm mjwt.PermStorage
}
func HandleCrud[T any](router *mux.Router, provider CrudProvider[T], checkAuth func(req *http.Request) error, findPath, itemPath string) {
z := &crudHandler[T]{provider: provider}
h := func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
fmt.Println("Running CRUD auth check")
err := checkAuth(req)
if GenericErrorMsg[T](rw, err, http.StatusForbidden, "Out of Scope") {
return
}
handler.ServeHTTP(rw, req)
})
func HandleCrud[T any](router *mux.Router, provider CrudProvider[T], verify mjwt.Provider, permBase string, sudoPerm mjwt.PermStorage, findPath, itemPath string) {
z := &crudHandler[T]{provider: provider, verify: verify, permBase: permBase, sudoPerm: sudoPerm}
router.HandleFunc(findPath, z.handleFind).Methods(http.MethodGet)
router.HandleFunc(findPath, z.handleAdd).Methods(http.MethodPost)
router.HandleFunc(itemPath, z.handleGet).Methods(http.MethodGet)
router.HandleFunc(itemPath, z.handlePut).Methods(http.MethodPut)
router.HandleFunc(itemPath, z.handlePatch).Methods(http.MethodPatch)
router.HandleFunc(itemPath, z.handleDelete).Methods(http.MethodDelete)
}
func (c *crudHandler[T]) checkAuth(req *http.Request, permSuffix string) (claims auth.AccessTokenClaims, err error) {
token := utils.GetBearerToken(req)
if token == "" {
return claims, jwt.ErrTokenMalformed
}
router.Handle(findPath, h(http.HandlerFunc(z.handleFind))).Methods(http.MethodGet)
router.Handle(findPath, h(http.HandlerFunc(z.handleAdd))).Methods(http.MethodPost)
router.Handle(itemPath, h(http.HandlerFunc(z.handleGet))).Methods(http.MethodGet)
router.Handle(itemPath, h(http.HandlerFunc(z.handlePut))).Methods(http.MethodPut)
router.Handle(itemPath, h(http.HandlerFunc(z.handlePatch))).Methods(http.MethodPatch)
router.Handle(itemPath, h(http.HandlerFunc(z.handleDelete))).Methods(http.MethodDelete)
// Verify as mfa flow token
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](c.verify, token)
if err != nil {
return claims, err
}
// Check perms
claims = b.Claims
if claims.Perms.Has(c.permBase+":"+permSuffix) || claims.Perms.OneOf(c.sudoPerm) {
return claims, nil
}
return claims, jwt.ErrTokenInvalidClaims
}
func (h *crudHandler[T]) handleFind(rw http.ResponseWriter, req *http.Request) {
a, err := h.provider.Find()
func (c *crudHandler[T]) handleFind(rw http.ResponseWriter, req *http.Request) {
_, err := c.checkAuth(req, "find")
if GenericErrorMsg[T](rw, err, http.StatusForbidden, "403 Forbidden") {
return
}
a, err := c.provider.Find()
if GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Find error") {
return
}
@ -54,13 +75,17 @@ func (h *crudHandler[T]) handleFind(rw http.ResponseWriter, req *http.Request) {
}
}
func (h *crudHandler[T]) handleAdd(rw http.ResponseWriter, req *http.Request) {
func (c *crudHandler[T]) handleAdd(rw http.ResponseWriter, req *http.Request) {
_, err := c.checkAuth(req, "add")
if GenericErrorMsg[T](rw, err, http.StatusForbidden, "403 Forbidden") {
return
}
var t T
err := json.NewDecoder(req.Body).Decode(&t)
err = json.NewDecoder(req.Body).Decode(&t)
if GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
return
}
t, err = h.provider.Add(t)
t, err = c.provider.Add(t)
if GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Add error") {
return
}
@ -72,19 +97,23 @@ func (h *crudHandler[T]) handleAdd(rw http.ResponseWriter, req *http.Request) {
}
}
func (h *crudHandler[T]) handleGet(rw http.ResponseWriter, req *http.Request) {
func (c *crudHandler[T]) handleGet(rw http.ResponseWriter, req *http.Request) {
claims, err := c.checkAuth(req, "get")
if GenericErrorMsg[T](rw, err, http.StatusForbidden, "403 Forbidden") {
return
}
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
GenericErrorMsg[T](rw, ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
id, err := strconv.ParseUint(s, 10, 64)
id, err := c.provider.Id(s, req, claims)
if err != nil {
GenericErrorMsg[T](rw, fmt.Errorf("parameter error: %w", err), http.StatusBadRequest, "Parameter error")
return
}
t, found, err := h.provider.Get(id)
t, found, err := c.provider.Get(id)
if GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Get error") {
return
}
@ -100,14 +129,18 @@ func (h *crudHandler[T]) handleGet(rw http.ResponseWriter, req *http.Request) {
}
}
func (h *crudHandler[T]) handlePut(rw http.ResponseWriter, req *http.Request) {
func (c *crudHandler[T]) handlePut(rw http.ResponseWriter, req *http.Request) {
claims, err := c.checkAuth(req, "put")
if GenericErrorMsg[T](rw, err, http.StatusForbidden, "403 Forbidden") {
return
}
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
GenericErrorMsg[T](rw, ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
id, err := strconv.ParseUint(s, 10, 64)
id, err := c.provider.Id(s, req, claims)
if err != nil {
GenericErrorMsg[T](rw, fmt.Errorf("parameter error: %w", err), http.StatusBadRequest, "Parameter error")
return
@ -117,21 +150,25 @@ func (h *crudHandler[T]) handlePut(rw http.ResponseWriter, req *http.Request) {
if GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
return
}
err = h.provider.Put(id, t)
err = c.provider.Put(id, t)
if GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Put error") {
return
}
rw.WriteHeader(http.StatusNoContent)
}
func (h *crudHandler[T]) handlePatch(rw http.ResponseWriter, req *http.Request) {
func (c *crudHandler[T]) handlePatch(rw http.ResponseWriter, req *http.Request) {
claims, err := c.checkAuth(req, "patch")
if GenericErrorMsg[T](rw, err, http.StatusForbidden, "403 Forbidden") {
return
}
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
GenericErrorMsg[T](rw, ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
id, err := strconv.ParseUint(s, 10, 64)
id, err := c.provider.Id(s, req, claims)
if err != nil {
GenericErrorMsg[T](rw, fmt.Errorf("parameter error: %w", err), http.StatusBadRequest, "Parameter error")
return
@ -141,26 +178,30 @@ func (h *crudHandler[T]) handlePatch(rw http.ResponseWriter, req *http.Request)
if GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
return
}
err = h.provider.Patch(id, t)
err = c.provider.Patch(id, t)
if GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Patch error") {
return
}
rw.WriteHeader(http.StatusNoContent)
}
func (h *crudHandler[T]) handleDelete(rw http.ResponseWriter, req *http.Request) {
func (c *crudHandler[T]) handleDelete(rw http.ResponseWriter, req *http.Request) {
claims, err := c.checkAuth(req, "delete")
if GenericErrorMsg[T](rw, err, http.StatusForbidden, "403 Forbidden") {
return
}
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
GenericErrorMsg[T](rw, ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
id, err := strconv.ParseUint(s, 10, 64)
id, err := c.provider.Id(s, req, claims)
if err != nil {
GenericErrorMsg[T](rw, fmt.Errorf("parameter error: %w", err), http.StatusBadRequest, "Parameter error")
return
}
err = h.provider.Delete(id)
err = c.provider.Delete(id)
if GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Delete error") {
return
}

View File

@ -2,7 +2,6 @@ package auth
import (
"code.mrmelon54.com/melon/summer/pkg/mjwt"
"encoding/json"
"time"
)
@ -21,23 +20,3 @@ func CreateAccessToken(p mjwt.Provider, sub, id string, userId uint64, perms mjw
Perms: perms,
})
}
func (a AccessTokenClaims) MarshalJSON() ([]byte, error) {
b := make(map[string]any)
b["uid"] = a.UserId
b["per"] = a.Perms
return json.Marshal(b)
}
func (a AccessTokenClaims) UnmarshalJSON(bytes []byte) error {
type z struct {
Uid uint64
Per []uint32
}
var b z
if err := json.Unmarshal(bytes, &b); err != nil {
return err
}
a.UserId = b.Uid
a.Perms = mjwt.ParsePermStorage(b.Per)
return nil
}

View File

@ -2,7 +2,6 @@ package flow
import (
"code.mrmelon54.com/melon/summer/pkg/mjwt"
"encoding/json"
"time"
)
@ -21,23 +20,3 @@ func CreateOAuthFlowToken(p mjwt.Provider, sub, id string, loginId uint64, scope
Scopes: scopes,
})
}
func (o OAuthFlowClaims) MarshalJSON() ([]byte, error) {
a := make(map[string]any)
a["lid"] = o.LoginId
a["sco"] = o.Scopes.Dump()
return json.Marshal(a)
}
func (o OAuthFlowClaims) UnmarshalJSON(bytes []byte) error {
type z struct {
Lid uint64
Sco []uint32
}
var a z
if err := json.Unmarshal(bytes, &a); err != nil {
return err
}
o.LoginId = a.Lid
o.Scopes = mjwt.ParsePermStorage(a.Sco)
return nil
}

View File

@ -1,10 +1,79 @@
package mjwt
import (
bitStorage "code.mrmelon54.com/melon/summer/pkg/bit-storage"
"encoding/json"
"gopkg.in/yaml.v3"
"sort"
)
type PermStorage bitStorage.BitStorage[uint32]
type PermStorage map[string]struct{}
func EmptyPermStorage() PermStorage { return bitStorage.EmptyInfiniteU32() }
func ParsePermStorage(v []uint32) PermStorage { return bitStorage.ParseInfiniteU32(v) }
func NewPermStorage() PermStorage { return make(PermStorage) }
func ParsePermStorage(a []string) PermStorage {
p := NewPermStorage()
p.prepare(a)
return p
}
func (p *PermStorage) Set(perm string) {
(*p)[perm] = struct{}{}
}
func (p *PermStorage) Clear(perm string) {
delete(*p, perm)
}
func (p *PermStorage) Has(perm string) bool {
_, ok := (*p)[perm]
return ok
}
func (p *PermStorage) OneOf(o PermStorage) bool {
for i, _ := range o {
if o.Has(i) {
return true
}
}
return false
}
func (p *PermStorage) dump() []string {
var a []string
for i := range *p {
a = append(a, i)
}
sort.Strings(a)
return a
}
func (p *PermStorage) prepare(a []string) {
for _, i := range a {
p.Set(i)
}
}
func (p *PermStorage) MarshalJSON() ([]byte, error) { return json.Marshal(p.dump()) }
func (p *PermStorage) UnmarshalJSON(bytes []byte) error {
*p = make(PermStorage)
var a []string
err := json.Unmarshal(bytes, &a)
if err != nil {
return err
}
p.prepare(a)
return nil
}
func (p *PermStorage) MarshalYAML() (interface{}, error) { return yaml.Marshal(p.dump()) }
func (p *PermStorage) UnmarshalYAML(value *yaml.Node) error {
*p = make(PermStorage)
var a []string
err := value.Decode(&a)
if err != nil {
return err
}
p.prepare(a)
return nil
}

View File

@ -14,7 +14,6 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
"net/http"
"strconv"
"strings"
"time"
"xorm.io/xorm"
@ -120,12 +119,7 @@ func (o *OAuthServer) handleOAuthAuthorize(rw http.ResponseWriter, req *http.Req
rScopes := strings.FieldsFunc(in.Scope, func(r rune) bool {
return strings.ContainsRune(" +,", r)
})
rScopeUint := make([]uint32, 0)
for i := range rScopes {
a, _ := strconv.ParseUint(rScopes[i], 10, 32)
rScopeUint[i] = uint32(a)
}
scopes := mjwt.ParsePermStorage(rScopeUint)
scopes := mjwt.ParsePermStorage(rScopes)
flowToken, err := flow.CreateOAuthFlowToken(o.signer, fmt.Sprint(in.ClientId), uuid.NewString(), b.Claims.UserId, scopes, time.Minute*10)
if api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Failed to sign token") {

View File

@ -3,8 +3,6 @@ package user
import (
"code.mrmelon54.com/melon/summer/pkg/mjwt"
"code.mrmelon54.com/melon/summer/pkg/utils"
"log"
"xorm.io/xorm"
)
type Perm struct {
@ -31,23 +29,10 @@ func (p Perm) ClearForNew() Perm {
return p
}
func CheckSpecificPerms(db *xorm.Engine, storage mjwt.PermStorage, v []string) []bool {
func CheckSpecificPerms(storage mjwt.PermStorage, v []string) []bool {
z := make([]bool, len(v))
y := make(map[string]int)
for i := range v {
y[v[i]] = i
}
var k []Perm
err := db.In("key", v).Find(&k)
if err != nil {
log.Println("[CheckSpecificPerms()] Database error:", err)
return z
}
for i := range k {
j := y[k[i].Key]
z[j] = storage.Get(uint32(k[i].Id))
z[i] = storage.Has(v[i])
}
return z
}

View File

@ -4,3 +4,8 @@ type LinkUserPerm struct {
UserId uint64 `json:"user_id"`
PermId uint64 `json:"perm_id"`
}
type ExtendUserPerm struct {
User User `xorm:"extends"`
Perm Perm `xorm:"extends"`
}

View File

@ -57,4 +57,19 @@ type User struct {
EmailVerified *bool `json:"email_verified"`
EmailToken string `json:"-"`
EnableRefresh *bool `json:"-"`
Enabled *bool `json:"enabled"`
}
func (u User) GetId() uint64 {
return u.Id
}
func (u User) SetEnabled(v bool) User {
u.Enabled = utils.PBool(v)
return u
}
func (u User) ClearForNew() User {
u.Id = 0
return u
}

View File

@ -1,8 +1,6 @@
package tcp
import (
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/mjwt"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"fmt"
"github.com/gorilla/mux"
@ -19,14 +17,12 @@ type Manager struct {
db *xorm.Engine
}
func NewTcpManager(router *mux.Router, db *xorm.Engine, verify mjwt.Provider, perm api.AuthConfig) *Manager {
func NewTcpManager(db *xorm.Engine) *Manager {
m := &Manager{
mutex: &sync.RWMutex{},
servers: make(map[uint64]*Proxy),
router: router,
db: db,
}
api.HandleCrudDatabase[web.TcpRedirect](router, db, m, api.AuthPermCheck(verify, perm.Perm["tcpManager"], perm.SudoPerm), "/", "/{id}")
return m
}

View File

@ -1,8 +1,6 @@
package udp
import (
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/mjwt"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"fmt"
"github.com/gorilla/mux"
@ -19,14 +17,12 @@ type Manager struct {
db *xorm.Engine
}
func NewUdpManager(router *mux.Router, db *xorm.Engine, verify mjwt.Provider, perm api.AuthConfig) *Manager {
func NewUdpManager(db *xorm.Engine) *Manager {
m := &Manager{
mutex: &sync.RWMutex{},
servers: make(map[uint64]*Proxy),
router: router,
db: db,
}
api.HandleCrudDatabase[web.UdpRedirect](router, db, m, api.AuthPermCheck(verify, perm.Perm["udpManager"], perm.SudoPerm), "/", "/{id}")
return m
}