Compare commits
No commits in common. "master" and "v0.0.6" have entirely different histories.
73 changed files with 1702 additions and 2271 deletions
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -1,9 +1,6 @@
|
||||||
.idea
|
.idea
|
||||||
/fulgens
|
fulgens
|
||||||
/database
|
databases
|
||||||
/databases
|
resources
|
||||||
/resources
|
services
|
||||||
/services
|
services-src/eternity-web
|
||||||
/services-src/eternity-web
|
|
||||||
/config.yaml
|
|
||||||
fulgens.log
|
|
||||||
|
|
30
README.md
30
README.md
|
@ -24,8 +24,6 @@ Then, build the server:
|
||||||
./build.sh
|
./build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
After that, configure the server using the `config.json` file (see below), and you're ready to go!
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
To run the server, simply run the binary:
|
To run the server, simply run the binary:
|
||||||
```sh
|
```sh
|
||||||
|
@ -33,7 +31,33 @@ To run the server, simply run the binary:
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
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).
|
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
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Contributions are welcome! Please open a pull request with your changes.
|
Contributions are welcome! Please open a pull request with your changes.
|
||||||
|
|
36
build.sh
36
build.sh
|
@ -1,33 +1,13 @@
|
||||||
#!/bin/sh
|
#!/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
|
path=$(realpath "$(dirname "$0")") || exit 1
|
||||||
searchDir="$path/services-src"
|
search_dir="$path/services-src"
|
||||||
find -L "$searchDir" -type f -name "build.sh" | while read -r buildScript; do
|
find -L "$search_dir" -type f -name "build.sh" | while read -r build_script; do
|
||||||
clear
|
echo "Running $build_script..."
|
||||||
buildDir=$(dirname "$buildScript")
|
build_dir=$(dirname "$build_script")
|
||||||
fancy "\033[1;104m" "Starting build of $(basename "$buildDir")..."
|
(cd "$build_dir" && ./build.sh) || {
|
||||||
(cd "$buildDir" && ./build.sh) || {
|
echo "Error: $build_script failed."
|
||||||
printf "\033[1;31mError: %s failed.\033[0m\n" "$buildScript"
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
clear
|
go build --ldflags "-s -w" -o "$path/fulgens" || exit 1
|
||||||
fancy "\033[1;105m" "Building Fulgens..."
|
echo "Fulgens has been built successfully."
|
||||||
go build -C "$path" --ldflags "-s -w" -o "$path/fulgens" || exit 1
|
|
||||||
clear
|
|
||||||
fancy "\033[1;102m" "Fulgens has been built successfully!"
|
|
||||||
|
|
30
config.json
Normal file
30
config.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,159 +0,0 @@
|
||||||
# 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
45
go.mod
|
@ -1,35 +1,40 @@
|
||||||
module git.ailur.dev/ailur/fulgens
|
module git.ailur.dev/ailur/fulgens
|
||||||
|
|
||||||
go 1.23.3
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.ailur.dev/ailur/fg-library/v3 v3.6.2
|
git.ailur.dev/ailur/fg-library v1.0.0
|
||||||
git.ailur.dev/ailur/fg-nucleus-library v1.2.2
|
git.ailur.dev/ailur/fg-nucleus-library v1.0.0
|
||||||
git.ailur.dev/ailur/pow v1.0.3
|
git.ailur.dev/ailur/pow v1.0.0
|
||||||
github.com/CAFxX/httpcompression v0.0.9
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0
|
github.com/cespare/xxhash/v2 v2.3.0
|
||||||
github.com/go-chi/chi/v5 v5.2.1
|
github.com/go-chi/chi/v5 v5.1.0
|
||||||
github.com/go-playground/validator/v10 v10.25.0
|
github.com/go-playground/validator/v10 v10.22.1
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/klauspost/compress v1.18.0
|
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
golang.org/x/crypto v0.28.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
modernc.org/sqlite v1.33.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/stretchr/testify v1.10.0 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
golang.org/x/net v0.37.0 // indirect
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // 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
|
||||||
)
|
)
|
||||||
|
|
136
go.sum
136
go.sum
|
@ -1,87 +1,93 @@
|
||||||
git.ailur.dev/ailur/fg-library/v3 v3.6.2 h1:PNJKxpvbel2iDeB9+/rpYRyMoim6JjRHOXPYFYky7Ng=
|
git.ailur.dev/ailur/fg-library v1.0.0 h1:61RkJW9g4PqAiZFjpwUnx6QUYYSeXJXLjLi1d47NfTA=
|
||||||
git.ailur.dev/ailur/fg-library/v3 v3.6.2/go.mod h1:ArNsafpqES2JuxQM5aM+bNe0FwHLIsL6pbjpiWvDwGs=
|
git.ailur.dev/ailur/fg-library v1.0.0/go.mod h1:hOUkxs2rRouSwNnNZlo7CsFVH12kmjqheyzPQ4to1N8=
|
||||||
git.ailur.dev/ailur/fg-nucleus-library v1.2.2 h1:JbclmxGSoL+ByGZAl0W6PqWRoyBBGTrKrizWDJ7rdI0=
|
git.ailur.dev/ailur/fg-nucleus-library v1.0.0 h1:TT1V4cfka+uUpvV1zU7bc4KXFkgnsI/sIvaZDDxXk+k=
|
||||||
git.ailur.dev/ailur/fg-nucleus-library v1.2.2/go.mod h1:stxiTyMv3Fa7GzpyLbBUh3ahlb7110p0NnCl8ZTjwBs=
|
git.ailur.dev/ailur/fg-nucleus-library v1.0.0/go.mod h1:m4gNSEypfgrUV8bXaR8NLB8zchUM59y0ellV1wp/C+I=
|
||||||
git.ailur.dev/ailur/pow v1.0.3 h1:LjLSol4ax+M+SoajVjbBoDjfmjH6pKu3fDka7bl2KGY=
|
git.ailur.dev/ailur/pow v1.0.0 h1:eCJiZSbskcmzmwR4Nv4YrYpsZci5kfoGM9ihkXAHHoU=
|
||||||
git.ailur.dev/ailur/pow v1.0.3/go.mod h1:ClAmIdHQ/N9wTq5S4YWhQ5d9CPUBcEjVuOkT07zBdJ4=
|
git.ailur.dev/ailur/pow v1.0.0/go.mod h1:BHl7H6B6uN+q2cCCUlno6JMhqLa2A52wkbAdJbq2izA=
|
||||||
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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
||||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
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/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||||
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k=
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||||
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s=
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
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 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||||
github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||||
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
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=
|
||||||
|
|
|
@ -5,19 +5,19 @@ resourceDir="$path/../../resources/00000000-0000-0000-0000-000000000004"
|
||||||
rm -rf "$resourceDir" || exit 1
|
rm -rf "$resourceDir" || exit 1
|
||||||
rm -rf "$path/../../services/auth.fgs" || exit 1
|
rm -rf "$path/../../services/auth.fgs" || exit 1
|
||||||
cd "$path" || 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
|
go build -o "$path/../../services/auth.fgs" --buildmode=plugin -ldflags "-s -w" || exit 1
|
||||||
mkdir -p "$resourceDir/static/wasm" || exit 1
|
mkdir -p "$resourceDir/static/wasm" || exit 1
|
||||||
find -L "$path/resources/wasm" -type f -name "main.go" | while read -r mainGo; do
|
cd "$path/resources/wasm/login" || exit 1
|
||||||
buildDir=$(dirname "$mainGo")
|
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/login.wasm" -ldflags "-s -w" || exit 1
|
||||||
baseName=$(basename "$buildDir")
|
cd "$path/resources/wasm/signup" || exit 1
|
||||||
printf "\033[1;34m\033[1;33mBuilding WASM object %s...\033[0m\n" "$baseName"
|
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/signup.wasm" -ldflags "-s -w" || exit 1
|
||||||
(cd "$buildDir" && GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/$(basename "$buildDir").wasm" -ldflags "-s -w") || {
|
cd "$path/resources/wasm/authorize" || exit 1
|
||||||
printf "\033[1;31mError: %s failed.\033[0m\n" "$mainGo"
|
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/authorize.wasm" -ldflags "-s -w" || exit 1
|
||||||
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
|
||||||
done
|
cd "$path/resources/wasm/testApp" || exit 1
|
||||||
printf "\033[1;34mCopying static files...\033[0m\n"
|
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
|
||||||
cp -r "$path/resources/static" "$resourceDir/" || exit 1
|
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"
|
|
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// Fulgens libraries
|
// Fulgens libraries
|
||||||
library "git.ailur.dev/ailur/fg-library/v3"
|
library "git.ailur.dev/ailur/fg-library"
|
||||||
authLibrary "git.ailur.dev/ailur/fg-nucleus-library"
|
authLibrary "git.ailur.dev/ailur/fg-nucleus-library"
|
||||||
"git.ailur.dev/ailur/pow"
|
"git.ailur.dev/ailur/pow"
|
||||||
|
|
||||||
|
@ -25,18 +25,20 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
// Extra libraries
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
|
||||||
// External libraries
|
// External libraries
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ServiceInformation = library.Service{
|
var ServiceInformation = library.Service{
|
||||||
Name: "Authentication",
|
Name: "Authentication",
|
||||||
Permissions: library.Permissions{
|
Permissions: library.Permissions{
|
||||||
Authenticate: false, // This service *is* the authentication service
|
Authenticate: false, // This service *is* the authentication service
|
||||||
Router: true, // This service does require a router
|
|
||||||
Database: true, // This service does require database access
|
Database: true, // This service does require database access
|
||||||
BlobStorage: false, // This service does not require blob storage
|
BlobStorage: false, // This service does not require blob storage
|
||||||
InterServiceCommunication: true, // This service does require inter-service communication
|
InterServiceCommunication: true, // This service does require inter-service communication
|
||||||
|
@ -45,34 +47,17 @@ var ServiceInformation = library.Service{
|
||||||
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"),
|
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var serviceIDBytes []byte
|
||||||
loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002")
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkScopes(scopes []string) (bool, string, error) {
|
func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
|
||||||
var clientKeyShare bool
|
|
||||||
for _, scope := range scopes {
|
|
||||||
if scope != "openid" && scope != "clientKeyShare" {
|
|
||||||
return false, "", errors.New("invalid scope")
|
|
||||||
} else {
|
|
||||||
if scope == "clientKeyShare" {
|
|
||||||
clientKeyShare = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal the scopes
|
|
||||||
scopeString, err := json.Marshal(scopes)
|
|
||||||
if err != nil {
|
|
||||||
return clientKeyShare, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientKeyShare, string(scopeString), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func logFunc(message string, messageType library.MessageCode, information *library.ServiceInitializationInformation) {
|
|
||||||
// Log the message to the logger service
|
// Log the message to the logger service
|
||||||
information.SendISMessage(loggerService, messageType, message)
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service
|
||||||
|
MessageType: messageType,
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureTrailingSlash(url string) string {
|
func ensureTrailingSlash(url string) string {
|
||||||
|
@ -108,33 +93,25 @@ func sha256Base64(s string) string {
|
||||||
return encoded
|
return encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(statusCode int, w http.ResponseWriter, data map[string]interface{}, templatePath string, information *library.ServiceInitializationInformation) {
|
func renderTemplate(statusCode int, w http.ResponseWriter, data map[string]interface{}, templatePath string, information library.ServiceInitializationInformation) {
|
||||||
var err error
|
var err error
|
||||||
var requestedTemplate *template.Template
|
var requestedTemplate *template.Template
|
||||||
// Output ls of the resource directory
|
// Output ls of the resource directory
|
||||||
requestedTemplate, err = template.ParseFS(information.ResourceDir, "templates/"+templatePath)
|
requestedTemplate, err = template.ParseFS(information.ResourceDir, "templates/"+templatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
http.Error(w, err.Error(), 500)
|
renderString(500, w, "Sorry, something went wrong on our end. Error code: 01. Please report to the administrator.", information)
|
||||||
} else {
|
} 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)
|
w.WriteHeader(statusCode)
|
||||||
err = requestedTemplate.Execute(w, data)
|
err = requestedTemplate.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
http.Error(w, err.Error(), 500)
|
renderString(500, w, "Sorry, something went wrong on our end. Error code: 02. Please report to the administrator.", information)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
w.WriteHeader(statusCode)
|
||||||
_, err := w.Write([]byte(data))
|
_, err := w.Write([]byte(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -142,8 +119,7 @@ func renderString(statusCode int, w http.ResponseWriter, data string, informatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderJSON(statusCode int, w http.ResponseWriter, data map[string]interface{}, information *library.ServiceInitializationInformation) {
|
func renderJSON(statusCode int, w http.ResponseWriter, data map[string]interface{}, information library.ServiceInitializationInformation) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(statusCode)
|
w.WriteHeader(statusCode)
|
||||||
err := json.NewEncoder(w).Encode(data)
|
err := json.NewEncoder(w).Encode(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -188,12 +164,11 @@ func verifyJwt(token string, publicKey ed25519.PublicKey, mem *sql.DB) ([]byte,
|
||||||
return userId, claims, true
|
return userId, claims, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func Main(information *library.ServiceInitializationInformation) {
|
func Main(information library.ServiceInitializationInformation) {
|
||||||
var conn library.Database
|
var conn *sql.DB
|
||||||
var mem *sql.DB
|
var mem *sql.DB
|
||||||
var publicKey ed25519.PublicKey
|
var publicKey ed25519.PublicKey
|
||||||
var privateKey ed25519.PrivateKey
|
var privateKey ed25519.PrivateKey
|
||||||
|
|
||||||
// Load the configuration
|
// Load the configuration
|
||||||
privacyPolicy := information.Configuration["privacyPolicy"].(string)
|
privacyPolicy := information.Configuration["privacyPolicy"].(string)
|
||||||
hostName := information.Configuration["url"].(string)
|
hostName := information.Configuration["url"].(string)
|
||||||
|
@ -205,86 +180,73 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
identifier := information.Configuration["identifier"].(string)
|
identifier := information.Configuration["identifier"].(string)
|
||||||
adminKey := information.Configuration["adminKey"].(string)
|
adminKey := information.Configuration["adminKey"].(string)
|
||||||
|
|
||||||
// Start the ISM processor
|
var err error
|
||||||
go information.StartISProcessor()
|
serviceIDBytes, err = ServiceInformation.ServiceID.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
logFunc(err.Error(), 3, information)
|
||||||
|
}
|
||||||
|
|
||||||
// Initiate a connection to the database
|
// Initiate a connection to the database
|
||||||
conn, err := information.GetDatabase()
|
// Call service ID 1 to get the database connection information
|
||||||
if err != nil {
|
information.Outbox <- library.InterServiceMessage{
|
||||||
logFunc(err.Error(), 3, information)
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), // Service initialization service
|
||||||
|
MessageType: 1, // Request connection information
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: nil,
|
||||||
}
|
}
|
||||||
if conn.DBType == library.Sqlite {
|
|
||||||
|
// Wait for the response
|
||||||
|
response := <-information.Inbox
|
||||||
|
if response.MessageType == 2 {
|
||||||
|
// This is the connection information
|
||||||
|
// Set up the database connection
|
||||||
|
conn = response.Message.(*sql.DB)
|
||||||
// Create the global table
|
// Create the global table
|
||||||
// Uniqueness check is a hack to ensure we only have one global row
|
// Uniqueness check is a hack to ensure we only have one global row
|
||||||
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
|
_, err := conn.Exec("CREATE TABLE IF NOT EXISTS global (key BLOB NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
// Create the users table
|
// 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)")
|
_, err = conn.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, password BLOB NOT NULL, salt BLOB NOT NULL)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
// Create the oauth table
|
// 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\"]')")
|
_, err = conn.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 {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Create the global table
|
|
||||||
// Uniqueness check is a hack to ensure we only have one global row
|
|
||||||
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS global (key BYTEA NOT NULL, uniquenessCheck BOOLEAN NOT NULL UNIQUE CHECK (uniquenessCheck = true) DEFAULT true)")
|
|
||||||
if err != nil {
|
|
||||||
logFunc(err.Error(), 3, information)
|
|
||||||
}
|
|
||||||
// Create the users table
|
|
||||||
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY NOT NULL UNIQUE, created INTEGER NOT NULL, username TEXT NOT NULL UNIQUE, publicKey BYTEA NOT NULL)")
|
|
||||||
if err != nil {
|
|
||||||
logFunc(err.Error(), 3, information)
|
|
||||||
}
|
|
||||||
// Create the oauth table
|
|
||||||
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS oauth (appId TEXT NOT NULL UNIQUE, secret TEXT, creator BYTEA NOT NULL, redirectUri TEXT NOT NULL, name TEXT NOT NULL, keyShareUri TEXT NOT NULL DEFAULT '', scopes TEXT NOT NULL DEFAULT '[\"openid\"]')")
|
|
||||||
if err != nil {
|
|
||||||
logFunc(err.Error(), 3, information)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Set up the in-memory cache
|
// Set up the in-memory cache
|
||||||
mem, err = sql.Open("sqlite3", "file:"+ServiceInformation.ServiceID.String()+"?mode=memory&cache=shared")
|
mem, err = sql.Open("sqlite", "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 {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
// Create the sessions table
|
// Create the sessions table
|
||||||
_, err = mem.Exec("CREATE TABLE sessions (id BLOB NOT NULL, session TEXT NOT NULL, device TEXT NOT NULL DEFAULT '?')")
|
_, err = mem.Exec("CREATE TABLE IF NOT EXISTS sessions (id BLOB NOT NULL, session TEXT NOT NULL, device TEXT NOT NULL DEFAULT '?')")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
// Create the logins table
|
// 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)")
|
_, err = mem.Exec("CREATE TABLE IF NOT EXISTS 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 {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
// Create the spent PoW table
|
// Create the spent PoW table
|
||||||
_, err = mem.Exec("CREATE TABLE spent (hash BLOB NOT NULL UNIQUE, expires INTEGER NOT NULL)")
|
_, err = mem.Exec("CREATE TABLE IF NOT EXISTS spent (hash BLOB NOT NULL UNIQUE, expires INTEGER NOT NULL)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// Set up the signing keys
|
// Set up the signing keys
|
||||||
// Check if the global table has the keys
|
// Check if the global table has the keys
|
||||||
err = conn.DB.QueryRow("SELECT key FROM global LIMIT 1").Scan(&privateKey)
|
err = conn.QueryRow("SELECT key FROM global LIMIT 1").Scan(&privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
// Generate a new key
|
// Generate a new key
|
||||||
|
@ -294,7 +256,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
// Insert the key into the global table
|
// Insert the key into the global table
|
||||||
_, err = conn.DB.Exec("INSERT INTO global (key) VALUES ($1)", privateKey)
|
_, err = conn.Exec("INSERT INTO global (key) VALUES (?)", privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
|
@ -306,17 +268,22 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the test app
|
// Set up the test app
|
||||||
_, err = conn.DB.Exec("DELETE FROM oauth WHERE appId = 'TestApp-DoNotUse'")
|
_, err = conn.Exec("DELETE FROM oauth WHERE appId = 'TestApp-DoNotUse'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
testAppIsAvailable = false
|
testAppIsAvailable = false
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
}
|
}
|
||||||
|
|
||||||
if testAppIsInternalApp {
|
if testAppIsInternalApp {
|
||||||
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", ServiceInformation.ServiceID[:], ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
|
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', ?, 'Test App', ?, '[\"openid\", \"clientKeyShare\"]', ?)", serviceIDBytes, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
|
||||||
} else {
|
} else {
|
||||||
testAppCreator := uuid.New()
|
testAppCreator, err := uuid.New().MarshalBinary()
|
||||||
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', $1, 'Test App', $2, '[\"openid\", \"clientKeyShare\"]', $3)", testAppCreator[:], ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
|
if err != nil {
|
||||||
|
testAppIsAvailable = false
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ('TestApp-DoNotUse', 'none', ?, 'Test App', ?, '[\"openid\", \"clientKeyShare\"]', ?)", testAppCreator, ensureTrailingSlash(hostName)+"testApp", ensureTrailingSlash(hostName)+"keyExchangeTester")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
testAppIsAvailable = false
|
testAppIsAvailable = false
|
||||||
|
@ -326,26 +293,6 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
// Set up the router
|
// Set up the router
|
||||||
router := information.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
|
// Set up the static routes
|
||||||
staticDir, err := fs.Sub(information.ResourceDir, "static")
|
staticDir, err := fs.Sub(information.ResourceDir, "static")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -389,22 +336,22 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Get("/authorize", func(w http.ResponseWriter, r *http.Request) {
|
router.Get("/authorize", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Query().Get("client_id") != "" {
|
|
||||||
var name string
|
var name string
|
||||||
var creator []byte
|
var creator []byte
|
||||||
err := conn.DB.QueryRow("SELECT name, creator FROM oauth WHERE appId = $1", r.URL.Query().Get("client_id")).Scan(&name, &creator)
|
if r.URL.Query().Get("client_id") != "" {
|
||||||
|
err := conn.QueryRow("SELECT name, creator FROM oauth WHERE appId = ?", r.URL.Query().Get("client_id")).Scan(&name, &creator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
renderString(404, w, "App not found", information)
|
renderString(404, w, "App not found", information)
|
||||||
} else {
|
} else {
|
||||||
logFunc(err.Error(), 2, 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)
|
renderString(500, w, "Sorry, something went wrong on our end. Error code: 03. Please report to the administrator.", information)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the app is internal
|
if !bytes.Equal(creator, serviceIDBytes) {
|
||||||
if !bytes.Equal(creator, ServiceInformation.ServiceID[:]) {
|
|
||||||
renderTemplate(200, w, map[string]interface{}{
|
renderTemplate(200, w, map[string]interface{}{
|
||||||
"identifier": identifier,
|
"identifier": identifier,
|
||||||
"name": name,
|
"name": name,
|
||||||
|
@ -415,9 +362,6 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
"name": name,
|
"name": name,
|
||||||
}, "autoAccept.html", information)
|
}, "autoAccept.html", information)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
http.Redirect(w, r, "/dashboard", 301)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) {
|
router.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -442,7 +386,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
|
|
||||||
// Check if they have the clientKeyShare scope
|
// Check if they have the clientKeyShare scope
|
||||||
var scopes string
|
var scopes string
|
||||||
err = conn.DB.QueryRow("SELECT scopes FROM oauth WHERE appId = $1", claims["aud"]).Scan(&scopes)
|
err = conn.QueryRow("SELECT scopes FROM oauth WHERE appId = ?", claims["aud"]).Scan(&scopes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderString(500, w, "Sorry, something went wrong on our end. Error code: 20. Please report to the administrator.", information)
|
renderString(500, w, "Sorry, something went wrong on our end. Error code: 20. Please report to the administrator.", information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -490,7 +434,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
router.Post("/api/changePassword", func(w http.ResponseWriter, r *http.Request) {
|
router.Post("/api/changePassword", func(w http.ResponseWriter, r *http.Request) {
|
||||||
type changePassword struct {
|
type changePassword struct {
|
||||||
Session string `json:"session"`
|
Session string `json:"session"`
|
||||||
NewPublicKey string `json:"newPublicKey"`
|
NewPassword string `json:"newPassword"`
|
||||||
}
|
}
|
||||||
var data changePassword
|
var data changePassword
|
||||||
err = json.NewDecoder(r.Body).Decode(&data)
|
err = json.NewDecoder(r.Body).Decode(&data)
|
||||||
|
@ -507,14 +451,34 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the public key
|
// Generate a new salt
|
||||||
_, err = conn.DB.Exec("UPDATE users SET publicKey = $1 WHERE id = $2", data.NewPublicKey, userId)
|
// We want it to be binary data, not alphanumerical, so we don't use randomChars
|
||||||
|
salt := make([]byte, 16)
|
||||||
|
_, err = rand.Read(salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "04"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "04"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode the new password
|
||||||
|
newPassword, err := base64.StdEncoding.DecodeString(data.NewPassword)
|
||||||
|
if err != nil {
|
||||||
|
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the password
|
||||||
|
hashedPassword := argon2.IDKey(newPassword, salt, 64, 4096, 1, 32)
|
||||||
|
|
||||||
|
// Update the password
|
||||||
|
_, err = conn.Exec("UPDATE users SET password = ?, salt = ? WHERE id = ?", hashedPassword, salt, userId)
|
||||||
|
if err != nil {
|
||||||
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "05"}, information)
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Invalidate all sessions
|
// Invalidate all sessions
|
||||||
_, err = mem.Exec("DELETE FROM sessions WHERE id = ?", userId)
|
_, err = mem.Exec("DELETE FROM sessions WHERE id = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -530,7 +494,8 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
router.Post("/api/signup", func(w http.ResponseWriter, r *http.Request) {
|
router.Post("/api/signup", func(w http.ResponseWriter, r *http.Request) {
|
||||||
type signup struct {
|
type signup struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
PublicKey string `json:"publicKey"`
|
Password string `json:"password"`
|
||||||
|
Salt string `json:"salt"`
|
||||||
ProofOfWork string `json:"proofOfWork"`
|
ProofOfWork string `json:"proofOfWork"`
|
||||||
}
|
}
|
||||||
var data signup
|
var data signup
|
||||||
|
@ -579,22 +544,29 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the public key
|
// Decode the password
|
||||||
publicKey, err := base64.StdEncoding.DecodeString(data.PublicKey)
|
password, err := base64.StdEncoding.DecodeString(data.Password)
|
||||||
|
if err != nil {
|
||||||
|
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the salt
|
||||||
|
salt, err := base64.StdEncoding.DecodeString(data.Salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
|
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to insert the user
|
// Try to insert the user
|
||||||
userID := uuid.New()
|
userId := uuid.New()
|
||||||
|
userIdBytes, err := userId.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "08"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "08"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
_, err = conn.Exec("INSERT INTO users (id, created, username, password, salt, created) VALUES (?, ?, ?, ?, ?, ?)", userIdBytes, time.Now().Unix(), data.Username, password, salt, 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 err != nil {
|
||||||
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
||||||
renderJSON(409, w, map[string]interface{}{"error": "Username already taken"}, information)
|
renderJSON(409, w, map[string]interface{}{"error": "Username already taken"}, information)
|
||||||
|
@ -616,42 +588,45 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the session
|
// Insert the session
|
||||||
_, err = mem.Exec("INSERT INTO sessions (id, session, device) VALUES (?, ?, ?)", userID[:], session, r.Header.Get("User-Agent"))
|
_, err = mem.Exec("INSERT INTO sessions (id, session, device) VALUES (?, ?, ?)", userIdBytes, session, r.Header.Get("User-Agent"))
|
||||||
|
|
||||||
// Return success, as well as the session token
|
// Return success, as well as the session token
|
||||||
renderJSON(200, w, map[string]interface{}{"key": session}, information)
|
renderJSON(200, w, map[string]interface{}{"key": session}, information)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Post("/api/loginChallenge", func(w http.ResponseWriter, r *http.Request) {
|
router.Post("/api/loginChallenge", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Generate a new challenge
|
type login struct {
|
||||||
challenge, err := randomChars(512)
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var data login
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "53"}, information)
|
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue a new JWT token with the challenge
|
// Get the salt for the user
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
|
var salt []byte
|
||||||
"challenge": challenge,
|
err = conn.QueryRow("SELECT salt FROM users WHERE username = ?", data.Username).Scan(&salt)
|
||||||
"exp": time.Now().Add(time.Second * 20).Unix(),
|
|
||||||
})
|
|
||||||
tokenString, err := token.SignedString(privateKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "51"}, information)
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
renderJSON(401, w, map[string]interface{}{"error": "Invalid username or password"}, information)
|
||||||
|
} else {
|
||||||
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "12"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the challenge
|
// Return the salt
|
||||||
renderJSON(200, w, map[string]interface{}{"challenge": challenge, "verifier": tokenString}, information)
|
renderJSON(200, w, map[string]interface{}{"salt": base64.StdEncoding.EncodeToString(salt)}, information)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Post("/api/login", func(w http.ResponseWriter, r *http.Request) {
|
router.Post("/api/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
type login struct {
|
type login struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Signature string `json:"signature"`
|
Password string `json:"password"`
|
||||||
Verifier string `json:"verifier"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var data login
|
var data login
|
||||||
|
@ -663,11 +638,11 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
|
|
||||||
// Try to select the user
|
// Try to select the user
|
||||||
var userId []byte
|
var userId []byte
|
||||||
var userPublicKey []byte
|
var hashedPassword []byte
|
||||||
err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &userPublicKey)
|
err = conn.QueryRow("SELECT id, password FROM users WHERE username = ?", data.Username).Scan(&userId, &hashedPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
renderJSON(401, w, map[string]interface{}{"error": "Invalid username"}, information)
|
renderJSON(401, w, map[string]interface{}{"error": "Invalid username or password"}, information)
|
||||||
} else {
|
} else {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "12"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "12"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -675,48 +650,16 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the challenge
|
// Decode the password
|
||||||
signature, err := base64.StdEncoding.DecodeString(data.Signature)
|
password, err := base64.StdEncoding.DecodeString(data.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
|
renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the challenge
|
// Verify the password
|
||||||
token, err := jwt.Parse(data.Verifier, func(token *jwt.Token) (interface{}, error) {
|
if !bytes.Equal(password, hashedPassword) {
|
||||||
return publicKey, nil
|
renderJSON(401, w, map[string]interface{}{"error": "Invalid username or password"}, information)
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
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(userPublicKey, []byte(challenge), signature) {
|
|
||||||
renderJSON(401, w, map[string]interface{}{"error": "Invalid signature"}, information)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -765,7 +708,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
// Get the username and the creation date
|
// Get the username and the creation date
|
||||||
var username string
|
var username string
|
||||||
var created int64
|
var created int64
|
||||||
err = conn.DB.QueryRow("SELECT username, created FROM users WHERE id = $1", userId).Scan(&username, &created)
|
err = conn.QueryRow("SELECT username, created FROM users WHERE id = ?", userId).Scan(&username, &created)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "15"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "15"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -828,7 +771,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
|
|
||||||
// Check if they have the openid scope
|
// Check if they have the openid scope
|
||||||
var scopes string
|
var scopes string
|
||||||
err = conn.DB.QueryRow("SELECT scopes FROM oauth WHERE appId = $1", claims["aud"]).Scan(&scopes)
|
err = conn.QueryRow("SELECT scopes FROM oauth WHERE appId = ?", claims["aud"]).Scan(&scopes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "17"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "17"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -859,7 +802,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
|
|
||||||
// Get the username
|
// Get the username
|
||||||
var username string
|
var username string
|
||||||
err := conn.DB.QueryRow("SELECT username FROM users WHERE id = $1", userId).Scan(&username)
|
err := conn.QueryRow("SELECT username FROM users WHERE id = ?", userId).Scan(&username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "19"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "19"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -870,8 +813,6 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
renderJSON(200, w, map[string]interface{}{"username": username, "sub": uuid.Must(uuid.FromBytes(userId)).String()}, information)
|
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) {
|
router.Post("/api/authorize", func(w http.ResponseWriter, r *http.Request) {
|
||||||
type authorize struct {
|
type authorize struct {
|
||||||
AppId string `json:"appId"`
|
AppId string `json:"appId"`
|
||||||
|
@ -892,7 +833,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
|
|
||||||
// Verify the AppID, redirectUri and scopes
|
// Verify the AppID, redirectUri and scopes
|
||||||
var appId, redirectUri, scopes string
|
var appId, redirectUri, scopes string
|
||||||
err = conn.DB.QueryRow("SELECT appId, redirectUri, scopes FROM oauth WHERE appId = $1", data.AppId).Scan(&appId, &redirectUri, &scopes)
|
err = conn.QueryRow("SELECT appId, redirectUri, scopes FROM oauth WHERE appId = ?", data.AppId).Scan(&appId, &redirectUri, &scopes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information)
|
renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information)
|
||||||
return
|
return
|
||||||
|
@ -1038,7 +979,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
|
|
||||||
// Verify the secret
|
// Verify the secret
|
||||||
var secret string
|
var secret string
|
||||||
err = conn.DB.QueryRow("SELECT \"secret\" FROM oauth WHERE appId = $1", r.Form.Get("client_id")).Scan(&secret)
|
err = conn.QueryRow("SELECT secret FROM oauth WHERE appId = ?", r.Form.Get("client_id")).Scan(&secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information)
|
renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information)
|
||||||
return
|
return
|
||||||
|
@ -1072,7 +1013,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
var openIDTokenString string
|
var openIDTokenString string
|
||||||
if openid {
|
if openid {
|
||||||
var username string
|
var username string
|
||||||
err := conn.DB.QueryRow("SELECT username FROM users WHERE id = $1", userId).Scan(&username)
|
err := conn.QueryRow("SELECT username FROM users WHERE id = ?", userId).Scan(&username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "29"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "29"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -1137,8 +1078,6 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Options("/api/oauth/token", disableCorsHandleFunc)
|
|
||||||
|
|
||||||
router.Post("/api/oauth/remove", func(w http.ResponseWriter, r *http.Request) {
|
router.Post("/api/oauth/remove", func(w http.ResponseWriter, r *http.Request) {
|
||||||
type remove struct {
|
type remove struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
|
@ -1161,7 +1100,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the oauth entry
|
// Delete the oauth entry
|
||||||
_, err = conn.DB.Exec("DELETE FROM oauth WHERE appId = $1 AND creator = $2", data.AppID, userId)
|
_, err = conn.Exec("DELETE FROM oauth WHERE appId = ? AND creator = ?", data.AppID, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information)
|
renderJSON(404, w, map[string]interface{}{"error": "App not found"}, information)
|
||||||
|
@ -1175,6 +1114,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Post("/api/oauth/add", func(w http.ResponseWriter, r *http.Request) {
|
router.Post("/api/oauth/add", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Conveniently, we use this one for ISB as well, so we can re-use the struct
|
// Conveniently, we use this one for ISB as well, so we can re-use the struct
|
||||||
var data authLibrary.OAuthInformation
|
var data authLibrary.OAuthInformation
|
||||||
err = json.NewDecoder(r.Body).Decode(&data)
|
err = json.NewDecoder(r.Body).Decode(&data)
|
||||||
|
@ -1210,17 +1150,35 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the scopes
|
// Validate the scopes
|
||||||
clientKeyShare, scopes, err := checkScopes(data.Scopes)
|
var clientKeyShare bool
|
||||||
|
for _, scope := range data.Scopes {
|
||||||
|
if scope != "openid" && scope != "clientKeyShare" {
|
||||||
|
renderJSON(400, w, map[string]interface{}{"error": "Invalid scope"}, information)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if scope == "clientKeyShare" {
|
||||||
|
clientKeyShare = true
|
||||||
|
} else if scope != "openid" {
|
||||||
|
logFunc("An impossible logic error has occurred, please move away from radiation or use ECC RAM", 1, information)
|
||||||
|
renderJSON(400, w, map[string]interface{}{"error": "Invalid scope"}, information)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the scopes
|
||||||
|
scopes, err := json.Marshal(data.Scopes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(400, w, map[string]interface{}{"error": err.Error()}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "36"}, information)
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the oauth entry
|
// Insert the oauth entry
|
||||||
if clientKeyShare {
|
if clientKeyShare {
|
||||||
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ($1, $2, $3, $4, $5, $6, $7)", appId, secret, userId, data.Name, data.RedirectUri, scopes, data.KeyShareUri)
|
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES (?, ?, ?, ?, ?, ?, ?)", appId, secret, userId, data.Name, data.RedirectUri, scopes, data.KeyShareUri)
|
||||||
} else {
|
} else {
|
||||||
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES ($1, $2, $3, $4, $5, $6)", appId, secret, userId, data.Name, data.RedirectUri, scopes)
|
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES (?, ?, ?, ?, ?, ?)", appId, secret, userId, data.Name, data.RedirectUri, scopes)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "37"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "37"}, information)
|
||||||
|
@ -1253,7 +1211,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the apps
|
// Get the apps
|
||||||
rows, err := conn.DB.Query("SELECT appId, name, redirectUri, scopes, keyShareUri FROM oauth WHERE creator = $1", userId)
|
rows, err := conn.Query("SELECT appId, name, redirectUri, scopes, keyShareUri FROM oauth WHERE creator = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "38"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "38"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -1328,7 +1286,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the user
|
// Delete the user
|
||||||
_, err = conn.DB.Exec("DELETE FROM users WHERE id = $1", userId)
|
_, err = conn.Exec("DELETE FROM users WHERE id = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "42"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "42"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -1344,7 +1302,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the user's oauth entries
|
// Delete the user's oauth entries
|
||||||
_, err = conn.DB.Exec("DELETE FROM oauth WHERE creator = ?", userId)
|
_, err = conn.Exec("DELETE FROM oauth WHERE creator = ?", userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "44"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "44"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -1447,7 +1405,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the session
|
// Delete the session
|
||||||
_, err = mem.Exec("DELETE FROM sessions WHERE id = $1 AND session = ?", userId, data.Session)
|
_, err = mem.Exec("DELETE FROM sessions WHERE id = ? AND session = ?", userId, data.Session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "49"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "49"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -1480,7 +1438,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the users
|
// Get the users
|
||||||
rows, err := conn.DB.Query("SELECT id, username, created FROM users")
|
rows, err := conn.Query("SELECT id, username, created FROM users")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "50"}, information)
|
renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "50"}, information)
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
|
@ -1546,7 +1504,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
// Sleep for half an hour
|
// Sleep for half an hour
|
||||||
time.Sleep(time.Minute * 30)
|
time.Sleep(time.Minute * 30)
|
||||||
|
|
||||||
// Delete everything in the spent and challenge-response tables that has expired
|
// Delete everything in the spent table past it's expiry date
|
||||||
affected, err := mem.Exec("DELETE FROM spent WHERE expires < ?", time.Now().Unix())
|
affected, err := mem.Exec("DELETE FROM spent WHERE expires < ?", time.Now().Unix())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 1, information)
|
logFunc(err.Error(), 1, information)
|
||||||
|
@ -1555,7 +1513,7 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 1, information)
|
logFunc(err.Error(), 1, information)
|
||||||
} else {
|
} else {
|
||||||
logFunc("Cleanup complete, deleted "+strconv.FormatInt(affectedCount, 10)+" entries", 0, information)
|
logFunc("Spent cleanup complete, deleted "+strconv.FormatInt(affectedCount, 10)+" entries", 0, information)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1564,79 +1522,146 @@ func Main(information *library.ServiceInitializationInformation) {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
// Wait for a message
|
// Wait for a message
|
||||||
message := information.AcceptMessage()
|
message := <-information.Inbox
|
||||||
|
|
||||||
|
if message.ServiceID != uuid.MustParse("00000000-0000-0000-0000-000000000001") {
|
||||||
// Check the message type
|
// Check the message type
|
||||||
switch message.MessageType {
|
switch message.MessageType {
|
||||||
case 0:
|
case 0:
|
||||||
// A service would like to have the hostname
|
// A service would like to know our hostname
|
||||||
// Send it to them
|
// Send it to them
|
||||||
message.Respond(library.Success, hostName, information)
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
MessageType: 0,
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: message.ServiceID,
|
||||||
|
Message: hostName,
|
||||||
|
SentAt: time.Now(),
|
||||||
|
}
|
||||||
case 1:
|
case 1:
|
||||||
// A service would like to register a new OAuth entry
|
// A service would like to register a new OAuth entry
|
||||||
// Validate the scopes
|
// Generate a new secret
|
||||||
clientKeyShare, scopes, err := checkScopes(message.Message.(authLibrary.OAuthInformation).Scopes)
|
// It must be able to be sent via JSON, so we can't have pure-binary data
|
||||||
|
secret, err := randomChars(512)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
message.Respond(library.BadRequest, err, information)
|
information.Outbox <- library.InterServiceMessage{
|
||||||
return
|
MessageType: 1,
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: message.ServiceID,
|
||||||
|
Message: "36",
|
||||||
|
SentAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the service already has an OAuth entry
|
|
||||||
var appId, secret string
|
|
||||||
err = conn.DB.QueryRow("SELECT appId, secret FROM oauth WHERE appId = $1", message.ServiceID.String()).Scan(&appId, &secret)
|
|
||||||
if err == nil && appId == message.ServiceID.String() {
|
|
||||||
// Update the entry to thew new scopes and redirect URI
|
|
||||||
if clientKeyShare {
|
|
||||||
_, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3, keyShareUri = $4 WHERE appId = $5", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri, message.ServiceID.String())
|
|
||||||
} else {
|
|
||||||
_, err = conn.DB.Exec("UPDATE oauth SET name = $1, redirectUri = $2, scopes = $3 WHERE appId = $4", message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.ServiceID.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
message.Respond(library.InternalError, err, information)
|
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
message.Respond(library.Success, authLibrary.OAuthResponse{
|
// Generate a new appId
|
||||||
AppID: appId,
|
// It must be able to be sent via JSON, so we can't have pure-binary data
|
||||||
SecretKey: secret,
|
appId, err := randomChars(32)
|
||||||
}, information)
|
if err != nil {
|
||||||
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
MessageType: 1,
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: message.ServiceID,
|
||||||
|
Message: "37",
|
||||||
|
SentAt: time.Now(),
|
||||||
|
}
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a new secret
|
// Validate the scopes
|
||||||
// It must be able to be sent via JSON, so we can't have pure-binary data
|
var clientKeyShare bool
|
||||||
secret, err = randomChars(512)
|
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 {
|
if err != nil {
|
||||||
message.Respond(library.InternalError, err, information)
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
MessageType: 1,
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: message.ServiceID,
|
||||||
|
Message: "38",
|
||||||
|
SentAt: time.Now(),
|
||||||
|
}
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the oauth entry
|
// Insert the oauth entry
|
||||||
if clientKeyShare {
|
if clientKeyShare {
|
||||||
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES ($1, $2, $3, $4, $5, $6, $7)", message.ServiceID.String(), secret, ServiceInformation.ServiceID[:], message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri)
|
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes, keyShareUri) VALUES (?, ?, ?, ?, ?, ?, ?)", appId, secret, serviceIDBytes, message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes, message.Message.(authLibrary.OAuthInformation).KeyShareUri)
|
||||||
} else {
|
} else {
|
||||||
_, err = conn.DB.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES ($1, $2, $3, $4, $5, $6)", message.ServiceID.String(), secret, ServiceInformation.ServiceID[:], message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes)
|
_, err = conn.Exec("INSERT INTO oauth (appId, secret, creator, name, redirectUri, scopes) VALUES (?, ?, ?, ?, ?, ?)", appId, secret, serviceIDBytes, message.Message.(authLibrary.OAuthInformation).Name, message.Message.(authLibrary.OAuthInformation).RedirectUri, scopes)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
message.Respond(library.InternalError, err, information)
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
MessageType: 1,
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: message.ServiceID,
|
||||||
|
Message: "39",
|
||||||
|
SentAt: time.Now(),
|
||||||
|
}
|
||||||
logFunc(err.Error(), 2, information)
|
logFunc(err.Error(), 2, information)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the appId and secret
|
// Return the appId and secret
|
||||||
message.Respond(library.Success, authLibrary.OAuthResponse{
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
MessageType: 0,
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: message.ServiceID,
|
||||||
|
Message: authLibrary.OAuthResponse{
|
||||||
AppID: appId,
|
AppID: appId,
|
||||||
SecretKey: secret,
|
SecretKey: secret,
|
||||||
}, information)
|
},
|
||||||
|
SentAt: time.Now(),
|
||||||
|
}
|
||||||
case 2:
|
case 2:
|
||||||
// A service would like to have the public key
|
// A service would like to have the public key
|
||||||
// Send it to them
|
// Send it to them
|
||||||
message.Respond(library.Success, publicKey, information)
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
MessageType: 2,
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: message.ServiceID,
|
||||||
|
Message: publicKey,
|
||||||
|
SentAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
module git.ailur.dev/fulgens/services-src/auth/resources
|
module git.ailur.dev/fulgens/services-src/auth/resources
|
||||||
|
|
||||||
go 1.23.3
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.ailur.dev/ailur/jsFetch v1.1.1
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0
|
github.com/cespare/xxhash/v2 v2.3.0
|
||||||
golang.org/x/crypto v0.29.0
|
golang.org/x/crypto v0.28.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require golang.org/x/sys v0.26.0 // indirect
|
||||||
git.ailur.dev/ailur/jsStreams v1.2.1 // indirect
|
|
||||||
golang.org/x/sys v0.27.0 // indirect
|
|
||||||
)
|
|
||||||
|
|
|
@ -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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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=
|
||||||
|
|
Binary file not shown.
|
@ -1,18 +1,9 @@
|
||||||
@font-face {
|
@import url("../fonts/inter.css");
|
||||||
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 {
|
:root {
|
||||||
--invertdm: 0%;
|
--invertdm: 0%;
|
||||||
--text-color: #000;
|
--text-color: #000000;
|
||||||
--background: #fff;
|
--editor: #ffffff;
|
||||||
--border-color: #dadada;
|
--border-color: #dadada;
|
||||||
--theme-color: #1c71d8;
|
--theme-color: #1c71d8;
|
||||||
--hover-theme-color: #4990e7;
|
--hover-theme-color: #4990e7;
|
||||||
|
@ -20,9 +11,9 @@ html {
|
||||||
--hover-nonimportant-theme-color: #dbdbdb;
|
--hover-nonimportant-theme-color: #dbdbdb;
|
||||||
--nonimportant-text-color: #000;
|
--nonimportant-text-color: #000;
|
||||||
--inOutDiv: #fafafa;
|
--inOutDiv: #fafafa;
|
||||||
--disabled: #d3d3d3;
|
--disabled: lightgray;
|
||||||
--disabled-hover: #a2a0a0;
|
--disabled-hover: #a2a0a0;
|
||||||
--disabled-text-color: #808080;
|
--disabled-text-color: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* dark mode */
|
/* dark mode */
|
||||||
|
@ -31,8 +22,8 @@ html {
|
||||||
:root {
|
:root {
|
||||||
--invertdm: 100%;
|
--invertdm: 100%;
|
||||||
--inOutDiv: #2d2f31;
|
--inOutDiv: #2d2f31;
|
||||||
--text-color: #fff;
|
--text-color: #ffffff;
|
||||||
--background: #1E1E1E;
|
--editor: #1E1E1E;
|
||||||
--nonimporant-theme-color: #8E8E8E;
|
--nonimporant-theme-color: #8E8E8E;
|
||||||
--nonimportant-text-color: #fff;
|
--nonimportant-text-color: #fff;
|
||||||
--border-color: #393b3d;
|
--border-color: #393b3d;
|
||||||
|
@ -40,10 +31,6 @@ html {
|
||||||
--disabled-hover: #737373;
|
--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 {
|
.inOutDiv p {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
@ -51,9 +38,23 @@ html {
|
||||||
.inOutDiv a {
|
.inOutDiv a {
|
||||||
color: #969696 !important;
|
color: #969696 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inOutDiv input {
|
||||||
|
color: white;
|
||||||
|
background-color: var(--editor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p, li, h1, h2, h3, h4, h5, span, h6 {
|
p,
|
||||||
|
li,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
span,
|
||||||
|
text,
|
||||||
|
h6 {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
}
|
}
|
||||||
|
@ -64,16 +65,18 @@ p#statusBox {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: "Figtree", sans-serif;
|
background-color: var(--editor);
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sign up/log in div */
|
/* Sign up/log in div */
|
||||||
|
|
||||||
.inOutDiv {
|
.inOutDiv {
|
||||||
border-radius: 25px;
|
border-radius: 8px;
|
||||||
margin: 10%;
|
margin: 10%;
|
||||||
|
padding: 30px;
|
||||||
|
border: solid 1px var(--border-color);
|
||||||
background-color: var(--inOutDiv);
|
background-color: var(--inOutDiv);
|
||||||
padding: 35px 35px 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
@ -84,22 +87,19 @@ input {
|
||||||
width: calc(100% - 35px);
|
width: calc(100% - 35px);
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
height: 35px;
|
height: 30px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
|
|
||||||
background-color: var(--background);
|
|
||||||
color: var(--text-color);
|
|
||||||
|
|
||||||
border: solid;
|
border: solid;
|
||||||
border-color: var(--border-color);
|
border-color: var(--border-color);
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-radius: 5px;
|
border-radius: 8px;
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputBox .captchaDiv {
|
.inputBox .captchaDiv {
|
||||||
background-color: var(--background);
|
background-color: var(--editor);
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: calc(100% - 15px);
|
width: calc(100% - 15px);
|
||||||
margin: 0 5px 0 5px;
|
margin: 0 5px 0 5px;
|
||||||
|
@ -109,18 +109,22 @@ input {
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputBox .captchaDiv button {
|
.inputBox .captchaDiv button {
|
||||||
|
background-color: var(--editor);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
border-right: 1px solid var(--border-color);
|
border-right: 1px solid var(--border-color);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 0 10px 0 0;
|
padding: 0 10px 0 0;
|
||||||
margin: 0 0 0 10px;
|
margin: 0 0 0 10px;
|
||||||
background-color: var(--background);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputBox .captchaDiv .vAlign {
|
.inputBox .captchaDiv .vAlign {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inputBox .captchaDiv .vAlign span {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.inputBox input {
|
.inputBox input {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
@ -138,13 +142,13 @@ input {
|
||||||
.inOutDiv {
|
.inOutDiv {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 10px;
|
||||||
right: 0;
|
right: 10px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
min-width: calc(100% - 20px);
|
min-width: calc(100% - 20px);
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
transform: none;
|
transform: none;
|
||||||
padding: 5px 10px;
|
padding: 5px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -153,6 +157,9 @@ input {
|
||||||
.inOutDiv p {
|
.inOutDiv p {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
.inOutDiv h2 {
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
.background {
|
.background {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -194,43 +201,42 @@ input {
|
||||||
|
|
||||||
.newOauth, .oauthList, .sessionEntry, .oauthEntry {
|
.newOauth, .oauthList, .sessionEntry, .oauthEntry {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
width: calc(100% - 17.5vh);
|
||||||
margin-top: 7vh;
|
margin-top: 7vh;
|
||||||
margin-left: 7vh;
|
margin-left: 7vh;
|
||||||
margin-right: 7vh;
|
margin-right: 7vh;
|
||||||
padding: 15px 10px 30px;
|
padding: 15px 10px 30px;
|
||||||
|
border-style: solid;
|
||||||
border-image: none;
|
border-image: none;
|
||||||
border-radius: 25px;
|
border-radius: 8px;
|
||||||
|
border-width: 1px;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
background-color: var(--inOutDiv);
|
background-color: var(--inOutDiv);
|
||||||
|
border-color: var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.oauthEntry, .sessionEntry {
|
.oauthEntry, .sessionEntry {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 20px;
|
padding: 5px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border: 3px dotted var(--border-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.oauthEntry button, .sessionEntry button {
|
.oauthEntry button, .sessionEntry button {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
|
||||||
|
|
||||||
.oauthEntry button, .sessionEntry button, #deleteAccountButton {
|
|
||||||
background-color: red;
|
background-color: red;
|
||||||
color: white
|
color: white
|
||||||
}
|
}
|
||||||
|
|
||||||
.oauthEntry button:hover, .sessionEntry button:hover, #deleteAccountButton:hover {
|
.oauthEntry button:hover, .sessionEntry button:hover {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oauthEntry img, .sessionEntry img {
|
.oauthEntry img, .sessionEntry img {
|
||||||
max-height: 64px;
|
max-height: 64px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
filter: invert(var(--invertdm));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
@ -281,9 +287,8 @@ button:hover {
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 10px;
|
margin-top: 20px;
|
||||||
font-weight: 600;
|
font-weight: 300;
|
||||||
font-size: 22px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inOutDiv a {
|
.inOutDiv a {
|
||||||
|
@ -291,6 +296,21 @@ h2 {
|
||||||
text-align: center;
|
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 {
|
.vAlign {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -301,81 +321,38 @@ h2 {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* swipe animation */
|
.w100 {
|
||||||
.swipe {
|
font-weight: 300;
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background-color: var(--background);
|
|
||||||
animation: swipe 0.2s forwards;
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.swipe-animate {
|
.w200 {
|
||||||
display: initial;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w300 {
|
||||||
/* swipe-out animation */
|
font-weight: 300;
|
||||||
.swipe-out {
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background-color: var(--background);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.swipe-out-animate {
|
.w400 {
|
||||||
animation: swipe-out 0.2s forwards;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes swipe {
|
.w500 {
|
||||||
0% {
|
font-weight: 500;
|
||||||
transform: translateX(-100%);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes swipe-out {
|
.w600 {
|
||||||
0% {
|
font-weight: 600;
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes swipe-reduced {
|
.w700 {
|
||||||
0% {
|
font-weight: 700;
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes swipe-out-reduced {
|
.w800 {
|
||||||
0% {
|
font-weight: 800;
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
.w900 {
|
||||||
.swipe {
|
font-weight: 900;
|
||||||
animation: swipe-reduced 0.5s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swipe-out {
|
|
||||||
animation: swipe-out-reduced 0.5s forwards;
|
|
||||||
}
|
|
||||||
}
|
}
|
BIN
services-src/auth/resources/static/fonts/Inter-Black.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-Black.woff2
Normal file
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-BlackItalic.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-BlackItalic.woff2
Normal file
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-Bold.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-Bold.woff2
Normal file
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-BoldItalic.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-ExtraBold.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-ExtraBold.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-ExtraLight.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-ExtraLight.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-Italic.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-Italic.woff2
Normal file
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-Light.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-Light.woff2
Normal file
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-LightItalic.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-LightItalic.woff2
Normal file
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-Medium.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-Medium.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-Regular.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-SemiBold.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-SemiBold.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-Thin.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-Thin.woff2
Normal file
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/Inter-ThinItalic.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/Inter-ThinItalic.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/InterDisplay-Bold.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/InterDisplay-Bold.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/InterDisplay-Thin.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/InterDisplay-Thin.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
services-src/auth/resources/static/fonts/InterVariable.woff2
Normal file
BIN
services-src/auth/resources/static/fonts/InterVariable.woff2
Normal file
Binary file not shown.
57
services-src/auth/resources/static/fonts/inter.css
Normal file
57
services-src/auth/resources/static/fonts/inter.css
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/* 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"); }
|
BIN
services-src/auth/resources/static/img/background.jpg
Normal file
BIN
services-src/auth/resources/static/img/background.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
BIN
services-src/auth/resources/static/img/background.png
Normal file
BIN
services-src/auth/resources/static/img/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 MiB |
|
@ -1,8 +1,7 @@
|
||||||
// @license magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt LGPL-3.0
|
// @license magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt LGPL-3.0
|
||||||
// This sad excuse for a script is used so LibreJS doesn't scream at me
|
// This sad excuse for a script is used so LibreJS doesn't scream at me
|
||||||
|
|
||||||
localStorage.removeItem("DONOTSHARE-clientKey")
|
localStorage.clear()
|
||||||
localStorage.removeItem("DONOTSHARE-secretKey")
|
|
||||||
window.location.replace("/login" + window.location.search)
|
window.location.replace("/login" + window.location.search)
|
||||||
|
|
||||||
// @license-end
|
// @license-end
|
|
@ -1,4 +1,4 @@
|
||||||
<html lang="en" style="display: none">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Authorize application - {{ .identifier }}</title>
|
<title>Authorize application - {{ .identifier }}</title>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
|
@ -12,8 +12,9 @@
|
||||||
<body>
|
<body>
|
||||||
<span id="passThrough" style="display: none;">{{ .name }}</span>
|
<span id="passThrough" style="display: none;">{{ .name }}</span>
|
||||||
<span id="autoAccept" style="display: none;">0</span>
|
<span id="autoAccept" style="display: none;">0</span>
|
||||||
|
<img src="/static/img/background.png" class="background" alt="">
|
||||||
<div class="inOutDiv">
|
<div class="inOutDiv">
|
||||||
<h2>Authorise Application</h2>
|
<h2 class="w300">Authorise Application</h2>
|
||||||
<p id="statusBox">Loading...</p>
|
<p id="statusBox">Loading...</p>
|
||||||
<br>
|
<br>
|
||||||
<div style="display: flex;justify-content: center;">
|
<div style="display: flex;justify-content: center;">
|
||||||
|
@ -23,8 +24,6 @@
|
||||||
<br>
|
<br>
|
||||||
<a href="/dashboard">Return to Dashboard</a>
|
<a href="/dashboard">Return to Dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="swipe" class="swipe"></div>
|
|
||||||
<div id="swipe-out" class="swipe-out"></div>
|
|
||||||
<script>
|
<script>
|
||||||
loadWasm("/static/wasm/authorize.wasm")
|
loadWasm("/static/wasm/authorize.wasm")
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<html lang="en" style="display: none">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Redirecting... - {{ .identifier }}</title>
|
<title>Redirecting... - {{ .identifier }}</title>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
|
@ -12,15 +12,14 @@
|
||||||
<body>
|
<body>
|
||||||
<span id="passThrough" style="display: none;">{{ .name }}</span>
|
<span id="passThrough" style="display: none;">{{ .name }}</span>
|
||||||
<span id="autoAccept" style="display: none;">1</span>
|
<span id="autoAccept" style="display: none;">1</span>
|
||||||
|
<img src="/static/img/background.png" class="background" alt="">
|
||||||
<div class="inOutDiv">
|
<div class="inOutDiv">
|
||||||
<h2>Authorizing application</h2>
|
<h2 class="w300">Authorizing application</h2>
|
||||||
<p id="statusBox">Please wait...</p>
|
<p id="statusBox">Please wait...</p>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
loadWasm("/static/wasm/authorize.wasm")
|
loadWasm("/static/wasm/authorize.wasm")
|
||||||
</script>
|
</script>
|
||||||
<div id="swipe" class="swipe"></div>
|
|
||||||
<div id="swipe-out" class="swipe-out"></div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" style="display: none">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Key Exchange - {{ .identifier }}</title>
|
<title>Key Exchange - {{ .identifier }}</title>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
|
@ -10,6 +10,7 @@
|
||||||
<script src="/static/js/wasm_exec.js"></script>
|
<script src="/static/js/wasm_exec.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<img src="/static/img/background.png" class="background" alt="">
|
||||||
<div class="inOutDiv">
|
<div class="inOutDiv">
|
||||||
<h2>Relaying back information, please wait...</h2>
|
<h2>Relaying back information, please wait...</h2>
|
||||||
<p id="statusBox">Processing information sent...</p>
|
<p id="statusBox">Processing information sent...</p>
|
||||||
|
@ -17,7 +18,5 @@
|
||||||
<script>
|
<script>
|
||||||
loadWasm("/static/wasm/clientKeyShare.wasm")
|
loadWasm("/static/wasm/clientKeyShare.wasm")
|
||||||
</script>
|
</script>
|
||||||
<div id="swipe" class="swipe"></div>
|
|
||||||
<div id="swipe-out" class="swipe-out"></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" style="display: none">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Dashboard - {{ .identifier }}</title>
|
<title>Dashboard - {{ .identifier }}</title>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
|
@ -10,6 +10,7 @@
|
||||||
<script src="/static/js/wasm_exec.js"></script>
|
<script src="/static/js/wasm_exec.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<img src="/static/img/background.png" class="background" alt="">
|
||||||
<div class="newOauth">
|
<div class="newOauth">
|
||||||
<h2>Dashboard</h2>
|
<h2>Dashboard</h2>
|
||||||
<p>Welcome to the {{ .identifier }} dashboard!</p>
|
<p>Welcome to the {{ .identifier }} dashboard!</p>
|
||||||
|
@ -20,13 +21,13 @@
|
||||||
<h2>Submit a new OAuth2 App</h2>
|
<h2>Submit a new OAuth2 App</h2>
|
||||||
<p id="statusBox"></p>
|
<p id="statusBox"></p>
|
||||||
<p>App Name:</p>
|
<p>App Name:</p>
|
||||||
<input id="nameBox" placeholder="Example App">
|
<input id="nameBox">
|
||||||
<p>Redirect URI:</p>
|
<p>Redirect URI:</p>
|
||||||
<input id="redirectUriBox" placeholder="https://example.com/oauth2/callback">
|
<input id="redirectUriBox">
|
||||||
<p>Enable OpenID:</p>
|
<p>Enable OpenID:</p>
|
||||||
<input type="checkbox" id="openIdBox">
|
<input type="checkbox" id="openIdBox">
|
||||||
<p>Client key-share URI (optional, will add the clientKeyShare scope):</p>
|
<p>Client key-share URI (optional, will add the clientKeyShare scope):</p>
|
||||||
<input id="clientKeyShareBox" placeholder="https://example.com/clientKeyShare">
|
<input id="clientKeyShareBox">
|
||||||
<br>
|
<br>
|
||||||
<button style="margin-top: 10px;" id="submitButton">Submit</button>
|
<button style="margin-top: 10px;" id="submitButton">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +50,5 @@
|
||||||
<script>
|
<script>
|
||||||
loadWasm("/static/wasm/dashboard.wasm")
|
loadWasm("/static/wasm/dashboard.wasm")
|
||||||
</script>
|
</script>
|
||||||
<div id="swipe" class="swipe"></div>
|
|
||||||
<div id="swipe-out" class="swipe-out"></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" style="display: none">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Login - {{ .identifier }}</title>
|
<title>Login - {{ .identifier }}</title>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
|
@ -12,8 +13,9 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<span id="passThrough" style="display: none;">{{ .identifier }}</span>
|
<span id="passThrough" style="display: none;">{{ .identifier }}</span>
|
||||||
|
<img src="/static/img/background.png" class="background" alt="">
|
||||||
<div class="inOutDiv">
|
<div class="inOutDiv">
|
||||||
<h2>Login</h2>
|
<h2 class="w300">Login</h2>
|
||||||
<p id="statusBox"></p>
|
<p id="statusBox"></p>
|
||||||
<div class="inputContainer" id="inputContainer">
|
<div class="inputContainer" id="inputContainer">
|
||||||
<div class="vAlign"><span id="inputNameBox"></span></div>
|
<div class="vAlign"><span id="inputNameBox"></span></div>
|
||||||
|
@ -31,6 +33,4 @@
|
||||||
<script>
|
<script>
|
||||||
loadWasm("/static/wasm/login.wasm")
|
loadWasm("/static/wasm/login.wasm")
|
||||||
</script>
|
</script>
|
||||||
<div id="swipe" class="swipe"></div>
|
|
||||||
<div id="swipe-out" class="swipe-out"></div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -9,4 +9,7 @@
|
||||||
<link rel="icon" href="/static/svg/favicon.svg">
|
<link rel="icon" href="/static/svg/favicon.svg">
|
||||||
<script src="/static/js/logout.js"></script>
|
<script src="/static/js/logout.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Logging out...</p>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" style="display: none">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Signup - {{ .identifier }}</title>
|
<title>Signup - {{ .identifier }}</title>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
|
@ -11,8 +11,9 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<p style="display: none;" id="passthrough">{{ .unique_token }}</p>
|
<p style="display: none;" id="passthrough">{{ .unique_token }}</p>
|
||||||
|
<img src="/static/img/background.png" class="background" alt="">
|
||||||
<div class="inOutDiv">
|
<div class="inOutDiv">
|
||||||
<h2>Signup</h2>
|
<h2 class="w300">Signup</h2>
|
||||||
<p>Signup to {{ .identifier }}!</p>
|
<p>Signup to {{ .identifier }}!</p>
|
||||||
<p id="statusBox"></p>
|
<p id="statusBox"></p>
|
||||||
<table id="inputContainer">
|
<table id="inputContainer">
|
||||||
|
@ -47,7 +48,5 @@
|
||||||
<script>
|
<script>
|
||||||
loadWasm("/static/wasm/signup.wasm")
|
loadWasm("/static/wasm/signup.wasm")
|
||||||
</script>
|
</script>
|
||||||
<div id="swipe" class="swipe"></div>
|
|
||||||
<div id="swipe-out" class="swipe-out"></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" style="display: none">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Tester - {{ .identifier }}</title>
|
<title>Tester - {{ .identifier }}</title>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
|
@ -10,6 +10,7 @@
|
||||||
<script src="/static/js/wasm_exec.js"></script>
|
<script src="/static/js/wasm_exec.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<img src="/static/img/background.png" class="background" alt="">
|
||||||
<div class="inOutDiv">
|
<div class="inOutDiv">
|
||||||
<h2>{{ .identifier }} Tester</h2>
|
<h2>{{ .identifier }} Tester</h2>
|
||||||
<p id="statusBox">Click authorize to begin the test</p>
|
<p id="statusBox">Click authorize to begin the test</p>
|
||||||
|
@ -19,7 +20,5 @@
|
||||||
<script>
|
<script>
|
||||||
loadWasm("/static/wasm/testApp.wasm")
|
loadWasm("/static/wasm/testApp.wasm")
|
||||||
</script>
|
</script>
|
||||||
<div id="swipe" class="swipe"></div>
|
|
||||||
<div id="swipe-out" class="swipe-out"></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" style="display: none">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Tester - {{ .identifier }}</title>
|
<title>Tester - {{ .identifier }}</title>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
|
@ -9,11 +9,10 @@
|
||||||
<link rel="stylesheet" type="text/css" href="/static/css/style.css"/>
|
<link rel="stylesheet" type="text/css" href="/static/css/style.css"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<img src="/static/img/background.png" class="background" alt="">
|
||||||
<div class="inOutDiv">
|
<div class="inOutDiv">
|
||||||
<h2>{{ .identifier }} Tester</h2>
|
<h2>{{ .identifier }} Tester</h2>
|
||||||
<p>The tester has been disabled by the administrator.</p>
|
<p>The tester has been disabled by the administrator.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="swipe" class="swipe"></div>
|
|
||||||
<div id="swipe-out" class="swipe-out"></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,17 +2,15 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
|
||||||
"git.ailur.dev/ailur/jsFetch"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func authorize(deny bool, query url.Values, sleepTime time.Duration) {
|
func authorize(deny bool, query url.Values) {
|
||||||
// Get the token from local storage
|
// Get the token from local storage
|
||||||
localStorage := js.Global().Get("localStorage")
|
localStorage := js.Global().Get("localStorage")
|
||||||
token := localStorage.Call("getItem", "DONOTSHARE-secretKey").String()
|
token := localStorage.Call("getItem", "DONOTSHARE-secretKey").String()
|
||||||
|
@ -50,7 +48,7 @@ func authorize(deny bool, query url.Values, sleepTime time.Duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the request
|
// Send the request
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Error contacting server: "+err.Error())
|
js.Global().Get("document").Call("getElementById", "statusBox").Set("innerText", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -70,7 +68,7 @@ func authorize(deny bool, query url.Values, sleepTime time.Duration) {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
|
@ -81,8 +79,6 @@ func authorize(deny bool, query url.Values, sleepTime time.Duration) {
|
||||||
denyUri += "&state=" + query.Get("state")
|
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)
|
js.Global().Get("window").Get("location").Call("replace", denyUri)
|
||||||
} else {
|
} else {
|
||||||
// Redirect to the redirect_uri with the code
|
// Redirect to the redirect_uri with the code
|
||||||
|
@ -91,8 +87,6 @@ func authorize(deny bool, query url.Values, sleepTime time.Duration) {
|
||||||
allowUri += "&state=" + query.Get("state")
|
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)
|
js.Global().Get("window").Get("location").Call("replace", allowUri)
|
||||||
}
|
}
|
||||||
} else if response.StatusCode == 401 {
|
} else if response.StatusCode == 401 {
|
||||||
|
@ -105,27 +99,18 @@ func authorize(deny bool, query url.Values, sleepTime time.Duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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
|
// Redirect to log-in if not signed in
|
||||||
localStorage := js.Global().Get("localStorage")
|
localStorage := js.Global().Get("localStorage")
|
||||||
if localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
|
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())
|
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
|
||||||
}
|
}
|
||||||
|
|
||||||
var query url.Values
|
var query url.Values
|
||||||
|
|
||||||
|
// Redirect to dashboard if client_id is not a URL parameter
|
||||||
|
if js.Global().Get("window").Get("location").Get("search").String() == "" {
|
||||||
|
js.Global().Get("window").Get("location").Call("replace", "/dashboard")
|
||||||
|
} else {
|
||||||
// Parse the url parameters using url.ParseQuery
|
// Parse the url parameters using url.ParseQuery
|
||||||
var err error
|
var err error
|
||||||
query, err = url.ParseQuery(strings.TrimPrefix(js.Global().Get("window").Get("location").Get("search").String(), "?"))
|
query, err = url.ParseQuery(strings.TrimPrefix(js.Global().Get("window").Get("location").Get("search").String(), "?"))
|
||||||
|
@ -134,6 +119,13 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redirect to dashboard if client_id is not a URL parameter
|
||||||
|
if !query.Has("client_id") {
|
||||||
|
js.Global().Get("window").Get("location").Call("replace", "/dashboard")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var statusBox = js.Global().Get("document").Call("getElementById", "statusBox")
|
var statusBox = js.Global().Get("document").Call("getElementById", "statusBox")
|
||||||
var autoAccept = js.Global().Get("document").Call("getElementById", "autoAccept")
|
var autoAccept = js.Global().Get("document").Call("getElementById", "autoAccept")
|
||||||
|
|
||||||
|
@ -155,7 +147,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -166,12 +158,10 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to log-out if not signed in
|
// 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())
|
js.Global().Get("window").Get("location").Call("replace", "/logout"+js.Global().Get("window").Get("location").Get("search").String())
|
||||||
return
|
return
|
||||||
} else if response.StatusCode == 500 {
|
} else if response.StatusCode == 500 {
|
||||||
|
@ -187,7 +177,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert the user if the server is down
|
// Alert the user if the server is down
|
||||||
|
@ -198,7 +188,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if autoAccept.Get("innerText").String() == "0" {
|
if autoAccept.Get("innerText").String() == "0" {
|
||||||
|
@ -208,18 +198,18 @@ func main() {
|
||||||
// Add an event listener to the Deny button
|
// 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{} {
|
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
|
// We still partially authorize the user to prevent open redirects
|
||||||
go authorize(true, query, sleepTime)
|
go authorize(true, query)
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Add an event listener to the Allow button
|
// 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{} {
|
js.Global().Get("document").Call("getElementById", "allowButton").Call("addEventListener", "click", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
|
||||||
go authorize(false, query, sleepTime)
|
go authorize(false, query)
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
// Auto-accept the request, as it's from an internal service
|
// Auto-accept the request, as it's from an internal service
|
||||||
go authorize(false, query, sleepTime)
|
go authorize(false, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for events
|
// Wait for events
|
||||||
|
|
|
@ -1,35 +1,20 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/ecdh"
|
"crypto/ecdh"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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
|
// Redirect to log-in if not signed in
|
||||||
localStorage := js.Global().Get("localStorage")
|
localStorage := js.Global().Get("localStorage")
|
||||||
if localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
|
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())
|
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +86,5 @@ func main() {
|
||||||
|
|
||||||
// Redirect back to the referrer with the encrypted client key
|
// Redirect back to the referrer with the encrypted client key
|
||||||
redirectUri := strings.Split(js.Global().Get("document").Get("referrer").String(), "?")[0]
|
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))
|
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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,13 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
"time"
|
||||||
"git.ailur.dev/ailur/jsFetch"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
|
func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
|
||||||
|
@ -22,7 +21,7 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var statusText = js.Global().Get("document").Call("createElement", "p")
|
var statusText = js.Global().Get("document").Call("createElement", "p")
|
||||||
statusText.Set("innerText", "Error contacting server: "+err.Error())
|
statusText.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
|
@ -46,7 +45,7 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
|
@ -96,7 +95,7 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -116,7 +115,7 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
|
@ -192,22 +191,9 @@ func fetchOauthClients(oauthList js.Value, localStorage js.Value, body []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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
|
// Redirect to log-in if not signed in
|
||||||
localStorage := js.Global().Get("localStorage")
|
localStorage := js.Global().Get("localStorage")
|
||||||
if localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
|
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())
|
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +233,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -258,12 +244,10 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to log-out if not signed in
|
// 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").Call("replace", "/logout")
|
||||||
return
|
return
|
||||||
} else if response.StatusCode == 500 {
|
} else if response.StatusCode == 500 {
|
||||||
|
@ -279,7 +263,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert the user if the server is down
|
// Alert the user if the server is down
|
||||||
|
@ -290,7 +274,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the OAuth clients
|
// Fetch the OAuth clients
|
||||||
|
@ -305,7 +289,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err = jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err = http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var statusText = js.Global().Get("document").Call("createElement", "p")
|
var statusText = js.Global().Get("document").Call("createElement", "p")
|
||||||
statusText.Set("innerText", "Error contacting server: "+err.Error())
|
statusText.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
|
@ -329,7 +313,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
|
@ -389,7 +373,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -409,14 +393,12 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
sessionElement.Call("remove")
|
sessionElement.Call("remove")
|
||||||
if session.(map[string]interface{})["session"].(string) == localStorage.Call("getItem", "DONOTSHARE-secretKey").String() {
|
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")
|
js.Global().Get("window").Get("location").Call("replace", "/logout")
|
||||||
}
|
}
|
||||||
} else if response.StatusCode != 500 {
|
} else if response.StatusCode != 500 {
|
||||||
|
@ -454,7 +436,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-use the body variable for this request
|
// Re-use the body variable for this request
|
||||||
response, err = jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err = http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -527,7 +509,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -547,7 +529,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
|
@ -561,7 +543,7 @@ func main() {
|
||||||
// Marshal the body
|
// Marshal the body
|
||||||
body, err := json.Marshal(bodyMap)
|
body, err := json.Marshal(bodyMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error marshaling body: " + err.Error() + ", this is non-fatal.")
|
fmt.Println("Error marshaling body: " + err.Error() + ", this is non-fatal.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,7 +580,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -618,12 +600,10 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
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")
|
js.Global().Get("window").Get("location").Call("replace", "/logout")
|
||||||
} else if response.StatusCode != 500 {
|
} else if response.StatusCode != 500 {
|
||||||
js.Global().Call("alert", responseMap["error"].(string))
|
js.Global().Call("alert", responseMap["error"].(string))
|
||||||
|
@ -669,7 +649,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the request
|
// Send the request
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(bodyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
js.Global().Call("alert", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -689,12 +669,10 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't care about the response, we're logging out anyway
|
// 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")
|
js.Global().Get("window").Get("location").Call("replace", "/logout")
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -2,30 +2,29 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"crypto/ed25519"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
|
|
||||||
"git.ailur.dev/ailur/jsFetch"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var currentInputType = 0
|
var currentInputType = 0
|
||||||
|
|
||||||
func hashPassword(password string, salt []byte) []byte {
|
func hashPassword(password string, salt []byte) string {
|
||||||
return argon2.IDKey(
|
return base64.StdEncoding.EncodeToString(
|
||||||
|
argon2.IDKey(
|
||||||
[]byte(password),
|
[]byte(password),
|
||||||
salt,
|
salt,
|
||||||
32,
|
32,
|
||||||
19264,
|
19264,
|
||||||
1,
|
1,
|
||||||
32,
|
32,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,34 +69,10 @@ func showInput(inputType int, inputContainer js.Value, usernameBox js.Value, sig
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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
|
// Redirect to app if already signed in
|
||||||
localStorage := js.Global().Get("localStorage")
|
localStorage := js.Global().Get("localStorage")
|
||||||
if !localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
|
if !localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
|
||||||
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())
|
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")
|
var usernameBox = js.Global().Get("document").Call("getElementById", "usernameBox")
|
||||||
|
@ -135,9 +110,9 @@ func main() {
|
||||||
|
|
||||||
// Hash the password
|
// Hash the password
|
||||||
statusBox.Set("innerText", "Hashing password...")
|
statusBox.Set("innerText", "Hashing password...")
|
||||||
println("Hashing password...")
|
fmt.Println("Hashing password...")
|
||||||
|
|
||||||
// Fetch the challenge from the server
|
// Fetch the salt from the server
|
||||||
body, err := json.Marshal(map[string]interface{}{
|
body, err := json.Marshal(map[string]interface{}{
|
||||||
"username": username,
|
"username": username,
|
||||||
})
|
})
|
||||||
|
@ -155,7 +130,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
|
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
|
||||||
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
|
@ -177,22 +152,25 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
// Hash the password
|
// Decode the salt
|
||||||
hashedPassword := hashPassword(password, []byte(username))
|
salt, err := base64.StdEncoding.DecodeString(responseMap["salt"].(string))
|
||||||
|
if err != nil {
|
||||||
|
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
|
||||||
|
statusBox.Set("innerText", "Error decoding salt: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Sign the challenge
|
hashedPassword := hashPassword(password, salt)
|
||||||
signature := ed25519.Sign(ed25519.NewKeyFromSeed(hashedPassword), []byte(responseMap["challenge"].(string)))
|
|
||||||
|
|
||||||
// Hashed password computed, contact server
|
// Hashed password computed, contact server
|
||||||
statusBox.Set("innerText", "Contacting server...")
|
statusBox.Set("innerText", "Contacting server...")
|
||||||
signupBody := map[string]interface{}{
|
signupBody := map[string]interface{}{
|
||||||
"username": username,
|
"username": username,
|
||||||
"signature": base64.StdEncoding.EncodeToString(signature),
|
"password": hashedPassword,
|
||||||
"verifier": responseMap["verifier"].(string),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal the body
|
// Marshal the body
|
||||||
|
@ -203,7 +181,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the request
|
// Send the password to the server
|
||||||
requestUri, err = url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/login")
|
requestUri, err = url.JoinPath(js.Global().Get("window").Get("location").Get("origin").String(), "/api/login")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
|
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
|
||||||
|
@ -212,8 +190,8 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the request
|
// Send the request
|
||||||
println("Sending request to", requestUri)
|
fmt.Println("Sending request to", requestUri)
|
||||||
response, err = jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err = http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
|
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
|
||||||
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
|
@ -221,7 +199,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the response
|
// Read the response
|
||||||
println("Reading response...")
|
fmt.Println("Reading response...")
|
||||||
decoder = json.NewDecoder(response.Body)
|
decoder = json.NewDecoder(response.Body)
|
||||||
err = decoder.Decode(&responseMap)
|
err = decoder.Decode(&responseMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -233,26 +211,20 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
// Logged in
|
// Logged in
|
||||||
println("Logged in!")
|
fmt.Println("Logged in!")
|
||||||
statusBox.Set("innerText", "Setting up encryption keys...")
|
statusBox.Set("innerText", "Setting up encryption keys...")
|
||||||
localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
|
localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
|
||||||
localStorage.Call("setItem", "DONOTSHARE-clientKey", base64.StdEncoding.EncodeToString(hashPassword(password, []byte("fg-auth-client"))))
|
localStorage.Call("setItem", "DONOTSHARE-clientKey", hashPassword(password, []byte("fg-auth-client")))
|
||||||
|
|
||||||
// Redirect to app
|
// Redirect to app
|
||||||
statusBox.Set("innerText", "Welcome!")
|
statusBox.Set("innerText", "Welcome!")
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
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())
|
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 {
|
} else if response.StatusCode == 401 {
|
||||||
// Login failed
|
// Login failed
|
||||||
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
|
showInput(1, inputContainer, usernameBox, signupButton, passwordBox, backButton, inputNameBox, statusBox, nextButton)
|
||||||
|
@ -285,11 +257,7 @@ func main() {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
signupButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
signupButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
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())
|
js.Global().Get("window").Get("location").Call("replace", "/signup"+js.Global().Get("window").Get("location").Get("search").String())
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -2,22 +2,21 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"syscall/js"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
|
|
||||||
"git.ailur.dev/ailur/jsFetch"
|
"syscall/js"
|
||||||
)
|
)
|
||||||
|
|
||||||
func showElements(show bool, elements ...js.Value) {
|
func showElements(show bool, elements ...js.Value) {
|
||||||
|
@ -30,14 +29,16 @@ func showElements(show bool, elements ...js.Value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashPassword(password string, salt []byte) []byte {
|
func hashPassword(password string, salt []byte) string {
|
||||||
return argon2.IDKey(
|
return base64.StdEncoding.EncodeToString(
|
||||||
|
argon2.IDKey(
|
||||||
[]byte(password),
|
[]byte(password),
|
||||||
salt,
|
salt,
|
||||||
32,
|
32,
|
||||||
19264,
|
19264,
|
||||||
1,
|
1,
|
||||||
32,
|
32,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,34 +58,10 @@ func pow(resource string) (string, string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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
|
// Redirect to app if already signed in
|
||||||
localStorage := js.Global().Get("localStorage")
|
localStorage := js.Global().Get("localStorage")
|
||||||
if !localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
|
if !localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
|
||||||
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())
|
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")
|
var usernameBox = js.Global().Get("document").Call("getElementById", "usernameBox")
|
||||||
|
@ -100,13 +77,11 @@ func main() {
|
||||||
usernameBox.Set("disabled", true)
|
usernameBox.Set("disabled", true)
|
||||||
passwordBox.Set("disabled", true)
|
passwordBox.Set("disabled", true)
|
||||||
signupButton.Set("disabled", true)
|
signupButton.Set("disabled", true)
|
||||||
if localStorage.Call("getItem", "DEBUG-customCaptcha").IsNull() {
|
|
||||||
if localStorage.Call("getItem", "CONFIG-captchaStarted").IsNull() {
|
if localStorage.Call("getItem", "CONFIG-captchaStarted").IsNull() {
|
||||||
captchaStatus.Set("innerText", "CAPTCHA not started - start CAPTCHA to signup.")
|
captchaStatus.Set("innerText", "CAPTCHA not started - start CAPTCHA to signup.")
|
||||||
} else {
|
} else {
|
||||||
captchaStatus.Set("innerText", "Captcha calculation paused.")
|
captchaStatus.Set("innerText", "Captcha calculation paused.")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var captcha string
|
var captcha string
|
||||||
|
|
||||||
|
@ -130,7 +105,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the signup process
|
// Start the signup process
|
||||||
println("Starting signup process for user: " + username)
|
fmt.Println("Starting signup process for user: " + username)
|
||||||
showElements(false, inputContainer, signupButton, loginButton)
|
showElements(false, inputContainer, signupButton, loginButton)
|
||||||
if captcha == "" {
|
if captcha == "" {
|
||||||
statusBox.Set("innerText", "You must have a valid captcha! Press the \"Start\" button to start calculating a captcha.")
|
statusBox.Set("innerText", "You must have a valid captcha! Press the \"Start\" button to start calculating a captcha.")
|
||||||
|
@ -139,17 +114,24 @@ func main() {
|
||||||
// PoW challenge computed, hash password
|
// PoW challenge computed, hash password
|
||||||
statusBox.Set("innerText", "Hashing password...")
|
statusBox.Set("innerText", "Hashing password...")
|
||||||
|
|
||||||
// Hash the password
|
// Generate a random salt
|
||||||
hashedPassword := hashPassword(password, []byte(username))
|
salt := make([]byte, 32)
|
||||||
|
_, err := rand.Read(salt)
|
||||||
|
if err != nil {
|
||||||
|
showElements(true, inputContainer, signupButton, loginButton)
|
||||||
|
statusBox.Set("innerText", "Error generating salt: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Create a keypair from the password
|
// Hash the password
|
||||||
publicKey := ed25519.NewKeyFromSeed(hashedPassword).Public().(ed25519.PublicKey)
|
hashedPassword := hashPassword(password, salt)
|
||||||
|
|
||||||
// Hashed password computed, contact server
|
// Hashed password computed, contact server
|
||||||
statusBox.Set("innerText", "Contacting server...")
|
statusBox.Set("innerText", "Contacting server...")
|
||||||
signupBody := map[string]interface{}{
|
signupBody := map[string]interface{}{
|
||||||
"username": username,
|
"username": username,
|
||||||
"publicKey": base64.StdEncoding.EncodeToString(publicKey),
|
"password": hashedPassword,
|
||||||
|
"salt": base64.StdEncoding.EncodeToString(salt),
|
||||||
"proofOfWork": captcha,
|
"proofOfWork": captcha,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +151,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
showElements(true, inputContainer, signupButton, loginButton)
|
showElements(true, inputContainer, signupButton, loginButton)
|
||||||
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
|
@ -191,25 +173,19 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
// Signup successful
|
// Signup successful
|
||||||
statusBox.Set("innerText", "Setting up encryption keys...")
|
statusBox.Set("innerText", "Setting up encryption keys...")
|
||||||
localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
|
localStorage.Call("setItem", "DONOTSHARE-secretKey", responseMap["key"].(string))
|
||||||
localStorage.Call("setItem", "DONOTSHARE-clientKey", base64.StdEncoding.EncodeToString(hashPassword(password, []byte("fg-auth-client"))))
|
localStorage.Call("setItem", "DONOTSHARE-clientKey", hashPassword(password, []byte("fg-auth-client")))
|
||||||
|
|
||||||
// Redirect to app
|
// Redirect to app
|
||||||
statusBox.Set("innerText", "Welcome!")
|
statusBox.Set("innerText", "Welcome!")
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
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())
|
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 {
|
} else if response.StatusCode == 409 {
|
||||||
// Username taken
|
// Username taken
|
||||||
showElements(true, inputContainer, signupButton, loginButton)
|
showElements(true, inputContainer, signupButton, loginButton)
|
||||||
|
@ -229,17 +205,12 @@ func main() {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
loginButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
loginButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
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())
|
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
|
||||||
}()
|
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
captchaInProgress := false
|
captchaInProgress := false
|
||||||
captchaButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
captchaButton.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
if localStorage.Call("getItem", "DEBUG-customCaptcha").IsNull() {
|
|
||||||
if !captchaInProgress {
|
if !captchaInProgress {
|
||||||
captchaInProgress = true
|
captchaInProgress = true
|
||||||
captchaButton.Set("innerText", "Pause")
|
captchaButton.Set("innerText", "Pause")
|
||||||
|
@ -283,15 +254,6 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
captchaInProgress = false
|
captchaInProgress = false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
captcha = localStorage.Call("getItem", "DEBUG-customCaptcha").String()
|
|
||||||
captchaStatus.Set("innerText", "Captcha calculated!")
|
|
||||||
captchaButton.Set("disabled", true)
|
|
||||||
captchaButton.Set("innerText", "Start")
|
|
||||||
usernameBox.Set("disabled", false)
|
|
||||||
passwordBox.Set("disabled", false)
|
|
||||||
signupButton.Set("disabled", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -2,11 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/ecdh"
|
"crypto/ecdh"
|
||||||
|
@ -14,11 +9,14 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/url"
|
"errors"
|
||||||
"syscall/js"
|
"fmt"
|
||||||
|
|
||||||
"git.ailur.dev/ailur/jsFetch"
|
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall/js"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sha256Base64(s string) string {
|
func sha256Base64(s string) string {
|
||||||
|
@ -48,22 +46,9 @@ func randomChars(length int) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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
|
// Redirect to log-in if not signed in
|
||||||
localStorage := js.Global().Get("localStorage")
|
localStorage := js.Global().Get("localStorage")
|
||||||
if localStorage.Call("getItem", "DONOTSHARE-secretKey").IsNull() {
|
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())
|
js.Global().Get("window").Get("location").Call("replace", "/login"+js.Global().Get("window").Get("location").Get("search").String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +73,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/json", bytes.NewReader(body))
|
response, err := http.Post(requestUri, "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -99,12 +84,10 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to log-out if not signed in
|
// 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())
|
js.Global().Get("window").Get("location").Call("replace", "/logout"+js.Global().Get("window").Get("location").Get("search").String())
|
||||||
return
|
return
|
||||||
} else if response.StatusCode == 500 {
|
} else if response.StatusCode == 500 {
|
||||||
|
@ -120,7 +103,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert the user if the server is down
|
// Alert the user if the server is down
|
||||||
|
@ -131,7 +114,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the URL has a code
|
// Check if the URL has a code
|
||||||
|
@ -160,7 +143,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := jsFetch.Post(requestUri, "application/x-www-form-urlencoded", strings.NewReader(formData.Encode()))
|
response, err := http.Post(requestUri, "application/x-www-form-urlencoded", strings.NewReader(formData.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -178,7 +161,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == 200 {
|
if response.StatusCode == 200 {
|
||||||
|
@ -190,7 +173,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the request
|
// Create the request
|
||||||
request, err := jsFetch.NewRequest("GET", requestUri, nil)
|
request, err := http.NewRequest("GET", requestUri, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusBox.Set("innerText", "Error creating request: "+err.Error())
|
statusBox.Set("innerText", "Error creating request: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -200,7 +183,7 @@ func main() {
|
||||||
request.Header.Set("Authorization", "Bearer "+responseMap["id_token"].(string))
|
request.Header.Set("Authorization", "Bearer "+responseMap["id_token"].(string))
|
||||||
|
|
||||||
// Send the request
|
// Send the request
|
||||||
response, err := jsFetch.Fetch.Do(request)
|
response, err := http.DefaultClient.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
statusBox.Set("innerText", "Error contacting server: "+err.Error())
|
||||||
return
|
return
|
||||||
|
@ -217,7 +200,7 @@ func main() {
|
||||||
// Close the response body
|
// Close the response body
|
||||||
err = response.Body.Close()
|
err = response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
fmt.Println("Could not close response body: " + err.Error() + ", memory leaks may occur")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the username
|
// Set the username
|
||||||
|
@ -234,9 +217,7 @@ func main() {
|
||||||
localStorage.Call("setItem", "TESTER-privateKey", base64.StdEncoding.EncodeToString(privateKey.Bytes()))
|
localStorage.Call("setItem", "TESTER-privateKey", base64.StdEncoding.EncodeToString(privateKey.Bytes()))
|
||||||
|
|
||||||
// Redirect to the client key exchange endpoint
|
// Redirect to the client key exchange endpoint
|
||||||
js.Global().Get("swipe").Get("classList").Call("add", "swipe-animate")
|
js.Global().Get("window").Get("location").Call("replace", "/clientKeyShare?ecdhPublicKey="+base64.URLEncoding.EncodeToString(privateKey.PublicKey().Bytes())+"&accessToken="+responseMap["access_token"].(string))
|
||||||
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
|
return
|
||||||
} else if response.StatusCode != 500 {
|
} else if response.StatusCode != 500 {
|
||||||
statusBox.Set("innerText", responseMap["error"].(string))
|
statusBox.Set("innerText", responseMap["error"].(string))
|
||||||
|
@ -336,8 +317,6 @@ func main() {
|
||||||
localStorage.Call("setItem", "TESTER-verifier", verifier)
|
localStorage.Call("setItem", "TESTER-verifier", verifier)
|
||||||
|
|
||||||
// Redirect to the authorization page
|
// 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")
|
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
|
return nil
|
||||||
|
|
|
@ -2,6 +2,4 @@
|
||||||
|
|
||||||
path=$(realpath "$(dirname "$0")") || exit 1
|
path=$(realpath "$(dirname "$0")") || exit 1
|
||||||
rm -rf "$path/../../services/storage.fgs" || 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
|
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"
|
|
|
@ -1,333 +1,420 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
library "git.ailur.dev/ailur/fg-library/v3"
|
|
||||||
nucleusLibrary "git.ailur.dev/ailur/fg-nucleus-library"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
library "git.ailur.dev/ailur/fg-library"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/google/uuid"
|
"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{
|
var ServiceInformation = library.Service{
|
||||||
Name: "Storage",
|
Name: "Storage",
|
||||||
Permissions: library.Permissions{
|
Permissions: library.Permissions{
|
||||||
Authenticate: false, // This service does not require authentication
|
Authenticate: false, // This service does not require authentication
|
||||||
Router: false, // This service does not serve web pages
|
|
||||||
Database: true, // This service requires database access to store quotas
|
Database: true, // This service requires database access to store quotas
|
||||||
BlobStorage: false, // This service *is* the blob storage
|
BlobStorage: false, // This service *is* the blob storage
|
||||||
InterServiceCommunication: true, // This service does require inter-service communication
|
InterServiceCommunication: true, // This service does require inter-service communication
|
||||||
Resources: false, // This service does not require access to its resource directory
|
|
||||||
},
|
},
|
||||||
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000003"),
|
ServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000003"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var conn *sql.DB
|
||||||
loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002")
|
|
||||||
)
|
|
||||||
|
|
||||||
func logFunc(message string, messageType library.MessageCode, information *library.ServiceInitializationInformation) {
|
func getQuota(user uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
|
||||||
// Log the message to the logger service
|
// Get the user's quota from the database
|
||||||
information.SendISMessage(loggerService, messageType, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func respondError(message library.InterServiceMessage, err error, information *library.ServiceInitializationInformation, myFault bool) {
|
|
||||||
// Respond with an error message
|
|
||||||
var errCode = library.BadRequest
|
|
||||||
if myFault {
|
|
||||||
// Log the error message to the logger service
|
|
||||||
logFunc(err.Error(), 2, information)
|
|
||||||
errCode = library.InternalError
|
|
||||||
}
|
|
||||||
|
|
||||||
message.Respond(errCode, err, information)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkUserExists(userID uuid.UUID, conn library.Database) bool {
|
|
||||||
// Check if a user exists in the database
|
|
||||||
var userCheck []byte
|
|
||||||
err := conn.DB.QueryRow("SELECT id FROM users WHERE id = $1", userID[:]).Scan(&userCheck)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return bytes.Equal(userCheck, userID[:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addQuota can be used with a negative quota to remove quota from a user
|
|
||||||
func addQuota(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) {
|
|
||||||
// Add more quota to a user
|
|
||||||
userID := message.Message.(nucleusLibrary.Quota).User
|
|
||||||
if checkUserExists(userID, conn) {
|
|
||||||
_, err := conn.DB.Exec("UPDATE users SET quota = quota + $1 WHERE id = $2", message.Message.(nucleusLibrary.Quota).Bytes, message.Message.(nucleusLibrary.Quota).User)
|
|
||||||
if err != nil {
|
|
||||||
respondError(message, err, information, true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int))+message.Message.(nucleusLibrary.Quota).Bytes)
|
|
||||||
if err != nil {
|
|
||||||
respondError(message, err, information, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success
|
|
||||||
message.Respond(library.Success, nil, information)
|
|
||||||
}
|
|
||||||
|
|
||||||
// And so does addReserved
|
|
||||||
func addReserved(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) {
|
|
||||||
// Add more reserved space to a user
|
|
||||||
userID := message.Message.(nucleusLibrary.Quota).User
|
|
||||||
if checkUserExists(userID, conn) {
|
|
||||||
// Check if the user has enough space
|
|
||||||
quota, err := getQuota(information, userID, conn)
|
|
||||||
if err != nil {
|
|
||||||
respondError(message, err, information, true)
|
|
||||||
}
|
|
||||||
used, err := getUsed(userID, information, conn)
|
|
||||||
if err != nil {
|
|
||||||
respondError(message, err, information, true)
|
|
||||||
}
|
|
||||||
if used+message.Message.(nucleusLibrary.Quota).Bytes > quota {
|
|
||||||
respondError(message, errors.New("insufficient storage"), information, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = conn.DB.Exec("UPDATE users SET reserved = reserved + $1 WHERE id = $2", message.Message.(nucleusLibrary.Quota).Bytes, userID[:])
|
|
||||||
if err != nil {
|
|
||||||
respondError(message, err, information, true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check if the user has enough space
|
|
||||||
if int64(information.Configuration["defaultQuota"].(int)) < message.Message.(nucleusLibrary.Quota).Bytes {
|
|
||||||
respondError(message, errors.New("insufficient storage"), information, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, $3)", userID[:], int64(information.Configuration["defaultQuota"].(int)), message.Message.(nucleusLibrary.Quota).Bytes)
|
|
||||||
if err != nil {
|
|
||||||
respondError(message, err, information, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success
|
|
||||||
message.Respond(library.Success, nil, information)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQuota(information *library.ServiceInitializationInformation, userID uuid.UUID, conn library.Database) (int64, error) {
|
|
||||||
// Get the quota for a user
|
|
||||||
var quota int64
|
var quota int64
|
||||||
err := conn.DB.QueryRow("SELECT quota FROM users WHERE id = $1", userID[:]).Scan("a)
|
err := conn.QueryRow("SELECT quota FROM quotas WHERE id = $1", user).Scan("a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int)))
|
// The user has no quota set, so we'll set it to the default quota
|
||||||
|
_, err = conn.Exec("INSERT INTO quotas (id, quota) VALUES ($1, $2)", user, int64(information.Configuration["defaultQuota"].(float64)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return int64(information.Configuration["defaultQuota"].(int)), nil
|
return int64(information.Configuration["defaultQuota"].(float64)), nil
|
||||||
} else {
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return quota, nil
|
return quota, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUsed(userID uuid.UUID, information *library.ServiceInitializationInformation, conn library.Database) (int64, error) {
|
func getUsed(user uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
|
||||||
// Get the used space for a user by first getting the reserved space from file storage
|
// Check the user's used space via the filesystem
|
||||||
_, err := os.Stat(filepath.Join(information.Configuration["path"].(string), userID.String()))
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
// Create the directory
|
|
||||||
err = os.Mkdir(filepath.Join(information.Configuration["path"].(string), userID.String()), 0755)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var used int64
|
var used int64
|
||||||
err = filepath.Walk(filepath.Join(information.Configuration["path"].(string), userID.String()), func(path string, entry os.FileInfo, err error) error {
|
_, err := 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
used += info.Size()
|
||||||
used += entry.Size()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
return used, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Then add the reserved space from the database
|
func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
|
||||||
var reserved int64
|
// Log the error message to the logger service
|
||||||
err = conn.DB.QueryRow("SELECT reserved FROM users WHERE id = $1", userID[:]).Scan(&reserved)
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service
|
||||||
|
MessageType: messageType,
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeFile(file 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
// First contact the logger service
|
||||||
_, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int)))
|
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
|
||||||
|
quota, err := getQuota(file.File.User, information)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
// First contact the logger service
|
||||||
}
|
logFunc(err.Error(), 2, information)
|
||||||
return 0, nil
|
|
||||||
} else {
|
// Then send the error message to the requesting service
|
||||||
return 0, err
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
|
MessageType: 1, // An error that's not your fault
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return used + reserved, nil
|
used, err := getUsed(file.File.User, information)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
logFunc(path, 0, information)
|
|
||||||
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
if err == nil {
|
|
||||||
// Delete the file
|
|
||||||
err = os.Remove(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(message, err, information, true)
|
// First contact the logger service
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
|
||||||
|
// Then send the error message to the requesting service
|
||||||
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
|
MessageType: 1, // An error that's not your fault
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: err.Error(),
|
||||||
}
|
}
|
||||||
} else if !os.IsNotExist(err) {
|
|
||||||
respondError(message, err, information, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has enough space
|
// Check if the user has enough space to store the file
|
||||||
quota, err := getQuota(information, message.Message.(nucleusLibrary.File).User, conn)
|
if used+file.File.Size > quota {
|
||||||
if err != nil {
|
// Then send the error message to the requesting service
|
||||||
respondError(message, err, information, true)
|
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",
|
||||||
}
|
}
|
||||||
used, err := getUsed(message.Message.(nucleusLibrary.File).User, information, conn)
|
|
||||||
if err != nil {
|
|
||||||
respondError(message, err, information, true)
|
|
||||||
}
|
|
||||||
if used+message.Message.(nucleusLibrary.File).Reader.N > quota {
|
|
||||||
respondError(message, errors.New("insufficient storage"), information, false)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a file to the user's storage
|
// Create a folder within that for the service if it doesn't exist
|
||||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
|
err = os.MkdirAll(filepath.Join(information.Configuration["path"].(string), file.File.User.String(), serviceID.String()), 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(message, err, information, true)
|
// First contact the logger service
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
|
||||||
|
// Then send the error message to the requesting service
|
||||||
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
|
MessageType: 1, // An error that's not your fault
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the file
|
// Write the file
|
||||||
_, err = io.Copy(file, message.Message.(nucleusLibrary.File).Reader)
|
_, err = io.Copy(fileStream, file.Stream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(message, err, information, true)
|
// First contact the logger service
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
|
||||||
|
// Then send the error message to the requesting service
|
||||||
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
|
MessageType: 1, // An error that's not your fault
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the file
|
// Close the file
|
||||||
err = file.Close()
|
err = fileStream.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(message, err, information, true)
|
// First contact the logger service
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
|
||||||
|
// 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(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success
|
// Report success
|
||||||
message.Respond(library.Success, nil, information)
|
information.Outbox <- library.InterServiceMessage{
|
||||||
}
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
func getFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage) {
|
MessageType: 0, // Success
|
||||||
// Check if the file exists
|
SentAt: time.Now(),
|
||||||
path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name)
|
Message: nil,
|
||||||
|
}
|
||||||
_, err := os.Stat(path)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
println("file not found: " + path)
|
|
||||||
respondError(message, errors.New("file not found"), information, false)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readFile(file ReadFile, serviceID uuid.UUID, information library.ServiceInitializationInformation) {
|
||||||
// Open the file
|
// Open the file
|
||||||
file, err := os.Open(path)
|
fileStream, err := os.Open(filepath.Join(information.Configuration["path"].(string), file.File.User.String(), serviceID.String(), file.File.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(message, err, information, true)
|
// First contact the logger service
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
|
||||||
|
// Then send the error message to the requesting service
|
||||||
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
|
MessageType: 1, // An error that's not your fault
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with the file
|
// Read the file
|
||||||
// It's their responsibility to close the file
|
_, err = io.Copy(file.Stream, fileStream)
|
||||||
message.Respond(library.Success, file, information)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage) {
|
|
||||||
// Check if the file exists
|
|
||||||
path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name)
|
|
||||||
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
respondError(message, errors.New("file not found"), information, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the file
|
|
||||||
err = os.Remove(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(message, err, information, true)
|
// First contact the logger service
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
|
||||||
|
// Then send the error message to the requesting service
|
||||||
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
|
MessageType: 1, // An error that's not your fault
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success
|
// Close the file
|
||||||
message.Respond(library.Success, nil, information)
|
err = fileStream.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(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// processInterServiceMessages listens for incoming messages and processes them
|
// Report success
|
||||||
func processInterServiceMessages(information *library.ServiceInitializationInformation, conn library.Database) {
|
information.Outbox <- library.InterServiceMessage{
|
||||||
// Listen for incoming messages
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
|
MessageType: 0, // Success
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
if err != nil {
|
||||||
|
// First contact the logger service
|
||||||
|
logFunc(err.Error(), 2, information)
|
||||||
|
|
||||||
|
// Then send the error message to the requesting service
|
||||||
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
|
MessageType: 1, // An error that's not your fault
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report success
|
||||||
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: serviceID,
|
||||||
|
MessageType: 0, // Success
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main(information library.ServiceInitializationInformation) {
|
||||||
|
go func() {
|
||||||
for {
|
for {
|
||||||
message := information.AcceptMessage()
|
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 {
|
switch message.MessageType {
|
||||||
case 1:
|
case 0:
|
||||||
// Add quota
|
// Insert file
|
||||||
addQuota(information, message, conn)
|
validate := validator.New()
|
||||||
case 2:
|
err := validate.Struct(message.Message.(InsertFile))
|
||||||
// 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 Main(information *library.ServiceInitializationInformation) {
|
|
||||||
// Start up the ISM processor
|
|
||||||
go information.StartISProcessor()
|
|
||||||
|
|
||||||
// Get the database connection
|
|
||||||
conn, err := information.GetDatabase()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
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.(*sql.DB)
|
||||||
// Create the quotas table if it doesn't exist
|
// Create the quotas table if it doesn't exist
|
||||||
if conn.DBType == library.Sqlite {
|
_, err := conn.Exec("CREATE TABLE IF NOT EXISTS quotas (id UUID PRIMARY KEY, quota BIGINT)")
|
||||||
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB PRIMARY KEY, quota BIGINT, reserved BIGINT)")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(err.Error(), 3, information)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA PRIMARY KEY, quota BIGINT, reserved BIGINT)")
|
// This is an error message
|
||||||
if err != nil {
|
// Log the error message to the logger service
|
||||||
logFunc(err.Error(), 3, information)
|
logFunc(response.Message.(error).Error(), 3, information)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for incoming messages
|
// Report a successful activation
|
||||||
go processInterServiceMessages(information, conn)
|
information.Outbox <- library.InterServiceMessage{
|
||||||
|
ServiceID: ServiceInformation.ServiceID,
|
||||||
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), // Activation service
|
||||||
|
MessageType: 0,
|
||||||
|
SentAt: time.Now(),
|
||||||
|
Message: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue