Compare commits
35 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 | |||
d8a6a48c43 | |||
5145c65d04 | |||
d04a40f655 | |||
9ca7caf2c3 | |||
a447fde86a | |||
1302147be2 | |||
e2a4c13a60 | |||
d40bc7dc8e | |||
eebe3763f5 | |||
13d00e8222 | |||
d302745c9a | |||
3114dd556e | |||
dac060cee5 |
13 changed files with 1071 additions and 1463 deletions
4
build.sh
4
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" "Fulgensfas has been built successfully!"
|
fancy "\033[1;102m" "Fulgens has been built successfully!"
|
|
@ -4,10 +4,6 @@
|
||||||
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.
|
||||||
|
@ -19,13 +15,6 @@ global: {
|
||||||
# level defines the compression level to use, possible values are 1-9 for gzip, 0-11 for brotli and 1-22 for zstd.
|
# level defines the compression level to use, possible values are 1-9 for gzip, 0-11 for brotli and 1-22 for zstd.
|
||||||
level: 5
|
level: 5
|
||||||
},
|
},
|
||||||
# https defines the HTTPS settings on a global level - per-route settings override these. It is optional.
|
|
||||||
https: {
|
|
||||||
# certificate defines the path to the certificate file (must be a wildcard in order to support multiple subdomains).
|
|
||||||
certificate: "./certs/localhost.crt",
|
|
||||||
# key defines the path to the key file (must be a wildcard in order to support multiple subdomains).
|
|
||||||
key: "./certs/localhost.key"
|
|
||||||
},
|
|
||||||
# logging defines the logging settings.
|
# logging defines the logging settings.
|
||||||
logging: {
|
logging: {
|
||||||
# enabled defines whether logging is enabled.
|
# enabled defines whether logging is enabled.
|
||||||
|
@ -41,6 +30,23 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +55,8 @@ 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"]
|
||||||
|
@ -56,7 +64,11 @@ 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",
|
||||||
# https defines the HTTPS settings for this route.
|
# port defines the port to use for this route. They do not have to be unique.
|
||||||
|
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",
|
||||||
|
@ -66,8 +78,8 @@ 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: [
|
||||||
{
|
{
|
||||||
# path defines the path to match. They can contain wildcards.
|
# paths defines the paths to match. They can contain wildcards.
|
||||||
path: "/static/*",
|
paths: ["/static", "/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 and redirect.
|
||||||
# static > proxy > redirect in terms of precedence.
|
# static > proxy > redirect in terms of precedence.
|
||||||
static: {
|
static: {
|
||||||
|
@ -79,31 +91,41 @@ routes: [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# path defines the path to match. They can contain wildcards.
|
# paths defines the paths to match. They can contain wildcards.
|
||||||
path: "/proxy/*",
|
paths: ["/proxy", "/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 and redirect.
|
||||||
# static > proxy > redirect in terms of precedence.
|
# static > proxy > redirect in terms of 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,
|
||||||
# TODO: In a future update, passing X-Forwarded-For and X-Forwarded-Proto headers will be supported.
|
headers: {
|
||||||
# TODO: In a future update, forbidding certain headers from being passed will be supported.
|
# forbid defines the headers to forbid from being sent to the proxied server.
|
||||||
# TODO: In a future update, passing X-Powered-By and Server headers will be supported.
|
forbid: [ "User-Agent" ],
|
||||||
# TODO: In a future update, passing Host header will be supported.
|
# preserveServer defines whether to preserve the server header from the proxied server.
|
||||||
},
|
preserveServer: true,
|
||||||
{
|
# preserveAltSvc defines whether to preserve the Alt-Svc header from the proxied server.
|
||||||
# path defines the path to match. They can contain wildcards.
|
preserveAltSvc: true,
|
||||||
path: "/redirect/*",
|
# preserveXPoweredBy defines whether to preserve the X-Powered-By header from the proxied server.
|
||||||
# redirect defines the redirect settings for this path. This conflicts with proxy and static.
|
preserveXPoweredBy: true,
|
||||||
# static > proxy > redirect in terms of precedence.
|
# passHost defines whether the host / :authority header should be sent to the proxied server.
|
||||||
redirect: {
|
passHost: true,
|
||||||
# url defines the URL to redirect to.
|
# xForward defines whether to send the X-Forwarded-For and X-Forwarded-Proto headers.
|
||||||
url: "https://www.google.com",
|
xForward: false
|
||||||
# permanent defines whether the redirect is permanent (301) or temporary (302).
|
|
||||||
permanent: true
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# 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
34
go.mod
|
@ -1,33 +1,35 @@
|
||||||
module git.ailur.dev/ailur/fulgens
|
module git.ailur.dev/ailur/fulgens
|
||||||
|
|
||||||
go 1.23.1
|
go 1.23.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.ailur.dev/ailur/fg-library/v2 v2.1.1
|
git.ailur.dev/ailur/fg-library/v3 v3.6.2
|
||||||
git.ailur.dev/ailur/fg-nucleus-library v1.0.3
|
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/andybalholm/brotli v1.1.1
|
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.28.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
github.com/andybalholm/brotli v1.1.1 // 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.1.0 // 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/stretchr/testify v1.9.0 // indirect
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/net v0.37.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // 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
|
||||||
)
|
)
|
||||||
|
|
85
go.sum
85
go.sum
|
@ -1,60 +1,87 @@
|
||||||
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 h1:PNJKxpvbel2iDeB9+/rpYRyMoim6JjRHOXPYFYky7Ng=
|
||||||
git.ailur.dev/ailur/fg-library/v2 v2.1.1/go.mod h1:Il3+GZ7tClNx4/QSt3eY4tR7NdvO+Qf00J0PKkxPrl4=
|
git.ailur.dev/ailur/fg-library/v3 v3.6.2/go.mod h1:ArNsafpqES2JuxQM5aM+bNe0FwHLIsL6pbjpiWvDwGs=
|
||||||
git.ailur.dev/ailur/fg-nucleus-library v1.0.3 h1:C0xgfZg7bkULhh9Ci7ZoAcx4QIqxLh+QW9/ng1kKTFU=
|
git.ailur.dev/ailur/fg-nucleus-library v1.2.2 h1:JbclmxGSoL+ByGZAl0W6PqWRoyBBGTrKrizWDJ7rdI0=
|
||||||
git.ailur.dev/ailur/fg-nucleus-library v1.0.3/go.mod h1:RbBVFRwtQgYvCWoru1mC3vUJ1dMftkNbvd7hVFtREFw=
|
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/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.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/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.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
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.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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
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-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
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=
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
@ -25,9 +25,6 @@ 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"
|
||||||
|
@ -39,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
|
||||||
|
@ -47,17 +45,34 @@ var ServiceInformation = library.Service{
|
||||||
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"),
|
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var serviceIDBytes []byte
|
var (
|
||||||
|
loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002")
|
||||||
|
)
|
||||||
|
|
||||||
func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
|
func checkScopes(scopes []string) (bool, string, error) {
|
||||||
// Log the message to the logger service
|
var clientKeyShare bool
|
||||||
information.Outbox <- library.InterServiceMessage{
|
for _, scope := range scopes {
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
if scope != "openid" && scope != "clientKeyShare" {
|
||||||
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service
|
return false, "", errors.New("invalid scope")
|
||||||
MessageType: messageType,
|
} else {
|
||||||
SentAt: time.Now(),
|
if scope == "clientKeyShare" {
|
||||||
Message: message,
|
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 {
|
||||||
|
@ -93,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
|
||||||
|
@ -118,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))
|
||||||
|
@ -127,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)
|
||||||
|
@ -173,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)
|
||||||
|
@ -189,109 +205,81 @@ 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)
|
||||||
|
|
||||||
var err error
|
// Start the ISM processor
|
||||||
serviceIDBytes, err = ServiceInformation.ServiceID.MarshalBinary()
|
go information.StartISProcessor()
|
||||||
|
|
||||||
|
// 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 {
|
||||||
// Initiate a connection to the database
|
// Create the global table
|
||||||
// Call service ID 1 to get the database connection information
|
// Uniqueness check is a hack to ensure we only have one global row
|
||||||
information.Outbox <- library.InterServiceMessage{
|
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
|
||||||
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)
|
||||||
}
|
}
|
||||||
// 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
|
||||||
|
@ -325,15 +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)", serviceIDBytes, 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, err := uuid.New().MarshalBinary()
|
testAppCreator := uuid.New()
|
||||||
if err != nil {
|
_, 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")
|
||||||
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
|
||||||
|
@ -407,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, serviceIDBytes) {
|
|
||||||
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)
|
||||||
|
@ -532,8 +489,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"`
|
||||||
NewPassword string `json:"newPassword"`
|
NewPublicKey string `json:"newPublicKey"`
|
||||||
}
|
}
|
||||||
var data changePassword
|
var data changePassword
|
||||||
err = json.NewDecoder(r.Body).Decode(&data)
|
err = json.NewDecoder(r.Body).Decode(&data)
|
||||||
|
@ -550,34 +507,14 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a new salt
|
// Update the public key
|
||||||
// We want it to be binary data, not alphanumerical, so we don't use randomChars
|
_, err = conn.DB.Exec("UPDATE users SET publicKey = $1 WHERE id = $2", data.NewPublicKey, userId)
|
||||||
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 {
|
||||||
|
@ -650,14 +587,14 @@ func Main(information library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to insert the user
|
// Try to insert the user
|
||||||
userID, err := uuid.New().MarshalBinary()
|
userID := uuid.New()
|
||||||
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)
|
||||||
|
@ -679,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 {
|
||||||
|
@ -718,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
|
||||||
|
@ -745,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)
|
||||||
|
@ -765,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
|
||||||
|
@ -1247,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)
|
||||||
|
@ -1283,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1646,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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1665,136 +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
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a new appId
|
|
||||||
// It must be able to be sent via JSON, so we can't have pure-binary data
|
|
||||||
appId, err := randomChars(32)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)", appId, secret, serviceIDBytes, 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)", appId, secret, serviceIDBytes, 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,14 +1,14 @@
|
||||||
module git.ailur.dev/fulgens/services-src/auth/resources
|
module git.ailur.dev/fulgens/services-src/auth/resources
|
||||||
|
|
||||||
go 1.23.0
|
go 1.23.3
|
||||||
|
|
||||||
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.28.0
|
golang.org/x/crypto v0.29.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.26.0 // indirect
|
golang.org/x/sys v0.27.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
|
|
@ -114,16 +114,13 @@ 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;
|
||||||
|
|
|
@ -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,16 +1,17 @@
|
||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,442 +19,315 @@ 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 getQuota(user uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
|
func logFunc(message string, messageType library.MessageCode, information *library.ServiceInitializationInformation) {
|
||||||
// Get the user's quota from the database
|
// Log the message to the logger service
|
||||||
var quota int64
|
information.SendISMessage(loggerService, messageType, message)
|
||||||
userBytes, err := user.MarshalBinary()
|
}
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
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
|
||||||
}
|
}
|
||||||
err = conn.DB.QueryRow("SELECT quota FROM quotas WHERE id = $1", userBytes).Scan("a)
|
|
||||||
|
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 err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
// The user has no quota set, so we'll set it to the default quota
|
return false
|
||||||
_, err = conn.DB.Exec("INSERT INTO quotas (id, quota) VALUES ($1, $2)", userBytes, int64(information.Configuration["defaultQuota"].(float64)))
|
} 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
|
||||||
|
err := conn.DB.QueryRow("SELECT quota FROM users WHERE id = $1", userID[:]).Scan("a)
|
||||||
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return int64(information.Configuration["defaultQuota"].(float64)), nil
|
return int64(information.Configuration["defaultQuota"].(int)), nil
|
||||||
|
} else {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return quota, nil
|
return quota, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUsed(user uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
|
func getUsed(userID uuid.UUID, information *library.ServiceInitializationInformation, conn library.Database) (int64, error) {
|
||||||
// Check the user's used space via the filesystem
|
// Get the used space for a user by first getting the reserved space from file storage
|
||||||
var used int64
|
_, err := os.Stat(filepath.Join(information.Configuration["path"].(string), userID.String()))
|
||||||
_, err := os.Stat(filepath.Join(information.Configuration["path"].(string), user.String()))
|
if os.IsNotExist(err) {
|
||||||
if err != nil {
|
// Create the directory
|
||||||
if os.IsNotExist(err) {
|
err = os.Mkdir(filepath.Join(information.Configuration["path"].(string), userID.String()), 0755)
|
||||||
// The user has no files stored, so we'll set it to 0
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
} else {
|
|
||||||
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return used, nil
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
|
var used int64
|
||||||
// Log the error message to the logger service
|
err = filepath.Walk(filepath.Join(information.Configuration["path"].(string), userID.String()), func(path string, entry os.FileInfo, err error) error {
|
||||||
information.Outbox <- library.InterServiceMessage{
|
if err != nil {
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
return err
|
||||||
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) {
|
used += entry.Size()
|
||||||
// Create a folder for the user if it doesn't exist
|
|
||||||
err := os.MkdirAll(filepath.Join(information.Configuration["path"].(string), file.User.String()), 0755)
|
return nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// First contact the logger service
|
return 0, err
|
||||||
logFunc(err.Error(), 2, information)
|
}
|
||||||
|
|
||||||
// Then send the error message to the requesting service
|
// Then add the reserved space from the database
|
||||||
information.Outbox <- library.InterServiceMessage{
|
var reserved int64
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
err = conn.DB.QueryRow("SELECT reserved FROM users WHERE id = $1", userID[:]).Scan(&reserved)
|
||||||
ForServiceID: serviceID,
|
if err != nil {
|
||||||
MessageType: 1, // An error that's not your fault
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
SentAt: time.Now(),
|
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int)))
|
||||||
Message: err.Error(),
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
} else {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has enough space to store the file
|
return used + reserved, nil
|
||||||
// Get the user's used space
|
}
|
||||||
used, err := getUsed(file.User, 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: serviceID,
|
|
||||||
MessageType: 1, // An error that's not your fault
|
|
||||||
SentAt: time.Now(),
|
|
||||||
Message: err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func modifyFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) {
|
||||||
// Check if the file already exists
|
// Check if the file already exists
|
||||||
stats, err := os.Stat(filepath.Join(information.Configuration["path"].(string), file.User.String(), serviceID.String(), 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)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// The file already exists, subtract the old file size from the user's used space
|
// Delete the file
|
||||||
used -= stats.Size()
|
err = os.Remove(path)
|
||||||
|
if err != nil {
|
||||||
|
respondError(message, err, information, true)
|
||||||
|
}
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
respondError(message, err, information, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the user's quota
|
// Check if the user has enough space
|
||||||
quota, err := getQuota(file.User, information)
|
quota, err := getQuota(information, message.Message.(nucleusLibrary.File).User, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// First contact the logger service
|
respondError(message, err, information, true)
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
used, err := getUsed(message.Message.(nucleusLibrary.File).User, information, conn)
|
||||||
// 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 {
|
if err != nil {
|
||||||
// First contact the logger service
|
respondError(message, err, information, true)
|
||||||
logFunc(err.Error(), 2, information)
|
}
|
||||||
|
if used+message.Message.(nucleusLibrary.File).Reader.N > quota {
|
||||||
// Then send the error message to the requesting service
|
respondError(message, errors.New("insufficient storage"), information, false)
|
||||||
information.Outbox <- library.InterServiceMessage{
|
return
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
|
||||||
ForServiceID: serviceID,
|
|
||||||
MessageType: 1, // An error that's not your fault
|
|
||||||
SentAt: time.Now(),
|
|
||||||
Message: err.Error(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the file
|
// Add a file to the user's storage
|
||||||
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)
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// First contact the logger service
|
respondError(message, err, information, true)
|
||||||
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 = fileStream.Write(file.Bytes)
|
_, err = io.Copy(file, message.Message.(nucleusLibrary.File).Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// First contact the logger service
|
respondError(message, err, information, true)
|
||||||
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 = fileStream.Close()
|
err = file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// First contact the logger service
|
respondError(message, err, information, true)
|
||||||
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
|
// Success
|
||||||
information.Outbox <- library.InterServiceMessage{
|
message.Respond(library.Success, nil, information)
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
|
||||||
ForServiceID: serviceID,
|
|
||||||
MessageType: 0, // Success
|
|
||||||
SentAt: time.Now(),
|
|
||||||
Message: nil,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFile(file nucleusLibrary.File, serviceID uuid.UUID, information library.ServiceInitializationInformation) {
|
func getFile(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) {
|
||||||
|
println("file not found: " + path)
|
||||||
|
respondError(message, errors.New("file not found"), information, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Open the file
|
// Open the file
|
||||||
fileStream, err := os.Open(filepath.Join(information.Configuration["path"].(string), file.User.String(), serviceID.String(), file.Name))
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// First contact the logger service
|
respondError(message, err, information, true)
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the reader
|
// Respond with the file
|
||||||
information.Outbox <- library.InterServiceMessage{
|
// It's their responsibility to close the file
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
message.Respond(library.Success, file, information)
|
||||||
ForServiceID: serviceID,
|
}
|
||||||
MessageType: 0, // Success
|
|
||||||
SentAt: time.Now(),
|
func deleteFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage) {
|
||||||
Message: fileStream,
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFile(file nucleusLibrary.File, serviceID uuid.UUID, information library.ServiceInitializationInformation) {
|
func Main(information *library.ServiceInitializationInformation) {
|
||||||
// Remove the file
|
// Start up the ISM processor
|
||||||
err := os.Remove(filepath.Join(information.Configuration["path"].(string), file.User.String(), serviceID.String(), file.Name))
|
go information.StartISProcessor()
|
||||||
|
|
||||||
|
// Get the database connection
|
||||||
|
conn, err := information.GetDatabase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// First contact the logger service
|
logFunc(err.Error(), 3, information)
|
||||||
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
|
// Create the quotas table if it doesn't exist
|
||||||
information.Outbox <- library.InterServiceMessage{
|
if conn.DBType == library.Sqlite {
|
||||||
ServiceID: ServiceInformation.ServiceID,
|
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY, quota BIGINT, reserved BIGINT)")
|
||||||
ForServiceID: serviceID,
|
if err != nil {
|
||||||
MessageType: 0, // Success
|
logFunc(err.Error(), 3, information)
|
||||||
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 {
|
||||||
// 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
|
||||||
|
go processInterServiceMessages(information, conn)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue