Format go files with tab
ci/woodpecker/push/build Pipeline was successful Details

This commit is contained in:
Melon 2022-12-06 20:21:58 +00:00
parent 3c3de0674e
commit ad3fa1e246
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
30 changed files with 1954 additions and 1954 deletions

View File

@ -9,10 +9,10 @@ charset = utf-8
end_of_line = lf
insert_final_newline = true
# CSS
# GO
[*.go]
indent_size = 2
indent_style = space
indent_style = tab
# HTML
[*.{htm,html}]

View File

@ -1,80 +1,80 @@
package main
import (
quickCert "code.mrmelon54.com/melon/summer/cmd/azalea/quick-cert"
"code.mrmelon54.com/melon/summer/cmd/azalea/servers"
"code.mrmelon54.com/melon/summer/pkg/cli"
"code.mrmelon54.com/melon/summer/pkg/proxy"
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
"code.mrmelon54.com/melon/summer/pkg/tables/renewal"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"errors"
"github.com/mrmelon54/mjwt"
"net/http"
"net/http/httputil"
"strconv"
"strings"
quickCert "code.mrmelon54.com/melon/summer/cmd/azalea/quick-cert"
"code.mrmelon54.com/melon/summer/cmd/azalea/servers"
"code.mrmelon54.com/melon/summer/pkg/cli"
"code.mrmelon54.com/melon/summer/pkg/proxy"
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
"code.mrmelon54.com/melon/summer/pkg/tables/renewal"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"errors"
"github.com/mrmelon54/mjwt"
"net/http"
"net/http/httputil"
"strconv"
"strings"
)
var usedTables = []any{
&web.Domain{},
&web.ApiRoute{},
&web.HttpHeader{},
&web.HttpService{},
&web.HttpRedirect{},
&renewal.AcmeContent{},
&certificate.Certificate{},
&certificate.CertificateData{},
&certificate.CertificateDomain{},
&web.Domain{},
&web.ApiRoute{},
&web.HttpHeader{},
&web.HttpService{},
&web.HttpRedirect{},
&renewal.AcmeContent{},
&certificate.Certificate{},
&certificate.CertificateData{},
&certificate.CertificateDomain{},
}
func main() {
conf := cli.DecodeConfig[AzaleaConfig]("azalea")
cli.Run("azalea", conf.Database, &Azalea{conf: conf})
conf := cli.DecodeConfig[AzaleaConfig]("azalea")
cli.Run("azalea", conf.Database, &Azalea{conf: conf})
}
type Azalea struct {
conf AzaleaConfig
runner *cli.Runner
reverseProxy *httputil.ReverseProxy
httpServer *http.Server
httpsServer *http.Server
apiServer *http.Server
verify mjwt.Provider
conf AzaleaConfig
runner *cli.Runner
reverseProxy *httputil.ReverseProxy
httpServer *http.Server
httpsServer *http.Server
apiServer *http.Server
verify mjwt.Provider
}
func (azalea *Azalea) Init(runner *cli.Runner) {
azalea.runner = runner
utils.Check("[Azalea.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
quickCert.QuickCert(runner.Database)
azalea.runner = runner
utils.Check("[Azalea.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
quickCert.QuickCert(runner.Database)
addrPort, err := splitPortCombo(azalea.conf.Listen.Https)
utils.Check("[Azalea.Init()] Failed to parse HTTPs port", err)
azalea.verify, err = mjwt.NewMJwtVerifierFromFile(azalea.conf.Auth.Public)
utils.Check("[Azalea.Init()] Failed to load verifier public key", err)
addrPort, err := splitPortCombo(azalea.conf.Listen.Https)
utils.Check("[Azalea.Init()] Failed to parse HTTPs port", err)
azalea.verify, err = mjwt.NewMJwtVerifierFromFile(azalea.conf.Auth.Public)
utils.Check("[Azalea.Init()] Failed to load verifier public key", err)
azalea.reverseProxy = proxy.CreateHybridReverseProxy()
azalea.httpServer = servers.NewHttpServer(azalea.conf.Listen.Http, addrPort, azalea.runner.Database)
azalea.httpsServer = servers.NewHttpsServer(azalea.conf.Listen.Https, azalea.runner.Database, azalea.reverseProxy, azalea.conf.ApiDomain)
azalea.apiServer = servers.NewApiServer(azalea.conf.Listen.Api, azalea.runner.Database, azalea.verify)
azalea.reverseProxy = proxy.CreateHybridReverseProxy()
azalea.httpServer = servers.NewHttpServer(azalea.conf.Listen.Http, addrPort, azalea.runner.Database)
azalea.httpsServer = servers.NewHttpsServer(azalea.conf.Listen.Https, azalea.runner.Database, azalea.reverseProxy, azalea.conf.ApiDomain)
azalea.apiServer = servers.NewApiServer(azalea.conf.Listen.Api, azalea.runner.Database, azalea.verify)
}
func (azalea *Azalea) Destroy() {
_ = azalea.httpServer.Close()
_ = azalea.httpsServer.Close()
_ = azalea.apiServer.Close()
_ = azalea.httpServer.Close()
_ = azalea.httpsServer.Close()
_ = azalea.apiServer.Close()
}
func splitPortCombo(a string) (uint16, error) {
i := strings.LastIndexByte(a, ':')
if i == -1 {
return 0, errors.New("not an ip:port")
}
port := a[i+1:]
parseInt, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return 0, err
}
return uint16(parseInt), err
i := strings.LastIndexByte(a, ':')
if i == -1 {
return 0, errors.New("not an ip:port")
}
port := a[i+1:]
parseInt, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return 0, err
}
return uint16(parseInt), err
}

View File

@ -1,33 +1,33 @@
package servers
import (
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"log"
"net/http"
"time"
"xorm.io/xorm"
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"log"
"net/http"
"time"
"xorm.io/xorm"
)
func NewApiServer(listen string, db *xorm.Engine, verify mjwt.Provider) *http.Server {
router := mux.NewRouter()
crud.NewCrudHandler[web.HttpService](router, verify, crud.NewSinglePermProvider[web.HttpService]("manage:http-service", crud.NewDatabaseProvider[web.HttpService](db, "id")), "/http-services", "/http-service/{id}")
crud.NewCrudHandler[web.HttpRedirect](router, verify, crud.NewSinglePermProvider[web.HttpRedirect]("manage:http-redirect", crud.NewDatabaseProvider[web.HttpRedirect](db, "id")), "/http-redirects", "/http-redirect/{id}")
crud.NewCrudHandler[web.ApiRoute](router, verify, crud.NewSinglePermProvider[web.ApiRoute]("manage:api-route", crud.NewDatabaseProvider[web.ApiRoute](db, "id")), "/api-routes", "/api-route/{id}")
router := mux.NewRouter()
crud.NewCrudHandler[web.HttpService](router, verify, crud.NewSinglePermProvider[web.HttpService]("manage:http-service", crud.NewDatabaseProvider[web.HttpService](db, "id")), "/http-services", "/http-service/{id}")
crud.NewCrudHandler[web.HttpRedirect](router, verify, crud.NewSinglePermProvider[web.HttpRedirect]("manage:http-redirect", crud.NewDatabaseProvider[web.HttpRedirect](db, "id")), "/http-redirects", "/http-redirect/{id}")
crud.NewCrudHandler[web.ApiRoute](router, verify, crud.NewSinglePermProvider[web.ApiRoute]("manage:api-route", crud.NewDatabaseProvider[web.ApiRoute](db, "id")), "/api-routes", "/api-route/{id}")
s := &http.Server{
Addr: listen,
Handler: router,
ReadTimeout: 1 * time.Minute,
ReadHeaderTimeout: 1 * time.Minute,
WriteTimeout: 1 * time.Minute,
IdleTimeout: 1 * time.Minute,
MaxHeaderBytes: 2500,
}
log.Printf("[API] Starting API server on: '%s'\n", s.Addr)
go utils.RunBackgroundHttp("API", s)
return s
s := &http.Server{
Addr: listen,
Handler: router,
ReadTimeout: 1 * time.Minute,
ReadHeaderTimeout: 1 * time.Minute,
WriteTimeout: 1 * time.Minute,
IdleTimeout: 1 * time.Minute,
MaxHeaderBytes: 2500,
}
log.Printf("[API] Starting API server on: '%s'\n", s.Addr)
go utils.RunBackgroundHttp("API", s)
return s
}

View File

@ -1,250 +1,250 @@
package servers
import (
"code.mrmelon54.com/melon/summer/pkg/proxy"
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"crypto/tls"
"fmt"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/mrmelon54/favicon"
"github.com/sethvargo/go-limiter/httplimit"
"github.com/sethvargo/go-limiter/memorystore"
"log"
"net/http"
"net/http/httputil"
"regexp"
"strconv"
"strings"
"time"
"xorm.io/xorm"
"code.mrmelon54.com/melon/summer/pkg/proxy"
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"crypto/tls"
"fmt"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/mrmelon54/favicon"
"github.com/sethvargo/go-limiter/httplimit"
"github.com/sethvargo/go-limiter/memorystore"
"log"
"net/http"
"net/http/httputil"
"regexp"
"strconv"
"strings"
"time"
"xorm.io/xorm"
)
var apiRoutePattern = regexp.MustCompile("^/v(\\d+)/([a-zA-Z-_]+)/(.+)$")
func NewHttpsServer(listen string, db *xorm.Engine, reverseProxy *httputil.ReverseProxy, apiDomain string) *http.Server {
router := mux.NewRouter()
setupHttpsRouter(router, db, reverseProxy, apiDomain)
router := mux.NewRouter()
setupHttpsRouter(router, db, reverseProxy, apiDomain)
certCache := utils.NewCertCache()
certCache := utils.NewCertCache()
s := &http.Server{
Addr: listen,
Handler: router,
TLSConfig: &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
if !web.CheckDomainInDb(db, info.ServerName) {
return nil, fmt.Errorf("invalid hostname used: '%s'", info.ServerName)
}
s := &http.Server{
Addr: listen,
Handler: router,
TLSConfig: &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
if !web.CheckDomainInDb(db, info.ServerName) {
return nil, fmt.Errorf("invalid hostname used: '%s'", info.ServerName)
}
if certGrab, ok := certCache.Get(info.ServerName); ok {
return certGrab, nil
}
if certGrab, ok := certCache.Get(info.ServerName); ok {
return certGrab, nil
}
// Just grab, save and output the certificate now
cert, err := certificate.GetCertForDomain(db, info.ServerName)
if err != nil {
return nil, err
}
certLeaf, err := cert.GetCertificate()
if err != nil {
return nil, err
}
certCache.Put(info.ServerName, certLeaf)
return certLeaf, nil
},
},
ReadTimeout: 150 * time.Second,
ReadHeaderTimeout: 150 * time.Second,
WriteTimeout: 150 * time.Second,
IdleTimeout: 150 * time.Second,
MaxHeaderBytes: 4096000,
}
s.SetKeepAlivesEnabled(false)
log.Printf("[HTTPS] Starting HTTPS server on: '%s'\n", s.Addr)
go utils.RunBackgroundHttps("HTTPS", s)
return s
// Just grab, save and output the certificate now
cert, err := certificate.GetCertForDomain(db, info.ServerName)
if err != nil {
return nil, err
}
certLeaf, err := cert.GetCertificate()
if err != nil {
return nil, err
}
certCache.Put(info.ServerName, certLeaf)
return certLeaf, nil
},
},
ReadTimeout: 150 * time.Second,
ReadHeaderTimeout: 150 * time.Second,
WriteTimeout: 150 * time.Second,
IdleTimeout: 150 * time.Second,
MaxHeaderBytes: 4096000,
}
s.SetKeepAlivesEnabled(false)
log.Printf("[HTTPS] Starting HTTPS server on: '%s'\n", s.Addr)
go utils.RunBackgroundHttps("HTTPS", s)
return s
}
func setupHttpsRouter(router *mux.Router, db *xorm.Engine, reverseProxy *httputil.ReverseProxy, apiDomain string) {
faviconColor := favicon.NewColor()
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,
}),
)
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) {
http.Error(rw, http.StatusText(http.StatusTeapot), http.StatusTeapot)
return
}
vars := mux.Vars(req)
ext := vars["ext"]
h := rw.Header()
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) {
http.Error(rw, http.StatusText(http.StatusTeapot), http.StatusTeapot)
return
}
vars := mux.Vars(req)
ext := vars["ext"]
h := rw.Header()
favSvg := favicon.NewSvg(req.Host, faviconColor)
favSvg := favicon.NewSvg(req.Host, faviconColor)
var b []byte
var err error
var b []byte
var err error
switch ext {
case "svg":
h.Set("Content-Type", "image/svg+xml")
b, err = favSvg.ProduceSvg()
case "png":
h.Set("Content-Type", "image/png")
b, err = favSvg.ProducePng()
case "ico":
h.Set("Content-Type", "image/x-icon")
b, err = favSvg.ProduceIco()
}
switch ext {
case "svg":
h.Set("Content-Type", "image/svg+xml")
b, err = favSvg.ProduceSvg()
case "png":
h.Set("Content-Type", "image/png")
b, err = favSvg.ProducePng()
case "ico":
h.Set("Content-Type", "image/x-icon")
b, err = favSvg.ProduceIco()
}
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
_, _ = rw.Write([]byte(http.StatusText(http.StatusInternalServerError)))
return
}
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
_, _ = rw.Write([]byte(http.StatusText(http.StatusInternalServerError)))
return
}
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write(b)
}))
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
}
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write(b)
}))
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
}
if hostDomain, hostPort, ok := utils.SplitDomainPort(req.Host, 443); ok {
if hostDomain == apiDomain {
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) {
return
}
if handleHttpRedirect(rw, req, db, hostDomain, hostPort) {
return
}
}
if hostDomain, hostPort, ok := utils.SplitDomainPort(req.Host, 443); ok {
if hostDomain == apiDomain {
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) {
return
}
if handleHttpRedirect(rw, req, db, hostDomain, hostPort) {
return
}
}
log.Println("[HTTPS] Cannot find valid HTTP/WS service")
unknownHttpService(rw)
})
log.Println("[HTTPS] Cannot find valid HTTP/WS service")
unknownHttpService(rw)
})
}
func handleApiRoute(rw http.ResponseWriter, req *http.Request, db *xorm.Engine, reverseProxy *httputil.ReverseProxy) {
sub := apiRoutePattern.FindStringSubmatch(req.URL.Path)
sub := apiRoutePattern.FindStringSubmatch(req.URL.Path)
if len(sub) != 4 {
rw.WriteHeader(http.StatusBadRequest)
_, _ = rw.Write([]byte("Invalid API path\n"))
return
}
if len(sub) != 4 {
rw.WriteHeader(http.StatusBadRequest)
_, _ = rw.Write([]byte("Invalid API path\n"))
return
}
pVersion, err := strconv.ParseUint(sub[1], 10, 64)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
_, _ = rw.Write([]byte("Invalid API version\n"))
return
}
pRoute := sub[2]
pPath := sub[3]
pVersion, err := strconv.ParseUint(sub[1], 10, 64)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
_, _ = rw.Write([]byte("Invalid API version\n"))
return
}
pRoute := sub[2]
pPath := sub[3]
var connect web.ApiRoute
ok, err := db.Where("route = ? and version = ? and enabled = 1", pRoute, pVersion).Limit(1).Get(&connect)
if err != nil {
log.Printf("[HTTPS] Failed to load api route with host address: %s\n", err)
return
}
if ok {
req = proxy.SetReverseProxyHost[proxy.ReverseProxyHeaderVar](req, connect, nil)
req.Close = true
req.RequestURI = ""
req.URL.Path = "/" + pPath
reverseProxy.ServeHTTP(rw, req)
return
}
var connect web.ApiRoute
ok, err := db.Where("route = ? and version = ? and enabled = 1", pRoute, pVersion).Limit(1).Get(&connect)
if err != nil {
log.Printf("[HTTPS] Failed to load api route with host address: %s\n", err)
return
}
if ok {
req = proxy.SetReverseProxyHost[proxy.ReverseProxyHeaderVar](req, connect, nil)
req.Close = true
req.RequestURI = ""
req.URL.Path = "/" + pPath
reverseProxy.ServeHTTP(rw, req)
return
}
rw.WriteHeader(http.StatusBadRequest)
_, _ = rw.Write([]byte("Invalid API route\n"))
rw.WriteHeader(http.StatusBadRequest)
_, _ = rw.Write([]byte("Invalid API route\n"))
}
func handleHttpService(rw http.ResponseWriter, req *http.Request, db *xorm.Engine, hostDomain string, reverseProxy *httputil.ReverseProxy) bool {
var connect web.HttpService
ok, err := db.Where("address = ? and enabled = 1", hostDomain).Limit(1).Get(&connect)
if err != nil {
log.Printf("[HTTPS] Failed to load http service with host address: %s\n", err)
return true
}
if ok {
var updateHeader []web.HttpHeader
err = db.Where("service_id = ?", connect.Id).Find(&updateHeader)
if err != nil {
log.Printf("[HTTPS] Failed to load http service override headers: %s\n", err)
return true
}
var connect web.HttpService
ok, err := db.Where("address = ? and enabled = 1", hostDomain).Limit(1).Get(&connect)
if err != nil {
log.Printf("[HTTPS] Failed to load http service with host address: %s\n", err)
return true
}
if ok {
var updateHeader []web.HttpHeader
err = db.Where("service_id = ?", connect.Id).Find(&updateHeader)
if err != nil {
log.Printf("[HTTPS] Failed to load http service override headers: %s\n", err)
return true
}
req = proxy.SetReverseProxyHost[web.HttpHeader](req, connect, updateHeader)
req.Close = true
req.RequestURI = ""
reverseProxy.ServeHTTP(rw, req)
return true
}
return false
req = proxy.SetReverseProxyHost[web.HttpHeader](req, connect, updateHeader)
req.Close = true
req.RequestURI = ""
reverseProxy.ServeHTTP(rw, req)
return true
}
return false
}
func handleHttpRedirect(rw http.ResponseWriter, req *http.Request, db *xorm.Engine, hostDomain string, hostPort uint16) bool {
var redirects []web.HttpRedirect
err := db.Where("address = ? and enabled = 1", hostDomain).Limit(1).Find(&redirects)
if err != nil {
log.Printf("Failed to load http redirect with host address: %s\n", err)
return true
}
if len(redirects) == 1 {
a := redirects[0].Target
a = strings.ReplaceAll(a, "%domain%", hostDomain)
a = strings.ReplaceAll(a, "%port%", fmt.Sprint(hostPort))
a = strings.ReplaceAll(a, "%host%", req.Host)
http.Redirect(rw, req, "https://"+a, http.StatusTemporaryRedirect)
return true
}
return false
var redirects []web.HttpRedirect
err := db.Where("address = ? and enabled = 1", hostDomain).Limit(1).Find(&redirects)
if err != nil {
log.Printf("Failed to load http redirect with host address: %s\n", err)
return true
}
if len(redirects) == 1 {
a := redirects[0].Target
a = strings.ReplaceAll(a, "%domain%", hostDomain)
a = strings.ReplaceAll(a, "%port%", fmt.Sprint(hostPort))
a = strings.ReplaceAll(a, "%host%", req.Host)
http.Redirect(rw, req, "https://"+a, http.StatusTemporaryRedirect)
return true
}
return false
}
func setupRateLimiter() mux.MiddlewareFunc {
store, err := memorystore.New(&memorystore.Config{
Tokens: 100,
Interval: time.Minute,
})
if err != nil {
log.Fatalln(err)
}
middleware, err := httplimit.NewMiddleware(store, httplimit.IPKeyFunc())
if err != nil {
log.Fatalln(err)
}
return middleware.Handle
store, err := memorystore.New(&memorystore.Config{
Tokens: 100,
Interval: time.Minute,
})
if err != nil {
log.Fatalln(err)
}
middleware, err := httplimit.NewMiddleware(store, httplimit.IPKeyFunc())
if err != nil {
log.Fatalln(err)
}
return middleware.Handle
}
func unknownHttpService(rw http.ResponseWriter) {
rw.WriteHeader(http.StatusTeapot)
_, _ = rw.Write([]byte(http.StatusText(http.StatusTeapot) + "\n"))
rw.WriteHeader(http.StatusTeapot)
_, _ = rw.Write([]byte(http.StatusText(http.StatusTeapot) + "\n"))
}

View File

@ -1,24 +1,24 @@
package api
import (
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/renewal"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"net/http"
"xorm.io/xorm"
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/renewal"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"net/http"
"xorm.io/xorm"
)
type PermBaseConfig struct {
Certificate string `yaml:"certificate"`
CertificatePair string `yaml:"certificatePair"`
Certificate string `yaml:"certificate"`
CertificatePair string `yaml:"certificatePair"`
}
func NewApiServer(listen string, db *xorm.Engine, renewal *renewal.Renewal, verify mjwt.Provider) *http.Server {
router := mux.NewRouter()
handleGetPublic(router, db, "/domain-certificate/{domain}")
crud.NewCrudHandler[CertDbItem](router, verify, crud.NewSinglePermProvider[CertDbItem]("manage:certificate", NewCertDbProvider(db, renewal)), "/certificates", "/certificate/{id}")
crud.NewCrudHandler[CertPrivate](router, verify, crud.NewSinglePermProvider[CertPrivate]("manage:certificate:admin", certPrivateProvider{db: db}), "/certificate-pairs", "/certificate-pair/{domain}")
return api.RunApiServer(listen, router)
router := mux.NewRouter()
handleGetPublic(router, db, "/domain-certificate/{domain}")
crud.NewCrudHandler[CertDbItem](router, verify, crud.NewSinglePermProvider[CertDbItem]("manage:certificate", NewCertDbProvider(db, renewal)), "/certificates", "/certificate/{id}")
crud.NewCrudHandler[CertPrivate](router, verify, crud.NewSinglePermProvider[CertPrivate]("manage:certificate:admin", certPrivateProvider{db: db}), "/certificate-pairs", "/certificate-pair/{domain}")
return api.RunApiServer(listen, router)
}

View File

@ -1,57 +1,57 @@
package main
import (
"code.mrmelon54.com/melon/summer/cmd/buttercup/api"
"code.mrmelon54.com/melon/summer/pkg/cli"
"code.mrmelon54.com/melon/summer/pkg/renewal"
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
renewal2 "code.mrmelon54.com/melon/summer/pkg/tables/renewal"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"github.com/mrmelon54/mjwt"
"net/http"
"sync"
"code.mrmelon54.com/melon/summer/cmd/buttercup/api"
"code.mrmelon54.com/melon/summer/pkg/cli"
"code.mrmelon54.com/melon/summer/pkg/renewal"
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
renewal2 "code.mrmelon54.com/melon/summer/pkg/tables/renewal"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"github.com/mrmelon54/mjwt"
"net/http"
"sync"
)
var usedTables = []any{
&web.Domain{},
&renewal2.AcmeContent{},
&certificate.Certificate{},
&certificate.CertificateData{},
&certificate.CertificateDomain{},
&renewal2.LetsEncryptAccount{},
&renewal2.NamesiloAccount{},
&web.Domain{},
&renewal2.AcmeContent{},
&certificate.Certificate{},
&certificate.CertificateData{},
&certificate.CertificateDomain{},
&renewal2.LetsEncryptAccount{},
&renewal2.NamesiloAccount{},
}
func main() {
conf := cli.DecodeConfig[ButtercupConfig]("buttercup")
cli.Run("buttercup", conf.Database, &Buttercup{conf: conf})
conf := cli.DecodeConfig[ButtercupConfig]("buttercup")
cli.Run("buttercup", conf.Database, &Buttercup{conf: conf})
}
type Buttercup struct {
conf ButtercupConfig
runner *cli.Runner
renewal *renewal.Renewal
renewalWg *sync.WaitGroup
apiServer *http.Server
verify mjwt.Provider
conf ButtercupConfig
runner *cli.Runner
renewal *renewal.Renewal
renewalWg *sync.WaitGroup
apiServer *http.Server
verify mjwt.Provider
}
func (buttercup *Buttercup) Init(runner *cli.Runner) {
buttercup.runner = runner
utils.Check("[Buttercup.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
var err error
buttercup.verify, err = mjwt.NewMJwtVerifierFromFile(buttercup.conf.Auth.Public)
utils.Check("[Buttercup.Init()] Failed to load verifier public key", err)
buttercup.runner = runner
utils.Check("[Buttercup.Init()] Failed to sync tables", runner.Database.Sync(usedTables...))
var err error
buttercup.verify, err = mjwt.NewMJwtVerifierFromFile(buttercup.conf.Auth.Public)
utils.Check("[Buttercup.Init()] Failed to load verifier public key", err)
buttercup.renewalWg = &sync.WaitGroup{}
provider := renewal.NewCustomHTTPProvider(runner.Database)
buttercup.renewal = renewal.NewRenewalService(buttercup.renewalWg, runner.Database, provider, buttercup.conf.Renewal)
buttercup.apiServer = api.NewApiServer(buttercup.conf.Listen, runner.Database, buttercup.renewal, buttercup.verify)
buttercup.renewalWg = &sync.WaitGroup{}
provider := renewal.NewCustomHTTPProvider(runner.Database)
buttercup.renewal = renewal.NewRenewalService(buttercup.renewalWg, runner.Database, provider, buttercup.conf.Renewal)
buttercup.apiServer = api.NewApiServer(buttercup.conf.Listen, runner.Database, buttercup.renewal, buttercup.verify)
}
func (buttercup *Buttercup) Destroy() {
buttercup.renewal.Shutdown()
buttercup.renewalWg.Wait()
_ = buttercup.apiServer.Close()
buttercup.renewal.Shutdown()
buttercup.renewalWg.Wait()
_ = buttercup.apiServer.Close()
}

View File

@ -1,124 +1,124 @@
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/api/crud"
"code.mrmelon54.com/melon/summer/pkg/cli"
loginChecker "code.mrmelon54.com/melon/summer/pkg/login-checker"
"code.mrmelon54.com/melon/summer/pkg/mailer"
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"
"code.mrmelon54.com/melon/summer/pkg/utils"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"github.com/go-oauth2/oauth2/v4/manage"
"github.com/go-oauth2/oauth2/v4/server"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"math/rand"
"net/http"
"os"
"strings"
"time"
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/api/crud"
"code.mrmelon54.com/melon/summer/pkg/cli"
loginChecker "code.mrmelon54.com/melon/summer/pkg/login-checker"
"code.mrmelon54.com/melon/summer/pkg/mailer"
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"
"code.mrmelon54.com/melon/summer/pkg/utils"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"github.com/go-oauth2/oauth2/v4/manage"
"github.com/go-oauth2/oauth2/v4/server"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"math/rand"
"net/http"
"os"
"strings"
"time"
)
var usedTables = []any{
&user.User{},
&user.Perm{},
&user.LinkUserPerm{},
&oauth.AuthNamespace{},
&oauth.Domain{},
&oauth.App{},
&user.User{},
&user.Perm{},
&user.LinkUserPerm{},
&oauth.AuthNamespace{},
&oauth.Domain{},
&oauth.App{},
}
func main() {
conf := cli.DecodeConfig[MarigoldConfig]("marigold")
cli.Run("marigold", conf.Database, &Marigold{conf: conf})
conf := cli.DecodeConfig[MarigoldConfig]("marigold")
cli.Run("marigold", conf.Database, &Marigold{conf: conf})
}
type Marigold struct {
conf MarigoldConfig
runner *cli.Runner
server *http.Server
signer mjwt.Provider
oauthManager *manage.Manager
oauthSrv *server.Server
checkLogin *loginChecker.LoginChecker
auth *accountServer.AccountServer
oauth *oauthServer.OAuthServer
mailer *mailer.Mailer
conf MarigoldConfig
runner *cli.Runner
server *http.Server
signer mjwt.Provider
oauthManager *manage.Manager
oauthSrv *server.Server
checkLogin *loginChecker.LoginChecker
auth *accountServer.AccountServer
oauth *oauthServer.OAuthServer
mailer *mailer.Mailer
}
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)
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)
if os.IsNotExist(err) {
privKey = generateNewKeys(marigold.conf.Auth)
file = pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privKey),
})
} else {
utils.Check("[Marigold.Init()] Failed to read token signing key", err)
block, _ := pem.Decode(file)
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.checkLogin = loginChecker.NewLoginChecker(runner.Database, marigold.signer)
var privKey *rsa.PrivateKey
file, err := os.ReadFile(marigold.conf.Auth.Key)
if os.IsNotExist(err) {
privKey = generateNewKeys(marigold.conf.Auth)
file = pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privKey),
})
} else {
utils.Check("[Marigold.Init()] Failed to read token signing key", err)
block, _ := pem.Decode(file)
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.checkLogin = loginChecker.NewLoginChecker(runner.Database, marigold.signer)
marigold.mailer, _ = mailer.NewMailer(marigold.conf.Smtp)
marigold.mailer, _ = mailer.NewMailer(marigold.conf.Smtp)
router := mux.NewRouter()
marigold.auth = accountServer.NewAccountServer(router.PathPrefix("/auth").Subrouter(), runner.Database, marigold.signer, marigold.mailer)
marigold.oauth = oauthServer.NewOAuthServer(router.PathPrefix("/oauth").Subrouter(), runner.Database, marigold.signer, marigold.conf.Auth.AuthClient)
crud.NewCrudHandler[user.User](router, marigold.signer, accountServer.NewUserProvider(runner.Database), "/users", "/user/{id}")
crud.NewCrudHandler[oauth.App](router, marigold.signer, oauthServer.NewOAuthAppProvider(runner.Database), "/apps", "/app/{id}")
router.HandleFunc("/scopes", func(rw http.ResponseWriter, req *http.Request) {
scopes := strings.Split(req.URL.Query().Get("scope"), " ")
foundScopes := make([]user.Perm, 0)
err = runner.Database.Where("is_scope = ?", true).In("key", scopes).Find(&foundScopes)
if api.GenericErrorMsg[user.Perm](rw, err, http.StatusInternalServerError, "Failed to find scopes in database") {
return
}
_ = json.NewEncoder(rw).Encode(foundScopes)
})
router := mux.NewRouter()
marigold.auth = accountServer.NewAccountServer(router.PathPrefix("/auth").Subrouter(), runner.Database, marigold.signer, marigold.mailer)
marigold.oauth = oauthServer.NewOAuthServer(router.PathPrefix("/oauth").Subrouter(), runner.Database, marigold.signer, marigold.conf.Auth.AuthClient)
crud.NewCrudHandler[user.User](router, marigold.signer, accountServer.NewUserProvider(runner.Database), "/users", "/user/{id}")
crud.NewCrudHandler[oauth.App](router, marigold.signer, oauthServer.NewOAuthAppProvider(runner.Database), "/apps", "/app/{id}")
router.HandleFunc("/scopes", func(rw http.ResponseWriter, req *http.Request) {
scopes := strings.Split(req.URL.Query().Get("scope"), " ")
foundScopes := make([]user.Perm, 0)
err = runner.Database.Where("is_scope = ?", true).In("key", scopes).Find(&foundScopes)
if api.GenericErrorMsg[user.Perm](rw, err, http.StatusInternalServerError, "Failed to find scopes in database") {
return
}
_ = json.NewEncoder(rw).Encode(foundScopes)
})
marigold.server = api.RunApiServer(marigold.conf.Listen, router)
marigold.server = api.RunApiServer(marigold.conf.Listen, router)
}
func (marigold *Marigold) Destroy() {
_ = marigold.server.Close()
_ = marigold.server.Close()
}
func generateNewKeys(auth AuthConfig) *rsa.PrivateKey {
// Generate key
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)
// Generate key
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)
// Create key files
createPriv, err := os.Create(auth.Key)
utils.Check("[Marigold.Init()] Failed to open private key file for writing", err)
createPub, err := os.Create(auth.Public)
utils.Check("[Marigold.Init()] Failed to open public key file for writing", err)
// Create key files
createPriv, err := os.Create(auth.Key)
utils.Check("[Marigold.Init()] Failed to open private key file for writing", err)
createPub, err := os.Create(auth.Public)
utils.Check("[Marigold.Init()] Failed to open public key file for writing", err)
// Encode and write keys
keyBytes := x509.MarshalPKCS1PrivateKey(key)
pubBytes := x509.MarshalPKCS1PublicKey(&key.PublicKey)
err = pem.Encode(createPriv, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
utils.Check("[Marigold.Init()] Failed to encode private key to file", err)
err = pem.Encode(createPub, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: pubBytes})
utils.Check("[Marigold.Init()] Failed to encode public key to file", err)
return key
// Encode and write keys
keyBytes := x509.MarshalPKCS1PrivateKey(key)
pubBytes := x509.MarshalPKCS1PublicKey(&key.PublicKey)
err = pem.Encode(createPriv, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
utils.Check("[Marigold.Init()] Failed to encode private key to file", err)
err = pem.Encode(createPub, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: pubBytes})
utils.Check("[Marigold.Init()] Failed to encode public key to file", err)
return key
}

View File

@ -1,54 +1,54 @@
package main
import (
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/cli"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/tcp"
"code.mrmelon54.com/melon/summer/pkg/udp"
"code.mrmelon54.com/melon/summer/pkg/utils"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"net/http"
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/cli"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/tcp"
"code.mrmelon54.com/melon/summer/pkg/udp"
"code.mrmelon54.com/melon/summer/pkg/utils"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"net/http"
)
var usedTables = []any{
&web.TcpRedirect{},
&web.UdpRedirect{},
&web.TcpRedirect{},
&web.UdpRedirect{},
}
func main() {
conf := cli.DecodeConfig[RoseConfig]("rose")
cli.Run("rose", conf.Database, &Rose{conf: conf})
conf := cli.DecodeConfig[RoseConfig]("rose")
cli.Run("rose", conf.Database, &Rose{conf: conf})
}
type Rose struct {
conf RoseConfig
runner *cli.Runner
server *http.Server
tcpApi *tcp.Manager
udpApi *udp.Manager
verify mjwt.Provider
conf RoseConfig
runner *cli.Runner
server *http.Server
tcpApi *tcp.Manager
udpApi *udp.Manager
verify mjwt.Provider
}
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)
utils.Check("[Rose.Init()] Failed to load verifier public key", err)
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)
utils.Check("[Rose.Init()] Failed to load verifier public key", err)
router := mux.NewRouter()
rose.tcpApi = tcp.NewTcpManager(runner.Database)
rose.udpApi = udp.NewUdpManager(runner.Database)
crud.NewCrudHandler[web.TcpRedirect](router, rose.verify, crud.NewSinglePermProvider[web.TcpRedirect]("manager:tcp", crud.NewDatabaseProvider[web.TcpRedirect](runner.Database, "id")), "/tcp", "/tcp/{id}")
crud.NewCrudHandler[web.UdpRedirect](router, rose.verify, crud.NewSinglePermProvider[web.UdpRedirect]("manager:udp", crud.NewDatabaseProvider[web.UdpRedirect](runner.Database, "id")), "/udp", "/udp/{id}")
rose.server = api.RunApiServer(rose.conf.Listen, router)
router := mux.NewRouter()
rose.tcpApi = tcp.NewTcpManager(runner.Database)
rose.udpApi = udp.NewUdpManager(runner.Database)
crud.NewCrudHandler[web.TcpRedirect](router, rose.verify, crud.NewSinglePermProvider[web.TcpRedirect]("manager:tcp", crud.NewDatabaseProvider[web.TcpRedirect](runner.Database, "id")), "/tcp", "/tcp/{id}")
crud.NewCrudHandler[web.UdpRedirect](router, rose.verify, crud.NewSinglePermProvider[web.UdpRedirect]("manager:udp", crud.NewDatabaseProvider[web.UdpRedirect](runner.Database, "id")), "/udp", "/udp/{id}")
rose.server = api.RunApiServer(rose.conf.Listen, router)
}
func (rose *Rose) Destroy() {
_ = rose.server.Close()
rose.tcpApi.Destroy()
rose.udpApi.Destroy()
_ = rose.server.Close()
rose.tcpApi.Destroy()
rose.udpApi.Destroy()
}

File diff suppressed because it is too large Load Diff

View File

