Compare commits

..

No commits in common. "master" and "v0.3.2" have entirely different histories.

13 changed files with 1497 additions and 1221 deletions

View file

@ -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 -C "$path" --ldflags "-s -w" -o "$path/fulgens" || exit 1 go build --ldflags "-s -w" -o "$path/fulgens" || exit 1
clear clear
fancy "\033[1;102m" "Fulgens has been built successfully!" fancy "\033[1;102m" "Fulgensfas has been built successfully!"

View file

@ -4,11 +4,15 @@
global: { global: {
# IP defines the IP address to bind to. # IP defines the IP address to bind to.
ip: "0.0.0.0", ip: "0.0.0.0",
# httpPort defines the port to bind to for HTTP.
httpPort: "8080",
# httpsPort defines the port to bind to for HTTPS (TLS).
httpsPort: "8443",
# serviceDirectory defines the directory to look for services in. # serviceDirectory defines the directory to look for services in.
serviceDirectory: "./services", serviceDirectory: "./services",
# resourceDirectory defines the directory to look for resources in. # resourceDirectory defines the directory to look for resources in.
resourceDirectory: "./resources", resourceDirectory: "./resources",
# compression defines the compression settings on a global level - per-route settings override these. It is optional. # compression defines the compression settings on a global level - per-route settings override these.
compression: { compression: {
# algorithm defines the compression algorithm to use, possible values are "gzip", "brotli" and "zstd". # algorithm defines the compression algorithm to use, possible values are "gzip", "brotli" and "zstd".
algorithm: "gzip", algorithm: "gzip",
@ -30,23 +34,6 @@ global: {
path: "./databases", path: "./databases",
# connectionString defines the connection string to use for the database (postgres only). # connectionString defines the connection string to use for the database (postgres only).
connectionString: "postgres://user:password@localhost:5432/database" connectionString: "postgres://user:password@localhost:5432/database"
},
# stealth enables stealth mode, which makes the server look like some preset http servers.
# stealth mode overrides all proxy preservations and headers.
stealth: {
# enabled defines whether stealth mode is enabled.
enabled: true,
# server defines the server to pretend to be, possible values are "nginx" or "net/http".
server: "nginx",
# php defines if the server should pretend to be running PHP. This should only be used on nginx.
php: {
# enabled defines whether PHP spoofing is enabled.
enabled: true,
# version defines the version of PHP to pretend to be.
version: "8.2.25"
},
# aspnet defines if the server should pretend to be running ASP.NET. This should only be used on nginx.
aspNet: true
} }
} }
@ -55,8 +42,6 @@ routes: [
{ {
# none is a special subdomain that matches all requests without a subdomain (Host header). # none is a special subdomain that matches all requests without a subdomain (Host header).
subdomain: "none", subdomain: "none",
# port defines the port to use for this route. They do not have to be unique.
port: "8080",
# services defines the services to use for this route. Services must be defined on a per-subdomain basis. # services defines the services to use for this route. Services must be defined on a per-subdomain basis.
# Each service may not be used more than once globally. The server will fail to start if this is violated. # Each service may not be used more than once globally. The server will fail to start if this is violated.
services: ["authentication"] services: ["authentication"]
@ -64,11 +49,7 @@ routes: [
{ {
# any subdomain value that isn't "none" will match that specific subdomain. # any subdomain value that isn't "none" will match that specific subdomain.
subdomain: "www.localhost", subdomain: "www.localhost",
# port defines the port to use for this route. They do not have to be unique. # https defines the HTTPS settings for this route.
port: "8443",
# https defines the HTTPS settings for this route. If this block is missing, HTTPS will not be enabled for this port.
# If https is set once for any subdomain with this port, it will be enabled for all subdomains with this port.
# The connection will fail if the above condition is true, but there is not an HTTPS block for that subdomain.
https: { https: {
# certificate defines the path to the certificate file. # certificate defines the path to the certificate file.
certificate: "./certs/localhost.crt", certificate: "./certs/localhost.crt",
@ -78,10 +59,10 @@ routes: [
# paths defines per-path settings (NOT for services, which MUST be defined on a per-subdomain basis). # paths defines per-path settings (NOT for services, which MUST be defined on a per-subdomain basis).
paths: [ paths: [
{ {
# paths defines the paths to match. They can contain wildcards. # path defines the path to match. They can contain wildcards.
paths: ["/static", "/static/*"], path: "/static/*",
# static defines the static file serving settings for this path. This conflicts with proxy and redirect. # static defines the static file serving settings for this path. This conflicts with proxy.
# static > proxy > redirect in terms of precedence. # If both proxy and static are defined, static will take precedence.
static: { static: {
# root defines the root directory to serve static files from. # root defines the root directory to serve static files from.
root: "./static", root: "./static",
@ -91,41 +72,19 @@ routes: [
} }
}, },
{ {
# paths defines the paths to match. They can contain wildcards. # path defines the path to match. They can contain wildcards.
paths: ["/proxy", "/proxy/*"], path: "/proxy/*",
# proxy defines the proxy settings for this path. This conflicts with static and redirect. # proxy defines the proxy settings for this path. This conflicts with static.
# static > proxy > redirect in terms of precedence. # If both proxy and static are defined, static will take precedence.
proxy: { proxy: {
# url defines the URL to proxy requests to. # url defines the URL to proxy requests to.
url: "http://localhost:8000", url: "http://localhost:8000",
# stripPrefix defines whether to strip the prefix from the path before proxying. # stripPrefix defines whether to strip the prefix from the path before proxying.
stripPrefix: true, stripPrefix: true
headers: { # TODO: In a future update, passing X-Forwarded-For and X-Forwarded-Proto headers will be supported.
# forbid defines the headers to forbid from being sent to the proxied server. # TODO: In a future update, forbidding certain headers from being passed will be supported.
forbid: [ "User-Agent" ], # TODO: In a future update, passing X-Powered-By and Server headers will be supported.
# preserveServer defines whether to preserve the server header from the proxied server. # TODO: In a future update, passing Host header will be supported.
preserveServer: true,
# preserveAltSvc defines whether to preserve the Alt-Svc header from the proxied server.
preserveAltSvc: true,
# preserveXPoweredBy defines whether to preserve the X-Powered-By header from the proxied server.
preserveXPoweredBy: true,
# passHost defines whether the host / :authority header should be sent to the proxied server.
passHost: true,
# xForward defines whether to send the X-Forwarded-For and X-Forwarded-Proto headers.
xForward: false
}
},
},
{
# paths defines the paths to match. They can contain wildcards.
paths: ["/redirect", "/redirect/*"],
# redirect defines the redirect settings for this path. This conflicts with proxy and static.
# static > proxy > redirect in terms of precedence.
redirect: {
# url defines the URL to redirect to.
url: "https://www.ailur.dev",
# permanent defines whether the redirect is permanent (301) or temporary (302).
permanent: true
} }
} }
] ]

34
go.mod
View file

@ -1,35 +1,33 @@
module git.ailur.dev/ailur/fulgens module git.ailur.dev/ailur/fulgens
go 1.23.3 go 1.23.1
require ( require (
git.ailur.dev/ailur/fg-library/v3 v3.6.2 git.ailur.dev/ailur/fg-library/v2 v2.1.1
git.ailur.dev/ailur/fg-nucleus-library v1.2.2 git.ailur.dev/ailur/fg-nucleus-library v1.0.3
git.ailur.dev/ailur/pow v1.0.3 git.ailur.dev/ailur/pow v1.0.2
github.com/CAFxX/httpcompression v0.0.9 github.com/andybalholm/brotli v1.1.1
github.com/cespare/xxhash/v2 v2.3.0 github.com/cespare/xxhash/v2 v2.3.0
github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/chi/v5 v5.1.0
github.com/go-playground/validator/v10 v10.25.0 github.com/go-playground/validator/v10 v10.22.1
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.18.0 github.com/klauspost/compress v1.17.11
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.28.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/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.1.0 // 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/stretchr/testify v1.9.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect golang.org/x/net v0.30.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/net v0.37.0 // indirect golang.org/x/text v0.19.0 // indirect
golang.org/x/sys v0.31.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
golang.org/x/text v0.23.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
) )

85
go.sum
View file

@ -1,87 +1,60 @@
git.ailur.dev/ailur/fg-library/v3 v3.6.2 h1:PNJKxpvbel2iDeB9+/rpYRyMoim6JjRHOXPYFYky7Ng= git.ailur.dev/ailur/fg-library/v2 v2.1.1 h1:49NgKud/gJjlTPZQfrZkmx4b/IEa3ZkO/NNM8QGA3qk=
git.ailur.dev/ailur/fg-library/v3 v3.6.2/go.mod h1:ArNsafpqES2JuxQM5aM+bNe0FwHLIsL6pbjpiWvDwGs= git.ailur.dev/ailur/fg-library/v2 v2.1.1/go.mod h1:Il3+GZ7tClNx4/QSt3eY4tR7NdvO+Qf00J0PKkxPrl4=
git.ailur.dev/ailur/fg-nucleus-library v1.2.2 h1:JbclmxGSoL+ByGZAl0W6PqWRoyBBGTrKrizWDJ7rdI0= git.ailur.dev/ailur/fg-nucleus-library v1.0.3 h1:C0xgfZg7bkULhh9Ci7ZoAcx4QIqxLh+QW9/ng1kKTFU=
git.ailur.dev/ailur/fg-nucleus-library v1.2.2/go.mod h1:stxiTyMv3Fa7GzpyLbBUh3ahlb7110p0NnCl8ZTjwBs= git.ailur.dev/ailur/fg-nucleus-library v1.0.3/go.mod h1:RbBVFRwtQgYvCWoru1mC3vUJ1dMftkNbvd7hVFtREFw=
git.ailur.dev/ailur/pow v1.0.3 h1:LjLSol4ax+M+SoajVjbBoDjfmjH6pKu3fDka7bl2KGY= git.ailur.dev/ailur/pow v1.0.2 h1:8tb6mXZdyQYjrKRW+AUmWMi5wJoHh9Ch3oRqiJr/ivs=
git.ailur.dev/ailur/pow v1.0.3/go.mod h1:ClAmIdHQ/N9wTq5S4YWhQ5d9CPUBcEjVuOkT07zBdJ4= git.ailur.dev/ailur/pow v1.0.2/go.mod h1:fjFb1z5KtF6V14HRhGWiDmmJKggO8KyAP20Lr5OJI/g=
github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWGKSqgrg=
github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 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.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1 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/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s=
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.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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5 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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
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-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

1206
main.go

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ package main
import ( import (
// Fulgens libraries // Fulgens libraries
library "git.ailur.dev/ailur/fg-library/v3" library "git.ailur.dev/ailur/fg-library/v2"
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"
@ -25,6 +25,9 @@ import (
"io/fs" "io/fs"
"net/http" "net/http"
// Extra libraries
"golang.org/x/crypto/argon2"
// External libraries // External libraries
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
@ -36,7 +39,6 @@ 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
@ -45,34 +47,17 @@ var ServiceInformation = library.Service{
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"), ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"),
} }
var ( var serviceIDBytes []byte
loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002")
)
func checkScopes(scopes []string) (bool, string, error) { func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
var clientKeyShare bool
for _, scope := range scopes {
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 // Log the message to the logger service
information.SendISMessage(loggerService, messageType, message) information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service
MessageType: messageType,
SentAt: time.Now(),
Message: message,
}
} }
func ensureTrailingSlash(url string) string { func ensureTrailingSlash(url string) string {
@ -108,7 +93,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
@ -133,7 +118,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))
@ -142,7 +127,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)
@ -188,12 +173,11 @@ 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)
@ -205,81 +189,109 @@ 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)
// Start the ISM processor var err error
go information.StartISProcessor() serviceIDBytes, err = ServiceInformation.ServiceID.MarshalBinary()
// Initiate a connection to the database
conn, err := information.GetDatabase()
if err != nil { if err != nil {
logFunc(err.Error(), 3, information) logFunc(err.Error(), 3, information)
} }
if conn.DBType == library.Sqlite {
// Create the global table // Initiate a connection to the database
// Uniqueness check is a hack to ensure we only have one global row // Call service ID 1 to get the database connection information
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)") 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
response := <-information.Inbox
if response.MessageType == 2 {
// This is the connection information
// Set up the database connection
conn = response.Message.(library.Database)
if conn.DBType == library.Sqlite {
// Create the global table
// Uniqueness check is a hack to ensure we only have one global row
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the users table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BLOB NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the oauth table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BLOB NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')")
if err != nil {
logFunc(err.Error(), 3, information)
}
} else {
// Create the global table
// Uniqueness check is a hack to ensure we only have one global row
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BYTEA NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the users table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BYTEA NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the oauth table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BYTEA NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')")
if err != nil {
logFunc(err.Error(), 3, information)
}
}
// Set up the in-memory cache
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)
} }
// Create the users table // Drop the tables if they exist
_, 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)") _, err = mem.Exec("DROP TABLE IF EXISTS sessions")
if err != nil { if err != nil {
logFunc(err.Error(), 3, information) logFunc(err.Error(), 3, information)
} }
// Create the oauth table _, err = mem.Exec("DROP TABLE IF EXISTS logins")
_, 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)
}
_, 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 {
// Create the global table // This is an error message
// Uniqueness check is a hack to ensure we only have one global row // Log the error message to the logger service
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BYTEA NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)") logFunc(response.Message.(error).Error(), 3, information)
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
@ -313,10 +325,15 @@ 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)", serviceIDBytes, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
} else { } else {
testAppCreator := uuid.New() testAppCreator, err := uuid.New().MarshalBinary()
_, 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 {
testAppIsAvailable = false
logFunc(err.Error(), 2, information)
}
_, 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
@ -390,30 +407,56 @@ 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") != "" {
var name string if conn.DBType == library.Sqlite {
var creator []byte var name string
err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator) var creator []byte
if err != nil { err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
if errors.Is(err, sql.ErrNoRows) { if err != nil {
renderString(404, w, "App not found", information) if errors.Is(err, sql.ErrNoRows) {
} else { renderString(404, w, "App not found", information)
logFunc(err.Error(), 2, information) } else {
renderString(500, w, "Sorry, something went wrong on our end. Error code: 02. Please report to the administrator.", information) logFunc(err.Error(), 2, information)
renderString(500, w, "Sorry, something went wrong on our end. Error code: 02. Please report to the administrator.", information)
}
return
} }
return
}
// Check if the app is internal if !bytes.Equal(creator, serviceIDBytes) {
if !bytes.Equal(creator, ServiceInformation.ServiceID[:]) { renderTemplate(200, w, map[string]interface{}{
renderTemplate(200, w, map[string]interface{}{ "identifier": identifier,
"identifier": identifier, "name": name,
"name": name, }, "authorize.html", information)
}, "authorize.html", information) } else {
renderTemplate(200, w, map[string]interface{}{
"identifier": identifier,
"name": name,
}, "autoAccept.html", information)
}
} else { } else {
renderTemplate(200, w, map[string]interface{}{ var name string
"identifier": identifier, var creator uuid.UUID
"name": name, err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
}, "autoAccept.html", information) if err != nil {
if errors.Is(err, sql.ErrNoRows) {
renderString(404, w, "App not found", information)
} else {
logFunc(err.Error(), 2, information)
renderString(500, w, "Sorry, something went wrong on our end. Error code: 03. Please report to the administrator.", information)
}
return
}
if 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)
@ -489,8 +532,8 @@ func Main(information *library.ServiceInitializationInformation) {
router.Post("/api/changePassword", func(w http.ResponseWriter, r *http.Request) { router.Post("/api/changePassword", func(w http.ResponseWriter, r *http.Request) {
type changePassword struct { type changePassword struct {
Session string `json:"session"` Session string `json:"session"`
NewPublicKey string `json:"newPublicKey"` NewPassword string `json:"newPassword"`
} }
var data changePassword var data changePassword
err = json.NewDecoder(r.Body).Decode(&data) err = json.NewDecoder(r.Body).Decode(&data)
@ -507,14 +550,34 @@ func Main(information *library.ServiceInitializationInformation) {
return return
} }
// Update the public key // Generate a new salt
_, err = conn.DB.Exec("UPDATE users SET publicKey = $1 WHERE id = $2", data.NewPublicKey, userId) // We want it to be binary data, not alphanumerical, so we don't use randomChars
salt := make([]byte, 16)
_, err = rand.Read(salt)
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "04"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "04"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
return return
} }
// Decode the new password
newPassword, err := base64.StdEncoding.DecodeString(data.NewPassword)
if err != nil {
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
return
}
// Hash the password
hashedPassword := argon2.IDKey(newPassword, salt, 64, 4096, 1, 32)
// Update the password
_, err = conn.DB.Exec("UPDATE users SET password = $1, salt = $2 WHERE id = $3", hashedPassword, salt, userId)
if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "05"}, information)
logFunc(err.Error(), 2, information)
return
}
// Invalidate all sessions // Invalidate all sessions
_, err = mem.Exec("DELETE FROM sessions WHERE id = ?", userId) _, err = mem.Exec("DELETE FROM sessions WHERE id = ?", userId)
if err != nil { if err != nil {
@ -587,14 +650,14 @@ func Main(information *library.ServiceInitializationInformation) {
} }
// Try to insert the user // Try to insert the user
userID := uuid.New() userID, err := uuid.New().MarshalBinary()
if err != nil { if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "08"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "08"}, information)
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
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)
@ -616,13 +679,37 @@ 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 {
@ -631,27 +718,22 @@ func Main(information *library.ServiceInitializationInformation) {
return return
} }
// Issue a new JWT token with the challenge // Insert the challenge with one minute expiration
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{ _, err = mem.Exec("INSERT INTO challengeResponse (challenge, userId, expires) VALUES (?, ?, ?)", challenge, userId, time.Now().Unix()+60)
"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": "51"}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "53"}, 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, "verifier": tokenString}, information) renderJSON(200, w, map[string]interface{}{"challenge": challenge}, 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
@ -663,8 +745,8 @@ func Main(information *library.ServiceInitializationInformation) {
// Try to select the user // Try to select the user
var userId []byte var userId []byte
var userPublicKey []byte var publicKey []byte
err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &userPublicKey) err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &publicKey)
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)
@ -683,43 +765,33 @@ func Main(information *library.ServiceInitializationInformation) {
} }
// Verify the challenge // Verify the challenge
token, err := jwt.Parse(data.Verifier, func(token *jwt.Token) (interface{}, error) { // Select the current challenge from the database
return publicKey, nil var challenge string
}) err = mem.QueryRow("SELECT challenge FROM challengeResponse WHERE userId = ?", userId).Scan(&challenge)
if err != nil { if err != nil {
renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier"}, information) if errors.Is(err, sql.ErrNoRows) {
return renderJSON(401, w, map[string]interface{}{"error": "Invalid challenge"}, information)
} } else {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "52"}, information)
claims, ok := token.Claims.(jwt.MapClaims) logFunc(err.Error(), 2, information)
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(userPublicKey, []byte(challenge), signature) { if !ed25519.Verify(publicKey, []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
@ -1175,6 +1247,7 @@ 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)
@ -1210,9 +1283,27 @@ func Main(information *library.ServiceInitializationInformation) {
} }
// Validate the scopes // Validate the scopes
clientKeyShare, scopes, err := checkScopes(data.Scopes) var clientKeyShare bool
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(400, w, map[string]interface{}{"error": err.Error()}, information) renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "36"}, information)
logFunc(err.Error(), 2, information)
return return
} }
@ -1555,7 +1646,17 @@ func Main(information *library.ServiceInitializationInformation) {
if err != nil { if err != nil {
logFunc(err.Error(), 1, information) logFunc(err.Error(), 1, information)
} else { } else {
logFunc("Cleanup complete, deleted "+strconv.FormatInt(affectedCount, 10)+" entries", 0, information) affected, err := mem.Exec("DELETE FROM challengeResponse WHERE expires < ?", time.Now().Unix())
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)
}
}
} }
} }
} }
@ -1564,78 +1665,136 @@ func Main(information *library.ServiceInitializationInformation) {
go func() { go func() {
for { for {
// Wait for a message // Wait for a message
message := information.AcceptMessage() message := <-information.Inbox
// Check the message type if message.ServiceID != uuid.MustParse("00000000-0000-0000-0000-000000000001") {
switch message.MessageType { // Check the message type
case 0: switch message.MessageType {
// A service would like to have the hostname case 0:
// Send it to them // A service would like to know our hostname
message.Respond(library.Success, hostName, information) // Send it to them
case 1: information.Outbox <- library.InterServiceMessage{
// A service would like to register a new OAuth entry MessageType: 0,
// Validate the scopes ServiceID: ServiceInformation.ServiceID,
clientKeyShare, scopes, err := checkScopes(message.Message.(authLibrary.OAuthInformation).Scopes) ForServiceID: message.ServiceID,
if err != nil { Message: hostName,
message.Respond(library.BadRequest, err, information) SentAt: time.Now(),
return
}
// Check if the service already has an OAuth entry
var appId, secret string
err = conn.DB.QueryRow("SELECT appId, secret FROM oauth WHERE appId = $1", message.ServiceID.String()).Scan(&appId, &secret)
if err == nil && appId == message.ServiceID.String() {
// Update the entry to thew new scopes and redirect URI
if clientKeyShare {
_, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3, keyShareUri = $4 WHERE appId = $5", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri, message.ServiceID.String())
} else {
_, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3 WHERE appId = $4", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.ServiceID.String())
} }
case 1:
// A service would like to register a new OAuth entry
// 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 { if err != nil {
message.Respond(library.InternalError, err, information) information.Outbox <- library.InterServiceMessage{
MessageType: 1,
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
Message: "36",
SentAt: time.Now(),
}
logFunc(err.Error(), 2, information) logFunc(err.Error(), 2, information)
return return
} }
message.Respond(library.Success, authLibrary.OAuthResponse{ // Generate a new appId
AppID: appId, // It must be able to be sent via JSON, so we can't have pure-binary data
SecretKey: secret, appId, err := randomChars(32)
}, information) if err != nil {
information.Outbox <- library.InterServiceMessage{
MessageType: 1,
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
Message: "37",
SentAt: time.Now(),
}
logFunc(err.Error(), 2, information)
return
}
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
}
}
}
// Generate a new secret // Marshal the scopes
// It must be able to be sent via JSON, so we can't have pure-binary data scopes, err := json.Marshal(message.Message.(authLibrary.OAuthInformation).Scopes)
secret, err = randomChars(512) if err != nil {
if err != nil { information.Outbox <- library.InterServiceMessage{
message.Respond(library.InternalError, err, information) MessageType: 1,
logFunc(err.Error(), 2, information) ServiceID: ServiceInformation.ServiceID,
return ForServiceID: message.ServiceID,
} Message: "38",
SentAt: time.Now(),
}
logFunc(err.Error(), 2, information)
return
}
// Insert the oauth entry // Insert the oauth entry
if clientKeyShare { 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) _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ($1, $2, $3, $4, $5, $6, $7)", appId, secret, serviceIDBytes, message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri)
} else { } 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) _, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES ($1, $2, $3, $4, $5, $6)", appId, secret, serviceIDBytes, message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes)
} }
if err != nil { if err != nil {
message.Respond(library.InternalError, err, information) information.Outbox <- library.InterServiceMessage{
logFunc(err.Error(), 2, information) MessageType: 1,
return ServiceID: ServiceInformation.ServiceID,
} ForServiceID: message.ServiceID,
Message: "39",
SentAt: time.Now(),
}
logFunc(err.Error(), 2, information)
return
}
// Return the appId and secret // Return the appId and secret
message.Respond(library.Success, authLibrary.OAuthResponse{ information.Outbox <- library.InterServiceMessage{
AppID: appId, MessageType: 0,
SecretKey: secret, ServiceID: ServiceInformation.ServiceID,
}, information) ForServiceID: message.ServiceID,
case 2: Message: authLibrary.OAuthResponse{
// A service would like to have the public key AppID: appId,
// Send it to them SecretKey: secret,
message.Respond(library.Success, publicKey, information) },
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(),
}
}
} }
} }
}() }()

