diff --git a/build.sh b/build.sh index dfa8a29..bfc3920 100755 --- a/build.sh +++ b/build.sh @@ -28,6 +28,6 @@ find -L "$searchDir" -type f -name "build.sh" | while read -r buildScript; do done clear fancy "\033[1;105m" "Building Fulgens..." -go build --ldflags "-s -w" -o "$path/fulgens" || exit 1 +go build -C "$path" --ldflags "-s -w" -o "$path/fulgens" || exit 1 clear fancy "\033[1;102m" "Fulgens has been built successfully!" \ No newline at end of file diff --git a/go.mod b/go.mod index 3015bd7..b72777b 100644 --- a/go.mod +++ b/go.mod @@ -3,33 +3,33 @@ module git.ailur.dev/ailur/fulgens go 1.23.3 require ( - git.ailur.dev/ailur/fg-library/v2 v2.1.2 + git.ailur.dev/ailur/fg-library/v3 v3.5.0 git.ailur.dev/ailur/fg-nucleus-library v1.0.5 - git.ailur.dev/ailur/pow v1.0.2 + git.ailur.dev/ailur/pow v1.0.3 github.com/CAFxX/httpcompression v0.0.9 github.com/cespare/xxhash/v2 v2.3.0 - github.com/go-chi/chi/v5 v5.1.0 - github.com/go-playground/validator/v10 v10.22.1 + github.com/go-chi/chi/v5 v5.2.0 + github.com/go-playground/validator/v10 v10.23.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/klauspost/compress v1.17.11 github.com/lib/pq v1.10.9 github.com/mattn/go-sqlite3 v1.14.24 - golang.org/x/crypto v0.29.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/andybalholm/brotli v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect - github.com/stretchr/testify v1.9.0 // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 3c728db..531c2a2 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,17 @@ git.ailur.dev/ailur/fg-library/v2 v2.1.2 h1:Gk8ztytJfV2GYhsnfTDRWmvTzJ3Cn19V5p2suFCvu4E= git.ailur.dev/ailur/fg-library/v2 v2.1.2/go.mod h1:gBnZQDV70YON6cnuwB+Jawm2EABbf9dGlV0Qw4obtxs= +git.ailur.dev/ailur/fg-library/v3 v3.2.0 h1:SXVj6iCPQ6sR2ZmCZXhAU51mPgH9ERN7KZbMUB03/p4= +git.ailur.dev/ailur/fg-library/v3 v3.2.0/go.mod h1:ArNsafpqES2JuxQM5aM+bNe0FwHLIsL6pbjpiWvDwGs= +git.ailur.dev/ailur/fg-library/v3 v3.3.0 h1:VRe/shTbIfD00OWaywclb4flduAqRY81lMBXGHmMz5g= +git.ailur.dev/ailur/fg-library/v3 v3.3.0/go.mod h1:ArNsafpqES2JuxQM5aM+bNe0FwHLIsL6pbjpiWvDwGs= +git.ailur.dev/ailur/fg-library/v3 v3.4.0 h1:8Et4J6Psh7GBpQiDsQEd/hqftLzq6IMuB2AcCG+rsQA= +git.ailur.dev/ailur/fg-library/v3 v3.4.0/go.mod h1:ArNsafpqES2JuxQM5aM+bNe0FwHLIsL6pbjpiWvDwGs= +git.ailur.dev/ailur/fg-library/v3 v3.5.0 h1:BGDlS4nQ2PyZWNU4gH3eckVqhZUFIM/zKIj/HXx8RmA= +git.ailur.dev/ailur/fg-library/v3 v3.5.0/go.mod h1:ArNsafpqES2JuxQM5aM+bNe0FwHLIsL6pbjpiWvDwGs= git.ailur.dev/ailur/fg-nucleus-library v1.0.5 h1:0YVSHFOeydGR/pfq5AfiKQ5gWuxSnx8u2K8mHvEDDTI= git.ailur.dev/ailur/fg-nucleus-library v1.0.5/go.mod h1:nKYjJ+zJD1YcrEGWlyyA5r6CrzW8DWHVAnL9hkn2tNw= -git.ailur.dev/ailur/pow v1.0.2 h1:8tb6mXZdyQYjrKRW+AUmWMi5wJoHh9Ch3oRqiJr/ivs= -git.ailur.dev/ailur/pow v1.0.2/go.mod h1:fjFb1z5KtF6V14HRhGWiDmmJKggO8KyAP20Lr5OJI/g= +git.ailur.dev/ailur/pow v1.0.3 h1:LjLSol4ax+M+SoajVjbBoDjfmjH6pKu3fDka7bl2KGY= +git.ailur.dev/ailur/pow v1.0.3/go.mod h1:ClAmIdHQ/N9wTq5S4YWhQ5d9CPUBcEjVuOkT07zBdJ4= github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWGKSqgrg= github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -15,18 +23,20 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= -github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= +github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= +github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= -github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= +github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k= @@ -64,21 +74,27 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g= github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/main.go b/main.go deleted file mode 100644 index 01bfdc7..0000000 --- a/main.go +++ /dev/null @@ -1,1143 +0,0 @@ -package main - -import ( - library "git.ailur.dev/ailur/fg-library/v2" - "os/signal" - "syscall" - - "errors" - "io" - "log" - "mime" - "os" - "plugin" - "strconv" - "strings" - "sync" - "time" - - "crypto/tls" - "database/sql" - "log/slog" - "net/http" - "net/http/httputil" - "net/url" - "path/filepath" - - "github.com/go-chi/chi/v5" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" - "gopkg.in/yaml.v3" - - "github.com/CAFxX/httpcompression" - "github.com/CAFxX/httpcompression/contrib/andybalholm/brotli" - "github.com/CAFxX/httpcompression/contrib/klauspost/gzip" - "github.com/CAFxX/httpcompression/contrib/klauspost/zstd" - kpzstd "github.com/klauspost/compress/zstd" - - _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" -) - -type Config struct { - Global struct { - IP string `yaml:"ip" validate:"required,ip_addr"` - ServiceDirectory string `yaml:"serviceDirectory" validate:"required"` - ResourceDirectory string `yaml:"resourceDirectory" validate:"required"` - Compression CompressionSettings `yaml:"compression"` - Logging struct { - Enabled bool `yaml:"enabled"` - File string `yaml:"file" validate:"required_if=Enabled true"` - } `yaml:"logging"` - Database struct { - Type string `yaml:"type" validate:"required,oneof=sqlite postgres"` - ConnectionString string `yaml:"connectionString" validate:"required_if=Type postgres"` - Path string `yaml:"path" validate:"required_if=Type sqlite"` - } `yaml:"database" validate:"required"` - Stealth struct { - Enabled bool `yaml:"enabled"` - Server string `yaml:"server" validate:"required_if=Enabled true"` - PHP struct { - Enabled bool `yaml:"enabled"` - Version string `yaml:"version" validate:"required_if=Enabled true"` - } `yaml:"php"` - ASPNet bool `yaml:"aspNet"` - } - } `yaml:"global" validate:"required"` - Routes []struct { - Port string `yaml:"port" validate:"required"` - Subdomain string `yaml:"subdomain" validate:"required"` - Services []string `yaml:"services"` - Paths []struct { - Paths []string `yaml:"paths" validate:"required"` - Proxy struct { - URL string `yaml:"url" validate:"required"` - StripPrefix bool `yaml:"stripPrefix"` - Headers HeaderSettings `yaml:"headers"` - } `yaml:"proxy" validate:"required_without=Static Redirect"` - Static struct { - Root string `yaml:"root" validate:"required,isDirectory"` - DirectoryListing bool `yaml:"directoryListing"` - } `yaml:"static" validate:"required_without_all=Proxy Redirect"` - Redirect struct { - URL string `yaml:"url" validate:"required"` - Permanent bool `yaml:"permanent"` - } `yaml:"redirect" validate:"required_without_all=Proxy Static"` - } `yaml:"paths"` - HTTPS struct { - CertificatePath string `yaml:"certificate" validate:"required"` - KeyPath string `yaml:"key" validate:"required"` - } `yaml:"https"` - Compression CompressionSettings `yaml:"compression"` - } `yaml:"routes"` - Services map[string]interface{} `yaml:"services"` -} - -type HeaderSettings struct { - Forbid []string `yaml:"forbid"` - PreserveServer bool `yaml:"preserveServer"` - PreserveXPoweredBy bool `yaml:"preserveXPoweredBy"` - PreserveAltSvc bool `yaml:"preserveAltSvc"` - PassHost bool `yaml:"passHost"` - XForward bool `yaml:"xForward"` -} - -type Service struct { - ServiceID uuid.UUID - ServiceMetadata library.Service - ServiceMainFunc func(library.ServiceInitializationInformation) - Inbox chan library.InterServiceMessage -} - -type CompressionSettings struct { - Algorithm string `yaml:"algorithm" validate:"omitempty,oneof=gzip brotli zstd"` - Level int `yaml:"level" validate:"omitempty,min=1,max=22"` -} - -type RouterAndCompression struct { - Router *chi.Mux - Compression CompressionSettings -} - -type PortRouter struct { - https struct { - enabled bool - httpSettings map[string]*tls.Certificate - } - routers map[string]RouterAndCompression -} - -func NewPortRouter() *PortRouter { - return &PortRouter{ - routers: make(map[string]RouterAndCompression), - https: struct { - enabled bool - httpSettings map[string]*tls.Certificate - }{enabled: false, httpSettings: make(map[string]*tls.Certificate)}, - } -} - -func (pr *PortRouter) Register(router *chi.Mux, compression CompressionSettings, subdomain string, certificate ...*tls.Certificate) { - pr.routers[subdomain] = RouterAndCompression{Router: router, Compression: compression} - if len(certificate) > 0 { - pr.https.enabled = true - pr.https.httpSettings[subdomain] = certificate[0] - } -} - -func (pr *PortRouter) Router(w http.ResponseWriter, r *http.Request) { - host := strings.Split(r.Host, ":")[0] - router, ok := pr.routers[host] - if !ok { - router, ok = pr.routers["none"] - } - - if router.Compression.Algorithm != "none" { - compressRouter(router.Compression, router.Router).ServeHTTP(w, r) - } else { - router.Router.ServeHTTP(w, r) - } -} - -func (pr *PortRouter) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { - cert, ok := pr.https.httpSettings[hello.ServerName] - if !ok { - return nil, errors.New("certificate not found") - } else { - return cert, nil - } -} - -func (pr *PortRouter) HTTPSEnabled() bool { - return pr.https.enabled -} - -func compressRouter(settings CompressionSettings, handler http.Handler) http.Handler { - switch settings.Algorithm { - case "gzip": - encoder, err := gzip.New(gzip.Options{Level: int(settings.Level)}) - if err != nil { - slog.Error("Error creating gzip encoder: " + err.Error()) - return handler - } - gzipHandler, err := httpcompression.Adapter(httpcompression.Compressor(gzip.Encoding, 0, encoder)) - if err != nil { - slog.Error("Error creating gzip handler: " + err.Error()) - return handler - } - return gzipHandler(handler) - case "brotli": - encoder, err := brotli.New(brotli.Options{Quality: int(settings.Level)}) - if err != nil { - slog.Error("Error creating brotli encoder: " + err.Error()) - return handler - } - brotliHandler, err := httpcompression.Adapter(httpcompression.Compressor(brotli.Encoding, 0, encoder)) - if err != nil { - slog.Error("Error creating brotli handler: " + err.Error()) - return handler - } - return brotliHandler(handler) - case "zstd": - encoder, err := zstd.New(kpzstd.WithEncoderLevel(kpzstd.EncoderLevelFromZstd(int(settings.Level)))) - if err != nil { - slog.Error("Error creating zstd encoder: " + err.Error()) - return handler - } - zstdHandler, err := httpcompression.Adapter(httpcompression.Compressor(zstd.Encoding, 0, encoder)) - if err != nil { - slog.Error("Error creating zstd handler: " + err.Error()) - return handler - } - return zstdHandler(handler) - default: - return handler - } -} - -func logger(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - next.ServeHTTP(w, r) - slog.Info(r.Method + " " + r.URL.Path) - }) -} - -func serverChanger(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !config.Global.Stealth.Enabled { - if !strings.Contains("Server", w.Header().Get(":X-Preserve-Headers")) { - w.Header().Set("Server", "Fulgens HTTP Server") - } - if !strings.Contains("X-Powered-By", w.Header().Get(":X-Preserve-Headers")) { - w.Header().Set("X-Powered-By", "Go net/http") - } - if !strings.Contains("Alt-Svc", w.Header().Get(":X-Preserve-Headers")) { - w.Header().Set("Alt-Svc", "h2=\":443\"; ma=3600") - } - } else { - switch config.Global.Stealth.Server { - case "nginx": - w.Header().Set("Server", "nginx") - } - - var poweredBy strings.Builder - if config.Global.Stealth.PHP.Enabled { - poweredBy.WriteString("PHP/" + config.Global.Stealth.PHP.Version) - } - - if config.Global.Stealth.ASPNet { - if poweredBy.Len() > 0 { - poweredBy.WriteString(", ") - } - - poweredBy.WriteString("ASP.NET") - } - - if poweredBy.Len() > 0 { - w.Header().Set("X-Powered-By", poweredBy.String()) - } - } - next.ServeHTTP(w, r) - }) -} - -func listDirectory(w http.ResponseWriter, r *http.Request, root string, path string) { - // Provide a directory listing - w.WriteHeader(200) - w.Header().Set("Content-Type", "text/html") - _, err := w.Write([]byte("

Directory listing

")) - if err != nil { - serverError(w, 500) - slog.Error("Error writing directory listing: " + err.Error()) - return - } -} - -func parseEndRange(w http.ResponseWriter, file *os.File, end string) { - endI64, err := strconv.ParseInt(end, 10, 64) - if err != nil { - serverError(w, 500) - slog.Error("Error parsing range: " + err.Error()) - return - } - _, err = file.Seek(-endI64, io.SeekEnd) - if err != nil { - serverError(w, 500) - slog.Error("Error seeking file: " + err.Error()) - return - } - _, err = io.Copy(w, file) - if err != nil { - serverError(w, 500) - slog.Error("Error writing file: " + err.Error()) - return - } -} - -func parseBeginningRange(w http.ResponseWriter, file *os.File, beginning string) { - beginningI64, err := strconv.ParseInt(beginning, 10, 64) - if err != nil { - serverError(w, 500) - slog.Error("Error parsing range: " + err.Error()) - return - } - _, err = file.Seek(beginningI64, io.SeekStart) - if err != nil { - serverError(w, 500) - slog.Error("Error seeking file: " + err.Error()) - return - } - _, err = io.Copy(w, file) - if err != nil { - serverError(w, 500) - slog.Error("Error writing file: " + err.Error()) - return - } -} - -func parsePartRange(w http.ResponseWriter, file *os.File, beginning, end string) { - beginningI64, err := strconv.ParseInt(beginning, 10, 64) - if err != nil { - serverError(w, 500) - slog.Error("Error parsing range: " + err.Error()) - return - } - endI64, err := strconv.ParseInt(end, 10, 64) - if err != nil { - serverError(w, 500) - slog.Error("Error parsing range: " + err.Error()) - return - } - _, err = file.Seek(beginningI64, io.SeekStart) - if err != nil { - serverError(w, 500) - slog.Error("Error seeking file: " + err.Error()) - return - } - _, err = io.CopyN(w, file, endI64-beginningI64) - if err != nil { - serverError(w, 500) - slog.Error("Error writing file: " + err.Error()) - return - } -} - -func newFileServer(root string, directoryListing bool, path string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - stat, err := os.Stat(filepath.Join(root, filepath.FromSlash(r.URL.Path))) - if err != nil { - serverError(w, 404) - return - } - - if stat.IsDir() { - // See if index.html exists - _, err := os.Stat(filepath.Join(root, filepath.FromSlash(r.URL.Path), "index.html")) - if err != nil { - if directoryListing { - listDirectory(w, r, root, path) - } else { - serverError(w, 403) - } - return - } else { - // Serve the index.html file - r.URL.Path = filepath.Join(r.URL.Path, "index.html") - } - } - - file, err := os.Open(filepath.Join(root, filepath.FromSlash(r.URL.Path))) - if err != nil { - serverError(w, 500) - return - } - - w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(r.URL.Path))) - - if strings.HasPrefix(r.Header.Get("Range"), "bytes=") { - // Parse the range header. If there is an int-int, seek to the first int then return a limitedReader. - // If there is an int-, seek to the first int and return the rest of the file. - // If there is an -int, seek to the end of the file minus int and return the last int bytes. - for _, item := range strings.Split(strings.TrimPrefix(r.Header.Get("Range"), "bytes="), ", ") { - if strings.Contains(item, "-") { - beginning := strings.Split(item, "-")[0] - end := strings.Split(item, "-")[1] - if beginning == "" { - parseEndRange(w, file, end) - } else if end == "" { - parseBeginningRange(w, file, beginning) - } else { - parsePartRange(w, file, beginning, end) - } - } else { - serverError(w, 416) - return - } - } - } else { - _, err = io.Copy(w, file) - if err != nil { - serverError(w, 500) - slog.Error("Error writing file: " + err.Error()) - return - } - - err = file.Close() - if err != nil { - slog.Error("Error closing file: " + err.Error()) - } - } - }) -} - -func newReverseProxy(uri *url.URL, headerSettings HeaderSettings) http.Handler { - proxy := httputil.NewSingleHostReverseProxy(uri) - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Strip the headers - for _, header := range headerSettings.Forbid { - r.Header.Del(header) - } - if !headerSettings.PassHost { - r.Host = uri.Host - } - if !headerSettings.XForward { - r.Header["X-Forwarded-For"] = nil - } else { - r.Header.Set("X-Forwarded-Host", r.Host) - if r.URL.Scheme != "" { - r.Header.Set("X-Forwarded-Proto", r.URL.Scheme) - } else { - r.Header.Set("X-Forwarded-Proto", "http") - } - } - - // Set the preserve headers which will be stripped by the server changer - var xPreserveHeaders strings.Builder - - if headerSettings.PreserveServer { - xPreserveHeaders.WriteString("Server") - } - - if headerSettings.PreserveXPoweredBy { - if xPreserveHeaders.Len() > 0 { - xPreserveHeaders.WriteString(", ") - } - xPreserveHeaders.WriteString("X-Powered-By") - } - - if headerSettings.PreserveAltSvc { - if xPreserveHeaders.Len() > 0 { - xPreserveHeaders.WriteString(", ") - } - xPreserveHeaders.WriteString("Alt-Svc") - } - - w.Header().Set(":X-Preserve-Headers", xPreserveHeaders.String()) - - proxy.ServeHTTP(w, r) - }) -} - -func serverError(w http.ResponseWriter, status int) { - w.Header().Set("Content-Type", "text/html") - w.WriteHeader(status) - if !config.Global.Stealth.Enabled { - _, err := w.Write([]byte("

" + strconv.Itoa(status) + " " + http.StatusText(status) + "

Fulgens HTTP Server")) - if err != nil { - slog.Error("Error writing " + strconv.Itoa(status) + ": " + err.Error()) - return - } - } else { - switch config.Global.Stealth.Server { - case "nginx": - _, err := w.Write([]byte("" + strconv.Itoa(status) + " " + http.StatusText(status) + "\n\n

" + strconv.Itoa(status) + " " + http.StatusText(status) + "

\n
nginx/1.27.2
\n\n\n")) - if err != nil { - slog.Error("Error writing " + strconv.Itoa(status) + ": " + err.Error()) - return - } - case "net/http": - _, err := w.Write([]byte(strconv.Itoa(status) + " " + http.StatusText(status))) - if err != nil { - slog.Error("Error writing " + strconv.Itoa(status) + ": " + err.Error()) - return - } - } - } -} - -var ( - validate *validator.Validate - lock sync.RWMutex - config Config - registeredServices = make(map[string]Service) - activeServices = make(map[uuid.UUID]Service) - portRouters = make(map[string]*PortRouter) -) - -func loadTLSCertificate(certificatePath, keyPath string) (*tls.Certificate, error) { - certificate, err := tls.LoadX509KeyPair(certificatePath, keyPath) - if err != nil { - return nil, err - } else { - return &certificate, nil - } -} - -func svInit(message library.InterServiceMessage) { - // Service database initialization message - // Check if the service has the necessary permissions - if activeServices[message.ServiceID].ServiceMetadata.Permissions.Database { - // Check if we are using sqlite or postgres - if config.Global.Database.Type == "sqlite" { - // Open the database and return the connection - pluginConn, err := sql.Open("sqlite3", filepath.Join(config.Global.Database.Path, message.ServiceID.String()+".db")) - if err != nil { - // Report an error - activeServices[message.ServiceID].Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: err, - } - } else { - // Report a successful activation - activeServices[message.ServiceID].Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 2, - SentAt: time.Now(), - Message: library.Database{ - DB: pluginConn, - DBType: library.Sqlite, - }, - } - } - } else if config.Global.Database.Type == "postgres" { - // Connect to the database - conn, err := sql.Open("postgres", config.Global.Database.ConnectionString) - if err != nil { - // Report an error - activeServices[message.ServiceID].Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: err, - } - } else { - // Try to create the schema - _, err = conn.Exec("CREATE SCHEMA IF NOT EXISTS \"" + message.ServiceID.String() + "\"") - if err != nil { - // Report an error - activeServices[message.ServiceID].Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: err, - } - } else { - // Create a new connection to the database - var connectionString string - if strings.Contains(config.Global.Database.ConnectionString, "?") { - connectionString = config.Global.Database.ConnectionString + "&search_path=\"" + message.ServiceID.String() + "\"" - } else { - connectionString = config.Global.Database.ConnectionString + "?search_path=\"" + message.ServiceID.String() + "\"" - } - pluginConn, err := sql.Open("postgres", connectionString) - if err != nil { - // Report an error - activeServices[message.ServiceID].Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: err, - } - } else { - // Test the connection - err = pluginConn.Ping() - if err != nil { - // Report an error - activeServices[message.ServiceID].Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: err, - } - } else { - // Report a successful activation - activeServices[message.ServiceID].Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 2, - SentAt: time.Now(), - Message: library.Database{ - DB: pluginConn, - DBType: library.Postgres, - }, - } - } - } - } - } - } - } else { - // Report an error - activeServices[message.ServiceID].Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: errors.New("database access not permitted"), - } - } -} - -func tryAuthAccess(message library.InterServiceMessage) { - // We need to check if the service is allowed to access the Authentication service - serviceMetadata, ok := activeServices[message.ServiceID] - if ok && serviceMetadata.ServiceMetadata.Permissions.Authenticate { - // Send message to Authentication service - service, ok := activeServices[uuid.MustParse("00000000-0000-0000-0000-000000000004")] - if ok { - service.Inbox <- message - } else { - // Send error message - service, ok := activeServices[message.ServiceID] - if ok { - service.Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: errors.New("authentication service not found"), - } - } else { - // This should never happen - slog.Error("Bit flip error: Impossible service ID. Move away from radiation or use ECC memory.") - os.Exit(1) - } - } - } else { - // Send error message - service, ok := activeServices[message.ServiceID] - if ok { - service.Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: errors.New("authentication not permitted"), - } - } else { - // This should never happen - slog.Error("Bit flip error: Impossible service ID. Move away from radiation or use ECC memory.") - os.Exit(1) - } - } -} - -func tryStorageAccess(message library.InterServiceMessage) { - // We need to check if the service is allowed to access the Blob Storage service - serviceMetadata, ok := activeServices[message.ServiceID] - if ok && serviceMetadata.ServiceMetadata.Permissions.BlobStorage { - // Send message to Blob Storage service - service, ok := activeServices[uuid.MustParse("00000000-0000-0000-0000-000000000003")] - if ok { - service.Inbox <- message - } else { - // Send error message - service, ok := activeServices[message.ServiceID] - if ok { - service.Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: errors.New("blob storage service not found"), - } - } else { - // This should never happen - slog.Error("Bit flip error: Impossible service ID. Move away from radiation or use ECC memory.") - os.Exit(1) - } - } - } else { - // Send error message - service, ok := activeServices[message.ServiceID] - if ok { - service.Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: errors.New("blob storage is not permitted"), - } - } else { - // This should never happen - slog.Error("Bit flip error: Impossible service ID. Move away from radiation or use ECC memory.") - os.Exit(1) - } - } -} - -func tryLogger(message library.InterServiceMessage) { - // Logger service - service, ok := activeServices[message.ServiceID] - if ok { - switch message.MessageType { - case 0: - // Log message - slog.Info(strings.ToLower(service.ServiceMetadata.Name) + " says: " + message.Message.(string)) - case 1: - // Warn message - slog.Warn(strings.ToLower(service.ServiceMetadata.Name) + " warns: " + message.Message.(string)) - case 2: - // Error message - slog.Error(strings.ToLower(service.ServiceMetadata.Name) + " complains: " + message.Message.(string)) - case 3: - // Fatal message - slog.Error(strings.ToLower(service.ServiceMetadata.Name) + "'s dying wish: " + message.Message.(string)) - os.Exit(1) - } - } -} - -func processInterServiceMessage(channel chan library.InterServiceMessage) { - for { - message := <-channel - if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000000") { - // Broadcast message - for _, service := range activeServices { - service.Inbox <- message - } - } else if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000001") { - // Service initialization service - switch message.MessageType { - case 0: - // This has been deprecated, ignore it - // Send "true" back - activeServices[message.ServiceID].Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 0, - SentAt: time.Now(), - Message: true, - } - case 1: - svInit(message) - } - } else if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000002") { - tryLogger(message) - } else if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000003") { - tryStorageAccess(message) - } else if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000004") { - tryAuthAccess(message) - } else { - serviceMetadata, ok := activeServices[message.ServiceID] - if ok && serviceMetadata.ServiceMetadata.Permissions.InterServiceCommunication { - // Send message to specific service - service, ok := activeServices[message.ForServiceID] - if !ok { - // Send error message - service, ok := activeServices[message.ServiceID] - if ok { - service.Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: errors.New("requested service not found"), - } - } else { - // This should never happen - slog.Error("Bit flip error: Impossible service ID. Move away from radiation or use ECC memory.") - os.Exit(1) - } - } - service.Inbox <- message - } else { - // Send error message - service, ok := activeServices[message.ServiceID] - if ok { - service.Inbox <- library.InterServiceMessage{ - ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), - ForServiceID: message.ServiceID, - MessageType: 1, - SentAt: time.Now(), - Message: errors.New("inter-service communication not permitted"), - } - } else { - // This should never happen - slog.Error("Bit flip error: Impossible service ID. Move away from radiation or use ECC memory.") - os.Exit(1) - } - } - } - } -} - -func parseConfig(path string) Config { - // Register the custom validators - validate = validator.New() - - // Register the custom isDirectory validator - err := validate.RegisterValidation("isDirectory", func(fl validator.FieldLevel) bool { - // Check if it exists - fileInfo, err := os.Stat(fl.Field().String()) - if err != nil { - return false - } - - // Check if it is a directory - return fileInfo.IsDir() - }) - - if err != nil { - slog.Error("Error registering custom validator: " + err.Error()) - os.Exit(1) - } - - // Parse the configuration file - configFile, err := os.Open(path) - if err != nil { - slog.Error("Error reading configuration file: " + err.Error()) - os.Exit(1) - } - - // Parse the configuration file - decoder := yaml.NewDecoder(configFile) - err = decoder.Decode(&config) - if err != nil { - slog.Error("Error parsing configuration file: " + err.Error()) - os.Exit(1) - } - - // Validate the configuration - err = validate.Struct(config) - if err != nil { - slog.Error("Invalid configuration: " + err.Error()) - os.Exit(1) - } - - // Check if we are logging to a file - if config.Global.Logging != (Config{}.Global.Logging) && config.Global.Logging.Enabled { - // Check if the log file is set - logFilePath := config.Global.Logging.File - - // Set the log file - logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - slog.Error("Error opening log file: " + err.Error()) - os.Exit(1) - } - - log.SetOutput(io.MultiWriter(os.Stdout, logFile)) - } - - return config -} - -func iterateThroughSubdomains(globalOutbox chan library.InterServiceMessage) { - for _, route := range config.Routes { - var compressionSettings CompressionSettings - if route.Compression != (CompressionSettings{}) { - compressionSettings = route.Compression - } else { - compressionSettings = config.Global.Compression - } - - // Create the subdomain router - subdomainRouter := chi.NewRouter() - subdomainRouter.NotFound(func(w http.ResponseWriter, r *http.Request) { - serverError(w, 404) - }) - - _, ok := portRouters[route.Port] - if !ok { - portRouters[route.Port] = NewPortRouter() - } - - // Check if HTTPS is enabled - if route.HTTPS.KeyPath != "" && route.HTTPS.CertificatePath != "" { - certificate, err := loadTLSCertificate(route.HTTPS.CertificatePath, route.HTTPS.KeyPath) - if err != nil { - slog.Error("Error loading TLS certificate: " + err.Error()) - os.Exit(1) - } - portRouters[route.Port].Register(subdomainRouter, compressionSettings, route.Subdomain, certificate) - } else { - portRouters[route.Port].Register(subdomainRouter, compressionSettings, route.Subdomain) - } - - // Check the services - if route.Services != nil { - // Iterate through the services - for _, service := range route.Services { - // Check if the service is registered - registeredService, ok := registeredServices[service] - if ok { - // Check if the service is already active - _, ok := activeServices[registeredService.ServiceMetadata.ServiceID] - if ok { - slog.Error("Service with ID " + service + " is already active, will not activate again") - os.Exit(1) - } - // Initialize the service - initializeService(registeredService, globalOutbox, subdomainRouter) - } else { - slog.Warn("Service with ID " + service + " is not registered") - } - } - } - - // Iterate through the paths - for _, pathBlock := range route.Paths { - for _, path := range pathBlock.Paths { - if pathBlock.Static.Root != "" { - // Serve the static directory - rawPath := strings.TrimSuffix(path, "*") - subdomainRouter.Handle(path, http.StripPrefix(rawPath, newFileServer(pathBlock.Static.Root, pathBlock.Static.DirectoryListing, rawPath))) - slog.Info("Serving static directory " + pathBlock.Static.Root + " on subdomain " + route.Subdomain + " with pattern " + path) - } else if pathBlock.Proxy.URL != "" { - // Create the proxy - parsedURL, err := url.Parse(pathBlock.Proxy.URL) - if err != nil { - slog.Error("Error parsing URL: " + err.Error()) - os.Exit(1) - } - if pathBlock.Proxy.StripPrefix { - subdomainRouter.Handle(path, http.StripPrefix(strings.TrimSuffix(path, "*"), newReverseProxy(parsedURL, pathBlock.Proxy.Headers))) - } else { - subdomainRouter.Handle(path, newReverseProxy(parsedURL, pathBlock.Proxy.Headers)) - } - } else if pathBlock.Redirect.URL != "" { - // Set the code - code := http.StatusFound - if pathBlock.Redirect.Permanent { - code = http.StatusMovedPermanently - } - - // Create the redirect - subdomainRouter.Handle(path, http.RedirectHandler(pathBlock.Redirect.URL, code)) - } - } - } - } -} - -func registerServices() (err error) { - err = filepath.Walk(config.Global.ServiceDirectory, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() || filepath.Ext(path) != ".fgs" { - return nil - } - - // Open the service - service, err := plugin.Open(path) - if err != nil { - return err - } - - // Load the service information - serviceInformation, err := service.Lookup("ServiceInformation") - if err != nil { - return errors.New("service lacks necessary information") - } - - // Load the main function - mainFunc, err := service.Lookup("Main") - if err != nil { - return errors.New("service lacks necessary main function") - } - - // Register the service - var inbox = make(chan library.InterServiceMessage, 1) - lock.Lock() - registeredServices[strings.ToLower(serviceInformation.(*library.Service).Name)] = Service{ - ServiceID: serviceInformation.(*library.Service).ServiceID, - Inbox: inbox, - ServiceMetadata: *serviceInformation.(*library.Service), - ServiceMainFunc: mainFunc.(func(library.ServiceInitializationInformation)), - } - lock.Unlock() - - // Log the service registration - slog.Info("Service " + strings.ToLower(serviceInformation.(*library.Service).Name) + " registered with ID " + serviceInformation.(*library.Service).ServiceID.String()) - - return nil - }) - - return err -} - -func initializeService(service Service, globalOutbox chan library.InterServiceMessage, subdomainRouter *chi.Mux) { - // Get the plugin from the map - slog.Info("Activating service " + strings.ToLower(service.ServiceMetadata.Name) + " with ID " + service.ServiceMetadata.ServiceID.String()) - - serviceInitializationInformation := library.ServiceInitializationInformation{ - Domain: strings.ToLower(service.ServiceMetadata.Name), - Configuration: config.Services[strings.ToLower(service.ServiceMetadata.Name)].(map[string]interface{}), - Outbox: globalOutbox, - Inbox: service.Inbox, - Router: subdomainRouter, - } - - // Check if they want a resource directory - if service.ServiceMetadata.Permissions.Resources { - serviceInitializationInformation.ResourceDir = os.DirFS(filepath.Join(config.Global.ResourceDirectory, service.ServiceMetadata.ServiceID.String())) - } - - // Add the service to the active services - lock.Lock() - activeServices[service.ServiceMetadata.ServiceID] = service - lock.Unlock() - - // Call the main function - service.ServiceMainFunc(serviceInitializationInformation) - - // Log the service activation - slog.Info("Service " + strings.ToLower(service.ServiceMetadata.Name) + " activated with ID " + service.ServiceMetadata.ServiceID.String()) -} - -func main() { - // Parse the configuration file - if len(os.Args) < 2 { - info, err := os.Stat("config.yaml") - if err != nil { - if errors.Is(err, os.ErrNotExist) { - slog.Error("No configuration file provided") - os.Exit(1) - } else { - slog.Error("Error reading configuration file: " + err.Error()) - os.Exit(1) - } - } - - if info.IsDir() { - slog.Error("No configuration file provided") - os.Exit(1) - } - - config = parseConfig("config.yaml") - } else { - config = parseConfig(os.Args[1]) - } - - // If we are using sqlite, create the database directory if it does not exist - if config.Global.Database.Type == "sqlite" { - err := os.MkdirAll(config.Global.Database.Path, 0755) - if err != nil { - slog.Error("Error creating database directory: " + err.Error()) - os.Exit(1) - } - } - - // Walk through the service directory and load the plugins - err := registerServices() - if err != nil { - slog.Error("Error registering services: " + err.Error()) - os.Exit(1) - } - - var globalOutbox = make(chan library.InterServiceMessage, 1) - - // Initialize the service discovery, health-check, and logging services - // Since these are core services, always allocate them the service IDs 0, 1, and 2 - // These are not dynamically loaded, as they are integral to the system functioning - go processInterServiceMessage(globalOutbox) - - // Start the storage service - initializeService(registeredServices["storage"], globalOutbox, nil) - - // Iterate through the subdomains and create the routers as well as the compression levels and service maps - iterateThroughSubdomains(globalOutbox) - - // Start the servers - slog.Info("Starting servers") - for port, router := range portRouters { - if !router.HTTPSEnabled() { - go func() { - // Start the HTTP server - err = http.ListenAndServe(config.Global.IP+":"+port, logger(serverChanger(http.HandlerFunc(router.Router)))) - slog.Error("Error starting server: " + err.Error()) - os.Exit(1) - }() - } else { - // Create the TLS server - server := &http.Server{ - Addr: config.Global.IP + ":" + port, - Handler: logger(serverChanger(http.HandlerFunc(router.Router))), - TLSConfig: &tls.Config{ - GetCertificate: router.GetCertificate, - }, - } - - go func() { - // Start the TLS server - err = server.ListenAndServeTLS("", "") - slog.Error("Error starting HTTPS server: " + err.Error()) - os.Exit(1) - }() - } - } - - slog.Info("Servers started. Fulgens is now running. Press Ctrl+C to stop the server.") - - // Wait for a signal to stop the server - signalChannel := make(chan os.Signal, 1) - signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) - <-signalChannel -} diff --git a/services-src/auth/main.go b/services-src/auth/main.go index 8a39940..963f1ab 100644 --- a/services-src/auth/main.go +++ b/services-src/auth/main.go @@ -2,7 +2,7 @@ package main import ( // Fulgens libraries - library "git.ailur.dev/ailur/fg-library/v2" + library "git.ailur.dev/ailur/fg-library/v3" authLibrary "git.ailur.dev/ailur/fg-nucleus-library" "git.ailur.dev/ailur/pow" @@ -36,6 +36,7 @@ var ServiceInformation = library.Service{ Name: "Authentication", Permissions: library.Permissions{ Authenticate: false, // This service *is* the authentication service + Router: true, // This service does require a router Database: true, // This service does require database access BlobStorage: false, // This service does not require blob storage InterServiceCommunication: true, // This service does require inter-service communication @@ -44,6 +45,10 @@ var ServiceInformation = library.Service{ ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"), } +var ( + loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002") +) + func checkScopes(scopes []string) (bool, string, error) { var clientKeyShare bool for _, scope := range scopes { @@ -65,15 +70,9 @@ func checkScopes(scopes []string) (bool, string, error) { return clientKeyShare, string(scopeString), nil } -func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) { +func logFunc(message string, messageType library.MessageCode, information library.ServiceInitializationInformation) { // Log the message to the logger service - information.Outbox <- library.InterServiceMessage{ - ServiceID: ServiceInformation.ServiceID, - ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service - MessageType: messageType, - SentAt: time.Now(), - Message: message, - } + information.SendISMessage(loggerService, messageType, message) } func ensureTrailingSlash(url string) string { @@ -194,6 +193,7 @@ func Main(information library.ServiceInitializationInformation) { var mem *sql.DB var publicKey ed25519.PublicKey var privateKey ed25519.PrivateKey + // Load the configuration privacyPolicy := information.Configuration["privacyPolicy"].(string) hostName := information.Configuration["url"].(string) @@ -205,109 +205,95 @@ func Main(information library.ServiceInitializationInformation) { identifier := information.Configuration["identifier"].(string) adminKey := information.Configuration["adminKey"].(string) - // Initiate a connection to the database - // Call service ID 1 to get the database connection information - information.Outbox <- library.InterServiceMessage{ - ServiceID: ServiceInformation.ServiceID, - ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), // Service initialization service - MessageType: 1, // Request connection information - SentAt: time.Now(), - Message: nil, - } + // Start the ISM processor + go information.StartISProcessor() - // Wait for the response - response := <-information.Inbox - if response.MessageType == 2 { - // This is the connection information - // Set up the database connection - conn = response.Message.(library.Database) - if conn.DBType == library.Sqlite { - // Create the global table - // Uniqueness check is a hack to ensure we only have one global row - _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)") - if err != nil { - logFunc(err.Error(), 3, information) - } - // Create the users table - _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BLOB NOT NULL)") - if err != nil { - logFunc(err.Error(), 3, information) - } - // Create the oauth table - _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BLOB NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')") - if err != nil { - logFunc(err.Error(), 3, information) - } - } else { - // Create the global table - // Uniqueness check is a hack to ensure we only have one global row - _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BYTEA NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)") - if err != nil { - logFunc(err.Error(), 3, information) - } - // Create the users table - _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BYTEA NOT NULL)") - if err != nil { - logFunc(err.Error(), 3, information) - } - // Create the oauth table - _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BYTEA NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')") - if err != nil { - logFunc(err.Error(), 3, information) - } - } - // Set up the in-memory cache - var err error - mem, err = sql.Open("sqlite3", "file:"+ServiceInformation.ServiceID.String()+"?mode=memory&cache=shared") + // Initiate a connection to the database + conn, err := information.GetDatabase() + if err != nil { + logFunc(err.Error(), 3, information) + } + if conn.DBType == library.Sqlite { + // Create the global table + // Uniqueness check is a hack to ensure we only have one global row + _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)") if err != nil { logFunc(err.Error(), 3, information) } - // Drop the tables if they exist - _, err = mem.Exec("DROP TABLE IF EXISTS sessions") + // Create the users table + _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BLOB NOT NULL)") if err != nil { logFunc(err.Error(), 3, information) } - _, err = mem.Exec("DROP TABLE IF EXISTS logins") - if err != nil { - logFunc(err.Error(), 3, information) - } - _, err = mem.Exec("DROP TABLE IF EXISTS spent") - if err != nil { - logFunc(err.Error(), 3, information) - } - _, err = mem.Exec("DROP TABLE IF EXISTS challengeResponse") - if err != nil { - logFunc(err.Error(), 3, information) - } - // Create the sessions table - _, err = mem.Exec("CREATE TABLE sessions (id BLOB NOT NULL, session TEXT NOT NULL, device TEXT NOT NULL DEFAULT '?')") - if err != nil { - logFunc(err.Error(), 3, information) - } - // Create the logins table - _, err = mem.Exec("CREATE TABLE logins (appId TEXT NOT NULL, exchangeCode TEXT NOT NULL UNIQUE, pkce TEXT, pkceMethod TEXT, openid BOOLEAN NOT NULL, userId BLOB NOT NULL UNIQUE, nonce TEXT NOT NULL DEFAULT '', token TEXT NOT NULL)") - if err != nil { - logFunc(err.Error(), 3, information) - } - // Create the spent PoW table - _, err = mem.Exec("CREATE TABLE spent (hash BLOB NOT NULL UNIQUE, expires INTEGER NOT NULL)") - if err != nil { - logFunc(err.Error(), 3, information) - } - // Create the challenge-response table - _, err = mem.Exec("CREATE TABLE challengeResponse (challenge TEXT NOT NULL UNIQUE, userId BLOB NOT NULL, expires INTEGER NOT NULL)") + // Create the oauth table + _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BLOB NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')") if err != nil { logFunc(err.Error(), 3, information) } } else { - // This is an error message - // Log the error message to the logger service - logFunc(response.Message.(error).Error(), 3, information) + // Create the global table + // Uniqueness check is a hack to ensure we only have one global row + _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BYTEA NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)") + if err != nil { + logFunc(err.Error(), 3, information) + } + // Create the users table + _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BYTEA NOT NULL)") + if err != nil { + logFunc(err.Error(), 3, information) + } + // Create the oauth table + _, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BYTEA NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')") + if err != nil { + logFunc(err.Error(), 3, information) + } + } + // Set up the in-memory cache + mem, err = sql.Open("sqlite3", "file:"+ServiceInformation.ServiceID.String()+"?mode=memory&cache=shared") + if err != nil { + logFunc(err.Error(), 3, information) + } + // Drop the tables if they exist + _, err = mem.Exec("DROP TABLE IF EXISTS sessions") + if err != nil { + logFunc(err.Error(), 3, information) + } + _, err = mem.Exec("DROP TABLE IF EXISTS logins") + if err != nil { + logFunc(err.Error(), 3, information) + } + _, err = mem.Exec("DROP TABLE IF EXISTS spent") + if err != nil { + logFunc(err.Error(), 3, information) + } + _, err = mem.Exec("DROP TABLE IF EXISTS challengeResponse") + if err != nil { + logFunc(err.Error(), 3, information) + } + // Create the sessions table + _, err = mem.Exec("CREATE TABLE sessions (id BLOB NOT NULL, session TEXT NOT NULL, device TEXT NOT NULL DEFAULT '?')") + if err != nil { + logFunc(err.Error(), 3, information) + } + // Create the logins table + _, err = mem.Exec("CREATE TABLE logins (appId TEXT NOT NULL, exchangeCode TEXT NOT NULL UNIQUE, pkce TEXT, pkceMethod TEXT, openid BOOLEAN NOT NULL, userId BLOB NOT NULL UNIQUE, nonce TEXT NOT NULL DEFAULT '', token TEXT NOT NULL)") + if err != nil { + logFunc(err.Error(), 3, information) + } + // Create the spent PoW table + _, err = mem.Exec("CREATE TABLE spent (hash BLOB NOT NULL UNIQUE, expires INTEGER NOT NULL)") + if err != nil { + logFunc(err.Error(), 3, information) + } + // Create the challenge-response table + _, err = mem.Exec("CREATE TABLE challengeResponse (challenge TEXT NOT NULL UNIQUE, userId BLOB NOT NULL, expires INTEGER NOT NULL)") + if err != nil { + logFunc(err.Error(), 3, information) } // Set up the signing keys // Check if the global table has the keys - err := conn.DB.QueryRow("SELECT key FROM global LIMIT 1").Scan(&privateKey) + err = conn.DB.QueryRow("SELECT key FROM global LIMIT 1").Scan(&privateKey) if err != nil { if errors.Is(err, sql.ErrNoRows) { // Generate a new key @@ -413,56 +399,30 @@ func Main(information library.ServiceInitializationInformation) { router.Get("/authorize", func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("client_id") != "" { - if conn.DBType == library.Sqlite { - var name string - var creator []byte - err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - renderString(404, w, "App not found", information) - } else { - logFunc(err.Error(), 2, information) - renderString(500, w, "Sorry, something went wrong on our end. Error code: 02. Please report to the administrator.", information) - } - return - } - - if !bytes.Equal(creator, ServiceInformation.ServiceID[:]) { - renderTemplate(200, w, map[string]interface{}{ - "identifier": identifier, - "name": name, - }, "authorize.html", information) + var name string + var creator []byte + err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + renderString(404, w, "App not found", information) } else { - renderTemplate(200, w, map[string]interface{}{ - "identifier": identifier, - "name": name, - }, "autoAccept.html", information) + logFunc(err.Error(), 2, information) + renderString(500, w, "Sorry, something went wrong on our end. Error code: 02. Please report to the administrator.", information) } + return + } + + // Check if the app is internal + if !bytes.Equal(creator, ServiceInformation.ServiceID[:]) { + renderTemplate(200, w, map[string]interface{}{ + "identifier": identifier, + "name": name, + }, "authorize.html", information) } else { - var name string - var creator []byte - err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - renderString(404, w, "App not found", information) - } else { - logFunc(err.Error(), 2, information) - renderString(500, w, "Sorry, something went wrong on our end. Error code: 03. Please report to the administrator.", information) - } - return - } - - if uuid.Must(uuid.FromBytes(creator)) != ServiceInformation.ServiceID { - renderTemplate(200, w, map[string]interface{}{ - "identifier": identifier, - "name": name, - }, "authorize.html", information) - } else { - renderTemplate(200, w, map[string]interface{}{ - "identifier": identifier, - "name": name, - }, "autoAccept.html", information) - } + renderTemplate(200, w, map[string]interface{}{ + "identifier": identifier, + "name": name, + }, "autoAccept.html", information) } } else { http.Redirect(w, r, "/dashboard", 301) @@ -1632,128 +1592,78 @@ func Main(information library.ServiceInitializationInformation) { go func() { for { // Wait for a message - message := <-information.Inbox + message := information.AcceptMessage() - if message.ServiceID != uuid.MustParse("00000000-0000-0000-0000-000000000001") { - // Check the message type - switch message.MessageType { - case 0: - // A service would like to know our hostname - // Send it to them - information.Outbox <- library.InterServiceMessage{ - MessageType: 0, - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - Message: hostName, - SentAt: time.Now(), - } - case 1: - // A service would like to register a new OAuth entry - // Validate the scopes - clientKeyShare, scopes, err := checkScopes(message.Message.(authLibrary.OAuthInformation).Scopes) - if err != nil { - information.Outbox <- library.InterServiceMessage{ - MessageType: 2, - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - Message: err.Error(), - SentAt: time.Now(), - } - return - } - - // Check if the service already has an OAuth entry - var appId, secret string - err = conn.DB.QueryRow("SELECT appId, secret FROM oauth WHERE appId = $1", message.ServiceID.String()).Scan(&appId, &secret) - if err == nil && appId == message.ServiceID.String() { - // Update the entry to thew new scopes and redirect URI - if clientKeyShare { - _, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3, keyShareUri = $4 WHERE appId = $5", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri, message.ServiceID.String()) - } else { - _, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3 WHERE appId = $4", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.ServiceID.String()) - } - - if err != nil { - information.Outbox <- library.InterServiceMessage{ - MessageType: 1, - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - Message: "38", - SentAt: time.Now(), - } - logFunc(err.Error(), 2, information) - return - } - - information.Outbox <- library.InterServiceMessage{ - MessageType: 0, - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - Message: authLibrary.OAuthResponse{ - AppID: appId, - SecretKey: secret, - }, - SentAt: time.Now(), - } - - return - } - - // Generate a new secret - // It must be able to be sent via JSON, so we can't have pure-binary data - secret, err = randomChars(512) - if err != nil { - information.Outbox <- library.InterServiceMessage{ - MessageType: 1, - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - Message: "36", - SentAt: time.Now(), - } - logFunc(err.Error(), 2, information) - return - } - - // Insert the oauth entry - if clientKeyShare { - _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ($1, $2, $3, $4, $5, $6, $7)", message.ServiceID.String(), secret, ServiceInformation.ServiceID[:], message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri) - } else { - _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES ($1, $2, $3, $4, $5, $6)", message.ServiceID.String(), secret, ServiceInformation.ServiceID[:], message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes) - } - if err != nil { - information.Outbox <- library.InterServiceMessage{ - MessageType: 1, - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - Message: "39", - SentAt: time.Now(), - } - logFunc(err.Error(), 2, information) - return - } - - // Return the appId and secret - information.Outbox <- library.InterServiceMessage{ - MessageType: 0, - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - Message: authLibrary.OAuthResponse{ - AppID: appId, - SecretKey: secret, - }, - SentAt: time.Now(), - } - case 2: - // A service would like to have the public key - // Send it to them - information.Outbox <- library.InterServiceMessage{ - MessageType: 2, - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - Message: publicKey, - SentAt: time.Now(), - } + // Check the message type + switch message.MessageType { + case 0: + // A service would like to have the hostname + // Send it to them + message.Respond(library.Success, hostName) + case 1: + // A service would like to register a new OAuth entry + // Validate the scopes + clientKeyShare, scopes, err := checkScopes(message.Message.(authLibrary.OAuthInformation).Scopes) + if err != nil { + message.Respond(library.BadRequest, err) + return } + + // Check if the service already has an OAuth entry + var appId, secret string + err = conn.DB.QueryRow("SELECT appId, secret FROM oauth WHERE appId = $1", message.ServiceID.String()).Scan(&appId, &secret) + if err == nil && appId == message.ServiceID.String() { + // Update the entry to thew new scopes and redirect URI + if clientKeyShare { + _, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3, keyShareUri = $4 WHERE appId = $5", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri, message.ServiceID.String()) + } else { + _, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3 WHERE appId = $4", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.ServiceID.String()) + } + + if err != nil { + message.Respond(library.InternalError, err) + logFunc(err.Error(), 2, information) + return + } + + message.Respond(library.Success, authLibrary.OAuthResponse{ + AppID: appId, + SecretKey: secret, + }) + + return + } + + // Generate a new secret + // It must be able to be sent via JSON, so we can't have pure-binary data + secret, err = randomChars(512) + if err != nil { + message.Respond(library.InternalError, err) + logFunc(err.Error(), 2, information) + return + } + + // Insert the oauth entry + if clientKeyShare { + _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ($1, $2, $3, $4, $5, $6, $7)", message.ServiceID.String(), secret, ServiceInformation.ServiceID[:], message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri) + } else { + _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES ($1, $2, $3, $4, $5, $6)", message.ServiceID.String(), secret, ServiceInformation.ServiceID[:], message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes) + } + if err != nil { + message.Respond(library.InternalError, err) + logFunc(err.Error(), 2, information) + return + } + + // Return the appId and secret + message.Respond(library.Success, authLibrary.OAuthResponse{ + AppID: appId, + SecretKey: secret, + }) + case 2: + // A service would like to have the public key + // Send it to them + message.Respond(library.Success, publicKey) } } }() diff --git a/services-src/auth/resources/wasm/testApp/main.go b/services-src/auth/resources/wasm/testApp/main.go index 154fb0d..c84d52c 100644 --- a/services-src/auth/resources/wasm/testApp/main.go +++ b/services-src/auth/resources/wasm/testApp/main.go @@ -236,7 +236,7 @@ func main() { // Redirect to the client key exchange endpoint js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate") time.Sleep(sleepTime) - js.Global().Get("window").Get("location").Call("replace", "/clientKeyShare?ecdhPublicKey="+base64.URLEncoding.EncodeToString(privateKey.PublicKey().Bytes())+"&accessToken="+responseMap["access_token"].(string)) + // js.Global().Get("window").Get("location").Call("replace", "/clientKeyShare?ecdhPublicKey="+base64.URLEncoding.EncodeToString(privateKey.PublicKey().Bytes())+"&accessToken="+responseMap["access_token"].(string)) return } else if response.StatusCode != 500 { statusBox.Set("innerText", responseMap["error"].(string)) diff --git a/services-src/storage/main.go b/services-src/storage/main.go index 030a9fe..8c1f2f0 100644 --- a/services-src/storage/main.go +++ b/services-src/storage/main.go @@ -2,7 +2,7 @@ package main import ( "bytes" - library "git.ailur.dev/ailur/fg-library/v2" + library "git.ailur.dev/ailur/fg-library/v3" nucleusLibrary "git.ailur.dev/ailur/fg-nucleus-library" "errors" @@ -26,34 +26,26 @@ var ServiceInformation = library.Service{ ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000003"), } -var conn library.Database +var ( + conn library.Database + loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002") +) -func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) { - // Log the error message to the logger service - information.Outbox <- library.InterServiceMessage{ - ServiceID: ServiceInformation.ServiceID, - ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service - MessageType: messageType, - SentAt: time.Now(), - Message: message, - } +func logFunc(message string, messageType library.MessageCode, information library.ServiceInitializationInformation) { + // Log the message to the logger service + information.SendISMessage(loggerService, messageType, message) } -func respondError(message string, information library.ServiceInitializationInformation, myFault bool, serviceID uuid.UUID) { +func respondError(message library.InterServiceMessage, err error, information library.ServiceInitializationInformation, myFault bool) { // Respond with an error message - var err uint64 = 1 + var errCode = library.BadRequest if myFault { // Log the error message to the logger service - logFunc(message, 2, information) - err = 2 - } - information.Outbox <- library.InterServiceMessage{ - ServiceID: ServiceInformation.ServiceID, - ForServiceID: serviceID, - MessageType: err, - SentAt: time.Now(), - Message: errors.New(message), + logFunc(err.Error(), 2, information) + errCode = library.InternalError } + + message.Respond(errCode, err) } func checkUserExists(userID uuid.UUID) bool { @@ -78,23 +70,17 @@ func addQuota(information library.ServiceInitializationInformation, message libr if checkUserExists(userID) { _, err := conn.DB.Exec("UPDATE users SET quota = quota + $1 WHERE id = $2", message.Message.(nucleusLibrary.Quota).Bytes, message.Message.(nucleusLibrary.Quota).User) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } } else { _, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int))+message.Message.(nucleusLibrary.Quota).Bytes) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } } // Success - information.Outbox <- library.InterServiceMessage{ - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - MessageType: 0, - SentAt: time.Now(), - Message: nil, - } + message.Respond(library.Success, nil) } // And so does addReserved @@ -105,40 +91,34 @@ func addReserved(information library.ServiceInitializationInformation, message l // Check if the user has enough space quota, err := getQuota(userID) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } used, err := getUsed(userID, information) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } if used+message.Message.(nucleusLibrary.Quota).Bytes > quota { - respondError("insufficient storage", information, false, message.ServiceID) + respondError(message, errors.New("insufficient storage"), information, false) return } _, err = conn.DB.Exec("UPDATE users SET reserved = reserved + $1 WHERE id = $2", message.Message.(nucleusLibrary.Quota).Bytes, userID[:]) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } } else { // Check if the user has enough space if int64(information.Configuration["defaultQuota"].(int)) < message.Message.(nucleusLibrary.Quota).Bytes { - respondError("insufficient storage", information, false, message.ServiceID) + respondError(message, errors.New("insufficient storage"), information, false) return } _, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, $3)", userID[:], int64(information.Configuration["defaultQuota"].(int)), message.Message.(nucleusLibrary.Quota).Bytes) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } } // Success - information.Outbox <- library.InterServiceMessage{ - ServiceID: ServiceInformation.ServiceID, - ForServiceID: message.ServiceID, - MessageType: 0, - SentAt: time.Now(), - Message: nil, - } + message.Respond(library.Success, nil) } func getQuota(userID uuid.UUID) (int64, error) { @@ -190,45 +170,49 @@ func modifyFile(information library.ServiceInitializationInformation, message li // Check if the file already exists path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name) + logFunc(path, 0, information) + _, err := os.Stat(path) - if os.IsNotExist(err) { + if err == nil { // Delete the file err = os.Remove(path) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } + } else if !os.IsNotExist(err) { + respondError(message, err, information, true) } // Check if the user has enough space quota, err := getQuota(message.Message.(nucleusLibrary.File).User) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } used, err := getUsed(message.Message.(nucleusLibrary.File).User, information) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } if used+int64(len(message.Message.(nucleusLibrary.File).Bytes)) > quota { - respondError("insufficient storage", information, false, message.ServiceID) + respondError(message, errors.New("insufficient storage"), information, false) return } // Add a file to the user's storage file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } // Write the file _, err = file.Write(message.Message.(nucleusLibrary.File).Bytes) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } // Close the file err = file.Close() if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } // Success @@ -247,14 +231,14 @@ func getFile(information library.ServiceInitializationInformation, message libra _, err := os.Stat(path) if os.IsNotExist(err) { - respondError("file not found", information, false, message.ServiceID) + respondError(message, errors.New("file not found"), information, false) return } // Open the file file, err := os.Open(path) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } // Respond with the file @@ -274,14 +258,14 @@ func deleteFile(information library.ServiceInitializationInformation, message li _, err := os.Stat(path) if os.IsNotExist(err) { - respondError("file not found", information, false, message.ServiceID) + respondError(message, errors.New("file not found"), information, false) return } // Delete the file err = os.Remove(path) if err != nil { - respondError(err.Error(), information, true, message.ServiceID) + respondError(message, err, information, true) } // Success @@ -298,7 +282,7 @@ func deleteFile(information library.ServiceInitializationInformation, message li func processInterServiceMessages(information library.ServiceInitializationInformation) { // Listen for incoming messages for { - message := <-information.Inbox + message := information.AcceptMessage() switch message.MessageType { case 1: // Add quota @@ -316,44 +300,32 @@ func processInterServiceMessages(information library.ServiceInitializationInform deleteFile(information, message) default: // Respond with an error message - respondError("invalid message type", information, false, message.ServiceID) + respondError(message, errors.New("invalid message type"), information, false) } } } func Main(information library.ServiceInitializationInformation) { - // Initiate a connection to the database - // Call service ID 1 to get the database connection information - information.Outbox <- library.InterServiceMessage{ - ServiceID: ServiceInformation.ServiceID, - ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), // Service initialization service - MessageType: 1, // Request connection information - SentAt: time.Now(), - Message: nil, + // Start up the ISM processor + information.StartISProcessor() + + // Get the database connection + conn, err := information.GetDatabase() + if err != nil { + logFunc(err.Error(), 3, information) } - // Wait for the response - response := <-information.Inbox - if response.MessageType == 2 { - // This is the connection information - // Set up the database connection - conn = response.Message.(library.Database) - // Create the quotas table if it doesn't exist - if conn.DBType == library.Sqlite { - _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY, quota BIGINT, reserved BIGINT)") - if err != nil { - logFunc(err.Error(), 3, information) - } - } else { - _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY, quota BIGINT, reserved BIGINT)") - if err != nil { - logFunc(err.Error(), 3, information) - } + // Create the quotas table if it doesn't exist + if conn.DBType == library.Sqlite { + _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY, quota BIGINT, reserved BIGINT)") + if err != nil { + logFunc(err.Error(), 3, information) } } else { - // This is an error message - // Log the error message to the logger service - logFunc(response.Message.(error).Error(), 3, information) + _, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY, quota BIGINT, reserved BIGINT)") + if err != nil { + logFunc(err.Error(), 3, information) + } } // Listen for incoming messages