@ -1,83 +1,83 @@
package account_server
import (
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/tables/user"
"fmt"
"time"
"xorm.io/xorm"
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/tables/user"
"fmt"
"time"
"xorm.io/xorm"
)
type userProvider struct {
db *xorm.Engine
dbCrud crud.CheckedProvider[user.User]
db *xorm.Engine
dbCrud crud.CheckedProvider[user.User]
}
func NewUserProvider(db *xorm.Engine) crud.Provider[user.User] {
return &userProvider{db, crud.NewDatabaseProvider[user.User](db, "id")}
return &userProvider{db, crud.NewDatabaseProvider[user.User](db, "id")}
}
func (u userProvider) Find(claims *auth.AccessTokenClaims) ([]user.User, error) {
if !claims.Perms.Has("super:admin") {
return nil, crud.ErrForbidden
}
return u.dbCrud.Find()
if !claims.Perms.Has("super:admin") {
return nil, crud.ErrForbidden
}
return u.dbCrud.Find()
}
func (u userProvider) Add(_ *auth.AccessTokenClaims, _ user.User) (user.User, error) {
// TODO: implement register instead of this
return user.User{}, crud.ErrMethodNotAllowed
// TODO: implement register instead of this
return user.User{}, crud.ErrMethodNotAllowed
}
func (u userProvider) Get(claims *auth.AccessTokenClaims, id string) (user.User, bool, error) {
if claims == nil {
return user.User{}, false, crud.ErrForbidden
}
myId := fmt.Sprint(claims.UserId)
if id == "@me" || id == myId {
return u.dbCrud.Get(myId)
}
if claims.Perms.Has("super:admin") {
return u.dbCrud.Get(id)
}
get, ok, err := u.dbCrud.Get(id)
if ok && err == nil {
if user.ParseVisibility(get.Visibility) == user.ProfileVisibilityPublic {
z := get
z.Email = "melon@mrmelon54.com"
z.Visibility = nil
z.Gender = nil
z.Birthday = time.UnixMilli(-1)
z.LastOnline = time.UnixMilli(-1)
z.EmailVerified = nil
z.Enabled = nil
return get, true, err
}
}
return user.User{}, false, crud.ErrForbidden
if claims == nil {
return user.User{}, false, crud.ErrForbidden
}
myId := fmt.Sprint(claims.UserId)
if id == "@me" || id == myId {
return u.dbCrud.Get(myId)
}
if claims.Perms.Has("super:admin") {
return u.dbCrud.Get(id)
}
get, ok, err := u.dbCrud.Get(id)
if ok && err == nil {
if user.ParseVisibility(get.Visibility) == user.ProfileVisibilityPublic {
z := get
z.Email = "melon@mrmelon54.com"
z.Visibility = nil
z.Gender = nil
z.Birthday = time.UnixMilli(-1)
z.LastOnline = time.UnixMilli(-1)
z.EmailVerified = nil
z.Enabled = nil
return get, true, err
}
}
return user.User{}, false, crud.ErrForbidden
}
func (u userProvider) Put(claims *auth.AccessTokenClaims, id string, data user.User) error {
if claims == nil {
return crud.ErrForbidden
}
myId := fmt.Sprint(claims.UserId)
if id == "@me" || id == myId {
return u.dbCrud.Put(id, data.ClearForPut())
}
if claims.Perms.Has("super:admin") {
return u.dbCrud.Put(id, data.ClearForAdminPut())
}
return crud.ErrForbidden
if claims == nil {
return crud.ErrForbidden
}
myId := fmt.Sprint(claims.UserId)
if id == "@me" || id == myId {
return u.dbCrud.Put(id, data.ClearForPut())
}
if claims.Perms.Has("super:admin") {
return u.dbCrud.Put(id, data.ClearForAdminPut())
}
return crud.ErrForbidden
}
func (u userProvider) Patch(_ *auth.AccessTokenClaims, _ string, _ user.User) error {
// TODO: implement patch
return crud.ErrMethodNotAllowed
// TODO: implement patch
return crud.ErrMethodNotAllowed
}
func (u userProvider) Delete(_ *auth.AccessTokenClaims, _ string) error {
// TODO: implement delete
return crud.ErrMethodNotAllowed
// TODO: implement delete
return crud.ErrMethodNotAllowed
}

View File

@ -1,93 +1,93 @@
package api
import (
"code.mrmelon54.com/melon/summer/pkg/utils"
"code.mrmelon54.com/melon/summer/pkg/utils/debug"
"encoding/json"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
"github.com/gorilla/mux"
"golang.org/x/crypto/bcrypt"
"log"
"net/http"
"runtime"
"time"
"code.mrmelon54.com/melon/summer/pkg/utils"
"code.mrmelon54.com/melon/summer/pkg/utils/debug"
"encoding/json"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
"github.com/gorilla/mux"
"golang.org/x/crypto/bcrypt"
"log"
"net/http"
"runtime"
"time"
)
var (
ErrNoError = errors.New("no error")
ErrParamMissing = errors.New("parameter is missing")
ErrParamWrongType = errors.New("parameter is of the wrong type")
noLogErrors = []error{
ErrNoError,
ErrParamMissing,
ErrParamWrongType,
}
// prevent logging these to stop clutter of the logs
noLogProdErrors = []error{
bcrypt.ErrMismatchedHashAndPassword,
jwt.ErrTokenInvalidClaims,
jwt.ErrTokenMalformed,
jwt.ErrTokenExpired,
}
ErrNoError = errors.New("no error")
ErrParamMissing = errors.New("parameter is missing")
ErrParamWrongType = errors.New("parameter is of the wrong type")
noLogErrors = []error{
ErrNoError,
ErrParamMissing,
ErrParamWrongType,
}
// prevent logging these to stop clutter of the logs
noLogProdErrors = []error{
bcrypt.ErrMismatchedHashAndPassword,
jwt.ErrTokenInvalidClaims,
jwt.ErrTokenMalformed,
jwt.ErrTokenExpired,
}
)
type logErrorOutput struct {
Message string `json:"message"`
LogId string `json:"log_id"`
Message string `json:"message"`
LogId string `json:"log_id"`
}
func GenericErrorMsg[T any](rw http.ResponseWriter, err error, code int, msg string, args ...any) bool {
if err == nil {
return false
}
m := fmt.Sprintf(msg, args...)
u := fmt.Sprintf("LOG_%s", uuid.New())
if err == nil {
return false
}
m := fmt.Sprintf(msg, args...)
u := fmt.Sprintf("LOG_%s", uuid.New())
var noLog bool
for _, i := range noLogErrors {
if errors.Is(err, i) {
noLog = true
}
}
if !debug.IsDebug {
for _, i := range noLogProdErrors {
if errors.Is(err, i) {
noLog = true
}
}
}
if !noLog {
var pr string
_, file, line, ok := runtime.Caller(1)
if ok {
pr = fmt.Sprintf(" (%s:%d)", file, line)
}
log.Printf("[GenericApi::%T] {%s}%s %s: %s\n", *new(T), u, pr, m, err)
}
rw.Header().Set("Content-Type", "application/json")
rw.Header().Set("X-Content-Type-Options", "nosniff")
rw.WriteHeader(code)
_ = json.NewEncoder(rw).Encode(logErrorOutput{
Message: m,
LogId: u,
})
return true
var noLog bool
for _, i := range noLogErrors {
if errors.Is(err, i) {
noLog = true
}
}
if !debug.IsDebug {
for _, i := range noLogProdErrors {
if errors.Is(err, i) {
noLog = true
}
}
}
if !noLog {
var pr string
_, file, line, ok := runtime.Caller(1)
if ok {
pr = fmt.Sprintf(" (%s:%d)", file, line)
}
log.Printf("[GenericApi::%T] {%s}%s %s: %s\n", *new(T), u, pr, m, err)
}
rw.Header().Set("Content-Type", "application/json")
rw.Header().Set("X-Content-Type-Options", "nosniff")
rw.WriteHeader(code)
_ = json.NewEncoder(rw).Encode(logErrorOutput{
Message: m,
LogId: u,
})
return true
}
func RunApiServer(listen string, router *mux.Router) *http.Server {
s := &http.Server{
Addr: listen,
Handler: router,
ReadTimeout: 1 * time.Minute,
ReadHeaderTimeout: 1 * time.Minute,
WriteTimeout: 1 * time.Minute,
IdleTimeout: 1 * time.Minute,
MaxHeaderBytes: 2500,
}
log.Printf("[API] Starting API server on: '%s'\n", s.Addr)
go utils.RunBackgroundHttp("API", s)
return s
s := &http.Server{
Addr: listen,
Handler: router,
ReadTimeout: 1 * time.Minute,
ReadHeaderTimeout: 1 * time.Minute,
WriteTimeout: 1 * time.Minute,
IdleTimeout: 1 * time.Minute,
MaxHeaderBytes: 2500,
}
log.Printf("[API] Starting API server on: '%s'\n", s.Addr)
go utils.RunBackgroundHttp("API", s)
return s
}

View File

@ -1,56 +1,56 @@
package crud
import (
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
)
type FunctionProvider[T any] struct {
InnerFind fFind[T]
InnerAdd fAdd[T]
InnerGet fGet[T]
InnerPut fPut[T]
InnerPatch fPatch[T]
InnerDelete fDelete
InnerFind fFind[T]
InnerAdd fAdd[T]
InnerGet fGet[T]
InnerPut fPut[T]
InnerPatch fPatch[T]
InnerDelete fDelete
}
func (f FunctionProvider[T]) Find(claims *auth.AccessTokenClaims) ([]T, error) {
if f.InnerFind == nil {
return nil, ErrMethodNotAllowed
}
return f.InnerFind(claims)
if f.InnerFind == nil {
return nil, ErrMethodNotAllowed
}
return f.InnerFind(claims)
}
func (f FunctionProvider[T]) Add(claims *auth.AccessTokenClaims, item T) (T, error) {
if f.InnerAdd == nil {
return *new(T), ErrMethodNotAllowed
}
return f.InnerAdd(claims, item)
if f.InnerAdd == nil {
return *new(T), ErrMethodNotAllowed
}
return f.InnerAdd(claims, item)
}
func (f FunctionProvider[T]) Get(claims *auth.AccessTokenClaims, id string) (T, bool, error) {
if f.InnerGet == nil {
return *new(T), false, ErrMethodNotAllowed
}
return f.InnerGet(claims, id)
if f.InnerGet == nil {
return *new(T), false, ErrMethodNotAllowed
}
return f.InnerGet(claims, id)
}
func (f FunctionProvider[T]) Put(claims *auth.AccessTokenClaims, id string, item T) error {
if f.InnerPut == nil {
return ErrMethodNotAllowed
}
return f.InnerPut(claims, id, item)
if f.InnerPut == nil {
return ErrMethodNotAllowed
}
return f.InnerPut(claims, id, item)
}
func (f FunctionProvider[T]) Patch(claims *auth.AccessTokenClaims, id string, item T) error {
if f.InnerPatch == nil {
return ErrMethodNotAllowed
}
return f.InnerPatch(claims, id, item)
if f.InnerPatch == nil {
return ErrMethodNotAllowed
}
return f.InnerPatch(claims, id, item)
}
func (f FunctionProvider[T]) Delete(claims *auth.AccessTokenClaims, id string) error {
if f.InnerDelete == nil {
return ErrMethodNotAllowed
}
return f.InnerDelete(claims, id)
if f.InnerDelete == nil {
return ErrMethodNotAllowed
}
return f.InnerDelete(claims, id)
}

View File

@ -1,171 +1,171 @@
package crud
import (
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/utils"
"encoding/json"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"net/http"
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/utils"
"encoding/json"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"net/http"
)
type crudHandler[T any] struct {
provider Provider[T]
verify mjwt.Provider
provider Provider[T]
verify mjwt.Provider
}
type secureHandlerFunc func(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims)
func NewCrudHandler[T any](router *mux.Router, verify mjwt.Provider, provider Provider[T], findPath, itemPath string) {
z := &crudHandler[T]{provider: provider, verify: verify}
router.HandleFunc(findPath, z.claimExtractor(z.handleFind)).Methods(http.MethodGet)
router.HandleFunc(findPath, z.claimExtractor(z.handleAdd)).Methods(http.MethodPost)
router.HandleFunc(itemPath, z.claimExtractor(z.handleGet)).Methods(http.MethodGet)
router.HandleFunc(itemPath, z.claimExtractor(z.handlePut)).Methods(http.MethodPut)
router.HandleFunc(itemPath, z.claimExtractor(z.handlePatch)).Methods(http.MethodPatch)
router.HandleFunc(itemPath, z.claimExtractor(z.handleDelete)).Methods(http.MethodDelete)
z := &crudHandler[T]{provider: provider, verify: verify}
router.HandleFunc(findPath, z.claimExtractor(z.handleFind)).Methods(http.MethodGet)
router.HandleFunc(findPath, z.claimExtractor(z.handleAdd)).Methods(http.MethodPost)
router.HandleFunc(itemPath, z.claimExtractor(z.handleGet)).Methods(http.MethodGet)
router.HandleFunc(itemPath, z.claimExtractor(z.handlePut)).Methods(http.MethodPut)
router.HandleFunc(itemPath, z.claimExtractor(z.handlePatch)).Methods(http.MethodPatch)
router.HandleFunc(itemPath, z.claimExtractor(z.handleDelete)).Methods(http.MethodDelete)
}
func (c *crudHandler[T]) claimExtractor(f secureHandlerFunc) func(rw http.ResponseWriter, req *http.Request) {
return func(rw http.ResponseWriter, req *http.Request) {
token := utils.GetBearerToken(req)
if token == "" {
f(rw, req, nil)
return
}
return func(rw http.ResponseWriter, req *http.Request) {
token := utils.GetBearerToken(req)
if token == "" {
f(rw, req, nil)
return
}
// Verify as mfa flow token
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](c.verify, token)
if err != nil {
f(rw, req, nil)
return
}
// Verify as mfa flow token
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](c.verify, token)
if err != nil {
f(rw, req, nil)
return
}
f(rw, req, &b.Claims)
}
f(rw, req, &b.Claims)
}
}
func (c *crudHandler[T]) handleFind(rw http.ResponseWriter, _ *http.Request, claims *auth.AccessTokenClaims) {
a, err := c.provider.Find(claims)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Find error") {
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
err = json.NewEncoder(rw).Encode(a)
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
return
}
a, err := c.provider.Find(claims)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Find error") {
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
err = json.NewEncoder(rw).Encode(a)
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
return
}
}
func (c *crudHandler[T]) handleAdd(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
var t T
err := json.NewDecoder(req.Body).Decode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
return
}
t, err = c.provider.Add(claims, t)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Add error") {
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusCreated)
err = json.NewEncoder(rw).Encode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
return
}
var t T
err := json.NewDecoder(req.Body).Decode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
return
}
t, err = c.provider.Add(claims, t)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Add error") {
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusCreated)
err = json.NewEncoder(rw).Encode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
return
}
}
func (c *crudHandler[T]) handleGet(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
t, found, err := c.provider.Get(claims, s)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Get error") {
return
}
if !found {
api.GenericErrorMsg[T](rw, err, http.StatusNotFound, "Not Found")
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
err = json.NewEncoder(rw).Encode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
return
}
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
t, found, err := c.provider.Get(claims, s)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Get error") {
return
}
if !found {
api.GenericErrorMsg[T](rw, err, http.StatusNotFound, "Not Found")
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
err = json.NewEncoder(rw).Encode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Encode error") {
return
}
}
func (c *crudHandler[T]) handlePut(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
var t T
err := json.NewDecoder(req.Body).Decode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
return
}
err = c.provider.Put(claims, s, t)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Put error") {
return
}
rw.WriteHeader(http.StatusNoContent)
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
var t T
err := json.NewDecoder(req.Body).Decode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
return
}
err = c.provider.Put(claims, s, t)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Put error") {
return
}
rw.WriteHeader(http.StatusNoContent)
}
func (c *crudHandler[T]) handlePatch(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
var t T
err := json.NewDecoder(req.Body).Decode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
return
}
err = c.provider.Patch(claims, s, t)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Patch error") {
return
}
rw.WriteHeader(http.StatusNoContent)
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
var t T
err := json.NewDecoder(req.Body).Decode(&t)
if api.GenericErrorMsg[T](rw, err, http.StatusBadRequest, "Decode error") {
return
}
err = c.provider.Patch(claims, s, t)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Patch error") {
return
}
rw.WriteHeader(http.StatusNoContent)
}
func (c *crudHandler[T]) handleDelete(rw http.ResponseWriter, req *http.Request, claims *auth.AccessTokenClaims) {
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
err := c.provider.Delete(claims, s)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Delete error") {
return
}
rw.WriteHeader(http.StatusNoContent)
vars := mux.Vars(req)
s, ok := vars["id"]
if !ok {
api.GenericErrorMsg[T](rw, api.ErrParamMissing, http.StatusBadRequest, "Parameter error")
return
}
err := c.provider.Delete(claims, s)
if HttpCatchError(rw, err) {
return
}
if api.GenericErrorMsg[T](rw, err, http.StatusInternalServerError, "Delete error") {
return
}
rw.WriteHeader(http.StatusNoContent)
}

View File

@ -3,21 +3,21 @@ package crud
import "code.mrmelon54.com/melon/summer/pkg/claims/auth"
type Provider[T any] interface {
Find(claims *auth.AccessTokenClaims) ([]T, error)
Add(claims *auth.AccessTokenClaims, item T) (T, error)
Get(claims *auth.AccessTokenClaims, id string) (T, bool, error)
Put(claims *auth.AccessTokenClaims, id string, item T) error
Patch(claims *auth.AccessTokenClaims, id string, item T) error
Delete(claims *auth.AccessTokenClaims, id string) error
Find(claims *auth.AccessTokenClaims) ([]T, error)
Add(claims *auth.AccessTokenClaims, item T) (T, error)
Get(claims *auth.AccessTokenClaims, id string) (T, bool, error)
Put(claims *auth.AccessTokenClaims, id string, item T) error
Patch(claims *auth.AccessTokenClaims, id string, item T) error
Delete(claims *auth.AccessTokenClaims, id string) error
}
type CheckedProvider[T any] interface {
Find() ([]T, error)
Add(item T) (T, error)
Get(id string) (T, bool, error)
Put(id string, item T) error
Patch(id string, item T) error
Delete(id string) error
Find() ([]T, error)
Add(item T) (T, error)
Get(id string) (T, bool, error)
Put(id string, item T) error
Patch(id string, item T) error
Delete(id string) error
}
type fFind[T any] func(claims *auth.AccessTokenClaims) ([]T, error)

View File

@ -1,56 +1,56 @@
package crud
import (
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
)
type singlePermProvider[T any] struct {
perm string
provider CheckedProvider[T]
perm string
provider CheckedProvider[T]
}
func NewSinglePermProvider[T any](perm string, provider CheckedProvider[T]) Provider[T] {
return &singlePermProvider[T]{perm, provider}
return &singlePermProvider[T]{perm, provider}
}
func (s *singlePermProvider[T]) Find(claims *auth.AccessTokenClaims) ([]T, error) {
if !claims.Perms.Has(s.perm) {
return nil, ErrForbidden
}
return s.provider.Find()
if !claims.Perms.Has(s.perm) {
return nil, ErrForbidden
}
return s.provider.Find()
}
func (s *singlePermProvider[T]) Add(claims *auth.AccessTokenClaims, item T) (T, error) {
if !claims.Perms.Has(s.perm) {
return *new(T), ErrForbidden
}
return s.provider.Add(item)
if !claims.Perms.Has(s.perm) {
return *new(T), ErrForbidden
}
return s.provider.Add(item)
}
func (s *singlePermProvider[T]) Get(claims *auth.AccessTokenClaims, id string) (T, bool, error) {
if !claims.Perms.Has(s.perm) {
return *new(T), false, ErrForbidden
}
return s.provider.Get(id)
if !claims.Perms.Has(s.perm) {
return *new(T), false, ErrForbidden
}
return s.provider.Get(id)
}
func (s *singlePermProvider[T]) Put(claims *auth.AccessTokenClaims, id string, item T) error {
if !claims.Perms.Has(s.perm) {
return ErrForbidden
}
return s.provider.Put(id, item)
if !claims.Perms.Has(s.perm) {
return ErrForbidden
}
return s.provider.Put(id, item)
}
func (s *singlePermProvider[T]) Patch(claims *auth.AccessTokenClaims, id string, item T) error {
if !claims.Perms.Has(s.perm) {
return ErrForbidden
}
return s.provider.Patch(id, item)
if !claims.Perms.Has(s.perm) {
return ErrForbidden
}
return s.provider.Patch(id, item)
}
func (s *singlePermProvider[T]) Delete(claims *auth.AccessTokenClaims, id string) error {
if !claims.Perms.Has(s.perm) {
return ErrForbidden
}
return s.provider.Delete(id)
if !claims.Perms.Has(s.perm) {
return ErrForbidden
}
return s.provider.Delete(id)
}

View File

@ -1,14 +1,14 @@
package auth
import (
"code.mrmelon54.com/melon/summer/pkg/claims"
"github.com/mrmelon54/mjwt"
"time"
"code.mrmelon54.com/melon/summer/pkg/claims"
"github.com/mrmelon54/mjwt"
"time"
)
type AccessTokenClaims struct {
UserId uint64 `json:"uid"`
Perms *claims.PermStorage `json:"per"`
UserId uint64 `json:"uid"`
Perms *claims.PermStorage `json:"per"`
}
func (a AccessTokenClaims) Valid() error { return nil }
@ -16,8 +16,8 @@ func (a AccessTokenClaims) Valid() error { return nil }
func (a AccessTokenClaims) Type() string { return "access-token" }
func CreateAccessToken(p mjwt.Provider, sub, id string, userId uint64, perms *claims.PermStorage) (string, error) {
return p.GenerateJwt(sub, id, time.Minute*15, &AccessTokenClaims{
UserId: userId,
Perms: perms,
})
return p.GenerateJwt(sub, id, time.Minute*15, &AccessTokenClaims{
UserId: userId,
Perms: perms,
})
}

View File

@ -1,19 +1,19 @@
package auth
import (
"github.com/mrmelon54/mjwt"
"time"
"github.com/mrmelon54/mjwt"
"time"
)
type RefreshTokenClaims struct {
UserId uint64 `json:"uid"`
UserId uint64 `json:"uid"`
}
func (r RefreshTokenClaims) Valid() error { return nil }
func (r RefreshTokenClaims) Type() string { return "refresh-token" }
func CreateRefreshToken(p mjwt.Provider, sub, id string, userId uint64) (string, error) {
return p.GenerateJwt(sub, id, time.Hour*24*7, RefreshTokenClaims{
UserId: userId,
})
return p.GenerateJwt(sub, id, time.Hour*24*7, RefreshTokenClaims{
UserId: userId,
})
}

View File

@ -1,21 +1,21 @@
package flow
import (
"github.com/mrmelon54/mjwt"
"time"
"github.com/mrmelon54/mjwt"
"time"
)
type LoginFlowClaims struct {
LoginId uint64 `json:"lid"`
NextState string `json:"nxs"`
LoginId uint64 `json:"lid"`
NextState string `json:"nxs"`
}
func (l LoginFlowClaims) Valid() error { return nil }
func (l LoginFlowClaims) Type() string { return "login-flow" }
func CreateLoginFlowToken(p mjwt.Provider, sub, id, nextState string, loginId uint64, dur time.Duration) (string, error) {
return p.GenerateJwt(sub, id, dur, LoginFlowClaims{
LoginId: loginId,
NextState: nextState,
})
return p.GenerateJwt(sub, id, dur, LoginFlowClaims{
LoginId: loginId,
NextState: nextState,
})
}

View File

@ -1,19 +1,19 @@
package flow
import (
"github.com/mrmelon54/mjwt"
"time"
"github.com/mrmelon54/mjwt"
"time"
)
type MfaFlowClaims struct {
LoginId uint64 `json:"lid"`
LoginId uint64 `json:"lid"`
}
func (m MfaFlowClaims) Valid() error { return nil }
func (m MfaFlowClaims) Type() string { return "mfa-flow" }
func CreateMfaFlowToken(p mjwt.Provider, sub, id string, loginId uint64, dur time.Duration) (string, error) {
return p.GenerateJwt(sub, id, dur, MfaFlowClaims{
LoginId: loginId,
})
return p.GenerateJwt(sub, id, dur, MfaFlowClaims{
LoginId: loginId,
})
}

View File

@ -1,14 +1,14 @@
package flow
import (
"code.mrmelon54.com/melon/summer/pkg/claims"
"github.com/mrmelon54/mjwt"
"time"
"code.mrmelon54.com/melon/summer/pkg/claims"
"github.com/mrmelon54/mjwt"
"time"
)
type OAuthFlowClaims struct {
LoginId uint64 `json:"lid"`
Scopes *claims.PermStorage `json:"sco"`
LoginId uint64 `json:"lid"`
Scopes *claims.PermStorage `json:"sco"`
}
func (o OAuthFlowClaims) Valid() error { return nil }
@ -16,8 +16,8 @@ func (o OAuthFlowClaims) Valid() error { return nil }
func (o OAuthFlowClaims) Type() string { return "oauth-flow" }
func CreateOAuthFlowToken(p mjwt.Provider, sub, id string, loginId uint64, scopes *claims.PermStorage, dur time.Duration) (string, error) {
return p.GenerateJwt(sub, id, dur, OAuthFlowClaims{
LoginId: loginId,
Scopes: scopes,
})
return p.GenerateJwt(sub, id, dur, OAuthFlowClaims{
LoginId: loginId,
Scopes: scopes,
})
}

View File

