Compare commits
22 commits
Author | SHA1 | Date | |
---|---|---|---|
a3409c0a42 | |||
6cda63c538 | |||
2ece60f84e | |||
ffc03f2213 | |||
a65b7f6e0d | |||
cff2e5b811 | |||
1e106bb4ca | |||
f7a1ecccdb | |||
d9a2999fae | |||
a6ef1c01fe | |||
7422201d98 | |||
f26a267421 | |||
f8fc9b7206 | |||
9736939a07 | |||
6eeea11a75 | |||
48547833e4 | |||
34a3580ec6 | |||
f0559ed5b5 | |||
8331219da4 | |||
9cbe1e8ecc | |||
a2dab0869d | |||
684c9d6d48 |
9 changed files with 657 additions and 833 deletions
2
build.sh
2
build.sh
|
@ -28,6 +28,6 @@ find -L "$searchDir" -type f -name "build.sh" | while read -r buildScript; do
|
||||||
done
|
done
|
||||||
clear
|
clear
|
||||||
fancy "\033[1;105m" "Building Fulgens..."
|
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
|
clear
|
||||||
fancy "\033[1;102m" "Fulgens has been built successfully!"
|
fancy "\033[1;102m" "Fulgens has been built successfully!"
|
24
go.mod
24
go.mod
|
@ -3,33 +3,33 @@ module git.ailur.dev/ailur/fulgens
|
||||||
go 1.23.3
|
go 1.23.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.ailur.dev/ailur/fg-library/v2 v2.1.2
|
git.ailur.dev/ailur/fg-library/v3 v3.6.2
|
||||||
git.ailur.dev/ailur/fg-nucleus-library v1.0.5
|
git.ailur.dev/ailur/fg-nucleus-library v1.2.2
|
||||||
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/CAFxX/httpcompression v0.0.9
|
||||||
github.com/cespare/xxhash/v2 v2.3.0
|
github.com/cespare/xxhash/v2 v2.3.0
|
||||||
github.com/go-chi/chi/v5 v5.1.0
|
github.com/go-chi/chi/v5 v5.2.1
|
||||||
github.com/go-playground/validator/v10 v10.22.1
|
github.com/go-playground/validator/v10 v10.25.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/klauspost/compress v1.17.11
|
github.com/klauspost/compress v1.18.0
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
golang.org/x/crypto v0.29.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
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/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
github.com/stretchr/testify v1.9.0 // indirect
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
golang.org/x/net v0.31.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/sys v0.27.0 // indirect
|
golang.org/x/net v0.37.0 // indirect
|
||||||
golang.org/x/text v0.20.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
|
golang.org/x/text v0.23.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
)
|
)
|
||||||
|
|
48
go.sum
48
go.sum
|
@ -1,9 +1,9 @@
|
||||||
git.ailur.dev/ailur/fg-library/v2 v2.1.2 h1:Gk8ztytJfV2GYhsnfTDRWmvTzJ3Cn19V5p2suFCvu4E=
|
git.ailur.dev/ailur/fg-library/v3 v3.6.2 h1:PNJKxpvbel2iDeB9+/rpYRyMoim6JjRHOXPYFYky7Ng=
|
||||||
git.ailur.dev/ailur/fg-library/v2 v2.1.2/go.mod h1:gBnZQDV70YON6cnuwB+Jawm2EABbf9dGlV0Qw4obtxs=
|
git.ailur.dev/ailur/fg-library/v3 v3.6.2/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.2.2 h1:JbclmxGSoL+ByGZAl0W6PqWRoyBBGTrKrizWDJ7rdI0=
|
||||||
git.ailur.dev/ailur/fg-nucleus-library v1.0.5/go.mod h1:nKYjJ+zJD1YcrEGWlyyA5r6CrzW8DWHVAnL9hkn2tNw=
|
git.ailur.dev/ailur/fg-nucleus-library v1.2.2/go.mod h1:stxiTyMv3Fa7GzpyLbBUh3ahlb7110p0NnCl8ZTjwBs=
|
||||||
git.ailur.dev/ailur/pow v1.0.2 h1:8tb6mXZdyQYjrKRW+AUmWMi5wJoHh9Ch3oRqiJr/ivs=
|
git.ailur.dev/ailur/pow v1.0.3 h1:LjLSol4ax+M+SoajVjbBoDjfmjH6pKu3fDka7bl2KGY=
|
||||||
git.ailur.dev/ailur/pow v1.0.2/go.mod h1:fjFb1z5KtF6V14HRhGWiDmmJKggO8KyAP20Lr5OJI/g=
|
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 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWGKSqgrg=
|
||||||
github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM=
|
github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
@ -15,18 +15,18 @@ 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.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.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
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/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 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
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.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
||||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
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/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=
|
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k=
|
||||||
|
@ -34,8 +34,8 @@ github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
@ -64,21 +64,21 @@ 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.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.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.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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
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/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 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
|
||||||
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
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 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|
552
main.go
552
main.go
|
@ -1,20 +1,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
library "git.ailur.dev/ailur/fg-library/v2"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
|
library "git.ailur.dev/ailur/fg-library/v3"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"mime"
|
"mime"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"plugin"
|
"plugin"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"syscall"
|
||||||
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
@ -64,35 +62,37 @@ type Config struct {
|
||||||
ASPNet bool `yaml:"aspNet"`
|
ASPNet bool `yaml:"aspNet"`
|
||||||
}
|
}
|
||||||
} `yaml:"global" validate:"required"`
|
} `yaml:"global" validate:"required"`
|
||||||
Routes []struct {
|
Routes []Route `yaml:"routes"`
|
||||||
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"`
|
Services map[string]interface{} `yaml:"services"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Route 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"`
|
||||||
|
}
|
||||||
|
|
||||||
type HeaderSettings struct {
|
type HeaderSettings struct {
|
||||||
Forbid []string `yaml:"forbid"`
|
Forbid []string `yaml:"forbid"`
|
||||||
PreserveServer bool `yaml:"preserveServer"`
|
PreserveServer bool `yaml:"preserveServer"`
|
||||||
|
@ -105,13 +105,13 @@ type HeaderSettings struct {
|
||||||
type Service struct {
|
type Service struct {
|
||||||
ServiceID uuid.UUID
|
ServiceID uuid.UUID
|
||||||
ServiceMetadata library.Service
|
ServiceMetadata library.Service
|
||||||
ServiceMainFunc func(library.ServiceInitializationInformation)
|
ServiceMainFunc func(*library.ServiceInitializationInformation)
|
||||||
Inbox chan library.InterServiceMessage
|
Inbox chan library.InterServiceMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompressionSettings struct {
|
type CompressionSettings struct {
|
||||||
Algorithm string `yaml:"algorithm" validate:"omitempty,oneof=gzip brotli zstd"`
|
Algorithm string `yaml:"algorithm" validate:"omitempty,oneof=gzip brotli zstd"`
|
||||||
Level float64 `yaml:"level" validate:"omitempty,min=1,max=22"`
|
Level int `yaml:"level" validate:"omitempty,min=1,max=22"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouterAndCompression struct {
|
type RouterAndCompression struct {
|
||||||
|
@ -175,7 +175,7 @@ func (pr *PortRouter) HTTPSEnabled() bool {
|
||||||
func compressRouter(settings CompressionSettings, handler http.Handler) http.Handler {
|
func compressRouter(settings CompressionSettings, handler http.Handler) http.Handler {
|
||||||
switch settings.Algorithm {
|
switch settings.Algorithm {
|
||||||
case "gzip":
|
case "gzip":
|
||||||
encoder, err := gzip.New(gzip.Options{Level: int(settings.Level)})
|
encoder, err := gzip.New(gzip.Options{Level: settings.Level})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error creating gzip encoder: " + err.Error())
|
slog.Error("Error creating gzip encoder: " + err.Error())
|
||||||
return handler
|
return handler
|
||||||
|
@ -187,7 +187,7 @@ func compressRouter(settings CompressionSettings, handler http.Handler) http.Han
|
||||||
}
|
}
|
||||||
return gzipHandler(handler)
|
return gzipHandler(handler)
|
||||||
case "brotli":
|
case "brotli":
|
||||||
encoder, err := brotli.New(brotli.Options{Quality: int(settings.Level)})
|
encoder, err := brotli.New(brotli.Options{Quality: settings.Level})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error creating brotli encoder: " + err.Error())
|
slog.Error("Error creating brotli encoder: " + err.Error())
|
||||||
return handler
|
return handler
|
||||||
|
@ -199,7 +199,7 @@ func compressRouter(settings CompressionSettings, handler http.Handler) http.Han
|
||||||
}
|
}
|
||||||
return brotliHandler(handler)
|
return brotliHandler(handler)
|
||||||
case "zstd":
|
case "zstd":
|
||||||
encoder, err := zstd.New(kpzstd.WithEncoderLevel(kpzstd.EncoderLevelFromZstd(int(settings.Level))))
|
encoder, err := zstd.New(kpzstd.WithEncoderLevel(kpzstd.EncoderLevelFromZstd(settings.Level)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error creating zstd encoder: " + err.Error())
|
slog.Error("Error creating zstd encoder: " + err.Error())
|
||||||
return handler
|
return handler
|
||||||
|
@ -392,7 +392,17 @@ func newFileServer(root string, directoryListing bool, path string) http.Handler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(filepath.Join(root, filepath.FromSlash(r.URL.Path)))
|
absolutePath, err := filepath.Abs(filepath.Join(root, filepath.FromSlash(r.URL.Path)))
|
||||||
|
if err != nil {
|
||||||
|
serverError(w, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(absolutePath, root) {
|
||||||
|
serverError(w, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(absolutePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serverError(w, 500)
|
serverError(w, 500)
|
||||||
return
|
return
|
||||||
|
@ -518,6 +528,11 @@ var (
|
||||||
registeredServices = make(map[string]Service)
|
registeredServices = make(map[string]Service)
|
||||||
activeServices = make(map[uuid.UUID]Service)
|
activeServices = make(map[uuid.UUID]Service)
|
||||||
portRouters = make(map[string]*PortRouter)
|
portRouters = make(map[string]*PortRouter)
|
||||||
|
broadcastService = uuid.MustParse("00000000-0000-0000-0000-000000000000")
|
||||||
|
databaseService = uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||||
|
logService = uuid.MustParse("00000000-0000-0000-0000-000000000002")
|
||||||
|
blobService = uuid.MustParse("00000000-0000-0000-0000-000000000003")
|
||||||
|
authService = uuid.MustParse("00000000-0000-0000-0000-000000000004")
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadTLSCertificate(certificatePath, keyPath string) (*tls.Certificate, error) {
|
func loadTLSCertificate(certificatePath, keyPath string) (*tls.Certificate, error) {
|
||||||
|
@ -529,205 +544,113 @@ func loadTLSCertificate(certificatePath, keyPath string) (*tls.Certificate, erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func svInit(message library.InterServiceMessage) {
|
var globalPGConn *sql.DB
|
||||||
// Service database initialization message
|
|
||||||
// Check if the service has the necessary permissions
|
func createPgSchema(id uuid.UUID) error {
|
||||||
if activeServices[message.ServiceID].ServiceMetadata.Permissions.Database {
|
_, err := globalPGConn.Exec("CREATE SCHEMA IF NOT EXISTS \"" + id.String() + "\"")
|
||||||
// Check if we are using sqlite or postgres
|
if err != nil {
|
||||||
if config.Global.Database.Type == "sqlite" {
|
return err
|
||||||
// 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"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func svInit(message library.InterServiceMessage) {
|
||||||
|
var dummyInfo = &library.ServiceInitializationInformation{Outbox: activeServices[message.ServiceID].Inbox}
|
||||||
|
if !activeServices[message.ServiceID].ServiceMetadata.Permissions.Database {
|
||||||
|
message.Respond(library.Unauthorized, errors.New("database access not permitted"), dummyInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var db library.Database
|
||||||
|
switch config.Global.Database.Type {
|
||||||
|
case "sqlite":
|
||||||
|
pluginConn, err := sql.Open("sqlite3", filepath.Join(config.Global.Database.Path, message.ServiceID.String()+".db"))
|
||||||
|
if err != nil {
|
||||||
|
message.Respond(library.InternalError, err, dummyInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pluginConn.Exec("PRAGMA journal_mode=WAL")
|
||||||
|
if err != nil {
|
||||||
|
message.Respond(library.InternalError, err, dummyInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db = library.Database{
|
||||||
|
DB: pluginConn,
|
||||||
|
DBType: library.Sqlite,
|
||||||
|
}
|
||||||
|
case "postgres":
|
||||||
|
err := createPgSchema(message.ServiceID)
|
||||||
|
if err != nil {
|
||||||
|
message.Respond(library.InternalError, err, dummyInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionString := config.Global.Database.ConnectionString
|
||||||
|
if strings.Contains(config.Global.Database.ConnectionString, "?") {
|
||||||
|
connectionString += "&"
|
||||||
|
} else {
|
||||||
|
connectionString += "?"
|
||||||
|
}
|
||||||
|
connectionString += "search_path=\"" + message.ServiceID.String() + "\""
|
||||||
|
|
||||||
|
pluginConn, err := sql.Open("postgres", connectionString)
|
||||||
|
if err != nil {
|
||||||
|
message.Respond(library.InternalError, err, dummyInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db = library.Database{
|
||||||
|
DB: pluginConn,
|
||||||
|
DBType: library.Postgres,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
message.Respond(library.InternalError, errors.New("database type not supported"), dummyInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.DB.Ping()
|
||||||
|
if err != nil {
|
||||||
|
message.Respond(library.InternalError, err, dummyInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Respond(library.Success, db, dummyInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryAuthAccess(message library.InterServiceMessage) {
|
func tryAuthAccess(message library.InterServiceMessage) {
|
||||||
// We need to check if the service is allowed to access the Authentication service
|
|
||||||
serviceMetadata, ok := activeServices[message.ServiceID]
|
serviceMetadata, ok := activeServices[message.ServiceID]
|
||||||
if ok && serviceMetadata.ServiceMetadata.Permissions.Authenticate {
|
var dummyInfo = &library.ServiceInitializationInformation{Outbox: serviceMetadata.Inbox}
|
||||||
// Send message to Authentication service
|
if !ok || !serviceMetadata.ServiceMetadata.Permissions.Authenticate {
|
||||||
service, ok := activeServices[uuid.MustParse("00000000-0000-0000-0000-000000000004")]
|
message.Respond(library.Unauthorized, errors.New("authentication not permitted"), dummyInfo)
|
||||||
if ok {
|
return
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service, ok := activeServices[authService]
|
||||||
|
if !ok {
|
||||||
|
message.Respond(library.InternalError, errors.New("authentication service not found"), dummyInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
service.Inbox <- message
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryStorageAccess(message library.InterServiceMessage) {
|
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]
|
serviceMetadata, ok := activeServices[message.ServiceID]
|
||||||
if ok && serviceMetadata.ServiceMetadata.Permissions.BlobStorage {
|
var dummyInfo = &library.ServiceInitializationInformation{Outbox: serviceMetadata.Inbox}
|
||||||
// Send message to Blob Storage service
|
if !ok || !serviceMetadata.ServiceMetadata.Permissions.BlobStorage {
|
||||||
service, ok := activeServices[uuid.MustParse("00000000-0000-0000-0000-000000000003")]
|
message.Respond(library.Unauthorized, errors.New("storage access not permitted"), dummyInfo)
|
||||||
if ok {
|
return
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service, ok := activeServices[blobService]
|
||||||
|
if !ok {
|
||||||
|
message.Respond(library.InternalError, errors.New("storage service not found"), dummyInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
service.Inbox <- message
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryLogger(message library.InterServiceMessage) {
|
func tryLogger(message library.InterServiceMessage) {
|
||||||
|
@ -752,75 +675,38 @@ func tryLogger(message library.InterServiceMessage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func processInterServiceMessage(channel chan library.InterServiceMessage) {
|
func processInterServiceMessage(listener library.Listener) {
|
||||||
for {
|
for {
|
||||||
message := <-channel
|
message := listener.AcceptMessage()
|
||||||
if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000000") {
|
switch message.ForServiceID {
|
||||||
|
case broadcastService:
|
||||||
// Broadcast message
|
// Broadcast message
|
||||||
for _, service := range activeServices {
|
for _, service := range activeServices {
|
||||||
service.Inbox <- message
|
service.Inbox <- message
|
||||||
}
|
}
|
||||||
} else if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000001") {
|
case databaseService:
|
||||||
// Service initialization service
|
// Database service
|
||||||
switch message.MessageType {
|
switch message.MessageType {
|
||||||
case 0:
|
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)
|
svInit(message)
|
||||||
}
|
}
|
||||||
} else if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000002") {
|
case logService:
|
||||||
tryLogger(message)
|
tryLogger(message)
|
||||||
} else if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000003") {
|
case blobService:
|
||||||
tryStorageAccess(message)
|
tryStorageAccess(message)
|
||||||
} else if message.ForServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000004") {
|
case authService:
|
||||||
tryAuthAccess(message)
|
tryAuthAccess(message)
|
||||||
} else {
|
default:
|
||||||
serviceMetadata, ok := activeServices[message.ServiceID]
|
serviceMetadata, ok := activeServices[message.ServiceID]
|
||||||
if ok && serviceMetadata.ServiceMetadata.Permissions.InterServiceCommunication {
|
var dummyInfo = &library.ServiceInitializationInformation{Outbox: serviceMetadata.Inbox}
|
||||||
// Send message to specific service
|
if !ok || !serviceMetadata.ServiceMetadata.Permissions.InterServiceCommunication {
|
||||||
|
message.Respond(library.Unauthorized, errors.New("inter-service communication not permitted"), dummyInfo)
|
||||||
|
} else {
|
||||||
service, ok := activeServices[message.ForServiceID]
|
service, ok := activeServices[message.ForServiceID]
|
||||||
if !ok {
|
if !ok {
|
||||||
// Send error message
|
message.Respond(library.BadRequest, errors.New("service not found"), dummyInfo)
|
||||||
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
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -887,6 +773,44 @@ func parseConfig(path string) Config {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkHTTPS(route Route, subdomainRouter *chi.Mux, compressionSettings CompressionSettings) {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkServices(route Route, globalOutbox chan library.InterServiceMessage, subdomainRouter *chi.Mux) {
|
||||||
|
// 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)
|
||||||
|
} else {
|
||||||
|
// Initialize the service
|
||||||
|
initializeService(registeredService, globalOutbox, subdomainRouter, &route.Subdomain)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slog.Warn("Service with ID " + service + " is not registered")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func iterateThroughSubdomains(globalOutbox chan library.InterServiceMessage) {
|
func iterateThroughSubdomains(globalOutbox chan library.InterServiceMessage) {
|
||||||
for _, route := range config.Routes {
|
for _, route := range config.Routes {
|
||||||
var compressionSettings CompressionSettings
|
var compressionSettings CompressionSettings
|
||||||
|
@ -902,43 +826,17 @@ func iterateThroughSubdomains(globalOutbox chan library.InterServiceMessage) {
|
||||||
serverError(w, 404)
|
serverError(w, 404)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Set the port router
|
||||||
_, ok := portRouters[route.Port]
|
_, ok := portRouters[route.Port]
|
||||||
if !ok {
|
if !ok {
|
||||||
portRouters[route.Port] = NewPortRouter()
|
portRouters[route.Port] = NewPortRouter()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if HTTPS is enabled
|
// Check if HTTPS is enabled
|
||||||
if route.HTTPS.KeyPath != "" && route.HTTPS.CertificatePath != "" {
|
checkHTTPS(route, subdomainRouter, compressionSettings)
|
||||||
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
|
// Check the services
|
||||||
if route.Services != nil {
|
checkServices(route, globalOutbox, subdomainRouter)
|
||||||
// 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
|
// Iterate through the paths
|
||||||
for _, pathBlock := range route.Paths {
|
for _, pathBlock := range route.Paths {
|
||||||
|
@ -994,13 +892,13 @@ func registerServices() (err error) {
|
||||||
// Load the service information
|
// Load the service information
|
||||||
serviceInformation, err := service.Lookup("ServiceInformation")
|
serviceInformation, err := service.Lookup("ServiceInformation")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("service lacks necessary information")
|
return errors.New("service " + path + " lacks necessary service information")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the main function
|
// Load the main function
|
||||||
mainFunc, err := service.Lookup("Main")
|
mainFunc, err := service.Lookup("Main")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("service lacks necessary main function")
|
return errors.New("service " + path + " lacks necessary main function")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the service
|
// Register the service
|
||||||
|
@ -1010,7 +908,7 @@ func registerServices() (err error) {
|
||||||
ServiceID: serviceInformation.(*library.Service).ServiceID,
|
ServiceID: serviceInformation.(*library.Service).ServiceID,
|
||||||
Inbox: inbox,
|
Inbox: inbox,
|
||||||
ServiceMetadata: *serviceInformation.(*library.Service),
|
ServiceMetadata: *serviceInformation.(*library.Service),
|
||||||
ServiceMainFunc: mainFunc.(func(library.ServiceInitializationInformation)),
|
ServiceMainFunc: mainFunc.(func(*library.ServiceInitializationInformation)),
|
||||||
}
|
}
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
|
|
||||||
|
@ -1023,23 +921,25 @@ func registerServices() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeService(service Service, globalOutbox chan library.InterServiceMessage, subdomainRouter *chi.Mux) {
|
func initializeService(service Service, globalOutbox chan library.InterServiceMessage, subdomainRouter *chi.Mux, subdomain *string) {
|
||||||
// Get the plugin from the map
|
// Get the plugin from the map
|
||||||
slog.Info("Activating service " + strings.ToLower(service.ServiceMetadata.Name) + " with ID " + service.ServiceMetadata.ServiceID.String())
|
slog.Info("Activating service " + strings.ToLower(service.ServiceMetadata.Name) + " with ID " + service.ServiceMetadata.ServiceID.String())
|
||||||
|
|
||||||
serviceInitializationInformation := library.ServiceInitializationInformation{
|
serviceInitializationInformation := library.NewServiceInitializationInformation(nil, globalOutbox, service.Inbox, nil, config.Services[strings.ToLower(service.ServiceMetadata.Name)].(map[string]interface{}), nil)
|
||||||
Domain: strings.ToLower(service.ServiceMetadata.Name),
|
|
||||||
Configuration: config.Services[strings.ToLower(service.ServiceMetadata.Name)].(map[string]interface{}),
|
serviceInitializationInformation.Service = &service.ServiceMetadata
|
||||||
Outbox: globalOutbox,
|
|
||||||
Inbox: service.Inbox,
|
|
||||||
Router: subdomainRouter,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if they want a resource directory
|
// Check if they want a resource directory
|
||||||
if service.ServiceMetadata.Permissions.Resources {
|
if service.ServiceMetadata.Permissions.Resources {
|
||||||
serviceInitializationInformation.ResourceDir = os.DirFS(filepath.Join(config.Global.ResourceDirectory, service.ServiceMetadata.ServiceID.String()))
|
serviceInitializationInformation.ResourceDir = os.DirFS(filepath.Join(config.Global.ResourceDirectory, service.ServiceMetadata.ServiceID.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if they want a router
|
||||||
|
if service.ServiceMetadata.Permissions.Router {
|
||||||
|
serviceInitializationInformation.Router = subdomainRouter
|
||||||
|
serviceInitializationInformation.Domain = subdomain
|
||||||
|
}
|
||||||
|
|
||||||
// Add the service to the active services
|
// Add the service to the active services
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
activeServices[service.ServiceMetadata.ServiceID] = service
|
activeServices[service.ServiceMetadata.ServiceID] = service
|
||||||
|
@ -1083,6 +983,14 @@ func main() {
|
||||||
slog.Error("Error creating database directory: " + err.Error())
|
slog.Error("Error creating database directory: " + err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Set the global database connection
|
||||||
|
var err error
|
||||||
|
globalPGConn, err = sql.Open("postgres", config.Global.Database.ConnectionString)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Error connecting to database: " + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk through the service directory and load the plugins
|
// Walk through the service directory and load the plugins
|
||||||
|
@ -1097,17 +1005,17 @@ func main() {
|
||||||
// Initialize the service discovery, health-check, and logging services
|
// Initialize the service discovery, health-check, and logging services
|
||||||
// Since these are core services, always allocate them the service IDs 0, 1, and 2
|
// 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
|
// These are not dynamically loaded, as they are integral to the system functioning
|
||||||
go processInterServiceMessage(globalOutbox)
|
go processInterServiceMessage(library.NewListener(globalOutbox))
|
||||||
|
|
||||||
// Start the storage service
|
// Start the storage service
|
||||||
initializeService(registeredServices["storage"], globalOutbox, nil)
|
// initializeService(registeredServices["storage"], globalOutbox, nil, nil)
|
||||||
|
|
||||||
// Iterate through the subdomains and create the routers as well as the compression levels and service maps
|
// Iterate through the subdomains and create the routers as well as the compression levels and service maps
|
||||||
iterateThroughSubdomains(globalOutbox)
|
iterateThroughSubdomains(globalOutbox)
|
||||||
|
|
||||||
// Start the servers
|
// Start the servers
|
||||||
|
slog.Info("Starting servers")
|
||||||
for port, router := range portRouters {
|
for port, router := range portRouters {
|
||||||
slog.Info("Starting server on port " + port)
|
|
||||||
if !router.HTTPSEnabled() {
|
if !router.HTTPSEnabled() {
|
||||||
go func() {
|
go func() {
|
||||||
// Start the HTTP server
|
// Start the HTTP server
|
||||||
|
@ -1134,6 +1042,8 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slog.Info("Servers started. Fulgens is now running. Press Ctrl+C to stop the server.")
|
||||||
|
|
||||||
// Wait for a signal to stop the server
|
// Wait for a signal to stop the server
|
||||||
signalChannel := make(chan os.Signal, 1)
|
signalChannel := make(chan os.Signal, 1)
|
||||||
signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// Fulgens libraries
|
// 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"
|
authLibrary "git.ailur.dev/ailur/fg-nucleus-library"
|
||||||
"git.ailur.dev/ailur/pow"
|
"git.ailur.dev/ailur/pow"
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ var ServiceInformation = library.Service{
|
||||||
Name: "Authentication",
|
Name: "Authentication",
|
||||||
Permissions: library.Permissions{
|
Permissions: library.Permissions{
|
||||||
Authenticate: false, // This service *is* the authentication service
|
Authenticate: false, // This service *is* the authentication service
|
||||||
|
Router: true, // This service does require a router
|
||||||
Database: true, // This service does require database access
|
Database: true, // This service does require database access
|
||||||
BlobStorage: false, // This service does not require blob storage
|
BlobStorage: false, // This service does not require blob storage
|
||||||
InterServiceCommunication: true, // This service does require inter-service communication
|
InterServiceCommunication: true, // This service does require inter-service communication
|
||||||
|
@ -44,15 +45,34 @@ var ServiceInformation = library.Service{
|
||||||
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"),
|
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"),
|
||||||
}
|
}
|
||||||
|
|
||||||
func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
|
var (
|
||||||
// Log the message to the logger service
|
loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002")
|
||||||
information.Outbox <- library.InterServiceMessage{
|
)
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
|
||||||
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service
|
func checkScopes(scopes []string) (bool, string, error) {
|
||||||
MessageType: messageType,
|
var clientKeyShare bool
|
||||||
SentAt: time.Now(),
|
for _, scope := range scopes {
|
||||||
Message: message,
|
if scope != "openid" && scope != "clientKeyShare" {
|
||||||
|
return false, "", errors.New("invalid scope")
|
||||||
|
} else {
|
||||||
|
if scope == "clientKeyShare" {
|
||||||
|
clientKeyShare = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Marshal the scopes
|
||||||
|
scopeString, err := json.Marshal(scopes)
|
||||||
|
if err != nil {
|
||||||
|
return clientKeyShare, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientKeyShare, string(scopeString), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func logFunc(message string, messageType library.MessageCode, information *library.ServiceInitializationInformation) {
|
||||||
|
// Log the message to the logger service
|
||||||
|
information.SendISMessage(loggerService, messageType, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureTrailingSlash(url string) string {
|
func ensureTrailingSlash(url string) string {
|
||||||
|
@ -88,7 +108,7 @@ func sha256Base64(s string) string {
|
||||||
return encoded
|
return encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(statusCode int, w http.ResponseWriter, data map[string]interface{}, templatePath string, information library.ServiceInitializationInformation) {
|
func renderTemplate(statusCode int, w http.ResponseWriter, data map[string]interface{}, templatePath string, information *library.ServiceInitializationInformation) {
|
||||||
var err error
|
var err error
|
||||||
var requestedTemplate *template.Template
|
var requestedTemplate *template.Template
|
||||||
// Output ls of the resource directory
|
// Output ls of the resource directory
|
||||||
|
@ -113,7 +133,7 @@ func renderTemplate(statusCode int, w http.ResponseWriter, data map[string]inter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderString(statusCode int, w http.ResponseWriter, data string, information library.ServiceInitializationInformation) {
|
func renderString(statusCode int, w http.ResponseWriter, data string, information *library.ServiceInitializationInformation) {
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
_, err := w.Write([]byte(data))
|
_, err := w.Write([]byte(data))
|
||||||
|
@ -122,7 +142,7 @@ func renderString(statusCode int, w http.ResponseWriter, data string, informatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderJSON(statusCode int, w http.ResponseWriter, data map[string]interface{}, information library.ServiceInitializationInformation) {
|
func renderJSON(statusCode int, w http.ResponseWriter, data map[string]interface{}, information *library.ServiceInitializationInformation) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
err := json.NewEncoder(w).Encode(data)
|
err := json.NewEncoder(w).Encode(data)
|
||||||
|
@ -168,11 +188,12 @@ func verifyJwt(token string, publicKey ed25519.PublicKey, mem *sql.DB) ([]byte,
|
||||||
return userId, claims, true
|
return userId, claims, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func Main(information library.ServiceInitializationInformation) {
|
func Main(information *library.ServiceInitializationInformation) {
|
||||||
var conn library.Database
|
var conn library.Database
|
||||||
var mem *sql.DB
|
var mem *sql.DB
|
||||||
var publicKey ed25519.PublicKey
|
var publicKey ed25519.PublicKey
|
||||||
var privateKey ed25519.PrivateKey
|
var privateKey ed25519.PrivateKey
|
||||||
|
|
||||||
// Load the configuration
|
// Load the configuration
|
||||||
privacyPolicy := information.Configuration["privacyPolicy"].(string)
|
privacyPolicy := information.Configuration["privacyPolicy"].(string)
|
||||||
hostName := information.Configuration["url"].(string)
|
hostName := information.Configuration["url"].(string)
|
||||||
|
@ -184,108 +205,86 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
identifier := information.Configuration["identifier"].(string)
|
identifier := information.Configuration["identifier"].(string)
|
||||||
adminKey := information.Configuration["adminKey"].(string)
|
adminKey := information.Configuration["adminKey"].(string)
|
||||||
|
|
||||||
// Initiate a connection to the database
|
// Start the ISM processor
|
||||||
// Call service ID 1 to get the database connection information
|
go information.StartISProcessor()
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the response
|
// Initiate a connection to the database
|
||||||
response := <-information.Inbox
|
conn, err := information.GetDatabase()
|
||||||
if response.MessageType == 2 {
|
if err != nil {
|
||||||
// This is the connection information
|
logFunc(err.Error(), 3, information)
|
||||||
// Set up the database connection
|
}
|
||||||
conn = response.Message.(library.Database)
|
if conn.DBType == library.Sqlite {
|
||||||
if conn.DBType == library.Sqlite {
|
// Create the global table
|
||||||
// Create the global table
|
// Uniqueness check is a hack to ensure we only have one global row
|
||||||
// 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)")
|
||||||
_, 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
|
|
||||||
mem, err := sql.Open("sqlite3", "file:"+ServiceInformation.ServiceID.String()+"?mode=memory&cache=shared")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
// Drop the tables if they exist
|
// Create the users table
|
||||||
_, err = mem.Exec("DROP TABLE IF EXISTS sessions")
|
_, 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 {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
_, err = mem.Exec("DROP TABLE IF EXISTS logins")
|
// Create the oauth table
|
||||||
if err != nil {
|
_, 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\"]')")
|
||||||
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 {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is an error message
|
// Create the global table
|
||||||
// Log the error message to the logger service
|
// Uniqueness check is a hack to ensure we only have one global row
|
||||||
logFunc(response.Message.(error).Error(), 3, information)
|
_, 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)
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the signing keys
|
// Set up the signing keys
|
||||||
// Check if the global table has the 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 err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
// Generate a new key
|
// Generate a new key
|
||||||
|
@ -314,11 +313,10 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if testAppIsInternalApp {
|
if testAppIsInternalApp {
|
||||||
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", ServiceInformation.ServiceID, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
|
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", ServiceInformation.ServiceID[:], ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
|
||||||
} else {
|
} else {
|
||||||
testAppCreator := uuid.New()
|
testAppCreator := uuid.New()
|
||||||
|
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", testAppCreator[:], ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
|
||||||
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", testAppCreator, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
testAppIsAvailable = false
|
testAppIsAvailable = false
|
||||||
|
@ -392,56 +390,30 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
|
|
||||||
router.Get("/authorize", func(w http.ResponseWriter, r *http.Request) {
|
router.Get("/authorize", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Query().Get("client_id") != "" {
|
if r.URL.Query().Get("client_id") != "" {
|
||||||
if conn.DBType == library.Sqlite {
|
var name string
|
||||||
var name string
|
var creator []byte
|
||||||
var creator []byte
|
err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
|
||||||
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 err != nil {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
renderString(404, w, "App not found", information)
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
renderTemplate(200, w, map[string]interface{}{
|
logFunc(err.Error(), 2, information)
|
||||||
"identifier": identifier,
|
renderString(500, w, "Sorry, something went wrong on our end. Error code: 02. Please report to the administrator.", information)
|
||||||
"name": name,
|
|
||||||
}, "autoAccept.html", 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 {
|
} else {
|
||||||
var name string
|
renderTemplate(200, w, map[string]interface{}{
|
||||||
var creator uuid.UUID
|
"identifier": identifier,
|
||||||
err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
|
"name": name,
|
||||||
if err != nil {
|
}, "autoAccept.html", information)
|
||||||
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 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(w, r, "/dashboard", 301)
|
http.Redirect(w, r, "/dashboard", 301)
|
||||||
|
@ -622,7 +594,7 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = conn.DB.Exec("INSERT INTO users (id, created, username, publicKey) VALUES ($1, $2, $3, $4)", userID, time.Now().Unix(), data.Username, publicKey)
|
_, err = conn.DB.Exec("INSERT INTO users (id, created, username, publicKey) VALUES ($1, $2, $3, $4)", userID[:], time.Now().Unix(), data.Username, publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
||||||
renderJSON(409, w, map[string]interface{}{"error": "Username already taken"}, information)
|
renderJSON(409, w, map[string]interface{}{"error": "Username already taken"}, information)
|
||||||
|
@ -644,37 +616,13 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the session
|
// Insert the session
|
||||||
_, err = mem.Exec("INSERT INTO sessions (id, session, device) VALUES (?, ?, ?)", userID, session, r.Header.Get("User-Agent"))
|
_, err = mem.Exec("INSERT INTO sessions (id, session, device) VALUES (?, ?, ?)", userID[:], session, r.Header.Get("User-Agent"))
|
||||||
|
|
||||||
// Return success, as well as the session token
|
// Return success, as well as the session token
|
||||||
renderJSON(200, w, map[string]interface{}{"key": session}, information)
|
renderJSON(200, w, map[string]interface{}{"key": session}, information)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Post("/api/loginChallenge", func(w http.ResponseWriter, r *http.Request) {
|
router.Post("/api/loginChallenge", func(w http.ResponseWriter, r *http.Request) {
|
||||||
type login struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var data login
|
|
||||||
err = json.NewDecoder(r.Body).Decode(&data)
|
|
||||||
if err != nil {
|
|
||||||
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the id for the user
|
|
||||||
var userId []byte
|
|
||||||
err = conn.DB.QueryRow("SELECT id FROM users WHERE username = $1", data.Username).Scan(&userId)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
renderJSON(401, w, map[string]interface{}{"error": "Invalid username"}, information)
|
|
||||||
} else {
|
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "12"}, information)
|
|
||||||
logFunc(err.Error(), 2, information)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a new challenge
|
// Generate a new challenge
|
||||||
challenge, err := randomChars(512)
|
challenge, err := randomChars(512)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -683,22 +631,27 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the challenge with one minute expiration
|
// Issue a new JWT token with the challenge
|
||||||
_, err = mem.Exec("INSERT INTO challengeResponse (challenge, userId, expires) VALUES (?, ?, ?)", challenge, userId, time.Now().Unix()+60)
|
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
|
||||||
|
"challenge": challenge,
|
||||||
|
"exp": time.Now().Add(time.Second * 20).Unix(),
|
||||||
|
})
|
||||||
|
tokenString, err := token.SignedString(privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "53"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "51"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the challenge
|
// Return the challenge
|
||||||
renderJSON(200, w, map[string]interface{}{"challenge": challenge}, information)
|
renderJSON(200, w, map[string]interface{}{"challenge": challenge, "verifier": tokenString}, information)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Post("/api/login", func(w http.ResponseWriter, r *http.Request) {
|
router.Post("/api/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
type login struct {
|
type login struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Signature string `json:"signature"`
|
Signature string `json:"signature"`
|
||||||
|
Verifier string `json:"verifier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var data login
|
var data login
|
||||||
|
@ -710,8 +663,8 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
|
|
||||||
// Try to select the user
|
// Try to select the user
|
||||||
var userId []byte
|
var userId []byte
|
||||||
var publicKey []byte
|
var userPublicKey []byte
|
||||||
err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &publicKey)
|
err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &userPublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
renderJSON(401, w, map[string]interface{}{"error": "Invalid username"}, information)
|
renderJSON(401, w, map[string]interface{}{"error": "Invalid username"}, information)
|
||||||
|
@ -730,33 +683,43 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the challenge
|
// Verify the challenge
|
||||||
// Select the current challenge from the database
|
token, err := jwt.Parse(data.Verifier, func(token *jwt.Token) (interface{}, error) {
|
||||||
var challenge string
|
return publicKey, nil
|
||||||
err = mem.QueryRow("SELECT challenge FROM challengeResponse WHERE userId = ?", userId).Scan(&challenge)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier"}, information)
|
||||||
renderJSON(401, w, map[string]interface{}{"error": "Invalid challenge"}, information)
|
return
|
||||||
} else {
|
}
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "52"}, information)
|
|
||||||
logFunc(err.Error(), 2, information)
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
}
|
if !ok {
|
||||||
|
renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier: no claims"}, information)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expired, err := claims.GetExpirationTime()
|
||||||
|
if err != nil {
|
||||||
|
renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier: no expiry"}, information)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if expired.Before(time.Now()) {
|
||||||
|
renderJSON(401, w, map[string]interface{}{"error": "Expired verifier"}, information)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
challenge, ok := claims["challenge"].(string)
|
||||||
|
if !ok {
|
||||||
|
renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier: no challenge"}, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the challenge is correct by verifying the signature
|
// Check if the challenge is correct by verifying the signature
|
||||||
if !ed25519.Verify(publicKey, []byte(challenge), signature) {
|
if !ed25519.Verify(userPublicKey, []byte(challenge), signature) {
|
||||||
renderJSON(401, w, map[string]interface{}{"error": "Invalid signature"}, information)
|
renderJSON(401, w, map[string]interface{}{"error": "Invalid signature"}, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the challenge
|
|
||||||
_, err = mem.Exec("DELETE FROM challengeResponse WHERE userId = ?", userId)
|
|
||||||
if err != nil {
|
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "53"}, information)
|
|
||||||
logFunc(err.Error(), 2, information)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new session
|
// Create a new session
|
||||||
// We want the session token to be somewhat legible, so we use randomChars
|
// We want the session token to be somewhat legible, so we use randomChars
|
||||||
// As a trade-off for this, we use a longer session token
|
// As a trade-off for this, we use a longer session token
|
||||||
|
@ -1212,7 +1175,6 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Post("/api/oauth/add", func(w http.ResponseWriter, r *http.Request) {
|
router.Post("/api/oauth/add", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Conveniently, we use this one for ISB as well, so we can re-use the struct
|
// Conveniently, we use this one for ISB as well, so we can re-use the struct
|
||||||
var data authLibrary.OAuthInformation
|
var data authLibrary.OAuthInformation
|
||||||
err = json.NewDecoder(r.Body).Decode(&data)
|
err = json.NewDecoder(r.Body).Decode(&data)
|
||||||
|
@ -1248,27 +1210,9 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the scopes
|
// Validate the scopes
|
||||||
var clientKeyShare bool
|
clientKeyShare, scopes, err := checkScopes(data.Scopes)
|
||||||
for _, scope := range data.Scopes {
|
|
||||||
if scope != "openid" && scope != "clientKeyShare" {
|
|
||||||
renderJSON(400, w, map[string]interface{}{"error": "Invalid scope"}, information)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
if scope == "clientKeyShare" {
|
|
||||||
clientKeyShare = true
|
|
||||||
} else if scope != "openid" {
|
|
||||||
logFunc("An impossible logic error has occurred, please move away from radiation or use ECC RAM", 1, information)
|
|
||||||
renderJSON(400, w, map[string]interface{}{"error": "Invalid scope"}, information)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal the scopes
|
|
||||||
scopes, err := json.Marshal(data.Scopes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "36"}, information)
|
renderJSON(400, w, map[string]interface{}{"error": err.Error()}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1611,17 +1555,7 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 1, information)
|
logFunc(err.Error(), 1, information)
|
||||||
} else {
|
} else {
|
||||||
affected, err := mem.Exec("DELETE FROM challengeResponse WHERE expires < ?", time.Now().Unix())
|
logFunc("Cleanup complete, deleted "+strconv.FormatInt(affectedCount, 10)+" entries", 0, information)
|
||||||
if err != nil {
|
|
||||||
logFunc(err.Error(), 1, information)
|
|
||||||
} else {
|
|
||||||
affectedCount2, err := affected.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
logFunc(err.Error(), 1, information)
|
|
||||||
} else {
|
|
||||||
logFunc("Cleanup complete, deleted "+strconv.FormatInt(affectedCount+affectedCount2, 10)+" entries", 0, information)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1630,139 +1564,78 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
// Wait for a message
|
// Wait for a message
|
||||||
message := <-information.Inbox
|
message := information.AcceptMessage()
|
||||||
|
|
||||||
if message.ServiceID != uuid.MustParse("00000000-0000-0000-0000-000000000001") {
|
// Check the message type
|
||||||
// Check the message type
|
switch message.MessageType {
|
||||||
switch message.MessageType {
|
case 0:
|
||||||
case 0:
|
// A service would like to have the hostname
|
||||||
// A service would like to know our hostname
|
// Send it to them
|
||||||
// Send it to them
|
message.Respond(library.Success, hostName, information)
|
||||||
information.Outbox <- library.InterServiceMessage{
|
case 1:
|
||||||
MessageType: 0,
|
// A service would like to register a new OAuth entry
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
// Validate the scopes
|
||||||
ForServiceID: message.ServiceID,
|
clientKeyShare, scopes, err := checkScopes(message.Message.(authLibrary.OAuthInformation).Scopes)
|
||||||
Message: hostName,
|
if err != nil {
|
||||||
SentAt: time.Now(),
|
message.Respond(library.BadRequest, err, information)
|
||||||
}
|
return
|
||||||
case 1:
|
|
||||||
// A service would like to register a new OAuth entry
|
|
||||||
// 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() {
|
|
||||||
// Don't complain, it's fine
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the scopes
|
|
||||||
var clientKeyShare bool
|
|
||||||
for _, scope := range message.Message.(authLibrary.OAuthInformation).Scopes {
|
|
||||||
if scope != "openid" && scope != "clientKeyShare" {
|
|
||||||
information.Outbox <- library.InterServiceMessage{
|
|
||||||
MessageType: 2,
|
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
|
||||||
ForServiceID: message.ServiceID,
|
|
||||||
Message: "Invalid scope",
|
|
||||||
SentAt: time.Now(),
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
if scope == "clientKeyShare" {
|
|
||||||
clientKeyShare = true
|
|
||||||
} else if scope != "openid" {
|
|
||||||
logFunc("An impossible logic error has occurred, please move away from radiation or use ECC RAM", 1, information)
|
|
||||||
information.Outbox <- library.InterServiceMessage{
|
|
||||||
MessageType: 2,
|
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
|
||||||
ForServiceID: message.ServiceID,
|
|
||||||
Message: "Invalid scope",
|
|
||||||
SentAt: time.Now(),
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal the scopes
|
|
||||||
scopes, err := json.Marshal(message.Message.(authLibrary.OAuthInformation).Scopes)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 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, information)
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Respond(library.Success, authLibrary.OAuthResponse{
|
||||||
|
AppID: appId,
|
||||||
|
SecretKey: secret,
|
||||||
|
}, information)
|
||||||
|
|
||||||
|
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, information)
|
||||||
|
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, information)
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the appId and secret
|
||||||
|
message.Respond(library.Success, authLibrary.OAuthResponse{
|
||||||
|
AppID: appId,
|
||||||
|
SecretKey: secret,
|
||||||
|
}, information)
|
||||||
|
case 2:
|
||||||
|
// A service would like to have the public key
|
||||||
|
// Send it to them
|
||||||
|
message.Respond(library.Success, publicKey, information)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// @license magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt LGPL-3.0
|
// @license magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt LGPL-3.0
|
||||||
// This sad excuse for a script is used so LibreJS doesn't scream at me
|
// This sad excuse for a script is used so LibreJS doesn't scream at me
|
||||||
|
|
||||||
localStorage.clear()
|
localStorage.removeItem("DONOTSHARE-clientKey")
|
||||||
|
localStorage.removeItem("DONOTSHARE-secretKey")
|
||||||
window.location.replace("/login" + window.location.search)
|
window.location.replace("/login" + window.location.search)
|
||||||
|
|
||||||
// @license-end
|
// @license-end
|
|
@ -192,6 +192,7 @@ func main() {
|
||||||
signupBody := map[string]interface{}{
|
signupBody := map[string]interface{}{
|
||||||
"username": username,
|
"username": username,
|
||||||
"signature": base64.StdEncoding.EncodeToString(signature),
|
"signature": base64.StdEncoding.EncodeToString(signature),
|
||||||
|
"verifier": responseMap["verifier"].(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal the body
|
// Marshal the body
|
||||||
|
|
|
@ -236,7 +236,7 @@ func main() {
|
||||||
// Redirect to the client key exchange endpoint
|
// Redirect to the client key exchange endpoint
|
||||||
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
|
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
|
||||||
time.Sleep(sleepTime)
|
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
|
return
|
||||||
} else if response.StatusCode != 500 {
|
} else if response.StatusCode != 500 {
|
||||||
statusBox.Set("innerText", responseMap["error"].(string))
|
statusBox.Set("innerText", responseMap["error"].(string))
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
library "git.ailur.dev/ailur/fg-library/v2"
|
library "git.ailur.dev/ailur/fg-library/v3"
|
||||||
nucleusLibrary "git.ailur.dev/ailur/fg-nucleus-library"
|
nucleusLibrary "git.ailur.dev/ailur/fg-nucleus-library"
|
||||||
|
"io"
|
||||||
|
|
||||||
"errors"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -18,47 +19,40 @@ var ServiceInformation = library.Service{
|
||||||
Name: "Storage",
|
Name: "Storage",
|
||||||
Permissions: library.Permissions{
|
Permissions: library.Permissions{
|
||||||
Authenticate: false, // This service does not require authentication
|
Authenticate: false, // This service does not require authentication
|
||||||
|
Router: false, // This service does not serve web pages
|
||||||
Database: true, // This service requires database access to store quotas
|
Database: true, // This service requires database access to store quotas
|
||||||
BlobStorage: false, // This service *is* the blob storage
|
BlobStorage: false, // This service *is* the blob storage
|
||||||
InterServiceCommunication: true, // This service does require inter-service communication
|
InterServiceCommunication: true, // This service does require inter-service communication
|
||||||
|
Resources: false, // This service does not require access to its resource directory
|
||||||
},
|
},
|
||||||
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000003"),
|
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000003"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var conn library.Database
|
var (
|
||||||
|
loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002")
|
||||||
|
)
|
||||||
|
|
||||||
func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
|
func logFunc(message string, messageType library.MessageCode, information *library.ServiceInitializationInformation) {
|
||||||
// Log the error message to the logger service
|
// Log the message to the logger service
|
||||||
information.Outbox <- library.InterServiceMessage{
|
information.SendISMessage(loggerService, messageType, message)
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
|
||||||
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service
|
|
||||||
MessageType: messageType,
|
|
||||||
SentAt: time.Now(),
|
|
||||||
Message: 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
|
// Respond with an error message
|
||||||
var err uint64 = 1
|
var errCode = library.BadRequest
|
||||||
if myFault {
|
if myFault {
|
||||||
// Log the error message to the logger service
|
// Log the error message to the logger service
|
||||||
logFunc(message, 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
err = 2
|
errCode = library.InternalError
|
||||||
}
|
|
||||||
information.Outbox <- library.InterServiceMessage{
|
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
|
||||||
ForServiceID: serviceID,
|
|
||||||
MessageType: err,
|
|
||||||
SentAt: time.Now(),
|
|
||||||
Message: errors.New(message),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message.Respond(errCode, err, information)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkUserExists(userID uuid.UUID) bool {
|
func checkUserExists(userID uuid.UUID, conn library.Database) bool {
|
||||||
// Check if a user exists in the database
|
// Check if a user exists in the database
|
||||||
var userCheck []byte
|
var userCheck []byte
|
||||||
err := conn.DB.QueryRow("SELECT id FROM users WHERE id = $1", userID).Scan(&userCheck)
|
err := conn.DB.QueryRow("SELECT id FROM users WHERE id = $1", userID[:]).Scan(&userCheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return false
|
return false
|
||||||
|
@ -66,74 +60,99 @@ func checkUserExists(userID uuid.UUID) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return uuid.Must(uuid.FromBytes(userCheck)) == userID
|
return bytes.Equal(userCheck, userID[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addQuota can be used with a negative quota to remove quota from a user
|
// addQuota can be used with a negative quota to remove quota from a user
|
||||||
func addQuota(information library.ServiceInitializationInformation, message library.InterServiceMessage) {
|
func addQuota(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) {
|
||||||
// Add more quota to a user
|
// Add more quota to a user
|
||||||
if checkUserExists(message.Message.(nucleusLibrary.Quota).User) {
|
userID := message.Message.(nucleusLibrary.Quota).User
|
||||||
|
if checkUserExists(userID, conn) {
|
||||||
_, err := conn.DB.Exec("UPDATE users SET quota = quota + $1 WHERE id = $2", message.Message.(nucleusLibrary.Quota).Bytes, message.Message.(nucleusLibrary.Quota).User)
|
_, 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 {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", message.Message.(nucleusLibrary.Quota).User, int64(information.Configuration["defaultQuota"].(float64))+message.Message.(nucleusLibrary.Quota).Bytes)
|
_, 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 {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
message.Respond(library.Success, nil, information)
|
||||||
}
|
}
|
||||||
|
|
||||||
// And so does addReserved
|
// And so does addReserved
|
||||||
func addReserved(information library.ServiceInitializationInformation, message library.InterServiceMessage) {
|
func addReserved(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) {
|
||||||
// Add more reserved space to a user
|
// Add more reserved space to a user
|
||||||
if checkUserExists(message.Message.(nucleusLibrary.Quota).User) {
|
userID := message.Message.(nucleusLibrary.Quota).User
|
||||||
|
if checkUserExists(userID, conn) {
|
||||||
// Check if the user has enough space
|
// Check if the user has enough space
|
||||||
quota, err := getQuota(message.Message.(nucleusLibrary.Quota).User)
|
quota, err := getQuota(information, userID, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
used, err := getUsed(message.Message.(nucleusLibrary.Quota).User, information)
|
used, err := getUsed(userID, information, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
if used+message.Message.(nucleusLibrary.Quota).Bytes > quota {
|
if used+message.Message.(nucleusLibrary.Quota).Bytes > quota {
|
||||||
respondError("insufficient storage", information, false, message.ServiceID)
|
respondError(message, errors.New("insufficient storage"), information, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = conn.DB.Exec("UPDATE users SET reserved = reserved + $1 WHERE id = $2", message.Message.(nucleusLibrary.Quota).Bytes, message.Message.(nucleusLibrary.Quota).User)
|
_, err = conn.DB.Exec("UPDATE users SET reserved = reserved + $1 WHERE id = $2", message.Message.(nucleusLibrary.Quota).Bytes, userID[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check if the user has enough space
|
// Check if the user has enough space
|
||||||
if int64(information.Configuration["defaultQuota"].(float64)) < message.Message.(nucleusLibrary.Quota).Bytes {
|
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
|
return
|
||||||
}
|
}
|
||||||
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, $3)", message.Message.(nucleusLibrary.Quota).User, int64(information.Configuration["defaultQuota"].(float64)), message.Message.(nucleusLibrary.Quota).Bytes)
|
_, 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 {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
message.Respond(library.Success, nil, information)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getQuota(userID uuid.UUID) (int64, error) {
|
func getQuota(information *library.ServiceInitializationInformation, userID uuid.UUID, conn library.Database) (int64, error) {
|
||||||
// Get the quota for a user
|
// Get the quota for a user
|
||||||
var quota int64
|
var quota int64
|
||||||
err := conn.DB.QueryRow("SELECT quota FROM users WHERE id = $1", userID).Scan("a)
|
err := conn.DB.QueryRow("SELECT quota FROM users WHERE id = $1", userID[:]).Scan("a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int)))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int64(information.Configuration["defaultQuota"].(int)), nil
|
||||||
|
} else {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return quota, nil
|
return quota, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUsed(userID uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
|
func getUsed(userID uuid.UUID, information *library.ServiceInitializationInformation, conn library.Database) (int64, error) {
|
||||||
// Get the used space for a user by first getting the reserved space from file storage
|
// Get the used space for a user by first getting the reserved space from file storage
|
||||||
|
_, err := os.Stat(filepath.Join(information.Configuration["path"].(string), userID.String()))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// Create the directory
|
||||||
|
err = os.Mkdir(filepath.Join(information.Configuration["path"].(string), userID.String()), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var used int64
|
var used int64
|
||||||
err := filepath.Walk(filepath.Join(information.Configuration["path"].(string), userID.String()), func(path string, entry os.FileInfo, err error) error {
|
err = filepath.Walk(filepath.Join(information.Configuration["path"].(string), userID.String()), func(path string, entry os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -148,147 +167,167 @@ func getUsed(userID uuid.UUID, information library.ServiceInitializationInformat
|
||||||
|
|
||||||
// Then add the reserved space from the database
|
// Then add the reserved space from the database
|
||||||
var reserved int64
|
var reserved int64
|
||||||
err = conn.DB.QueryRow("SELECT reserved FROM users WHERE id = $1", userID).Scan(&reserved)
|
err = conn.DB.QueryRow("SELECT reserved FROM users WHERE id = $1", userID[:]).Scan(&reserved)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int)))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
} else {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return used + reserved, nil
|
return used + reserved, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func modifyFile(information library.ServiceInitializationInformation, message library.InterServiceMessage) {
|
func modifyFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) {
|
||||||
// Check if the file already exists
|
// Check if the file already exists
|
||||||
path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name)
|
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)
|
_, err := os.Stat(path)
|
||||||
if os.IsNotExist(err) {
|
if err == nil {
|
||||||
// Delete the file
|
// Delete the file
|
||||||
err = os.Remove(path)
|
err = os.Remove(path)
|
||||||
if err != nil {
|
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
|
// Check if the user has enough space
|
||||||
quota, err := getQuota(message.Message.(nucleusLibrary.File).User)
|
quota, err := getQuota(information, message.Message.(nucleusLibrary.File).User, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
used, err := getUsed(message.Message.(nucleusLibrary.File).User, information)
|
used, err := getUsed(message.Message.(nucleusLibrary.File).User, information, conn)
|
||||||
if err != nil {
|
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 {
|
if used+message.Message.(nucleusLibrary.File).Reader.N > quota {
|
||||||
respondError("insufficient storage", information, false, message.ServiceID)
|
respondError(message, errors.New("insufficient storage"), information, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a file to the user's storage
|
// Add a file to the user's storage
|
||||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the file
|
// Write the file
|
||||||
_, err = file.Write(message.Message.([]byte))
|
_, err = io.Copy(file, message.Message.(nucleusLibrary.File).Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the file
|
// Close the file
|
||||||
err = file.Close()
|
err = file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
message.Respond(library.Success, nil, information)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFile(information library.ServiceInitializationInformation, message library.InterServiceMessage) {
|
func getFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage) {
|
||||||
// Check if the file exists
|
// Check if the file exists
|
||||||
path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name)
|
path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name)
|
||||||
|
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
respondError("file not found", information, false, message.ServiceID)
|
println("file not found: " + path)
|
||||||
|
respondError(message, errors.New("file not found"), information, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the file
|
// Open the file
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(err.Error(), information, true, message.ServiceID)
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with the file
|
// Respond with the file
|
||||||
// It's their responsibility to close the file
|
// It's their responsibility to close the file
|
||||||
information.Outbox <- library.InterServiceMessage{
|
message.Respond(library.Success, file, information)
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
}
|
||||||
ForServiceID: message.ServiceID,
|
|
||||||
MessageType: 1,
|
func deleteFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage) {
|
||||||
SentAt: time.Now(),
|
// Check if the file exists
|
||||||
Message: file,
|
path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name)
|
||||||
|
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
respondError(message, errors.New("file not found"), information, false)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete the file
|
||||||
|
err = os.Remove(path)
|
||||||
|
if err != nil {
|
||||||
|
respondError(message, err, information, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
message.Respond(library.Success, nil, information)
|
||||||
}
|
}
|
||||||
|
|
||||||
// processInterServiceMessages listens for incoming messages and processes them
|
// processInterServiceMessages listens for incoming messages and processes them
|
||||||
func processInterServiceMessages(information library.ServiceInitializationInformation) {
|
func processInterServiceMessages(information *library.ServiceInitializationInformation, conn library.Database) {
|
||||||
// Listen for incoming messages
|
// Listen for incoming messages
|
||||||
for {
|
for {
|
||||||
message := <-information.Inbox
|
message := information.AcceptMessage()
|
||||||
switch message.MessageType {
|
switch message.MessageType {
|
||||||
case 1:
|
case 1:
|
||||||
// Add quota
|
// Add quota
|
||||||
addQuota(information, message)
|
addQuota(information, message, conn)
|
||||||
case 2:
|
case 2:
|
||||||
// Add reserved
|
// Add reserved
|
||||||
addReserved(information, message)
|
addReserved(information, message, conn)
|
||||||
case 3:
|
case 3:
|
||||||
// Modify file
|
// Modify file
|
||||||
modifyFile(information, message)
|
modifyFile(information, message, conn)
|
||||||
case 4:
|
case 4:
|
||||||
// Get file
|
// Get file
|
||||||
getFile(information, message)
|
getFile(information, message)
|
||||||
|
case 5:
|
||||||
|
deleteFile(information, message)
|
||||||
default:
|
default:
|
||||||
// Respond with an error message
|
// 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) {
|
func Main(information *library.ServiceInitializationInformation) {
|
||||||
// Initiate a connection to the database
|
// Start up the ISM processor
|
||||||
// Call service ID 1 to get the database connection information
|
go information.StartISProcessor()
|
||||||
information.Outbox <- library.InterServiceMessage{
|
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
// Get the database connection
|
||||||
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), // Service initialization service
|
conn, err := information.GetDatabase()
|
||||||
MessageType: 1, // Request connection information
|
if err != nil {
|
||||||
SentAt: time.Now(),
|
logFunc(err.Error(), 3, information)
|
||||||
Message: nil,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the response
|
// Create the quotas table if it doesn't exist
|
||||||
response := <-information.Inbox
|
if conn.DBType == library.Sqlite {
|
||||||
if response.MessageType == 2 {
|
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY, quota BIGINT, reserved BIGINT)")
|
||||||
// This is the connection information
|
if err != nil {
|
||||||
// Set up the database connection
|
logFunc(err.Error(), 3, information)
|
||||||
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 quotas (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 UUID PRIMARY KEY, quota BIGINT, reserved BIGINT)")
|
|
||||||
if err != nil {
|
|
||||||
logFunc(err.Error(), 3, information)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is an error message
|
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY, quota BIGINT, reserved BIGINT)")
|
||||||
// Log the error message to the logger service
|
if err != nil {
|
||||||
logFunc(response.Message.(error).Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for incoming messages
|
// Listen for incoming messages
|
||||||
go processInterServiceMessages(information)
|
go processInterServiceMessages(information, conn)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue