Compare commits

..

84 commits

Author SHA1 Message Date
a3409c0a42 Updated chi
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-03-20 18:40:22 +00:00
6cda63c538 Fixed a potential directory traversal, fixed storage being very borked, made the auth login process less memory intensive and rely more on JWTs
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-25 14:47:51 +00:00
2ece60f84e Updated nucleus libs, yet again
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-11 15:50:51 +00:00
ffc03f2213 Updated nucleus libs, tidied modfile
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-11 15:45:57 +00:00
a65b7f6e0d Updated nucleus libs
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-11 15:42:55 +00:00
cff2e5b811 Made storage fork the ISM processor
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-08 19:10:18 +00:00
1e106bb4ca Fixed broken imports, made storage not send raw ISMs
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-08 19:07:56 +00:00
f7a1ecccdb Updated libraries
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-08 18:39:29 +00:00
d9a2999fae Updated libraries
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-08 18:37:48 +00:00
a6ef1c01fe Didn't include the main??
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-08 18:31:48 +00:00
7422201d98 ISM rewrite
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-08 18:31:34 +00:00
f26a267421 Upgrade fg library version
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-08 15:44:29 +00:00
f8fc9b7206 ISM rewrite
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2025-01-08 13:47:05 +00:00
9736939a07 Added file deletion functionality to the storage service
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-12-09 21:09:00 +00:00
6eeea11a75 Fixed the storage service not properly reading bytes out of the correct struct, and instead attempting to assert the struct as raw byte data
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-12-08 16:36:29 +00:00
48547833e4 Made the storage service make the user folders
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-15 18:54:28 +00:00
34a3580ec6 Fixed the storage service being very broken
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-15 18:51:33 +00:00
f0559ed5b5 Use int instead of float64 in configs
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-15 18:37:44 +00:00
8331219da4 Fixed autoaccept not working, made logout not run localStorage.Clear()
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-15 17:00:06 +00:00
9cbe1e8ecc Fixed registration not changing the redirect URI
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-15 16:45:13 +00:00
a2dab0869d Made mem work correctly
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-15 16:34:40 +00:00
684c9d6d48 Cleaned up the logging
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-14 19:52:57 +00:00
d8a6a48c43 Got rid of debug code
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-14 19:51:47 +00:00
5145c65d04 Added support for custom ports
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-14 19:48:39 +00:00
d04a40f655 Made it not constantly regenerate the oauth entries for services
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-13 16:36:58 +00:00
9ca7caf2c3 Fixed the password changing API using the old argon2 hashing algorithm
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-11 17:25:01 +00:00
a447fde86a Updated fulgens library versions
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-08 18:09:26 +00:00
1302147be2 Updated go version, updated dependencies
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-08 18:02:41 +00:00
e2a4c13a60 Made addReserved check for space
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-04 17:17:49 +00:00
d40bc7dc8e Fixed a bug where setting compression would crash the server on local routes, fixed some useless never-will-happen eventualities
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-04 17:09:45 +00:00
eebe3763f5 Added support for more advanced proxy configuration
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 19:10:28 +00:00
13d00e8222 Rewrote the entire blob storage service, made auth not use MarshalBinary, which is useless, and made the Start button in captchaDiv the correct colour and same size as it's surrounding elements
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 17:03:35 +00:00
d302745c9a Switch to httpcompression in order to save me some coding time and also fix all of our assorted compression problems
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 15:55:11 +00:00
3114dd556e Ok forget http/3
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 15:33:12 +00:00
dac060cee5 HTTP/3: Attempt 1
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 14:59:05 +00:00
8d27ad6b98 Add a "stealth mode" in case for whatever reason you don't want to show the entire world you're using a super obscure http server
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 14:26:02 +00:00
694f62f238 Added redirects, make one pathBlock able to contain multiple paths
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 13:20:09 +00:00
ac7230c6b5 Fixed not parsing yaml https certificates correctly
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 13:05:44 +00:00
322e901d16 Add globally defined HTTPS certs, fixed an ineffective assignment, isolated registering services into its own function
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 13:03:49 +00:00
975b24a13f This is hell
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 12:21:45 +00:00
144452f798 Fix the directory listing, once and for all
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 12:13:34 +00:00
65de87137d Make it always redirect to dir/ for directories otherwise it breaks HTML relatives
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 12:11:40 +00:00
b0fb00d7fd This directory listing isn't very good, is it...
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 12:09:05 +00:00
7b1188545d Made the directory listing not uber-broken, made it serve index.html by default
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 12:07:53 +00:00
d25e0a4877 Fixed subdomain routers not working if the service is activated after the file servers, which always happened. Also, do not load services not specified. Also, switch to a yaml config.
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-03 11:58:33 +00:00
7bc3ca8a37 Made readme accurate 2024-11-01 13:15:43 +00:00
cf5dbe7946 Switched to a new config file format, switched to a custom error handling screen instead of 404 Page Not Found, switched to per-route static file service, added proxying (in beta), reduced the cyclomatic complexity of main() function, broke up the compression handlers into different functions, added HTTPS functionality (beta), made the global router not special, use a custom http handler to automatically switch between compression schemes based on per-route compression settings, support comments in the configuration file.
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-11-01 13:09:35 +00:00
3fe89adee8 Fixed gitignore again
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-29 14:37:04 +00:00
9ec38c34b6 Fixed gitignore, fixed a font file being missing
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-29 14:36:44 +00:00
6b483f712e Added fancy printing when building, fixed compressionlevel attempting to be set when no compression is specified
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-29 12:51:04 +00:00
d6adc1c775 Added compression support, made all fetches use jsFetch for improved binary sizes
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-29 11:20:01 +00:00
5910a61f19 Tidied up the CSS files, made the delete account button red
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-27 15:50:18 +00:00
28003d7093 Fixed up dark mode, added placeholders for the dashboard inputs
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-27 15:29:21 +00:00
88bde3f592 fix the custom server header
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-24 19:41:37 +01:00
dfe1fdf6f0 Use a custom server header
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-24 19:39:45 +01:00
4fc99aeb42 Add support for static files
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-24 19:27:25 +01:00
6f0bdb66db Fixed the body revealing transitions, removed broken font files
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-23 19:13:35 +01:00
eff9f20fb3 Fixed the broken signup.html
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-22 20:31:09 +01:00
6e33618a75 Fixed mobile mode
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-22 20:27:45 +01:00
af693051fa Delete the now unused backgrounds
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-22 20:24:09 +01:00
13b91d9916 Rename config.json, removed some accidental third-party modules that slipped in there
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-22 20:22:25 +01:00
93a984b951 Add some much-needed eye-candy :D
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-22 20:20:46 +01:00
6eb9e76316 Try to fix subdomain architecture
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-20 19:51:16 +01:00
5ebb572251 Switched to the CGO sqlite driver to fix an out of memory bug
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-20 18:39:20 +01:00
5b2d21825c Update storeFile to be able to remove the older file from the database when writing new information to it
Signed-off-by: arzumify <jliwin98@danwin1210.de>
2024-10-20 09:31:59 +01:00
36bdc178f2 Add functions for getting the quota and used for a user
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-18 17:52:01 +01:00
5aa3082315 Updated fulgens library and used a better files api for blob storage
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-16 18:05:52 +01:00
8943808264 Disable CORS again...
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 20:13:38 +01:00
7a9af4ae67 Specify the CORS headers because it needs that for some reason on some browsers (god I hate CORS)
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 20:12:11 +01:00
20aa69b26d Only disable CORS for the ones which need CORS disabled
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 19:45:55 +01:00
8a994bb127 Uhh, I think I did the pattern wrong. I guess someone did get CORS.
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 19:42:09 +01:00
343a0b0eb7 Disable even more CORS! Nobody gets CORS!
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 19:40:58 +01:00
b1e06cf673 Disable CORS
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 19:39:16 +01:00
5492212611 Attempt yet another method of activating services
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 19:17:29 +01:00
44a584e34d Updated fulgens library
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 19:08:16 +01:00
02372cb9ed Moved to the v2 version of the fulgens library
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 19:06:05 +01:00
518232d853 I forgot to mount the thing
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 18:46:04 +01:00
673366f2c5 Remove accidental log 2024-10-15 18:34:59 +01:00
660d784fba Update gitignore to not replace my config 2024-10-15 18:34:47 +01:00
eb4b07840e Try to fix subdomains (2/2)
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 18:33:45 +01:00
c4dae7ac03 Try to fix subdomains
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-15 18:29:16 +01:00
1d445cb61b Made renderX provide content types to not make forgejo complain
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-14 13:10:51 +01:00
f4ee272865 Fixed duplicate created entry
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-14 12:31:14 +01:00
58838f51ad Made the postgres connection be tested before opening, fixed the postgres create new users table
Signed-off-by: Arzumify <jliwin98@danwin1210.de>
2024-10-14 12:23:33 +01:00
73 changed files with 2134 additions and 1671 deletions

13
.gitignore vendored
View file

@ -1,6 +1,9 @@
.idea
fulgens
databases
resources
services
services-src/eternity-web
/fulgens
/database
/databases
/resources
/services
/services-src/eternity-web
/config.yaml
fulgens.log

View file