@ -1,85 +1,85 @@
package claims
import (
"encoding/json"
"gopkg.in/yaml.v3"
"sort"
"encoding/json"
"gopkg.in/yaml.v3"
"sort"
)
type PermStorage struct {
values map[string]struct{}
values map[string]struct{}
}
func NewPermStorage() *PermStorage {
return new(PermStorage).setup()
return new(PermStorage).setup()
}
func (p *PermStorage) setup() *PermStorage {
if p.values == nil {
p.values = make(map[string]struct{})
}
return p
if p.values == nil {
p.values = make(map[string]struct{})
}
return p
}
func (p *PermStorage) Set(perm string) {
p.values[perm] = struct{}{}
p.values[perm] = struct{}{}
}
func (p *PermStorage) Clear(perm string) {
delete(p.values, perm)
delete(p.values, perm)
}
func (p *PermStorage) Has(perm string) bool {
_, ok := p.values[perm]
return ok
_, ok := p.values[perm]
return ok
}
func (p *PermStorage) OneOf(o *PermStorage) bool {
for i := range o.values {
if o.Has(i) {
return true
}
}
return false
for i := range o.values {
if o.Has(i) {
return true
}
}
return false
}
func (p *PermStorage) dump() []string {
var a []string
for i := range p.values {
a = append(a, i)
}
sort.Strings(a)
return a
var a []string
for i := range p.values {
a = append(a, i)
}
sort.Strings(a)
return a
}
func (p *PermStorage) prepare(a []string) {
for _, i := range a {
p.Set(i)
}
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.setup()
var a []string
err := json.Unmarshal(bytes, &a)
if err != nil {
return err
}
p.prepare(a)
return nil
p.setup()
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.setup()
var a []string
err := value.Decode(&a)
if err != nil {
return err
}
p.prepare(a)
return nil
p.setup()
var a []string
err := value.Decode(&a)
if err != nil {
return err
}
p.prepare(a)
return nil
}

View File

@ -1,75 +1,75 @@
package login_checker
import (
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/tables/user"
"code.mrmelon54.com/melon/summer/pkg/utils"
"errors"
"github.com/mrmelon54/mjwt"
"net/http"
"net/url"
"xorm.io/xorm"
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/tables/user"
"code.mrmelon54.com/melon/summer/pkg/utils"
"errors"
"github.com/mrmelon54/mjwt"
"net/http"
"net/url"
"xorm.io/xorm"
)
type LoginChecker struct {
db *xorm.Engine
signer mjwt.Provider
loginSite url.URL
mfaLoginSite url.URL
db *xorm.Engine
signer mjwt.Provider
loginSite url.URL
mfaLoginSite url.URL
}
func NewLoginChecker(db *xorm.Engine, signer mjwt.Provider) *LoginChecker {
return &LoginChecker{
db: db,
signer: signer,
loginSite: url.URL{Path: "/login"},
mfaLoginSite: url.URL{Path: "/mfa"},
}
return &LoginChecker{
db: db,
signer: signer,
loginSite: url.URL{Path: "/login"},
mfaLoginSite: url.URL{Path: "/mfa"},
}
}
func (lc *LoginChecker) GetUserID(req *http.Request) (uint64, error) {
token := utils.GetBearerToken(req)
if token == "" {
return 0, errors.New("access token missing")
}
token := utils.GetBearerToken(req)
if token == "" {
return 0, errors.New("access token missing")
}
// Verify as access token
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](lc.signer, token)
if err != nil {
return 0, err
}
// Verify as access token
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](lc.signer, token)
if err != nil {
return 0, err
}
var u user.User
get, err := lc.db.Where("id = ?", b.Claims.UserId).Get(&u)
if err != nil {
return 0, err
}
if !utils.SBool(u.EmailVerified) {
return 0, errors.New("requires email verification")
}
if !utils.SBool(u.MfaActive) {
return 0, errors.New("requires MFA")
}
if get {
return u.Id, nil
}
return 0, errors.New("failed login check")
var u user.User
get, err := lc.db.Where("id = ?", b.Claims.UserId).Get(&u)
if err != nil {
return 0, err
}
if !utils.SBool(u.EmailVerified) {
return 0, errors.New("requires email verification")
}
if !utils.SBool(u.MfaActive) {
return 0, errors.New("requires MFA")
}
if get {
return u.Id, nil
}
return 0, errors.New("failed login check")
}
func (lc *LoginChecker) CheckRequired(rw http.ResponseWriter, req *http.Request, cbGood func(uint64)) {
userId, err := lc.GetUserID(req)
if api.GenericErrorMsg[LoginChecker](rw, err, http.StatusUnauthorized, "Token Not Valid") {
return
}
cbGood(userId)
userId, err := lc.GetUserID(req)
if api.GenericErrorMsg[LoginChecker](rw, err, http.StatusUnauthorized, "Token Not Valid") {
return
}
cbGood(userId)
}
func (lc *LoginChecker) CheckOptional(rw http.ResponseWriter, req *http.Request, cbGood func(uint64), cbBad func(uint64)) {
userId, err := lc.GetUserID(req)
if err != nil {
cbBad(0)
return
}
cbGood(userId)
userId, err := lc.GetUserID(req)
if err != nil {
cbBad(0)
return
}
cbGood(userId)
}

View File

@ -1,46 +1,46 @@
package oauth_server
import (
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
"xorm.io/xorm"
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
"xorm.io/xorm"
)
type oAuthAppProvider struct {
db *xorm.Engine
dbCrud crud.CheckedProvider[oauth.App]
db *xorm.Engine
dbCrud crud.CheckedProvider[oauth.App]
}
func NewOAuthAppProvider(db *xorm.Engine) crud.Provider[oauth.App] {
return &oAuthAppProvider{db, crud.NewDatabaseProvider[oauth.App](db, "app_id")}
return &oAuthAppProvider{db, crud.NewDatabaseProvider[oauth.App](db, "app_id")}
}
func (o oAuthAppProvider) Find(_ *auth.AccessTokenClaims) ([]oauth.App, error) {
// TODO: implement this for users and admins
return nil, crud.ErrMethodNotAllowed
// TODO: implement this for users and admins
return nil, crud.ErrMethodNotAllowed
}
func (o oAuthAppProvider) Add(_ *auth.AccessTokenClaims, item oauth.App) (oauth.App, error) {
return o.dbCrud.Add(item)
return o.dbCrud.Add(item)
}
func (o oAuthAppProvider) Get(_ *auth.AccessTokenClaims, id string) (oauth.App, bool, error) {
var a oauth.App
ok, err := o.db.Where("app_id = ? and enabled = ?", id, true).Get(&a)
return a, ok, err
var a oauth.App
ok, err := o.db.Where("app_id = ? and enabled = ?", id, true).Get(&a)
return a, ok, err
}
func (o oAuthAppProvider) Put(claims *auth.AccessTokenClaims, id string, item oauth.App) error {
_, err := o.db.Where("app_id = ? and user_id = ?", id, claims.UserId).Update(&item)
return err
_, err := o.db.Where("app_id = ? and user_id = ?", id, claims.UserId).Update(&item)
return err
}
func (o oAuthAppProvider) Patch(claims *auth.AccessTokenClaims, id string, item oauth.App) error {
_, err := o.db.Where("app_id = ? and user_id = ?", id, claims.UserId).Update(&item)
return err
_, err := o.db.Where("app_id = ? and user_id = ?", id, claims.UserId).Update(&item)
return err
}
func (o oAuthAppProvider) Delete(_ *auth.AccessTokenClaims, _ string) error {
return crud.ErrMethodNotAllowed
return crud.ErrMethodNotAllowed
}

View File

@ -1,218 +1,218 @@
package oauth_server
import (
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/claims"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/claims/flow"
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
"code.mrmelon54.com/melon/summer/pkg/tables/user"
"code.mrmelon54.com/melon/summer/pkg/utils"
"encoding/json"
"fmt"
"github.com/go-oauth2/oauth2/v4/errors"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"net/http"
"net/url"
"time"
"xorm.io/xorm"
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/claims"
"code.mrmelon54.com/melon/summer/pkg/claims/auth"
"code.mrmelon54.com/melon/summer/pkg/claims/flow"
"code.mrmelon54.com/melon/summer/pkg/tables/oauth"
"code.mrmelon54.com/melon/summer/pkg/tables/user"
"code.mrmelon54.com/melon/summer/pkg/utils"
"encoding/json"
"fmt"
"github.com/go-oauth2/oauth2/v4/errors"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/mrmelon54/mjwt"
"net/http"
"net/url"
"time"
"xorm.io/xorm"
)
var ErrInvalidClient = errors.New("invalid client")
type OAuthServer struct {
signer mjwt.Provider
router *mux.Router
db *xorm.Engine
authClient string
signer mjwt.Provider
router *mux.Router
db *xorm.Engine
authClient string
}
type OAuthFlowInput struct {
ResponseType string `json:"response_type"`
ClientId uint64 `json:"client_id"`
Scope *claims.PermStorage `json:"scope"`
State string `json:"state"`
RedirectUrl string `json:"redirect_url,omitempty"`
ResponseType string `json:"response_type"`
ClientId uint64 `json:"client_id"`
Scope *claims.PermStorage `json:"scope"`
State string `json:"state"`
RedirectUrl string `json:"redirect_url,omitempty"`
}
type OAuthFlowOutput struct {
Code string `json:"code"`
RedirectUrl string `json:"redirect_url,omitempty"`
Code string `json:"code"`
RedirectUrl string `json:"redirect_url,omitempty"`
}
type OAuthTokenOutput struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn uint64 `json:"expires_in,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
Scope *claims.PermStorage `json:"scope,omitempty"`
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn uint64 `json:"expires_in,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
Scope *claims.PermStorage `json:"scope,omitempty"`
}
func NewOAuthServer(router *mux.Router, db *xorm.Engine, signer mjwt.Provider, authClient string) *OAuthServer {
s := &OAuthServer{
signer: signer,
router: router,
db: db,
authClient: authClient,
}
router.HandleFunc("/authorize", s.handleOAuthAuthorize).Methods(http.MethodPost, http.MethodGet)
router.HandleFunc("/token", s.handleOAuthToken).Methods(http.MethodPost)
return s
s := &OAuthServer{
signer: signer,
router: router,
db: db,
authClient: authClient,
}
router.HandleFunc("/authorize", s.handleOAuthAuthorize).Methods(http.MethodPost, http.MethodGet)
router.HandleFunc("/token", s.handleOAuthToken).Methods(http.MethodPost)
return s
}
func ClientAuthHandler(req *http.Request) (string, string, error) {
cId, cSecret, err := ClientBasicHandler(req)
if cId == "" && cSecret == "" {
cId, cSecret, err = ClientFormHandler(req)
}
return cId, cSecret, err
cId, cSecret, err := ClientBasicHandler(req)
if cId == "" && cSecret == "" {
cId, cSecret, err = ClientFormHandler(req)
}
return cId, cSecret, err
}
// ClientFormHandler get client data from form
func ClientFormHandler(r *http.Request) (string, string, error) {
clientID := r.Form.Get("client_id")
if clientID == "" {
return "", "", ErrInvalidClient
}
clientSecret := r.Form.Get("client_secret")
return clientID, clientSecret, nil
clientID := r.Form.Get("client_id")
if clientID == "" {
return "", "", ErrInvalidClient
}
clientSecret := r.Form.Get("client_secret")
return clientID, clientSecret, nil
}
// ClientBasicHandler get client data from basic authorization
func ClientBasicHandler(r *http.Request) (string, string, error) {
username, password, ok := r.BasicAuth()
if !ok {
return "", "", ErrInvalidClient
}
return username, password, nil
username, password, ok := r.BasicAuth()
if !ok {
return "", "", ErrInvalidClient
}
return username, password, nil
}
func (o *OAuthServer) handleOAuthAuthorize(rw http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodGet {
appUrl, err := url.Parse(o.authClient)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
_, _ = rw.Write([]byte("Invalid application configuration\n"))
return
}
appUrl.RawQuery = req.URL.RawQuery
http.Redirect(rw, req, appUrl.String(), http.StatusFound)
return
}
if req.Method == http.MethodGet {
appUrl, err := url.Parse(o.authClient)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
_, _ = rw.Write([]byte("Invalid application configuration\n"))
return
}
appUrl.RawQuery = req.URL.RawQuery
http.Redirect(rw, req, appUrl.String(), http.StatusFound)
return
}
token := utils.GetBearerToken(req)
if token == "" {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
return
}
token := utils.GetBearerToken(req)
if token == "" {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusInternalServerError, "Access token missing")
return
}
var in OAuthFlowInput
err := json.NewDecoder(req.Body).Decode(&in)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to decode JSON input") {
return
}
var in OAuthFlowInput
err := json.NewDecoder(req.Body).Decode(&in)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to decode JSON input") {
return
}
// Verify as access token
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](o.signer, token)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
return
}
// Verify as access token
_, b, err := mjwt.ExtractClaims[auth.AccessTokenClaims](o.signer, token)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
return
}
if in.ResponseType == "code" {
count, err := o.db.Where("app_id = ?", in.ClientId).Count(&oauth.App{})
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid Client") {
return
}
if count != 1 {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Client")
return
}
if in.ResponseType == "code" {
count, err := o.db.Where("app_id = ?", in.ClientId).Count(&oauth.App{})
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid Client") {
return
}
if count != 1 {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Client")
return
}
redirect, err := url.Parse(in.RedirectUrl)
if api.GenericErrorMsg[OAuthServer](rw, err, 419, "Invalid Redirect URL") {
return
}
appUrl := fmt.Sprintf("%s://%s", redirect.Scheme, redirect.Host)
n, err := o.db.Where("app_id = ? and domain = ?", in.ClientId, appUrl).Count(&oauth.Domain{})
if api.GenericErrorMsg[OAuthServer](rw, err, 419, "Invalid Redirect URL") {
return
}
if n < 1 {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, 419, "Invalid Redirect URL")
return
}
redirect, err := url.Parse(in.RedirectUrl)
if api.GenericErrorMsg[OAuthServer](rw, err, 419, "Invalid Redirect URL") {
return
}
appUrl := fmt.Sprintf("%s://%s", redirect.Scheme, redirect.Host)
n, err := o.db.Where("app_id = ? and domain = ?", in.ClientId, appUrl).Count(&oauth.Domain{})
if api.GenericErrorMsg[OAuthServer](rw, err, 419, "Invalid Redirect URL") {
return
}
if n < 1 {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, 419, "Invalid Redirect URL")
return
}
flowToken, err := flow.CreateOAuthFlowToken(o.signer, fmt.Sprint(in.ClientId), uuid.NewString(), b.Claims.UserId, in.Scope, time.Minute*10)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to sign token") {
return
}
flowToken, err := flow.CreateOAuthFlowToken(o.signer, fmt.Sprint(in.ClientId), uuid.NewString(), b.Claims.UserId, in.Scope, time.Minute*10)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to sign token") {
return
}
rw.Header().Set("Content-Type", "application/json")
rw.Header().Set("X-Content-Type-Options", "nosniff")
rw.WriteHeader(http.StatusAccepted)
err = json.NewEncoder(rw).Encode(OAuthFlowOutput{Code: flowToken})
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to encode JSON output")
} else {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid response type")
}
rw.Header().Set("Content-Type", "application/json")
rw.Header().Set("X-Content-Type-Options", "nosniff")
rw.WriteHeader(http.StatusAccepted)
err = json.NewEncoder(rw).Encode(OAuthFlowOutput{Code: flowToken})
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to encode JSON output")
} else {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid response type")
}
}
func (o *OAuthServer) handleOAuthToken(rw http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid POST request form") {
return
}
err := req.ParseForm()
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid POST request form") {
return
}
cId, cSecret, err := ClientAuthHandler(req)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Unauthorized") {
return
}
count, err := o.db.Where("app_id = ? and secret = ?", cId, cSecret).Count(&oauth.App{})
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid Client") {
return
}
if count != 1 {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Client")
return
}
cId, cSecret, err := ClientAuthHandler(req)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Unauthorized") {
return
}
count, err := o.db.Where("app_id = ? and secret = ?", cId, cSecret).Count(&oauth.App{})
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid Client") {
return
}
if count != 1 {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid Client")
return
}
form := req.Form
if form.Get("grant_type") == "authorization_code" {
code := form.Get("code")
form := req.Form
if form.Get("grant_type") == "authorization_code" {
code := form.Get("code")
// Verify as access token
_, b, err := mjwt.ExtractClaims[flow.OAuthFlowClaims](o.signer, code)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
return
}
// Verify as access token
_, b, err := mjwt.ExtractClaims[flow.OAuthFlowClaims](o.signer, code)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusUnauthorized, "Token Not Valid") {
return
}
var u user.User
ok, err := o.db.Where("id = ?", b.Claims.LoginId).Get(&u)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid User") {
return
}
if !ok {
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid User")
return
}
var u user.User
ok, err := o.db.Where("id = ?", b.Claims.LoginId).Get(&u)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid User") {
return
}
if !ok {
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Invalid User")
return
}
token, err := auth.CreateAccessToken(o.signer, cId, fmt.Sprint(u.Id), u.Id, b.Claims.Scopes)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
return
}
token, err := auth.CreateAccessToken(o.signer, cId, fmt.Sprint(u.Id), u.Id, b.Claims.Scopes)
if api.GenericErrorMsg[OAuthServer](rw, err, http.StatusInternalServerError, "Token Signing Error") {
return
}
rw.Header().Set("Content-Type", "application/json")
rw.Header().Set("X-Content-Type-Options", "nosniff")
rw.WriteHeader(http.StatusAccepted)
err = json.NewEncoder(rw).Encode(OAuthTokenOutput{
AccessToken: token,
TokenType: "Bearer",
ExpiresIn: uint64((time.Minute * 15).Seconds()),
})
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to encode JSON output")
} else {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid grant type")
}
rw.Header().Set("Content-Type", "application/json")
rw.Header().Set("X-Content-Type-Options", "nosniff")
rw.WriteHeader(http.StatusAccepted)
err = json.NewEncoder(rw).Encode(OAuthTokenOutput{
AccessToken: token,
TokenType: "Bearer",
ExpiresIn: uint64((time.Minute * 15).Seconds()),
})
api.GenericErrorMsg[OAuthServer](rw, err, http.StatusBadRequest, "Failed to encode JSON output")
} else {
api.GenericErrorMsg[OAuthServer](rw, api.ErrNoError, http.StatusBadRequest, "Invalid grant type")
}
}