View file

@ -1,14 +1,14 @@
module git.ailur.dev/fulgens/services-src/auth/resources module git.ailur.dev/fulgens/services-src/auth/resources
go 1.23.3 go 1.23.0
require ( require (
git.ailur.dev/ailur/jsFetch v1.1.1 git.ailur.dev/ailur/jsFetch v1.1.1
github.com/cespare/xxhash/v2 v2.3.0 github.com/cespare/xxhash/v2 v2.3.0
golang.org/x/crypto v0.29.0 golang.org/x/crypto v0.28.0
) )
require ( require (
git.ailur.dev/ailur/jsStreams v1.2.1 // indirect git.ailur.dev/ailur/jsStreams v1.2.1 // indirect
golang.org/x/sys v0.27.0 // indirect golang.org/x/sys v0.26.0 // indirect
) )

View file

@ -1,10 +1,12 @@
git.ailur.dev/ailur/jsFetch v1.1.1 h1:kdCkrNr2mRvTG6hlK3YwnqlwfvzIQaw4z4AXLXewQ38= git.ailur.dev/ailur/jsFetch v1.1.1 h1:kdCkrNr2mRvTG6hlK3YwnqlwfvzIQaw4z4AXLXewQ38=
git.ailur.dev/ailur/jsFetch v1.1.1/go.mod h1:eaQVFOlHwcPHCqh3oyQkQrpltmILOaiA9DKq3oTHBbM= git.ailur.dev/ailur/jsFetch v1.1.1/go.mod h1:eaQVFOlHwcPHCqh3oyQkQrpltmILOaiA9DKq3oTHBbM=
git.ailur.dev/ailur/jsStreams v1.2.0 h1:BRtLEyjkUoPKPu0Y6odUbSMlKCYNyR792TYRtujKfPw=
git.ailur.dev/ailur/jsStreams v1.2.0/go.mod h1:/ZCvbUcWkZRuKIkO7jH6b5vIjzdxIOP8ET8X0src5Go=
git.ailur.dev/ailur/jsStreams v1.2.1 h1:nXZYZrxHJCVwR0Kx/X+TenMBmS6Gh8Uc2DMinbyiGoo= git.ailur.dev/ailur/jsStreams v1.2.1 h1:nXZYZrxHJCVwR0Kx/X+TenMBmS6Gh8Uc2DMinbyiGoo=
git.ailur.dev/ailur/jsStreams v1.2.1/go.mod h1:/ZCvbUcWkZRuKIkO7jH6b5vIjzdxIOP8ET8X0src5Go= git.ailur.dev/ailur/jsStreams v1.2.1/go.mod h1:/ZCvbUcWkZRuKIkO7jH6b5vIjzdxIOP8ET8X0src5Go=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View file