@ -24,6 +24,8 @@ Then, build the server:
./build.sh
```
After that, configure the server using the `config.json` file (see below), and you're ready to go!
## Usage
To run the server, simply run the binary:
```sh
@ -31,33 +33,7 @@ To run the server, simply run the binary:
```
## Configuration
The server can be configured using a `config.json` file. An example configuration file is provided in the repository.
### Global
- `port` - The port the server listens on
- `ip` - The IP address the server listens on
- `serviceDirectory` - The directory where services are stored
- `resourceDirectory` - The directory where service resources are stored
### Logging
- `enabled` - Whether file logging is enabled
- `file` - The file to log to
### Database
- `type` - The type of database to use (sqlite or postgres)
- `connectionString` - The connection string for the database (postgres only)
- `databasePath` - The **directory** to store the databases (sqlite only)
It is necessary to have a separate directory for each service, as SQLite does not support multiple schemas in a single file.
### Services
#### For all services
- `subdomain` - The subdomain the service is hosted on (optional, will run on the root domain if not specified)
#### Storage
- `path` - The path to store blobs
- `defaultQuota` - The maximum size of the storage in bytes
#### Auth
- `privacyPolicy` - The URL to the privacy policy
- `url` - The URL it is being hosted on
- `testAppEnabled` - Whether to enable the OAuth2 test app
- `testAppIsInteralApp` - Whether the test app should have seamless logon like an internal service (required if `testAppEnabled` is true)
- `identifier` - The name of the OAuth2 service
- `adminKey` - The key used to access the admin panel and list users
The server can be configured using a `config.conf` file. You can see the config format in [config.conf.example](https://git.ailur.dev/Ailur/fulgens/src/branch/master/config.conf.example).
## Contributing
Contributions are welcome! Please open a pull request with your changes.
@ -67,4 +43,4 @@ Plugins require the use of the `library` package, found [here](https://pkg.go.de
This provides them with the necessary resources to interact with the rest of the server
## Enterprise support
For enterprise support, please visit [Ailur Enterprise](https://ailur.dev/enterprise).
For enterprise support, please visit [Ailur Enterprise](https://ailur.dev/enterprise).

View file

@ -1,13 +1,33 @@
#!/bin/sh
clear
fancy() {
width="$(tput cols)"
# Create a string of spaces based on the width
spaces=$(printf '%*s' "$width" '' | tr ' ' ' ')
# Print the formatted output
printf "%b%s %s %s%s\n\033[0m" "$1" "$spaces" "$2" "$(printf '%*s' "$((width - ${#2} - 4))" '' | tr ' ' ' ')" "$spaces"
}
fancy "\033[1;106m" "Welcome to fulgens! Starting build..."
sleep 1
path=$(realpath "$(dirname "$0")") || exit 1
search_dir="$path/services-src"
find -L "$search_dir" -type f -name "build.sh" | while read -r build_script; do
echo "Running $build_script..."
build_dir=$(dirname "$build_script")
(cd "$build_dir" && ./build.sh) || {
echo "Error: $build_script failed."
searchDir="$path/services-src"
find -L "$searchDir" -type f -name "build.sh" | while read -r buildScript; do
clear
buildDir=$(dirname "$buildScript")
fancy "\033[1;104m" "Starting build of $(basename "$buildDir")..."
(cd "$buildDir" && ./build.sh) || {
printf "\033[1;31mError: %s failed.\033[0m\n" "$buildScript"
exit 1
}
done
go build --ldflags "-s -w" -o "$path/fulgens" || exit 1
echo "Fulgens has been built successfully."
clear
fancy "\033[1;105m" "Building Fulgens..."
go build -C "$path" --ldflags "-s -w" -o "$path/fulgens" || exit 1
clear
fancy "\033[1;102m" "Fulgens has been built successfully!"

View file

@ -1,30 +0,0 @@
{
"global": {
"ip": "0.0.0.0",
"port": "8000",
"serviceDirectory": "./services",
"resourceDirectory": "./resources"
},
"logging": {
"enabled": true,
"file": "fulgens.log"
},
"database": {
"databaseType": "sqlite",
"databasePath": "./databases"
},
"services": {
"storage": {
"path": "./blob",
"defaultQuota": 50000000
},
"authentication": {
"privacyPolicy": "https://git.ailur.dev/Paperwork/nucleus/src/commit/5d191eea87cffae8bdca42017ac26dc19e6cb3de/Privacy.md",
"url": "http://localhost:8000",
"identifier": "Authenticator",
"adminKey": "supersecretkey",
"testAppIsInternalApp": true,
"testAppEnabled": true
}
}
}

159
config.yaml.example Normal file
View file

@ -0,0 +1,159 @@
# This is just YAML, but I decided to use JSON-like formatting because I like it better.
# Global configuration
global: {
# IP defines the IP address to bind to.
ip: "0.0.0.0",
# serviceDirectory defines the directory to look for services in.
serviceDirectory: "./services",
# resourceDirectory defines the directory to look for resources in.
resourceDirectory: "./resources",
# compression defines the compression settings on a global level - per-route settings override these. It is optional.
compression: {
# algorithm defines the compression algorithm to use, possible values are "gzip", "brotli" and "zstd".
algorithm: "gzip",
# 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
},
# logging defines the logging settings.
logging: {
# enabled defines whether logging is enabled.
enabled: true,
# file defines the file to log to, relative to the working directory.
file: "fulgens.log"
},
# database defines the database settings.
database: {
# type defines the type of database to use, possible values are "sqlite" and "postgres".
type: "sqlite",
# path defines the path to the directory to store database files in (sqlite only).
path: "./databases",
# connectionString defines the connection string to use for the database (postgres only).
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
}
}
# Routes define per-subdomain routing settings.
routes: [
{
# none is a special subdomain that matches all requests without a subdomain (Host header).
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.
# Each service may not be used more than once globally. The server will fail to start if this is violated.
services: ["authentication"]
},
{
# any subdomain value that isn't "none" will match that specific subdomain.
subdomain: "www.localhost",
# 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: {
# certificate defines the path to the certificate file.
certificate: "./certs/localhost.crt",
# key defines the path to the key file.
key: "./certs/localhost.key"
},
# paths defines per-path settings (NOT for services, which MUST be defined on a per-subdomain basis).
paths: [
{
# paths defines the paths to match. They can contain wildcards.
paths: ["/static", "/static/*"],
# static defines the static file serving settings for this path. This conflicts with proxy and redirect.
# static > proxy > redirect in terms of precedence.
static: {
# root defines the root directory to serve static files from.
root: "./static",
# directoryListing defines whether to show a directory listing when a directory is requested.
# if it is false or unset, a 403 Forbidden will be returned instead.
directoryListing: true
}
},
{
# paths defines the paths to match. They can contain wildcards.
paths: ["/proxy", "/proxy/*"],
# proxy defines the proxy settings for this path. This conflicts with static and redirect.
# static > proxy > redirect in terms of precedence.
proxy: {
# url defines the URL to proxy requests to.
url: "http://localhost:8000",
# stripPrefix defines whether to strip the prefix from the path before proxying.
stripPrefix: true,
headers: {
# forbid defines the headers to forbid from being sent to the proxied server.
forbid: [ "User-Agent" ],
# 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.
preserveAltSvc: true,
# preserveXPoweredBy defines whether to preserve the X-Powered-By header from the proxied server.
preserveXPoweredBy: true,
# passHost defines whether the host / :authority header should be sent to the proxied server.
passHost: true,
# xForward defines whether to send the X-Forwarded-For and X-Forwarded-Proto headers.
xForward: false
}
},
},
{
# paths defines the paths to match. They can contain wildcards.
paths: ["/redirect", "/redirect/*"],
# redirect defines the redirect settings for this path. This conflicts with proxy and static.
# static > proxy > redirect in terms of precedence.
redirect: {
# url defines the URL to redirect to.
url: "https://www.ailur.dev",
# permanent defines whether the redirect is permanent (301) or temporary (302).
permanent: true
}
}
]
}
]
# Services define the settings for services.
services: {
# authentication defines the settings for the authentication service, which is built-in.
authentication: {
# privacyPolicy defines the URL to the privacy policy.
privacyPolicy: "https://git.ailur.dev/Paperwork/nucleus/src/commit/5d191eea87cffae8bdca42017ac26dc19e6cb3de/Privacy.md",
# url defines the publicly-facing URL of the service, in case of it being behind a reverse proxy.
url: "http://localhost:8000",
# identifier defines the identifier for the service, in the form of [Identifier] Accounts.
identifier: "Authenticator",
# adminKey defines the key to use for administrative operations, such as listing all users.
adminKey: "supersecretkey",
# testAppIsInternalApp defines whether the test app is an internal app, which allows it to bypass the user consent screen.
testAppIsInternalApp: true,
# testAppEnabled defines whether the test app is enabled, which is recommended for testing purposes.
testAppEnabled: true
},
# storage defines the settings for the storage service, which is built-in.
storage: {
# path defines the path to store blobs in.
path: "./blob",
# defaultQuota defines the default quota for users in bytes.
defaultQuota: 50000000
}
}

45
go.mod
View file

@ -1,40 +1,35 @@
module git.ailur.dev/ailur/fulgens
go 1.23.1
go 1.23.3
require (
git.ailur.dev/ailur/fg-library v1.0.1
git.ailur.dev/ailur/fg-nucleus-library v1.0.0
git.ailur.dev/ailur/pow v1.0.0
git.ailur.dev/ailur/fg-library/v3 v3.6.2
git.ailur.dev/ailur/fg-nucleus-library v1.2.2
git.ailur.dev/ailur/pow v1.0.3
github.com/CAFxX/httpcompression v0.0.9
github.com/cespare/xxhash/v2 v2.3.0
github.com/go-chi/chi/v5 v5.1.0
github.com/go-playground/validator/v10 v10.22.1
github.com/go-chi/chi/v5 v5.2.1
github.com/go-playground/validator/v10 v10.25.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/klauspost/compress v1.18.0
github.com/lib/pq v1.10.9
golang.org/x/crypto v0.28.0
modernc.org/sqlite v1.33.1
github.com/mattn/go-sqlite3 v1.14.24
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // 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/universal-translator v0.18.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect
modernc.org/libc v1.61.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/stretchr/testify v1.10.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

134
go.sum
View file

@ -1,91 +1,87 @@
git.ailur.dev/ailur/fg-library v1.0.1 h1:7TY2shmYNfKPzCTeYC80uj+sFZPbBWeOlqKT6ZsKFmc=
git.ailur.dev/ailur/fg-library v1.0.1/go.mod h1:hOUkxs2rRouSwNnNZlo7CsFVH12kmjqheyzPQ4to1N8=
git.ailur.dev/ailur/fg-nucleus-library v1.0.0 h1:TT1V4cfka+uUpvV1zU7bc4KXFkgnsI/sIvaZDDxXk+k=
git.ailur.dev/ailur/fg-nucleus-library v1.0.0/go.mod h1:m4gNSEypfgrUV8bXaR8NLB8zchUM59y0ellV1wp/C+I=
git.ailur.dev/ailur/pow v1.0.0 h1:eCJiZSbskcmzmwR4Nv4YrYpsZci5kfoGM9ihkXAHHoU=
git.ailur.dev/ailur/pow v1.0.0/go.mod h1:BHl7H6B6uN+q2cCCUlno6JMhqLa2A52wkbAdJbq2izA=
git.ailur.dev/ailur/fg-library/v3 v3.6.2 h1:PNJKxpvbel2iDeB9+/rpYRyMoim6JjRHOXPYFYky7Ng=
git.ailur.dev/ailur/fg-library/v3 v3.6.2/go.mod h1:ArNsafpqES2JuxQM5aM+bNe0FwHLIsL6pbjpiWvDwGs=
git.ailur.dev/ailur/fg-nucleus-library v1.2.2 h1:JbclmxGSoL+ByGZAl0W6PqWRoyBBGTrKrizWDJ7rdI0=
git.ailur.dev/ailur/fg-nucleus-library v1.2.2/go.mod h1:stxiTyMv3Fa7GzpyLbBUh3ahlb7110p0NnCl8ZTjwBs=
git.ailur.dev/ailur/pow v1.0.3 h1:LjLSol4ax+M+SoajVjbBoDjfmjH6pKu3fDka7bl2KGY=
git.ailur.dev/ailur/pow v1.0.3/go.mod h1:ClAmIdHQ/N9wTq5S4YWhQ5d9CPUBcEjVuOkT07zBdJ4=
github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWGKSqgrg=
github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
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/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
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/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
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/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/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/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/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/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE=
modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

1384
main.go

File diff suppressed because it is too large Load diff

View file

@ -5,19 +5,19 @@ resourceDir="$path/../../resources/00000000-0000-0000-0000-000000000004"
rm -rf "$resourceDir" || exit 1
rm -rf "$path/../../services/auth.fgs" || exit 1
cd "$path" || exit 1
printf "\033[1;35mBuilding auth.fgs...\033[0m\n"
go build -o "$path/../../services/auth.fgs" --buildmode=plugin -ldflags "-s -w" || exit 1
mkdir -p "$resourceDir/static/wasm" || exit 1
cd "$path/resources/wasm/login" || exit 1
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/login.wasm" -ldflags "-s -w" || exit 1
cd "$path/resources/wasm/signup" || exit 1
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/signup.wasm" -ldflags "-s -w" || exit 1
cd "$path/resources/wasm/authorize" || exit 1
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/authorize.wasm" -ldflags "-s -w" || exit 1
cd "$path/resources/wasm/dashboard" || exit 1
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/dashboard.wasm" -ldflags "-s -w" || exit 1
cd "$path/resources/wasm/testApp" || exit 1
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/testApp.wasm" -ldflags "-s -w" || exit 1
cd "$path/resources/wasm/clientKeyShare" || exit 1
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/clientKeyShare.wasm" -ldflags "-s -w" || exit 1
find -L "$path/resources/wasm" -type f -name "main.go" | while read -r mainGo; do
buildDir=$(dirname "$mainGo")
baseName=$(basename "$buildDir")
printf "\033[1;34m\033[1;33mBuilding WASM object %s...\033[0m\n" "$baseName"
(cd "$buildDir" && GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/$(basename "$buildDir").wasm" -ldflags "-s -w") || {
printf "\033[1;31mError: %s failed.\033[0m\n" "$mainGo"
exit 1
}
done
printf "\033[1;34mCopying static files...\033[0m\n"
cp -r "$path/resources/static" "$resourceDir/" || exit 1
cp -r "$path/resources/templates" "$resourceDir/" || exit 1
cp -r "$path/resources/templates" "$resourceDir/" || exit 1
printf "\033[1;36mauth.fgs has been built successfully!\033[0m\n"

View file

@ -2,7 +2,7 @@ package main
import (
// Fulgens libraries
library "git.ailur.dev/ailur/fg-library"
library "git.ailur.dev/ailur/fg-library/v3"
authLibrary "git.ailur.dev/ailur/fg-nucleus-library"
"git.ailur.dev/ailur/pow"
@ -25,20 +25,18 @@ import (
"io/fs"
"net/http"
// Extra libraries
"golang.org/x/crypto/argon2"
// External libraries
"github.com/cespare/xxhash/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
_ "modernc.org/sqlite"
_ "github.com/mattn/go-sqlite3"
)
var ServiceInformation = library.Service{
Name: "Authentication",
Permissions: library.Permissions{
Authenticate: false, // This service *is* the authentication service
Router: true, // This service does require a router
Database: true, // This service does require database access
BlobStorage: false, // This service does not require blob storage
InterServiceCommunication: true, // This service does require inter-service communication
@ -47,17 +45,34 @@ var ServiceInformation = library.Service{
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) {
// Log the message to the logger service
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service
MessageType: messageType,
SentAt: time.Now(),
Message: message,
func checkScopes(scopes []string) (bool, string, error) {
var clientKeyShare bool
for _, scope := range scopes {
if scope != "openid" && scope != "clientKeyShare" {
return false, "", errors.New("invalid scope")
} else {
if scope == "clientKeyShare" {
clientKeyShare = true
}
}
}
// Marshal the scopes
scopeString, err := json.Marshal(scopes)
if err != nil {
return clientKeyShare, "", err
}
return clientKeyShare, string(scopeString), nil
}
func logFunc(message string, messageType library.MessageCode, information *library.ServiceInitializationInformation) {
// Log the message to the logger service
information.SendISMessage(loggerService, messageType, message)
}
func ensureTrailingSlash(url string) string {
@ -93,25 +108,33 @@ func sha256Base64(s string) string {
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 requestedTemplate *template.Template
// Output ls of the resource directory
requestedTemplate, err = template.ParseFS(information.ResourceDir, "templates/"+templatePath)
if err != nil {
logFunc(err.Error(), 2, information)
renderString(500, w, "Sorry, something went wrong on our end. Error code: 01. Please report to the administrator.", information)
http.Error(w, err.Error(), 500)
} else {
if strings.HasSuffix(templatePath, ".html") {
w.Header().Set("Content-Type", "text/html")
} else if strings.HasSuffix(templatePath, ".json") {
w.Header().Set("Content-Type", "application/json")
} else {
w.Header().Set("Content-Type", "text/plain")
}
w.WriteHeader(statusCode)
err = requestedTemplate.Execute(w, data)
if err != nil {
logFunc(err.Error(), 2, information)
renderString(500, w, "Sorry, something went wrong on our end. Error code: 02. Please report to the administrator.", information)
http.Error(w, err.Error(), 500)
}
}
}
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.WriteHeader(statusCode)
_, err := w.Write([]byte(data))
if err != nil {
@ -119,7 +142,8 @@ 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.WriteHeader(statusCode)
err := json.NewEncoder(w).Encode(data)
if err != nil {
@ -164,11 +188,12 @@ func verifyJwt(token string, publicKey ed25519.PublicKey, mem *sql.DB) ([]byte,
return userId, claims, true
}
func Main(information library.ServiceInitializationInformation) {
func Main(information *library.ServiceInitializationInformation) {
var conn library.Database
var mem *sql.DB
var publicKey ed25519.PublicKey
var privateKey ed25519.PrivateKey
// Load the configuration
privacyPolicy := information.Configuration["privacyPolicy"].(string)
hostName := information.Configuration["url"].(string)
@ -180,109 +205,81 @@ func Main(information library.ServiceInitializationInformation) {
identifier := information.Configuration["identifier"].(string)
adminKey := information.Configuration["adminKey"].(string)
var err error
serviceIDBytes, err = ServiceInformation.ServiceID.MarshalBinary()
// Start the ISM processor
go information.StartISProcessor()
// Initiate a connection to the database
conn, err := information.GetDatabase()
if err != nil {
logFunc(err.Error(), 3, information)
}
// 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)
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, password BYTEA NOT NULL, salt 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("sqlite", "file:"+ServiceInformation.ServiceID.String()+"?mode=memory&cache=shared")
if conn.DBType == library.Sqlite {
// Create the global table
// Uniqueness check is a hack to ensure we only have one global row
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Drop the tables if they exist
_, err = mem.Exec("DROP TABLE IF EXISTS sessions")
// Create the users table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BLOB NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
_, err = mem.Exec("DROP TABLE IF EXISTS logins")
if err != nil {
logFunc(err.Error(), 3, information)
}
_, err = mem.Exec("DROP TABLE IF EXISTS spent")
if err != nil {
logFunc(err.Error(), 3, information)
}
_, err = mem.Exec("DROP TABLE IF EXISTS challengeResponse")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the sessions table
_, err = mem.Exec("CREATE TABLE sessions (id BLOB NOT NULL, session TEXT NOT NULL, device TEXT NOT NULL DEFAULT '?')")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the logins table
_, err = mem.Exec("CREATE TABLE logins (appId TEXT NOT NULL, exchangeCode TEXT NOT NULL UNIQUE, pkce TEXT, pkceMethod TEXT, openid BOOLEAN NOT NULL, userId BLOB NOT NULL UNIQUE, nonce TEXT NOT NULL DEFAULT '', token TEXT NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the spent PoW table
_, err = mem.Exec("CREATE TABLE spent (hash BLOB NOT NULL UNIQUE, expires INTEGER NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the challenge-response table
_, err = mem.Exec("CREATE TABLE challengeResponse (challenge TEXT NOT NULL UNIQUE, userId BLOB NOT NULL, expires INTEGER NOT NULL)")
// Create the oauth table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BLOB NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')")
if err != nil {
logFunc(err.Error(), 3, information)
}
} else {
// This is an error message
// Log the error message to the logger service
logFunc(response.Message.(error).Error(), 3, information)
// Create the global table
// Uniqueness check is a hack to ensure we only have one global row
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BYTEA NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the users table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BYTEA NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the oauth table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BYTEA NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')")
if err != nil {
logFunc(err.Error(), 3, information)
}
}
// Set up the in-memory cache
mem, err = sql.Open("sqlite3", "file:"+ServiceInformation.ServiceID.String()+"?mode=memory&cache=shared")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Drop the tables if they exist
_, err = mem.Exec("DROP TABLE IF EXISTS sessions")
if err != nil {
logFunc(err.Error(), 3, information)
}
_, err = mem.Exec("DROP TABLE IF EXISTS logins")
if err != nil {
logFunc(err.Error(), 3, information)
}
_, err = mem.Exec("DROP TABLE IF EXISTS spent")
if err != nil {
logFunc(err.Error(), 3, information)
}
// 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
@ -316,15 +313,10 @@ func Main(information library.ServiceInitializationInformation) {
}
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 {
testAppCreator, err := uuid.New().MarshalBinary()
if err != nil {
testAppIsAvailable = false
logFunc(err.Error(), 2, information)
}
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", testAppCreator, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
testAppCreator := uuid.New()
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", testAppCreator[:], ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
}
if err != nil {
testAppIsAvailable = false
@ -334,6 +326,26 @@ func Main(information library.ServiceInitializationInformation) {
// Set up the router
router := information.Router
// Add the CORS middleware
disableCors := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "authorization, content-type")
w.Header().Set("Access-Control-Allow-Methods", "*")
next.ServeHTTP(w, r)
})
}
router.Use(disableCors)
disableCorsHandleFunc := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST")
w.Header().Set("Access-Control-Allow-Headers", "authorization, content-type")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
}
// Set up the static routes
staticDir, err := fs.Sub(information.ResourceDir, "static")
if err != nil {
@ -378,56 +390,30 @@ func Main(information library.ServiceInitializationInformation) {
router.Get("/authorize", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("client_id") != "" {
if conn.DBType == library.Sqlite {
var name string
var creator []byte
err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
renderString(404, w, "App not found", information)
} else {
logFunc(err.Error(), 2, information)
renderString(500, w, "Sorry, something went wrong on our end. Error code: 03. 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)
var name string
var creator []byte
err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
renderString(404, w, "App not found", information)
} else {
renderTemplate(200, w, map[string]interface{}{
"identifier": identifier,
"name": name,
}, "autoAccept.html", information)
logFunc(err.Error(), 2, information)
renderString(500, w, "Sorry, something went wrong on our end. Error code: 02. Please report to the administrator.", information)
}
return
}
// Check if the app is internal
if !bytes.Equal(creator, ServiceInformation.ServiceID[:]) {
renderTemplate(200, w, map[string]interface{}{
"identifier": identifier,
"name": name,
}, "authorize.html", information)
} else {
var name string
var creator uuid.UUID
err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
renderString(404, w, "App not found", information)
} else {
logFunc(err.Error(), 2, information)
renderString(500, w, "Sorry, something went wrong on our end. Error code: 03. Please report to the administrator.", information)
}
return
}
if creator != ServiceInformation.ServiceID {
renderTemplate(200, w, map[string]interface{}{
"identifier": identifier,
"name": name,
}, "authorize.html", information)
} else {
renderTemplate(200, w, map[string]interface{}{
"identifier": identifier,
"name": name,
}, "autoAccept.html", information)
}
renderTemplate(200, w, map[string]interface{}{
"identifier": identifier,
"name": name,
}, "autoAccept.html", information)
}
} else {
http.Redirect(w, r, "/dashboard", 301)
@ -503,8 +489,8 @@ func Main(information library.ServiceInitializationInformation) {
router.Post("/api/changePassword", func(w http.ResponseWriter, r *http.Request) {
type changePassword struct {
Session string `json:"session"`
NewPassword string `json:"newPassword"`
Session string `json:"session"`
NewPublicKey string `json:"newPublicKey"`
}
var data changePassword
err = json.NewDecoder(r.Body).Decode(&data)
@ -521,34 +507,14 @@ func Main(information library.ServiceInitializationInformation) {
return
}
// Generate a new salt
// We want it to be binary data, not alphanumerical, so we don't use randomChars
salt := make([]byte, 16)
_, err = rand.Read(salt)
// Update the public key
_, err = conn.DB.Exec("UPDATE users SET publicKey = $1 WHERE id = $2", data.NewPublicKey, userId)
if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "04"}, information)
logFunc(err.Error(), 2, information)
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
_, err = mem.Exec("DELETE FROM sessions WHERE id = ?", userId)
if err != nil {
@ -621,14 +587,14 @@ func Main(information library.ServiceInitializationInformation) {
}
// Try to insert the user
userID, err := uuid.New().MarshalBinary()
userID := uuid.New()
if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "08"}, information)
logFunc(err.Error(), 2, information)
return
}
_, err = conn.DB.Exec("INSERT INTO users (id, created, username, publicKey, created) VALUES ($1, $2, $3, $4, $5)", userID, time.Now().Unix(), data.Username, publicKey, time.Now().Unix())
_, 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 strings.Contains(err.Error(), "UNIQUE constraint failed") {
renderJSON(409, w, map[string]interface{}{"error": "Username already taken"}, information)
@ -650,37 +616,13 @@ func Main(information library.ServiceInitializationInformation) {
}
// 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
renderJSON(200, w, map[string]interface{}{"key": session}, information)
})
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
challenge, err := randomChars(512)
if err != nil {
@ -689,22 +631,27 @@ func Main(information library.ServiceInitializationInformation) {
return
}
// Insert the challenge with one minute expiration
_, err = mem.Exec("INSERT INTO challengeResponse (challenge, userId, expires) VALUES (?, ?, ?)", challenge, userId, time.Now().Unix()+60)
// Issue a new JWT token with the challenge
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 {
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)
return
}
// 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) {
type login struct {
Username string `json:"username"`
Signature string `json:"signature"`
Verifier string `json:"verifier"`
}
var data login
@ -716,8 +663,8 @@ func Main(information library.ServiceInitializationInformation) {
// Try to select the user
var userId []byte
var publicKey []byte
err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &publicKey)
var userPublicKey []byte
err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &userPublicKey)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
renderJSON(401, w, map[string]interface{}{"error": "Invalid username"}, information)
@ -736,33 +683,43 @@ func Main(information library.ServiceInitializationInformation) {
}
// Verify the challenge
// Select the current challenge from the database
var challenge string
err = mem.QueryRow("SELECT challenge FROM challengeResponse WHERE userId = ?", userId).Scan(&challenge)
token, err := jwt.Parse(data.Verifier, func(token *jwt.Token) (interface{}, error) {
return publicKey, nil
})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
renderJSON(401, w, map[string]interface{}{"error": "Invalid challenge"}, information)
} else {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "52"}, information)
logFunc(err.Error(), 2, information)
}
renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier"}, information)
return
}
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
}
// 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)
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
// 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
@ -913,6 +870,8 @@ func Main(information library.ServiceInitializationInformation) {
renderJSON(200, w, map[string]interface{}{"username": username, "sub": uuid.Must(uuid.FromBytes(userId)).String()}, information)
})
router.Options("/api/oauth/userinfo", disableCorsHandleFunc)
router.Post("/api/authorize", func(w http.ResponseWriter, r *http.Request) {
type authorize struct {
AppId string `json:"appId"`
@ -1178,6 +1137,8 @@ func Main(information library.ServiceInitializationInformation) {
}
})
router.Options("/api/oauth/token", disableCorsHandleFunc)
router.Post("/api/oauth/remove", func(w http.ResponseWriter, r *http.Request) {
type remove struct {
Token string `json:"token"`
@ -1214,7 +1175,6 @@ func Main(information library.ServiceInitializationInformation) {
})
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
var data authLibrary.OAuthInformation
err = json.NewDecoder(r.Body).Decode(&data)
@ -1250,27 +1210,9 @@ func Main(information library.ServiceInitializationInformation) {
}
// Validate the scopes
var clientKeyShare bool
for _, scope := range data.Scopes {
if scope != "openid" && scope != "clientKeyShare" {
renderJSON(400, w, map[string]interface{}{"error": "Invalid scope"}, information)
return
} else {
if scope == "clientKeyShare" {
clientKeyShare = true
} else if scope != "openid" {
logFunc("An impossible logic error has occurred, please move away from radiation or use ECC RAM", 1, information)
renderJSON(400, w, map[string]interface{}{"error": "Invalid scope"}, information)
return
}
}
}
// Marshal the scopes
scopes, err := json.Marshal(data.Scopes)
clientKeyShare, scopes, err := checkScopes(data.Scopes)
if err != nil {
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "36"}, information)
logFunc(err.Error(), 2, information)
renderJSON(400, w, map[string]interface{}{"error": err.Error()}, information)
return
}
@ -1613,17 +1555,7 @@ func Main(information library.ServiceInitializationInformation) {
if err != nil {
logFunc(err.Error(), 1, information)
} else {
affected, err := mem.Exec("DELETE FROM challengeResponse WHERE expires < ?", time.Now().Unix())
if err != nil {
logFunc(err.Error(), 1, information)
} else {
affectedCount2, err := affected.RowsAffected()
if err != nil {
logFunc(err.Error(), 1, information)
} else {
logFunc("Cleanup complete, deleted "+strconv.FormatInt(affectedCount+affectedCount2, 10)+" entries", 0, information)
}
}
logFunc("Cleanup complete, deleted "+strconv.FormatInt(affectedCount, 10)+" entries", 0, information)
}
}
}
@ -1632,146 +1564,79 @@ func Main(information library.ServiceInitializationInformation) {
go func() {
for {
// Wait for a message
message := <-information.Inbox
message := information.AcceptMessage()
if message.ServiceID != uuid.MustParse("00000000-0000-0000-0000-000000000001") {
// Check the message type
switch message.MessageType {
case 0:
// A service would like to know our hostname
// Send it to them
information.Outbox <- library.InterServiceMessage{
MessageType: 0,
ServiceID: ServiceInformation.ServiceID,
ForServiceID: message.ServiceID,
Message: hostName,
SentAt: time.Now(),
}
case 1:
// A service would like to register a new OAuth entry
// 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 the message type
switch message.MessageType {
case 0:
// A service would like to have the hostname
// Send it to them
message.Respond(library.Success, hostName, information)
case 1:
// A service would like to register a new OAuth entry
// Validate the scopes
clientKeyShare, scopes, err := checkScopes(message.Message.(authLibrary.OAuthInformation).Scopes)
if err != nil {
message.Respond(library.BadRequest, err, information)
return
}
// Check if the service already has an OAuth entry
var appId, secret string
err = conn.DB.QueryRow("SELECT appId, secret FROM oauth WHERE appId = $1", message.ServiceID.String()).Scan(&appId, &secret)
if err == nil && appId == message.ServiceID.String() {
// Update the entry to thew new scopes and redirect URI
if clientKeyShare {
_, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3, keyShareUri = $4 WHERE appId = $5", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri, message.ServiceID.String())
} else {
_, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3 WHERE appId = $4", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.ServiceID.String())
}
if err != nil {
message.Respond(library.InternalError, err, 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)
}
}
}()
// Report a successful activation
information.Outbox <- library.InterServiceMessage{
ServiceID: ServiceInformation.ServiceID,
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), // Activation service
MessageType: 0,
SentAt: time.Now(),
Message: true,
}
}

View file

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

View file

@ -1,10 +1,10 @@
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/jsStreams v1.2.1 h1:nXZYZrxHJCVwR0Kx/X+TenMBmS6Gh8Uc2DMinbyiGoo=
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/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View file

@ -1,9 +1,18 @@
@import url("../fonts/inter.css");
@font-face {
font-family: 'Figtree';
src: url("fonts/Figtree.woff2") format("woff2");
font-style: normal;
}
html {
background: linear-gradient(to top left, rgb(217, 236, 255), rgb(228, 249, 255), rgb(221, 255, 238), rgb(249,255,253)) no-repeat center center fixed;
background-size: cover;
}
:root {
--invertdm: 0%;
--text-color: #000000;
--editor: #ffffff;
--text-color: #000;
--background: #fff;
--border-color: #dadada;
--theme-color: #1c71d8;
--hover-theme-color: #4990e7;
@ -11,9 +20,9 @@
--hover-nonimportant-theme-color: #dbdbdb;
--nonimportant-text-color: #000;
--inOutDiv: #fafafa;
--disabled: lightgray;
--disabled: #d3d3d3;
--disabled-hover: #a2a0a0;
--disabled-text-color: gray;
--disabled-text-color: #808080;
}
/* dark mode */
@ -22,8 +31,8 @@
:root {
--invertdm: 100%;
--inOutDiv: #2d2f31;
--text-color: #ffffff;
--editor: #1E1E1E;
--text-color: #fff;
--background: #1E1E1E;
--nonimporant-theme-color: #8E8E8E;
--nonimportant-text-color: #fff;
--border-color: #393b3d;
@ -31,6 +40,10 @@
--disabled-hover: #737373;
}
html {
background: linear-gradient(to top left, rgb(0 17 35), rgb(7 36 45), rgb(28 45 36), rgb(49 49 49)) no-repeat center center fixed;
}
.inOutDiv p {
color: white !important;
}
@ -38,23 +51,9 @@
.inOutDiv a {
color: #969696 !important;
}
.inOutDiv input {
color: white;
background-color: var(--editor);
}
}
p,
li,
h1,
h2,
h3,
h4,
h5,
span,
text,
h6 {
p, li, h1, h2, h3, h4, h5, span, h6 {
color: var(--text-color);
white-space: break-spaces;
}
@ -65,18 +64,16 @@ p#statusBox {
body {
margin: 0;
background-color: var(--editor);
font-family: "Inter", sans-serif;
font-family: "Figtree", sans-serif;
}
/* Sign up/log in div */
.inOutDiv {
border-radius: 8px;
border-radius: 25px;
margin: 10%;
padding: 30px;
border: solid 1px var(--border-color);
background-color: var(--inOutDiv);
padding: 35px 35px 50px;
}
table {
@ -87,19 +84,22 @@ input {
width: calc(100% - 35px);
margin-left: 10px;
margin-right: 10px;
height: 30px;
height: 35px;
padding-left: 10px;
padding-right: 10px;
background-color: var(--background);
color: var(--text-color);
border: solid;
border-color: var(--border-color);
border-width: 1px;
border-radius: 8px;
border-radius: 5px;
min-width: 20px;
}
.inputBox .captchaDiv {
background-color: var(--editor);
background-color: var(--background);
height: 32px;
width: calc(100% - 15px);
margin: 0 5px 0 5px;
@ -109,22 +109,18 @@ input {
}
.inputBox .captchaDiv button {
background-color: var(--editor);
color: var(--text-color);
border-right: 1px solid var(--border-color);
border-radius: 0;
padding: 0 10px 0 0;
margin: 0 0 0 10px;
background-color: var(--background);
}
.inputBox .captchaDiv .vAlign {
margin-left: 5px;
}
.inputBox .captchaDiv .vAlign span {
font-size: 14px;
}
.inputBox input {
margin-left: 5px;
margin-right: 0;
@ -142,13 +138,13 @@ input {
.inOutDiv {
position: absolute;
top: 0;
left: 10px;
right: 10px;
left: 0;
right: 0;
border-radius: 0;
min-width: calc(100% - 20px);
min-height: 100%;
transform: none;
padding: 5px;
padding: 5px 10px;
overflow-y: auto;
overflow-x: auto;
margin: 0;
@ -157,9 +153,6 @@ input {
.inOutDiv p {
font-size: 14px;
}
.inOutDiv h2 {
font-size: 21px;
}
.background {
display: none;
}
@ -201,42 +194,43 @@ input {
.newOauth, .oauthList, .sessionEntry, .oauthEntry {
text-align: center;
width: calc(100% - 17.5vh);
margin-top: 7vh;
margin-left: 7vh;
margin-right: 7vh;
padding: 15px 10px 30px;
border-style: solid;
border-image: none;
border-radius: 8px;
border-width: 1px;
border-radius: 25px;
font-size: 17px;
background-color: var(--inOutDiv);
border-color: var(--border-color);
}
.oauthEntry, .sessionEntry {
display: flex;
flex-direction: column;
justify-content: center;
padding: 5px;
padding: 20px;
margin-top: 0;
margin-bottom: 20px;
border: 3px dotted var(--border-color);
}
.oauthEntry button, .sessionEntry button {
padding: 10px;
}
.oauthEntry button, .sessionEntry button, #deleteAccountButton {
background-color: red;
color: white
}
.oauthEntry button:hover, .sessionEntry button:hover {
.oauthEntry button:hover, .sessionEntry button:hover, #deleteAccountButton:hover {
background-color: black;
}
.oauthEntry img, .sessionEntry img {
max-height: 64px;
margin-top: 10px;
filter: invert(var(--invertdm));
}
button {
@ -287,8 +281,9 @@ button:hover {
h2 {
display: block;
margin-top: 20px;
font-weight: 300;
margin-top: 10px;
font-weight: 600;
font-size: 22px;
}
.inOutDiv a {
@ -296,21 +291,6 @@ h2 {
text-align: center;
}
.background {
position: fixed;
z-index: -2;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
-webkit-user-drag: none;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
pointer-events: none;
}
.vAlign {
display: flex;
flex-direction: column;
@ -321,38 +301,81 @@ h2 {
display: none !important;
}
.w100 {
font-weight: 300;
/* swipe animation */
.swipe {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: var(--background);
animation: swipe 0.2s forwards;
display: none;
}
.w200 {
font-weight: 300;
.swipe-animate {
display: initial;
}
.w300 {
font-weight: 300;
/* swipe-out animation */
.swipe-out {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: var(--background);
}
.w400 {
font-weight: 400;
.swipe-out-animate {
animation: swipe-out 0.2s forwards;
}
.w500 {
font-weight: 500;
@keyframes swipe {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0);
}
}
.w600 {
font-weight: 600;
@keyframes swipe-out {
0% {
transform: translateX(0);
}
100% {
transform: translateX(100%);
}
}
.w700 {
font-weight: 700;
@keyframes swipe-reduced {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.w800 {
font-weight: 800;
@keyframes swipe-out-reduced {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.w900 {
font-weight: 900;
}
@media (prefers-reduced-motion: reduce) {
.swipe {
animation: swipe-reduced 0.5s forwards;
}
.swipe-out {
animation: swipe-out-reduced 0.5s forwards;
}
}

View file

@ -1,57 +0,0 @@
/* Variable fonts usage:
:root { font-family: "Inter", sans-serif; }
@supports (font-variation-settings: normal) {
:root { font-family: "InterVariable", sans-serif; font-optical-sizing: auto; }
} */
@font-face {
font-family: InterVariable;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("InterVariable.woff2") format("woff2");
}
@font-face {
font-family: InterVariable;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url("InterVariable-Italic.woff2") format("woff2");
}
/* static fonts */
@font-face { font-family: "Inter"; font-style: normal; font-weight: 100; font-display: swap; src: url("Inter-Thin.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: italic; font-weight: 100; font-display: swap; src: url("Inter-ThinItalic.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 200; font-display: swap; src: url("Inter-ExtraLight.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: italic; font-weight: 200; font-display: swap; src: url("Inter-ExtraLightItalic.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 300; font-display: swap; src: url("Inter-Light.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: italic; font-weight: 300; font-display: swap; src: url("Inter-LightItalic.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap; src: url("Inter-Regular.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: italic; font-weight: 400; font-display: swap; src: url("Inter-Italic.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap; src: url("Inter-Medium.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: italic; font-weight: 500; font-display: swap; src: url("Inter-MediumItalic.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap; src: url("Inter-SemiBold.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: italic; font-weight: 600; font-display: swap; src: url("Inter-SemiBoldItalic.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 700; font-display: swap; src: url("Inter-Bold.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: italic; font-weight: 700; font-display: swap; src: url("Inter-BoldItalic.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 800; font-display: swap; src: url("Inter-ExtraBold.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: italic; font-weight: 800; font-display: swap; src: url("Inter-ExtraBoldItalic.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 900; font-display: swap; src: url("Inter-Black.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: italic; font-weight: 900; font-display: swap; src: url("Inter-BlackItalic.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 100; font-display: swap; src: url("InterDisplay-Thin.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 100; font-display: swap; src: url("InterDisplay-ThinItalic.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 200; font-display: swap; src: url("InterDisplay-ExtraLight.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 200; font-display: swap; src: url("InterDisplay-ExtraLightItalic.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 300; font-display: swap; src: url("InterDisplay-Light.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 300; font-display: swap; src: url("InterDisplay-LightItalic.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 400; font-display: swap; src: url("InterDisplay-Regular.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 400; font-display: swap; src: url("InterDisplay-Italic.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 500; font-display: swap; src: url("InterDisplay-Medium.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 500; font-display: swap; src: url("InterDisplay-MediumItalic.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 600; font-display: swap; src: url("InterDisplay-SemiBold.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 600; font-display: swap; src: url("InterDisplay-SemiBoldItalic.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 700; font-display: swap; src: url("InterDisplay-Bold.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 700; font-display: swap; src: url("InterDisplay-BoldItalic.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 800; font-display: swap; src: url("InterDisplay-ExtraBold.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 800; font-display: swap; src: url("InterDisplay-ExtraBoldItalic.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 900; font-display: swap; src: url("InterDisplay-Black.woff2") format("woff2"); }
@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 900; font-display: swap; src: url("InterDisplay-BlackItalic.woff2") format("woff2"); }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

View file

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

View file

@ -1,4 +1,4 @@
<html lang="en">
<html lang="en" style="display: none">
<head>
<title>Authorize application - {{ .identifier }}</title>
<meta charset="UTF-8"/>
@ -12,9 +12,8 @@
<body>
<span id="passThrough" style="display: none;">{{ .name }}</span>
<span id="autoAccept" style="display: none;">0</span>
<img src="/static/img/background.png" class="background" alt="">
<div class="inOutDiv">
<h2 class="w300">Authorise Application</h2>
<h2>Authorise Application</h2>
<p id="statusBox">Loading...</p>
<br>
<div style="display: flex;justify-content: center;">
@ -24,6 +23,8 @@
<br>
<a href="/dashboard">Return to Dashboard</a>
</div>
<div id="swipe" class="swipe"></div>
<div id="swipe-out" class="swipe-out"></div>
<script>
loadWasm("/static/wasm/authorize.wasm")
</script>

View file

@ -1,4 +1,4 @@
<html lang="en">
<html lang="en" style="display: none">
<head>
<title>Redirecting... - {{ .identifier }}</title>
<meta charset="UTF-8"/>
@ -12,14 +12,15 @@
<body>
<span id="passThrough" style="display: none;">{{ .name }}</span>
<span id="autoAccept" style="display: none;">1</span>
<img src="/static/img/background.png" class="background" alt="">
<div class="inOutDiv">
<h2 class="w300">Authorizing application</h2>
<h2>Authorizing application</h2>
<p id="statusBox">Please wait...</p>
</div>
<script>
<script>
loadWasm("/static/wasm/authorize.wasm")
</script>
</script>
<div id="swipe" class="swipe"></div>
<div id="swipe-out" class="swipe-out"></div>
</body>
</html>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" style="display: none">
<head>
<title>Key Exchange - {{ .identifier }}</title>
<meta charset="UTF-8"/>
@ -10,7 +10,6 @@
<script src="/static/js/wasm_exec.js"></script>
</head>
<body>
<img src="/static/img/background.png" class="background" alt="">
<div class="inOutDiv">
<h2>Relaying back information, please wait...</h2>
<p id="statusBox">Processing information sent...</p>
@ -18,5 +17,7 @@
<script>
loadWasm("/static/wasm/clientKeyShare.wasm")
</script>
<div id="swipe" class="swipe"></div>
<div id="swipe-out" class="swipe-out"></div>
</body>
</html>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" style="display: none">
<head>
<title>Dashboard - {{ .identifier }}</title>
<meta charset="UTF-8"/>
@ -10,7 +10,6 @@
<script src="/static/js/wasm_exec.js"></script>
</head>
<body>
<img src="/static/img/background.png" class="background" alt="">
<div class="newOauth">
<h2>Dashboard</h2>
<p>Welcome to the {{ .identifier }} dashboard!</p>
@ -21,13 +20,13 @@
<h2>Submit a new OAuth2 App</h2>
<p id="statusBox"></p>
<p>App Name:</p>
<input id="nameBox">
<input id="nameBox" placeholder="Example App">
<p>Redirect URI:</p>
<input id="redirectUriBox">
<input id="redirectUriBox" placeholder="https://example.com/oauth2/callback">
<p>Enable OpenID:</p>
<input type="checkbox" id="openIdBox">
<p>Client key-share URI (optional, will add the clientKeyShare scope):</p>
<input id="clientKeyShareBox">
<input id="clientKeyShareBox" placeholder="https://example.com/clientKeyShare">
<br>
<button style="margin-top: 10px;" id="submitButton">Submit</button>
</div>
@ -50,5 +49,7 @@
<script>
loadWasm("/static/wasm/dashboard.wasm")
</script>
<div id="swipe" class="swipe"></div>
<div id="swipe-out" class="swipe-out"></div>
</body>
</html>

View file

@ -1,6 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" style="display: none">
<head>
<title>Login - {{ .identifier }}</title>
<meta charset="UTF-8"/>
@ -13,9 +12,8 @@
<body>
<span id="passThrough" style="display: none;">{{ .identifier }}</span>
<img src="/static/img/background.png" class="background" alt="">
<div class="inOutDiv">
<h2 class="w300">Login</h2>
<h2>Login</h2>
<p id="statusBox"></p>
<div class="inputContainer" id="inputContainer">
<div class="vAlign"><span id="inputNameBox"></span></div>
@ -33,4 +31,6 @@
<script>
loadWasm("/static/wasm/login.wasm")
</script>
<div id="swipe" class="swipe"></div>
<div id="swipe-out" class="swipe-out"></div>
</body>

View file

@ -9,7 +9,4 @@
<link rel="icon" href="/static/svg/favicon.svg">
<script src="/static/js/logout.js"></script>
</head>
<body>
<p>Logging out...</p>
</body>
</html>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" style="display: none">
<head>
<title>Signup - {{ .identifier }}</title>
<meta charset="UTF-8"/>
@ -11,9 +11,8 @@
<body>
<p style="display: none;" id="passthrough">{{ .unique_token }}</p>
<img src="/static/img/background.png" class="background" alt="">
<div class="inOutDiv">
<h2 class="w300">Signup</h2>
<h2>Signup</h2>
<p>Signup to {{ .identifier }}!</p>
<p id="statusBox"></p>
<table id="inputContainer">
@ -48,5 +47,7 @@
<script>
loadWasm("/static/wasm/signup.wasm")
</script>
<div id="swipe" class="swipe"></div>
<div id="swipe-out" class="swipe-out"></div>
</body>
</html>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" style="display: none">
<head>
<title>Tester - {{ .identifier }}</title>
<meta charset="UTF-8"/>
@ -10,7 +10,6 @@
<script src="/static/js/wasm_exec.js"></script>
</head>
<body>
<img src="/static/img/background.png" class="background" alt="">
<div class="inOutDiv">
<h2>{{ .identifier }} Tester</h2>
<p id="statusBox">Click authorize to begin the test</p>
@ -20,5 +19,7 @@
<script>
loadWasm("/static/wasm/testApp.wasm")
</script>
<div id="swipe" class="swipe"></div>
<div id="swipe-out" class="swipe-out"></div>
</body>
</html>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" style="display: none">
<head>
<title>Tester - {{ .identifier }}</title>
<meta charset="UTF-8"/>
@ -9,10 +9,11 @@
<link rel="stylesheet" type="text/css" href="/static/css/style.css"/>
</head>
<body>
<img src="/static/img/background.png" class="background" alt="">
<div class="inOutDiv">
<h2>{{ .identifier }} Tester</h2>
<p>The tester has been disabled by the administrator.</p>
</div>
<div id="swipe" class="swipe"></div>
<div id="swipe-out" class="swipe-out"></div>
</body>
</html>

View file

@ -2,15 +2,17 @@ package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"encoding/json"
"net/url"
"syscall/js"
"git.ailur.dev/ailur/jsFetch"
)
func authorize(deny bool, query url.Values) {
func authorize(deny bool, query url.Values, sleepTime time.Duration) {
// Get the token from local storage
localStorage := js.Global().Get("localStorage")
token := localStorage.Call("getItem", "DONOTSHARE-secretKey").String()
@ -48,7 +50,7 @@ func authorize(deny bool, query url.Values) {
}
// Send the request
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Error contacting server: "+err.Error())
return
@ -68,7 +70,7 @@ func authorize(deny bool, query url.Values) {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
@ -79,6 +81,8 @@ func authorize(deny bool, query url.Values) {
denyUri += "&state=" + query.Get("state")
}
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", denyUri)
} else {
// Redirect to the redirect_uri with the code
@ -87,6 +91,8 @@ func authorize(deny bool, query url.Values) {
allowUri += "&state=" + query.Get("state")
}
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", allowUri)
}
} else if response.StatusCode == 401 {
@ -99,9 +105,22 @@ func authorize(deny bool, query url.Values) {
}
func main() {
// Transition in
js.Global().Get("document").Get("documentElement").Get("style").Set("display", "initial")
js.Global().Get("swipe-out").Get("classList").Call("add", "swipe-out-animate")
var sleepTime = 200 * time.Millisecond
if js.Global().Get("window").Call("matchMedia", "(prefers-reduced-motion: reduce)").Get("matches").Bool() {
sleepTime = 500 * time.Millisecond
}
time.Sleep(sleepTime)
// Redirect to log-in if not signed in
localStorage := js.Global().Get("localStorage")
if localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
}
@ -136,7 +155,7 @@ func main() {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
statusBox.Set("innerText", "Error contacting server: "+err.Error())
return
@ -147,10 +166,12 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Redirect to log-out if not signed in
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/logout"+js.Global().Get("window").Get("location").Get("search").String())
return
} else if response.StatusCode == 500 {
@ -166,7 +187,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Alert the user if the server is down
@ -177,7 +198,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if autoAccept.Get("innerText").String() == "0" {
@ -187,18 +208,18 @@ func main() {
// Add an event listener to the Deny button
js.Global().Get("document").Call("getElementById", "denyButton").Call("addEventListener", "click", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
// We still partially authorize the user to prevent open redirects
go authorize(true, query)
go authorize(true, query, sleepTime)
return nil
}))
// Add an event listener to the Allow button
js.Global().Get("document").Call("getElementById", "allowButton").Call("addEventListener", "click", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
go authorize(false, query)
go authorize(false, query, sleepTime)
return nil
}))
} else {
// Auto-accept the request, as it's from an internal service
go authorize(false, query)
go authorize(false, query, sleepTime)
}
// Wait for events

View file

@ -1,20 +1,35 @@
package main
import (
"strings"
"time"
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"net/url"
"strings"
"syscall/js"
)
func main() {
// Transition in
js.Global().Get("document").Get("documentElement").Get("style").Set("display", "initial")
js.Global().Get("swipe-out").Get("classList").Call("add", "swipe-out-animate")
var sleepTime = 200 * time.Millisecond
if js.Global().Get("window").Call("matchMedia", "(prefers-reduced-motion: reduce)").Get("matches").Bool() {
sleepTime = 500 * time.Millisecond
}
time.Sleep(sleepTime)
// Redirect to log-in if not signed in
localStorage := js.Global().Get("localStorage")
if localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
}
@ -86,5 +101,7 @@ func main() {
// Redirect back to the referrer with the encrypted client key
redirectUri := strings.Split(js.Global().Get("document").Get("referrer").String(), "?")[0]
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", redirectUri+"?ecdhPublicKey="+base64.URLEncoding.EncodeToString(privateKey.PublicKey().Bytes())+"&nonce="+base64.URLEncoding.EncodeToString(nonce)+"&cipherText="+base64.URLEncoding.EncodeToString(encryptedClientKey))
}

View file

@ -2,13 +2,14 @@ package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"syscall/js"
"time"
"encoding/json"
"net/url"
"syscall/js"
"git.ailur.dev/ailur/jsFetch"
)
func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
@ -21,7 +22,7 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
var statusText = js.Global().Get("document").Call("createElement", "p")
statusText.Set("innerText", "Error contacting server: "+err.Error())
@ -45,7 +46,7 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
@ -95,7 +96,7 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
if err != nil {
js.Global().Call("alert", "Error contacting server: "+err.Error())
return
@ -115,7 +116,7 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
@ -191,9 +192,22 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
}
func main() {
// Transition in
js.Global().Get("document").Get("documentElement").Get("style").Set("display", "initial")
js.Global().Get("swipe-out").Get("classList").Call("add", "swipe-out-animate")
var sleepTime = 200 * time.Millisecond
if js.Global().Get("window").Call("matchMedia", "(prefers-reduced-motion: reduce)").Get("matches").Bool() {
sleepTime = 500 * time.Millisecond
}
time.Sleep(sleepTime)
// Redirect to log-in if not signed in
localStorage := js.Global().Get("localStorage")
if localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
}
@ -233,7 +247,7 @@ func main() {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
js.Global().Call("alert", "Error contacting server: "+err.Error())
return
@ -244,10 +258,12 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Redirect to log-out if not signed in
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/logout")
return
} else if response.StatusCode == 500 {
@ -263,7 +279,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Alert the user if the server is down
@ -274,7 +290,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Fetch the OAuth clients
@ -289,7 +305,7 @@ func main() {
return
}
response, err = http.Post(requestUri, "application/json", bytes.NewReader(body))
response, err = jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
var statusText = js.Global().Get("document").Call("createElement", "p")
statusText.Set("innerText", "Error contacting server: "+err.Error())
@ -313,7 +329,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
@ -373,7 +389,7 @@ func main() {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
if err != nil {
js.Global().Call("alert", "Error contacting server: "+err.Error())
return
@ -393,12 +409,14 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
sessionElement.Call("remove")
if session.(map[string]interface{})["session"].(string) == localStorage.Call("getItem", "DONOTSHARE-secretKey").String() {
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/logout")
}
} else if response.StatusCode != 500 {
@ -436,7 +454,7 @@ func main() {
}
// Re-use the body variable for this request
response, err = http.Post(requestUri, "application/json", bytes.NewReader(body))
response, err = jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
js.Global().Call("alert", "Error contacting server: "+err.Error())
return
@ -509,7 +527,7 @@ func main() {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
if err != nil {
statusBox.Set("innerText", "Error contacting server: "+err.Error())
return
@ -529,7 +547,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
@ -543,7 +561,7 @@ func main() {
// Marshal the body
body, err := json.Marshal(bodyMap)
if err != nil {
fmt.Println("Error marshaling body: " + err.Error() + ", this is non-fatal.")
println("Error marshaling body: " + err.Error() + ", this is non-fatal.")
return
}
@ -580,7 +598,7 @@ func main() {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
if err != nil {
statusBox.Set("innerText", "Error contacting server: "+err.Error())
return
@ -600,10 +618,12 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/logout")
} else if response.StatusCode != 500 {
js.Global().Call("alert", responseMap["error"].(string))
@ -649,7 +669,7 @@ func main() {
}
// Send the request
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
if err != nil {
js.Global().Call("alert", "Error contacting server: "+err.Error())
return
@ -669,10 +689,12 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// We don't care about the response, we're logging out anyway
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/logout")
}()
return nil

View file

@ -2,16 +2,18 @@ package main
import (
"bytes"
"strings"
"time"
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"syscall/js"
"time"
"golang.org/x/crypto/argon2"
"git.ailur.dev/ailur/jsFetch"
)
var currentInputType = 0
@ -68,10 +70,34 @@ func showInput(inputType int, inputContainer js.Value, usernameBox js.Value, sig
}
func main() {
// Transition in
js.Global().Get("document").Get("documentElement").Get("style").Set("display", "initial")
js.Global().Get("swipe-out").Get("classList").Call("add", "swipe-out-animate")
var sleepTime = 200 * time.Millisecond
if js.Global().Get("window").Call("matchMedia", "(prefers-reduced-motion: reduce)").Get("matches").Bool() {
sleepTime = 500 * time.Millisecond
}
time.Sleep(sleepTime)
// Parse the url parameters using url.ParseQuery
dashboard := false
_, err := url.ParseQuery(strings.TrimPrefix(js.Global().Get("window").Get("location").Get("search").String(), "?"))
if err != nil {
dashboard = true
}
// Redirect to app if already signed in
localStorage := js.Global().Get("localStorage")
if !localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
if !dashboard {
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
} else {
js.Global().Get("window").Get("location").Call("replace", "/dashboard")
}
}
var usernameBox = js.Global().Get("document").Call("getElementById", "usernameBox")
@ -109,7 +135,7 @@ func main() {
// Hash the password
statusBox.Set("innerText", "Hashing password...")
fmt.Println("Hashing password...")
println("Hashing password...")
// Fetch the challenge from the server
body, err := json.Marshal(map[string]interface{}{
@ -129,7 +155,7 @@ func main() {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error contacting server: "+err.Error())
@ -151,7 +177,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
@ -166,6 +192,7 @@ func main() {
signupBody := map[string]interface{}{
"username": username,
"signature": base64.StdEncoding.EncodeToString(signature),
"verifier": responseMap["verifier"].(string),
}
// Marshal the body
@ -185,8 +212,8 @@ func main() {
}
// Send the request
fmt.Println("Sending request to", requestUri)
response, err = http.Post(requestUri, "application/json", bytes.NewReader(body))
println("Sending request to", requestUri)
response, err = jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
statusBox.Set("innerText", "Error contacting server: "+err.Error())
@ -194,7 +221,7 @@ func main() {
}
// Read the response
fmt.Println("Reading response...")
println("Reading response...")
decoder = json.NewDecoder(response.Body)
err = decoder.Decode(&responseMap)
if err != nil {
@ -206,12 +233,12 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
// Logged in
fmt.Println("Logged in!")
println("Logged in!")
statusBox.Set("innerText", "Setting up encryption keys...")
localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
localStorage.Call("setItem", "DONOTSHARE-clientKey", base64.StdEncoding.EncodeToString(hashPassword(password, []byte("fg-auth-client"))))
@ -219,7 +246,13 @@ func main() {
// Redirect to app
statusBox.Set("innerText", "Welcome!")
time.Sleep(time.Second)
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
if !dashboard {
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
} else {
js.Global().Get("window").Get("location").Call("replace", "/dashboard")
}
} else if response.StatusCode == 401 {
// Login failed
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
@ -252,7 +285,11 @@ func main() {
}))
signupButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
js.Global().Get("window").Get("location").Call("replace", "/signup"+js.Global().Get("window").Get("location").Get("search").String())
go func() {
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/signup"+js.Global().Get("window").Get("location").Get("search").String())
}()
return nil
}))

View file

@ -2,22 +2,22 @@ package main
import (
"bytes"
"strconv"
"strings"
"time"
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"fmt"
"strconv"
"time"
"encoding/json"
"net/http"
"net/url"
"syscall/js"
"golang.org/x/crypto/argon2"
"syscall/js"
"git.ailur.dev/ailur/jsFetch"
)
func showElements(show bool, elements ...js.Value) {
@ -57,10 +57,34 @@ func pow(resource string) (string, string, error) {
}
func main() {
// Transition in
js.Global().Get("document").Get("documentElement").Get("style").Set("display", "initial")
js.Global().Get("swipe-out").Get("classList").Call("add", "swipe-out-animate")
var sleepTime = 200 * time.Millisecond
if js.Global().Get("window").Call("matchMedia", "(prefers-reduced-motion: reduce)").Get("matches").Bool() {
sleepTime = 500 * time.Millisecond
}
time.Sleep(sleepTime)
// Parse the url parameters using url.ParseQuery
dashboard := false
_, err := url.ParseQuery(strings.TrimPrefix(js.Global().Get("window").Get("location").Get("search").String(), "?"))
if err != nil {
dashboard = true
}
// Redirect to app if already signed in
localStorage := js.Global().Get("localStorage")
if !localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
if !dashboard {
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
} else {
js.Global().Get("window").Get("location").Call("replace", "/dashboard")
}
}
var usernameBox = js.Global().Get("document").Call("getElementById", "usernameBox")
@ -106,7 +130,7 @@ func main() {
}
// Start the signup process
fmt.Println("Starting signup process for user: " + username)
println("Starting signup process for user: " + username)
showElements(false, inputContainer, signupButton, loginButton)
if captcha == "" {
statusBox.Set("innerText", "You must have a valid captcha! Press the \"Start\" button to start calculating a captcha.")
@ -145,7 +169,7 @@ func main() {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
showElements(true, inputContainer, signupButton, loginButton)
statusBox.Set("innerText", "Error contacting server: "+err.Error())
@ -167,7 +191,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
@ -179,7 +203,13 @@ func main() {
// Redirect to app
statusBox.Set("innerText", "Welcome!")
time.Sleep(time.Second)
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
if !dashboard {
js.Global().Get("window").Get("location").Call("replace", "/authorize"+js.Global().Get("window").Get("location").Get("search").String())
} else {
js.Global().Get("window").Get("location").Call("replace", "/dashboard")
}
} else if response.StatusCode == 409 {
// Username taken
showElements(true, inputContainer, signupButton, loginButton)
@ -199,7 +229,11 @@ func main() {
}))
loginButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
go func() {
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
}()
return nil
}))

View file

@ -2,6 +2,11 @@ package main
import (
"bytes"
"errors"
"strconv"
"strings"
"time"
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
@ -9,14 +14,11 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/cespare/xxhash/v2"
"net/http"
"net/url"
"strconv"
"strings"
"syscall/js"
"git.ailur.dev/ailur/jsFetch"
"github.com/cespare/xxhash/v2"
)
func sha256Base64(s string) string {
@ -46,9 +48,22 @@ func randomChars(length int) (string, error) {
}
func main() {
// Transition in
js.Global().Get("document").Get("documentElement").Get("style").Set("display", "initial")
js.Global().Get("swipe-out").Get("classList").Call("add", "swipe-out-animate")
var sleepTime = 200 * time.Millisecond
if js.Global().Get("window").Call("matchMedia", "(prefers-reduced-motion: reduce)").Get("matches").Bool() {
sleepTime = 500 * time.Millisecond
}
time.Sleep(sleepTime)
// Redirect to log-in if not signed in
localStorage := js.Global().Get("localStorage")
if localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
}
@ -73,7 +88,7 @@ func main() {
return
}
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
if err != nil {
statusBox.Set("innerText", "Error contacting server: "+err.Error())
return
@ -84,10 +99,12 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Redirect to log-out if not signed in
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/logout"+js.Global().Get("window").Get("location").Get("search").String())
return
} else if response.StatusCode == 500 {
@ -103,7 +120,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Alert the user if the server is down
@ -114,7 +131,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Check if the URL has a code
@ -143,7 +160,7 @@ func main() {
return
}
response, err := http.Post(requestUri, "application/x-www-form-urlencoded", strings.NewReader(formData.Encode()))
response, err := jsFetch.Post(requestUri, "application/x-www-form-urlencoded", strings.NewReader(formData.Encode()))
if err != nil {
statusBox.Set("innerText", "Error contacting server: "+err.Error())
return
@ -161,7 +178,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
if response.StatusCode == 200 {
@ -173,7 +190,7 @@ func main() {
}
// Create the request
request, err := http.NewRequest("GET", requestUri, nil)
request, err := jsFetch.NewRequest("GET", requestUri, nil)
if err != nil {
statusBox.Set("innerText", "Error creating request: "+err.Error())
return
@ -183,7 +200,7 @@ func main() {
request.Header.Set("Authorization", "Bearer "+responseMap["id_token"].(string))
// Send the request
response, err := http.DefaultClient.Do(request)
response, err := jsFetch.Fetch.Do(request)
if err != nil {
statusBox.Set("innerText", "Error contacting server: "+err.Error())
return
@ -200,7 +217,7 @@ func main() {
// Close the response body
err = response.Body.Close()
if err != nil {
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
}
// Set the username
@ -217,7 +234,9 @@ func main() {
localStorage.Call("setItem", "TESTER-privateKey", base64.StdEncoding.EncodeToString(privateKey.Bytes()))
// Redirect to the client key exchange endpoint
js.Global().Get("window").Get("location").Call("replace", "/clientKeyShare?ecdhPublicKey="+base64.URLEncoding.EncodeToString(privateKey.PublicKey().Bytes())+"&accessToken="+responseMap["access_token"].(string))
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
// js.Global().Get("window").Get("location").Call("replace", "/clientKeyShare?ecdhPublicKey="+base64.URLEncoding.EncodeToString(privateKey.PublicKey().Bytes())+"&accessToken="+responseMap["access_token"].(string))
return
} else if response.StatusCode != 500 {
statusBox.Set("innerText", responseMap["error"].(string))
@ -317,6 +336,8 @@ func main() {
localStorage.Call("setItem", "TESTER-verifier", verifier)
// Redirect to the authorization page
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
time.Sleep(sleepTime)
js.Global().Get("window").Get("location").Call("replace", "/authorize?response_type=code&client_id=TestApp-DoNotUse&redirect_uri="+url.QueryEscape(js.Global().Get("window").Get("location").Get("origin").String()+"/testApp")+"&code_challenge="+verifierChallenge+"&code_challenge_method=S256")
}()
return nil

View file

@ -2,4 +2,6 @@
path=$(realpath "$(dirname "$0")") || exit 1
rm -rf "$path/../../services/storage.fgs" || exit 1
printf "\033[1;35mBuilding storage.fgs...\033[0m\n"
go build -o "$path/../../services/storage.fgs" --buildmode=plugin -ldflags "-s -w" || exit 1
printf "\033[1;36mstorage.fgs has been built successfully!\033[0m\n"

View file

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