View File

@ -1,175 +1,175 @@
package proxy
import (
"context"
"crypto/tls"
"errors"
"fmt"
"golang.org/x/net/proxy"
"log"
"net"
"net/http"
"net/http/httputil"
"path"
"sync"
"time"
"context"
"crypto/tls"
"errors"
"fmt"
"golang.org/x/net/proxy"
"log"
"net"
"net/http"
"net/http/httputil"
"path"
"sync"
"time"
)
type reverseProxyHostKey int
type ReverseProxyHostVar interface {
TargetHost() string
TargetPort() uint16
TargetPath() string
IsSecureMode() bool
IsIgnoreCert() bool
TargetHost() string
TargetPort() uint16
TargetPath() string
IsSecureMode() bool
IsIgnoreCert() bool
}
type ReverseProxyHeaderVar interface {
HeaderKey() string
HeaderValue() string
HeaderKey() string
HeaderValue() string
}
func SetReverseProxyHost[T ReverseProxyHeaderVar](req *http.Request, hf ReverseProxyHostVar, header []T) *http.Request {
if header == nil {
header = make([]T, 0)
}
ctx := req.Context()
ctx2 := context.WithValue(ctx, reverseProxyHostKey(0), hf)
ctx3 := context.WithValue(ctx2, reverseProxyHostKey(1), header)
return req.WithContext(ctx3)
if header == nil {
header = make([]T, 0)
}
ctx := req.Context()
ctx2 := context.WithValue(ctx, reverseProxyHostKey(0), hf)
ctx3 := context.WithValue(ctx2, reverseProxyHostKey(1), header)
return req.WithContext(ctx3)
}
func CreateHybridReverseProxy() *httputil.ReverseProxy {
return &httputil.ReverseProxy{
Director: func(req *http.Request) {},
Transport: NewHybridTransport(),
ModifyResponse: func(rw *http.Response) error { return nil },
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
log.Printf("[ReverseProxy] Request: %#v\n -- Error: %s\n", req, err)
rw.WriteHeader(http.StatusBadGateway)
_, _ = rw.Write([]byte("502 Bad gateway\n"))
},
}
return &httputil.ReverseProxy{
Director: func(req *http.Request) {},
Transport: NewHybridTransport(),
ModifyResponse: func(rw *http.Response) error { return nil },
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
log.Printf("[ReverseProxy] Request: %#v\n -- Error: %s\n", req, err)
rw.WriteHeader(http.StatusBadGateway)
_, _ = rw.Write([]byte("502 Bad gateway\n"))
},
}
}
type HybridTransport struct {
baseDialer *net.Dialer
normalTransport http.RoundTripper
insecureTransport http.RoundTripper
socksSync *sync.RWMutex
socksTransport map[string]http.RoundTripper
baseDialer *net.Dialer
normalTransport http.RoundTripper
insecureTransport http.RoundTripper
socksSync *sync.RWMutex
socksTransport map[string]http.RoundTripper
}
func NewHybridTransport() *HybridTransport {
h := &HybridTransport{
baseDialer: &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
},
socksSync: &sync.RWMutex{},
socksTransport: make(map[string]http.RoundTripper),
}
h.normalTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: h.baseDialer.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 15,
TLSHandshakeTimeout: 10 * time.Second,
IdleConnTimeout: 30 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
}
h.insecureTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: h.baseDialer.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 15,
TLSHandshakeTimeout: 10 * time.Second,
IdleConnTimeout: 30 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
return h
h := &HybridTransport{
baseDialer: &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
},
socksSync: &sync.RWMutex{},
socksTransport: make(map[string]http.RoundTripper),
}
h.normalTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: h.baseDialer.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 15,
TLSHandshakeTimeout: 10 * time.Second,
IdleConnTimeout: 30 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
}
h.insecureTransport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: h.baseDialer.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 15,
TLSHandshakeTimeout: 10 * time.Second,
IdleConnTimeout: 30 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
return h
}
func (h *HybridTransport) RoundTrip(req *http.Request) (*http.Response, error) {
newHost := req.Context().Value(reverseProxyHostKey(0))
hf, ok := newHost.(ReverseProxyHostVar)
if !ok {
return nil, errors.New("failed to detect reverse proxy configuration")
}
newHost := req.Context().Value(reverseProxyHostKey(0))
hf, ok := newHost.(ReverseProxyHostVar)
if !ok {
return nil, errors.New("failed to detect reverse proxy configuration")
}
newHead := req.Context().Value(reverseProxyHostKey(1))
headers, ok := newHead.([]ReverseProxyHeaderVar)
if !ok {
return nil, errors.New("failed to detect reverse proxy header configuration")
}
newHead := req.Context().Value(reverseProxyHostKey(1))
headers, ok := newHead.([]ReverseProxyHeaderVar)
if !ok {
return nil, errors.New("failed to detect reverse proxy header configuration")
}
req.URL.Scheme = "http"
if hf.IsSecureMode() {
req.URL.Scheme = "https"
}
req.URL.Host = fmt.Sprintf("%s:%d", hf.TargetHost(), hf.TargetPort())
if hf.TargetPath() != "" {
req.URL.Path = "/" + path.Join(hf.TargetPath(), req.URL.Path)
}
req.URL.Scheme = "http"
if hf.IsSecureMode() {
req.URL.Scheme = "https"
}
req.URL.Host = fmt.Sprintf("%s:%d", hf.TargetHost(), hf.TargetPort())
if hf.TargetPath() != "" {
req.URL.Path = "/" + path.Join(hf.TargetPath(), req.URL.Path)
}
// Do a round trip using existing transports
var trip *http.Response
var err error
if hf.IsIgnoreCert() {
trip, err = h.insecureTransport.RoundTrip(req)
} else {
trip, err = h.normalTransport.RoundTrip(req)
}
if err != nil {
return nil, err
}
// Do a round trip using existing transports
var trip *http.Response
var err error
if hf.IsIgnoreCert() {
trip, err = h.insecureTransport.RoundTrip(req)
} else {
trip, err = h.normalTransport.RoundTrip(req)
}
if err != nil {
return nil, err
}
// Override headers
for _, i := range headers {
trip.Header.Set(i.HeaderKey(), i.HeaderValue())
}
return trip, nil
// Override headers
for _, i := range headers {
trip.Header.Set(i.HeaderKey(), i.HeaderValue())
}
return trip, nil
}
func (h *HybridTransport) getSocksProxy(addr string, insecure bool) (http.RoundTripper, error) {
if insecure {
addr = "%i-" + addr
}
h.socksSync.RLock()
s, ok := h.socksTransport[addr]
h.socksSync.RUnlock()
if ok {
return s, nil
}
if insecure {
addr = "%i-" + addr
}
h.socksSync.RLock()
s, ok := h.socksTransport[addr]
h.socksSync.RUnlock()
if ok {
return s, nil
}
dialer, err := proxy.SOCKS5("tcp", addr, nil, proxy.Direct)
if err != nil {
return nil, fmt.Errorf("cannot connect to the proxy: %s", err)
}
dialer, err := proxy.SOCKS5("tcp", addr, nil, proxy.Direct)
if err != nil {
return nil, fmt.Errorf("cannot connect to the proxy: %s", err)
}
if f, ok := dialer.(proxy.ContextDialer); ok {
t := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: f.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 15,
TLSHandshakeTimeout: 10 * time.Second,
IdleConnTimeout: 30 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
DisableKeepAlives: true,
}
if insecure {
t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
h.socksSync.Lock()
h.socksTransport[addr] = t
h.socksSync.Unlock()
return t, nil
}
return nil, errors.New("cannot create socks5 dialer")
if f, ok := dialer.(proxy.ContextDialer); ok {
t := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: f.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 15,
TLSHandshakeTimeout: 10 * time.Second,
IdleConnTimeout: 30 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
DisableKeepAlives: true,
}
if insecure {
t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
h.socksSync.Lock()
h.socksTransport[addr] = t
h.socksSync.Unlock()
return t, nil
}
return nil, errors.New("cannot create socks5 dialer")
}