@ -114,13 +114,16 @@ input {
border-radius: 0; border-radius: 0;
padding: 0 10px 0 0; padding: 0 10px 0 0;
margin: 0 0 0 10px; margin: 0 0 0 10px;
background-color: var(--background);
} }
.inputBox .captchaDiv .vAlign { .inputBox .captchaDiv .vAlign {
margin-left: 5px; margin-left: 5px;
} }
.inputBox .captchaDiv .vAlign span {
font-size: 14px;
}
.inputBox input { .inputBox input {
margin-left: 5px; margin-left: 5px;
margin-right: 0; margin-right: 0;

View file

@ -1,8 +1,7 @@
// @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.removeItem("DONOTSHARE-clientKey") localStorage.clear()
localStorage.removeItem("DONOTSHARE-secretKey")
window.location.replace("/login" + window.location.search) window.location.replace("/login" + window.location.search)
// @license-end // @license-end

View file

@ -192,7 +192,6 @@ 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

View file

@ -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))

View file

@ -1,17 +1,16 @@
package main package main
import ( import (
library "git.ailur.dev/ailur/fg-library/v3"
nucleusLibrary "git.ailur.dev/ailur/fg-nucleus-library"
"io"
"bytes"
"os"
"database/sql" "database/sql"
"errors" "errors"
library "git.ailur.dev/ailur/fg-library/v2"
nucleusLibrary "git.ailur.dev/ailur/fg-nucleus-library"
"path/filepath" "path/filepath"
"os"
"time"
"github.com/go-playground/validator/v10"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -19,315 +18,442 @@ 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 ( var conn library.Database
loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002")
)
func logFunc(message string, messageType library.MessageCode, information *library.ServiceInitializationInformation) { func getQuota(user uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
// Log the message to the logger service // Get the user's quota from the database
information.SendISMessage(loggerService, messageType, message)
}
func respondError(message library.InterServiceMessage, err error, information *library.ServiceInitializationInformation, myFault bool) {
// Respond with an error message
var errCode = library.BadRequest
if myFault {
// Log the error message to the logger service
logFunc(err.Error(), 2, information)
errCode = library.InternalError
}
message.Respond(errCode, err, information)
}
func checkUserExists(userID uuid.UUID, conn library.Database) bool {
// Check if a user exists in the database
var userCheck []byte
err := conn.DB.QueryRow("SELECT id FROM users WHERE id = $1", userID[:]).Scan(&userCheck)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return false
} else {
return false
}
} else {
return bytes.Equal(userCheck, userID[:])
}
}
// addQuota can be used with a negative quota to remove quota from a user
func addQuota(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) {
// Add more quota to a 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)
if err != nil {
respondError(message, err, information, true)
}
} else {
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int))+message.Message.(nucleusLibrary.Quota).Bytes)
if err != nil {
respondError(message, err, information, true)
}
}
// Success
message.Respond(library.Success, nil, information)
}
// And so does addReserved
func addReserved(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) {
// Add more reserved space to a user
userID := message.Message.(nucleusLibrary.Quota).User
if checkUserExists(userID, conn) {
// Check if the user has enough space
quota, err := getQuota(information, userID, conn)
if err != nil {
respondError(message, err, information, true)
}
used, err := getUsed(userID, information, conn)
if err != nil {
respondError(message, err, information, true)
}
if used+message.Message.(nucleusLibrary.Quota).Bytes > quota {
respondError(message, errors.New("insufficient storage"), information, false)
return
}
_, err = conn.DB.Exec("UPDATE users SET reserved = reserved + $1 WHERE id = $2", message.Message.(nucleusLibrary.Quota).Bytes, userID[:])
if err != nil {
respondError(message, err, information, true)
}
} else {
// Check if the user has enough space
if int64(information.Configuration["defaultQuota"].(int)) < message.Message.(nucleusLibrary.Quota).Bytes {
respondError(message, errors.New("insufficient storage"), information, false)
return
}
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, $3)", userID[:], int64(information.Configuration["defaultQuota"].(int)), message.Message.(nucleusLibrary.Quota).Bytes)
if err != nil {
respondError(message, err, information, true)
}
}
// Success
message.Respond(library.Success, nil, information)
}
func getQuota(information *library.ServiceInitializationInformation, userID uuid.UUID, conn library.Database) (int64, error) {
// 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(&quota) userBytes, err := user.MarshalBinary()
if err != nil {
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
}
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
_, 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
err = filepath.Walk(filepath.Join(information.Configuration["path"].(string), userID.String()), func(path string, entry os.FileInfo, err error) error {
if err != nil {
return err
}
used += entry.Size()
return nil
})
if err != nil { if err != nil {
return 0, err return 0, err
} }
err = conn.DB.QueryRow("SELECT quota FROM quotas WHERE id = $1", userBytes).Scan(&quota)
// Then add the reserved space from the database
var reserved int64
err = conn.DB.QueryRow("SELECT reserved FROM users WHERE id = $1", userID[:]).Scan(&reserved)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { 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))) // The user has no quota set, so we'll set it to the default quota
_, err = conn.DB.Exec("INSERT INTO quotas (id, quota) VALUES ($1, $2)", userBytes, int64(information.Configuration["defaultQuota"].(float64)))
if err != nil { if err != nil {
return 0, err return 0, err
} }
return 0, nil return int64(information.Configuration["defaultQuota"].(float64)), nil
} else {
return 0, err
} }
return 0, err
} }
return used + reserved, nil return quota, nil
} }
func modifyFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) { func getUsed(user uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
// Check if the file already exists // Check the user's used space via the filesystem
path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name) var used int64
_, err := os.Stat(filepath.Join(information.Configuration["path"].(string), user.String()))
logFunc(path, 0, information) if err != nil {
if os.IsNotExist(err) {
_, err := os.Stat(path) // The user has no files stored, so we'll set it to 0
if err == nil { return 0, nil
// Delete the file }
err = os.Remove(path) return 0, err
if err != nil { } else {
respondError(message, err, information, true) err := filepath.Walk(filepath.Join(information.Configuration["path"].(string), user.String()), func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
used += info.Size()
return nil
})
if err != nil {
return 0, err
}
return used, nil
}
}
func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
// Log the error message to the logger service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service
MessageType: messageType,
SentAt: time.Now(),
Message: message,
}
}
func storeFile(file nucleusLibrary.File, serviceID uuid.UUID, information library.ServiceInitializationInformation) {
// Create a folder for the user if it doesn't exist
err := os.MkdirAll(filepath.Join(information.Configuration["path"].(string), file.User.String()), 0755)
if err != nil {
// First contact the logger service
logFunc(err.Error(), 2, information)
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 1, // An error that's not your fault
SentAt: time.Now(),
Message: err.Error(),
} }
} else if !os.IsNotExist(err) {
respondError(message, err, information, true)
} }
// Check if the user has enough space // Check if the user has enough space to store the file
quota, err := getQuota(information, message.Message.(nucleusLibrary.File).User, conn) // Get the user's used space
used, err := getUsed(file.User, information)
if err != nil { if err != nil {
respondError(message, err, information, true) // First contact the logger service
} logFunc(err.Error(), 2, information)
used, err := getUsed(message.Message.(nucleusLibrary.File).User, information, conn)
if err != nil { // Then send the error message to the requesting service
respondError(message, err, information, true) information.Outbox <- library.InterServiceMessage{
} ServiceID: ServiceInformation.ServiceID,
if used+message.Message.(nucleusLibrary.File).Reader.N > quota { ForServiceID: serviceID,
respondError(message, errors.New("insufficient storage"), information, false) MessageType: 1, // An error that's not your fault
return SentAt: time.Now(),
Message: err.Error(),
}
} }
// Add a file to the user's storage // Check if the file already exists
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) stats, err := os.Stat(filepath.Join(information.Configuration["path"].(string), file.User.String(), serviceID.String(), file.Name))
if err == nil {
// The file already exists, subtract the old file size from the user's used space
used -= stats.Size()
}
// Get the user's quota
quota, err := getQuota(file.User, information)
if err != nil { if err != nil {
respondError(message, err, information, true) // First contact the logger service
logFunc(err.Error(), 2, information)
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 1, // An error that's not your fault
SentAt: time.Now(),
Message: err.Error(),
}
}
// Check if the user has enough space to store the file
if used+int64(len(file.Bytes)) > quota {
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 3, // It's the user's fault (never say that to the customer ;P)
SentAt: time.Now(),
Message: "User has exceeded their quota",
}
}
// Create a folder within that for the service if it doesn't exist
err = os.MkdirAll(filepath.Join(information.Configuration["path"].(string), file.User.String(), serviceID.String()), 0755)
if err != nil {
// First contact the logger service
logFunc(err.Error(), 2, information)
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 1, // An error that's not your fault
SentAt: time.Now(),
Message: err.Error(),
}
}
// Store the file
fileStream, err := os.OpenFile(filepath.Join(information.Configuration["path"].(string), file.User.String(), serviceID.String(), file.Name), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
// First contact the logger service
logFunc(err.Error(), 2, information)
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 1, // An error that's not your fault
SentAt: time.Now(),
Message: err.Error(),
}
} }
// Write the file // Write the file
_, err = io.Copy(file, message.Message.(nucleusLibrary.File).Reader) _, err = fileStream.Write(file.Bytes)
if err != nil { if err != nil {
respondError(message, err, information, true) // First contact the logger service
logFunc(err.Error(), 2, information)
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 1, // An error that's not your fault
SentAt: time.Now(),
Message: err.Error(),
}
} }
// Close the file // Close the file
err = file.Close() err = fileStream.Close()
if err != nil { if err != nil {
respondError(message, err, information, true) // First contact the logger service
} logFunc(err.Error(), 2, information)
// Success // Then send the error message to the requesting service
message.Respond(library.Success, nil, information) information.Outbox <- library.InterServiceMessage{
} ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
func getFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage) { MessageType: 1, // An error that's not your fault
// Check if the file exists SentAt: time.Now(),
path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name) Message: err.Error(),
_, err := os.Stat(path)
if os.IsNotExist(err) {
println("file not found: " + path)
respondError(message, errors.New("file not found"), information, false)
return
}
// Open the file
file, err := os.Open(path)
if err != nil {
respondError(message, err, information, true)
}
// Respond with the file
// It's their responsibility to close the file
message.Respond(library.Success, file, information)
}
func deleteFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage) {
// Check if the file exists
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
func processInterServiceMessages(information *library.ServiceInitializationInformation, conn library.Database) {
// Listen for incoming messages
for {
message := information.AcceptMessage()
switch message.MessageType {
case 1:
// Add quota
addQuota(information, message, conn)
case 2:
// Add reserved
addReserved(information, message, conn)
case 3:
// Modify file
modifyFile(information, message, conn)
case 4:
// Get file
getFile(information, message)
case 5:
deleteFile(information, message)
default:
// Respond with an error message
respondError(message, errors.New("invalid message type"), information, false)
} }
} }
// Report success
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 0, // Success
SentAt: time.Now(),
Message: nil,
}
} }
func Main(information *library.ServiceInitializationInformation) { func readFile(file nucleusLibrary.File, serviceID uuid.UUID, information library.ServiceInitializationInformation) {
// Start up the ISM processor // Open the file
go information.StartISProcessor() fileStream, err := os.Open(filepath.Join(information.Configuration["path"].(string), file.User.String(), serviceID.String(), file.Name))
// Get the database connection
conn, err := information.GetDatabase()
if err != nil { if err != nil {
logFunc(err.Error(), 3, information) // First contact the logger service
logFunc(err.Error(), 2, information)
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 1, // An error that's not your fault
SentAt: time.Now(),
Message: err.Error(),
}
} }
// Create the quotas table if it doesn't exist // Return the reader
if conn.DBType == library.Sqlite { information.Outbox <- library.InterServiceMessage{
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY, quota BIGINT, reserved BIGINT)") ServiceID: ServiceInformation.ServiceID,
if err != nil { ForServiceID: serviceID,
logFunc(err.Error(), 3, information) MessageType: 0, // Success
SentAt: time.Now(),
Message: fileStream,
}
}
func removeFile(file nucleusLibrary.File, serviceID uuid.UUID, information library.ServiceInitializationInformation) {
// Remove the file
err := os.Remove(filepath.Join(information.Configuration["path"].(string), file.User.String(), serviceID.String(), file.Name))
if err != nil {
// First contact the logger service
logFunc(err.Error(), 2, information)
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 1, // An error that's not your fault
SentAt: time.Now(),
Message: err.Error(),
}
}
// Report success
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: serviceID,
MessageType: 0, // Success
SentAt: time.Now(),
Message: nil,
}
}
func Main(information library.ServiceInitializationInformation) {
go func() {
for {
message := <-information.Inbox
if message.ServiceID == uuid.MustParse("00000000-0000-0000-0000-000000000001") {
if message.MessageType == 1 {
// We've received an error message. This should never happen.
logFunc("Bit flip error: Error given to non-errored service. Move away from radiation or use ECC memory.", 3, information)
}
} else {
switch message.MessageType {
case 0:
// Insert file
validate := validator.New()
err := validate.Struct(message.Message.(nucleusLibrary.File))
if err != nil {
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
MessageType: 2, // An error that's your fault
SentAt: time.Now(),
Message: err.Error(),
}
} else {
// Store file
storeFile(message.Message.(nucleusLibrary.File), message.ServiceID, information)
}
case 1:
// Read file
validate := validator.New()
err := validate.Struct(message.Message.(nucleusLibrary.File))
if err != nil {
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
MessageType: 2, // An error that's your fault
SentAt: time.Now(),
Message: err.Error(),
}
} else {
// Read file
readFile(message.Message.(nucleusLibrary.File), message.ServiceID, information)
}
case 2:
// Remove file
validate := validator.New()
err := validate.Struct(message.Message.(nucleusLibrary.File))
if err != nil {
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
MessageType: 2, // An error that's your fault
SentAt: time.Now(),
Message: err.Error(),
}
} else {
// Remove file
removeFile(message.Message.(nucleusLibrary.File), message.ServiceID, information)
}
case 3:
// Get quota
validate := validator.New()
err := validate.Struct(message.Message.(uuid.UUID))
if err != nil {
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
MessageType: 2, // An error that's your fault
SentAt: time.Now(),
Message: err.Error(),
}
} else {
// Get quota
quota, err := getQuota(message.Message.(uuid.UUID), information)
if err != nil {
// First contact the logger service
logFunc(err.Error(), 2, information)
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
MessageType: 1, // An error that's not your fault
SentAt: time.Now(),
Message: err.Error(),
}
}
// Report success
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
MessageType: 0, // Success
SentAt: time.Now(),
Message: quota,
}
}
case 4:
// Get used
validate := validator.New()
err := validate.Struct(message.Message.(uuid.UUID))
if err != nil {
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
MessageType: 2, // An error that's your fault
SentAt: time.Now(),
Message: err.Error(),
}
} else {
// Get used
used, err := getUsed(message.Message.(uuid.UUID), information)
if err != nil {
// First contact the logger service
logFunc(err.Error(), 2, information)
// Then send the error message to the requesting service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
MessageType: 1, // An error that's not your fault
SentAt: time.Now(),
Message: err.Error(),
}
}
// Report success
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
MessageType: 0, // Success
SentAt: time.Now(),
Message: used,
}
}
}
}
}
}()
// Initiate a connection to the database
// Call service ID 1 to get the database connection information
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), // Service initialization service
MessageType: 1, // Request connection information
SentAt: time.Now(),
Message: nil,
}
// Wait for the response
response := <-information.Inbox
if response.MessageType == 2 {
// This is the connection information
// Set up the database connection
conn = response.Message.(library.Database)
// Create the quotas table if it doesn't exist
if conn.DBType == library.Sqlite {
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS quotas (id BLOB PRIMARY KEY, quota BIGINT)")
if err != nil {
logFunc(err.Error(), 3, information)
}
} else {
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS quotas (id BYTEA PRIMARY KEY, quota BIGINT)")
if err != nil {
logFunc(err.Error(), 3, information)
}
} }
} else { } else {
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY, quota BIGINT, reserved BIGINT)") // This is an error message
if err != nil { // Log the error message to the logger service
logFunc(err.Error(), 3, information) logFunc(response.Message.(error).Error(), 3, information)
}
} }
// Listen for incoming messages
go processInterServiceMessages(information, conn)
} }