View File

@ -1,39 +1,39 @@
package user
import (
"code.mrmelon54.com/melon/summer/pkg/claims"
"code.mrmelon54.com/melon/summer/pkg/utils"
"code.mrmelon54.com/melon/summer/pkg/claims"
"code.mrmelon54.com/melon/summer/pkg/utils"
)
type Perm struct {
Id uint64 `json:"id" xorm:"pk autoincr"`
Key string `json:"key"`
Name string `json:"name"`
Desc string `json:"desc"`
Visible *bool `json:"visible"`
Enabled *bool `json:"enabled"`
IsPerm *bool `json:"isPerm"`
IsScope *bool `json:"isScope"`
Id uint64 `json:"id" xorm:"pk autoincr"`
Key string `json:"key"`
Name string `json:"name"`
Desc string `json:"desc"`
Visible *bool `json:"visible"`
Enabled *bool `json:"enabled"`
IsPerm *bool `json:"isPerm"`
IsScope *bool `json:"isScope"`
}
func (p Perm) GetId() uint64 {
return p.Id
return p.Id
}
func (p Perm) SetEnabled(v bool) Perm {
p.Enabled = utils.PBool(v)
return p
p.Enabled = utils.PBool(v)
return p
}
func (p Perm) ClearForNew() Perm {
p.Id = 0
return p
p.Id = 0
return p
}
func CheckSpecificPerms(storage *claims.PermStorage, v []string) []bool {
z := make([]bool, len(v))
for i := range v {
z[i] = storage.Has(v[i])
}
return z
z := make([]bool, len(v))
for i := range v {
z[i] = storage.Has(v[i])
}
return z
}

View File

@ -1,132 +1,132 @@
package user
import (
"code.mrmelon54.com/melon/summer/pkg/utils"
"github.com/sec51/twofactor"
"time"
"code.mrmelon54.com/melon/summer/pkg/utils"
"github.com/sec51/twofactor"
"time"
)
type ProfileVisibility byte
const (
ProfileVisibilityPrivate = ProfileVisibility(iota)
ProfileVisibilityFriends
ProfileVisibilityPublic
ProfileVisibilityPrivate = ProfileVisibility(iota)
ProfileVisibilityFriends
ProfileVisibilityPublic
)
func ParseVisibility(a *byte) ProfileVisibility {
b := ProfileVisibility(utils.SByte(a))
if b > ProfileVisibilityPublic {
return ProfileVisibilityPrivate
}
return b
b := ProfileVisibility(utils.SByte(a))
if b > ProfileVisibilityPublic {
return ProfileVisibilityPrivate
}
return b
}
func (v ProfileVisibility) AsByte() *byte {
return utils.PByte(byte(v))
return utils.PByte(byte(v))
}
type ProfilePronouns byte
const (
ProfilePronounsUnknown = ProfilePronouns(iota)
ProfilePronounsHeHim
ProfilePronounsSheHer
ProfilePronounsTheyThem
ProfilePronounsUnknown = ProfilePronouns(iota)
ProfilePronounsHeHim
ProfilePronounsSheHer
ProfilePronounsTheyThem
)
func ParsePronouns(a *byte) ProfilePronouns {
b := ProfilePronouns(utils.SByte(a))
if b > ProfilePronounsTheyThem {
return ProfilePronounsUnknown
}
return b
b := ProfilePronouns(utils.SByte(a))
if b > ProfilePronounsTheyThem {
return ProfilePronounsUnknown
}
return b
}
func (p ProfilePronouns) AsByte() *byte {
return utils.PByte(byte(p))
return utils.PByte(byte(p))
}
type User struct {
Id uint64 `json:"id" xorm:"pk autoincr"`
Email string `json:"email"`
Username string `json:"username"`
Password string `json:"-"`
Visibility *byte `json:"visibility" xorm:"unsigned tinyint(3)"`
Gender *string `json:"gender"`
Pronouns *byte `json:"pronouns" xorm:"unsigned tinyint(3)"`
Birthday time.Time `json:"birthday"`
DisplayName string `json:"display_name"`
Created time.Time `json:"created"`
LastOnline time.Time `json:"last_online"`
Icon string `json:"icon"`
MfaActive *bool `json:"-"`
MfaBytes []byte `json:"-"`
MfaObj *twofactor.Totp `json:"-" xorm:"-"`
EmailVerified *bool `json:"email_verified"`
EmailToken string `json:"-"`
EnableRefresh *bool `json:"-"`
Enabled *bool `json:"enabled"`
Id uint64 `json:"id" xorm:"pk autoincr"`
Email string `json:"email"`
Username string `json:"username"`
Password string `json:"-"`
Visibility *byte `json:"visibility" xorm:"unsigned tinyint(3)"`
Gender *string `json:"gender"`
Pronouns *byte `json:"pronouns" xorm:"unsigned tinyint(3)"`
Birthday time.Time `json:"birthday"`
DisplayName string `json:"display_name"`
Created time.Time `json:"created"`
LastOnline time.Time `json:"last_online"`
Icon string `json:"icon"`
MfaActive *bool `json:"-"`
MfaBytes []byte `json:"-"`
MfaObj *twofactor.Totp `json:"-" xorm:"-"`
EmailVerified *bool `json:"email_verified"`
EmailToken string `json:"-"`
EnableRefresh *bool `json:"-"`
Enabled *bool `json:"enabled"`
}
func (u User) GetId() uint64 {
return u.Id
return u.Id
}
func (u User) SetEnabled(v bool) User {
u.Enabled = utils.PBool(v)
return u
u.Enabled = utils.PBool(v)
return u
}
func (u User) ClearForNew() User {
u.Id = 0
return u
u.Id = 0
return u
}
func (u User) ClearForPut() User {
return User{
Id: 0,
Email: "",
Username: "",
Password: "",
Visibility: u.Visibility,
Gender: u.Gender,
Pronouns: u.Pronouns,
Birthday: u.Birthday,
DisplayName: u.DisplayName,
Created: time.Time{},
LastOnline: time.Time{},
Icon: u.Icon,
MfaActive: nil,
MfaBytes: nil,
MfaObj: nil,
EmailVerified: nil,
EmailToken: "",
EnableRefresh: nil,
Enabled: nil,
}
return User{
Id: 0,
Email: "",
Username: "",
Password: "",
Visibility: u.Visibility,
Gender: u.Gender,
Pronouns: u.Pronouns,
Birthday: u.Birthday,
DisplayName: u.DisplayName,
Created: time.Time{},
LastOnline: time.Time{},
Icon: u.Icon,
MfaActive: nil,
MfaBytes: nil,
MfaObj: nil,
EmailVerified: nil,
EmailToken: "",
EnableRefresh: nil,
Enabled: nil,
}
}
func (u User) ClearForAdminPut() User {
return User{
Id: 0,
Email: "",
Username: "",
Password: "",
Visibility: nil,
Gender: nil,
Pronouns: nil,
Birthday: time.Time{},
DisplayName: "",
Created: time.Time{},
LastOnline: time.Time{},
Icon: "",
MfaActive: nil,
MfaBytes: nil,
MfaObj: nil,
EmailVerified: nil,
EmailToken: "",
EnableRefresh: u.EnableRefresh,
Enabled: u.Enabled,
}
return User{
Id: 0,
Email: "",
Username: "",
Password: "",
Visibility: nil,
Gender: nil,
Pronouns: nil,
Birthday: time.Time{},
DisplayName: "",
Created: time.Time{},
LastOnline: time.Time{},
Icon: "",
MfaActive: nil,
MfaBytes: nil,
MfaObj: nil,
EmailVerified: nil,
EmailToken: "",
EnableRefresh: u.EnableRefresh,
Enabled: u.Enabled,
}
}

View File

@ -3,52 +3,52 @@ package web
import "code.mrmelon54.com/melon/summer/pkg/utils"
type ApiRoute struct {
Id uint64 `json:"id" xorm:"pk autoincr"`
Route string `json:"route"`
Version uint64 `json:"version"`
DstHost string `json:"dst_host"`
DstPort uint16 `json:"dst_port" xorm:"int(10) unsigned"`
SecureMode *bool `json:"secure_mode,omitempty"`
IgnoreCert *bool `json:"ignore_cert,omitempty"`
Enabled *bool `json:"enabled"`
Id uint64 `json:"id" xorm:"pk autoincr"`
Route string `json:"route"`
Version uint64 `json:"version"`
DstHost string `json:"dst_host"`
DstPort uint16 `json:"dst_port" xorm:"int(10) unsigned"`
SecureMode *bool `json:"secure_mode,omitempty"`
IgnoreCert *bool `json:"ignore_cert,omitempty"`
Enabled *bool `json:"enabled"`
}
func (a ApiRoute) GetId() uint64 {
return a.Id
return a.Id
}
func (a ApiRoute) SetEnabled(v bool) ApiRoute {
a.Enabled = utils.PBool(v)
return a
a.Enabled = utils.PBool(v)
return a
}
func (a ApiRoute) ClearForNew() ApiRoute {
a.Id = 0
return a
a.Id = 0
return a
}
func (a ApiRoute) TargetHost() string {
return a.DstHost
return a.DstHost
}
func (a ApiRoute) TargetPort() uint16 {
return a.DstPort
return a.DstPort
}
func (a ApiRoute) TargetPath() string {
return ""
return ""
}
func (a ApiRoute) IsSecureMode() bool {
if a.SecureMode == nil {
return false
}
return *a.SecureMode
if a.SecureMode == nil {
return false
}
return *a.SecureMode
}
func (a ApiRoute) IsIgnoreCert() bool {
if a.IgnoreCert == nil {
return false
}
return *a.IgnoreCert
if a.IgnoreCert == nil {
return false
}
return *a.IgnoreCert
}

View File

@ -1,19 +1,19 @@
package web
type HttpHeader struct {
Id uint64 `json:"id" xorm:"pk autoincr"`
ServiceId uint64 `json:"service_id"`
Key string `json:"key"`
Value *string `json:"value"`
Id uint64 `json:"id" xorm:"pk autoincr"`
ServiceId uint64 `json:"service_id"`
Key string `json:"key"`
Value *string `json:"value"`
}
func (h HttpHeader) HeaderKey() string {
return h.Key
return h.Key
}
func (h HttpHeader) HeaderValue() string {
if h.Value == nil {
return ""
}
return *h.Value
if h.Value == nil {
return ""
}
return *h.Value
}

View File

@ -3,51 +3,51 @@ package web
import "code.mrmelon54.com/melon/summer/pkg/utils"
type HttpService struct {
Id uint64 `json:"id" xorm:"pk autoincr"`
Address string `json:"address"`
DstHost string `json:"dst_host"`
DstPort uint16 `json:"dst_port" xorm:"int(10) unsigned"`
SecureMode *bool `json:"secure_mode,omitempty"`
IgnoreCert *bool `json:"ignore_cert,omitempty"`
Enabled *bool `json:"enabled"`
Id uint64 `json:"id" xorm:"pk autoincr"`
Address string `json:"address"`
DstHost string `json:"dst_host"`
DstPort uint16 `json:"dst_port" xorm:"int(10) unsigned"`
SecureMode *bool `json:"secure_mode,omitempty"`
IgnoreCert *bool `json:"ignore_cert,omitempty"`
Enabled *bool `json:"enabled"`
}
func (h HttpService) GetId() uint64 {
return h.Id
return h.Id
}
func (h HttpService) SetEnabled(v bool) HttpService {
h.Enabled = utils.PBool(v)
return h
h.Enabled = utils.PBool(v)
return h
}
func (h HttpService) ClearForNew() HttpService {
h.Id = 0
return h
h.Id = 0
return h
}
func (h HttpService) TargetHost() string {
return h.DstHost
return h.DstHost
}
func (h HttpService) TargetPort() uint16 {
return h.DstPort
return h.DstPort
}
func (h HttpService) TargetPath() string {
return ""
return ""
}
func (h HttpService) IsSecureMode() bool {
if h.SecureMode == nil {
return false
}
return *h.SecureMode
if h.SecureMode == nil {
return false
}
return *h.SecureMode
}
func (h HttpService) IsIgnoreCert() bool {
if h.IgnoreCert == nil {
return false
}
return *h.IgnoreCert
if h.IgnoreCert == nil {
return false
}
return *h.IgnoreCert
}