Compare commits
2 Commits
v1.0.0
...
refactorin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c06843cd28 | ||
|
|
55ac50f3be |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ storage/
|
|||||||
.idea/
|
.idea/
|
||||||
build
|
build
|
||||||
*.log
|
*.log
|
||||||
|
id_rsa*
|
||||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2022 Aurélie Delhaie
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package admin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"opensavecloudserver/database"
|
|
||||||
"opensavecloudserver/upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RemoveUser rome the user from the db and all his datas
|
|
||||||
func RemoveUser(user *database.User) error {
|
|
||||||
if err := database.RemoveAllUserGameEntries(user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := upload.RemoveFolders(user.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return database.RemoveUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetAdmin(user *database.User) error {
|
|
||||||
user.Role = database.AdminRole
|
|
||||||
user.IsAdmin = true
|
|
||||||
return database.SaveUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveAdminRole(user *database.User) error {
|
|
||||||
user.Role = database.UserRole
|
|
||||||
user.IsAdmin = false
|
|
||||||
return database.SaveUser(user)
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package authentication
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
|
||||||
"github.com/golang-jwt/jwt"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
"log"
|
|
||||||
"opensavecloudserver/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
var secret []byte
|
|
||||||
|
|
||||||
type AccessToken struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Registration struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
secret = make([]byte, 512)
|
|
||||||
_, err := rand.Read(secret)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Connect(username, password string) (*AccessToken, error) {
|
|
||||||
user, err := database.UserByUsername(username)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := bcrypt.CompareHashAndPassword(user.Password, []byte(password)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
token, err := token(user.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &AccessToken{
|
|
||||||
Token: token,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseToken(token string) (int, error) {
|
|
||||||
var claims jwt.MapClaims
|
|
||||||
_, err := jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
|
|
||||||
return secret, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if userId, ok := claims["sub"]; ok {
|
|
||||||
return int(userId.(float64)), nil
|
|
||||||
}
|
|
||||||
return 0, errors.New("this token does not have a userId in it")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Register(user *Registration) error {
|
|
||||||
_, err := database.UserByUsername(user.Username)
|
|
||||||
if err == nil {
|
|
||||||
return errors.New("this username already exist")
|
|
||||||
}
|
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return database.AddUser(user.Username, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func token(userId int) (string, error) {
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
|
|
||||||
"sub": userId,
|
|
||||||
})
|
|
||||||
return token.SignedString(secret)
|
|
||||||
}
|
|
||||||
6
build.sh
6
build.sh
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
platforms=("windows/amd64" "linux/amd64" "linux/arm64" "linux/arm")
|
platforms=("windows/amd64" "linux/amd64" "linux/arm64")
|
||||||
|
|
||||||
if [[ -d "./build" ]]
|
if [[ -d "./build" ]]
|
||||||
then
|
then
|
||||||
@@ -20,9 +20,6 @@ do
|
|||||||
output_name+='.exe'
|
output_name+='.exe'
|
||||||
go generate
|
go generate
|
||||||
env GOAMD64=v3 GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=1 go build -o $output_name -a
|
env GOAMD64=v3 GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=1 go build -o $output_name -a
|
||||||
else
|
|
||||||
if [ $GOARCH = "arm" ]; then
|
|
||||||
env GOARM=7 GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=0 go build -o $output_name -a
|
|
||||||
else
|
else
|
||||||
if [ $GOARCH = "amd64" ]; then
|
if [ $GOARCH = "amd64" ]; then
|
||||||
env GOAMD64=v3 GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=0 go build -o $output_name -a
|
env GOAMD64=v3 GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=0 go build -o $output_name -a
|
||||||
@@ -30,6 +27,5 @@ do
|
|||||||
env GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=0 go build -o $output_name -a
|
env GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=0 go build -o $output_name -a
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
done
|
done
|
||||||
15
common.go
15
common.go
@@ -3,24 +3,17 @@ package main
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"opensavecloudserver/config"
|
|
||||||
"opensavecloudserver/database"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitCommon() {
|
func initLogger() (err error) {
|
||||||
f, err := os.OpenFile("server.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
f, err := os.OpenFile("server.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error opening file: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
defer func(f *os.File) {
|
defer func(f *os.File) {
|
||||||
err := f.Close()
|
err = f.Close()
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}(f)
|
}(f)
|
||||||
log.SetOutput(io.MultiWriter(os.Stdout, f))
|
log.SetOutput(io.MultiWriter(os.Stdout, f))
|
||||||
|
return nil
|
||||||
config.Init()
|
|
||||||
database.Init()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
---
|
---
|
||||||
server:
|
server:
|
||||||
port: 8080
|
port: 8080
|
||||||
|
ping: no
|
||||||
|
compress: no
|
||||||
database:
|
database:
|
||||||
host: localhost
|
url: "postgres://username:password@localhost:5432/database_name"
|
||||||
password: root
|
|
||||||
port: 3306
|
|
||||||
username: root
|
|
||||||
features:
|
features:
|
||||||
allow_register: false
|
allow_register: no
|
||||||
password_hash_cost: 16
|
password_hash_cost: 16
|
||||||
path:
|
path:
|
||||||
cache: "/var/osc/cache"
|
cache: "/var/osc/cache"
|
||||||
|
|||||||
@@ -1,84 +1,69 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"fmt"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Configuration struct {
|
type (
|
||||||
|
Configuration struct {
|
||||||
Server ServerConfiguration `yaml:"server"`
|
Server ServerConfiguration `yaml:"server"`
|
||||||
Database DatabaseConfiguration `yaml:"database"`
|
Database DatabaseConfiguration `yaml:"database"`
|
||||||
Features FeaturesConfiguration `yaml:"features"`
|
Features FeaturesConfiguration `yaml:"features"`
|
||||||
Path PathConfiguration `yaml:"path"`
|
Path PathConfiguration `yaml:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PathConfiguration struct {
|
PathConfiguration struct {
|
||||||
Cache string `yaml:"cache"`
|
Cache string `yaml:"cache"`
|
||||||
Storage string `yaml:"storage"`
|
Storage string `yaml:"storage"`
|
||||||
|
RSAKey string `yaml:"rsa_key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerConfiguration struct {
|
ServerConfiguration struct {
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
|
PingEndPoint bool `yaml:"ping"`
|
||||||
|
Compress bool `yaml:"compress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatabaseConfiguration struct {
|
DatabaseConfiguration struct {
|
||||||
Host string `yaml:"host"`
|
URL string `yaml:"url"`
|
||||||
Port int `yaml:"port"`
|
|
||||||
Username string `yaml:"username"`
|
|
||||||
Password *string `yaml:"password"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FeaturesConfiguration struct {
|
FeaturesConfiguration struct {
|
||||||
AllowRegister bool `yaml:"allow_register"`
|
AllowRegister bool `yaml:"allow_register"`
|
||||||
PasswordHashCost *int `yaml:"password_hash_cost"`
|
PasswordHashCost *int `yaml:"password_hash_cost"`
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
var currentConfig *Configuration
|
func Load(path string) (Configuration, error) {
|
||||||
|
configYamlContent, err := os.ReadFile(path)
|
||||||
func Init() {
|
|
||||||
path := flag.String("config", "./config.yml", "Set the configuration file path")
|
|
||||||
flag.Parse()
|
|
||||||
configYamlContent, err := os.ReadFile(*path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return Configuration{}, err
|
||||||
}
|
}
|
||||||
err = yaml.Unmarshal(configYamlContent, ¤tConfig)
|
var config Configuration
|
||||||
if err != nil {
|
if err := yaml.Unmarshal(configYamlContent, &config); err != nil {
|
||||||
log.Fatalf("error: %s", err)
|
return Configuration{}, err
|
||||||
}
|
}
|
||||||
checkConfig()
|
if err := checkConfig(config); err != nil {
|
||||||
|
return Configuration{}, err
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkConfig() {
|
func checkConfig(c Configuration) error {
|
||||||
if currentConfig.Features.PasswordHashCost == nil {
|
if c.Features.PasswordHashCost == nil {
|
||||||
currentConfig.Features.PasswordHashCost = new(int)
|
c.Features.PasswordHashCost = new(int)
|
||||||
*currentConfig.Features.PasswordHashCost = bcrypt.DefaultCost
|
*c.Features.PasswordHashCost = bcrypt.DefaultCost
|
||||||
} else if *currentConfig.Features.PasswordHashCost < bcrypt.MinCost && *currentConfig.Features.PasswordHashCost > bcrypt.MaxCost {
|
} else if *c.Features.PasswordHashCost < bcrypt.MinCost && *c.Features.PasswordHashCost > bcrypt.MaxCost {
|
||||||
log.Fatalf("password_hash_cost is not on the supported range (%d < x < %d)", bcrypt.MinCost, bcrypt.MaxCost)
|
return fmt.Errorf("password_hash_cost is not on the supported range (%d < x < %d)", bcrypt.MinCost, bcrypt.MaxCost)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(currentConfig.Path.Storage); err != nil {
|
if _, err := os.Stat(c.Path.Storage); err != nil {
|
||||||
log.Fatal(err)
|
return nil
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(currentConfig.Path.Cache); err != nil {
|
if _, err := os.Stat(c.Path.Cache); err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
|
|
||||||
func Database() *DatabaseConfiguration {
|
|
||||||
return ¤tConfig.Database
|
|
||||||
}
|
|
||||||
|
|
||||||
func Features() *FeaturesConfiguration {
|
|
||||||
return ¤tConfig.Features
|
|
||||||
}
|
|
||||||
|
|
||||||
func Path() *PathConfiguration {
|
|
||||||
return ¤tConfig.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
func Server() *ServerConfiguration {
|
|
||||||
return ¤tConfig.Server
|
|
||||||
}
|
}
|
||||||
|
|||||||
118
config/security/security.go
Normal file
118
config/security/security.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package security
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPrivateKeyName = "id_rsa"
|
||||||
|
defaultPublicKeyName = "id_rsa.pub"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateNewRSAKey generates a new RSA key, writes it to a file and return the private key
|
||||||
|
func GenerateNewRSAKey(savePath string, bitSize uint16) (*rsa.PrivateKey, error) {
|
||||||
|
pkPath := path.Join(savePath, defaultPrivateKeyName)
|
||||||
|
pubPath := path.Join(savePath, defaultPublicKeyName)
|
||||||
|
|
||||||
|
privateKey, err := generatePrivateKey(int(bitSize))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyBytes, err := generatePublicKey(&privateKey.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKeyBytes := encodePrivateKeyToPEM(privateKey)
|
||||||
|
|
||||||
|
if err := writeKeyToFile(privateKeyBytes, pkPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeKeyToFile(publicKeyBytes, pubPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadPrivateKey(folderPath string) (*rsa.PrivateKey, error) {
|
||||||
|
pkPath := path.Join(folderPath, defaultPrivateKeyName)
|
||||||
|
f, err := os.ReadFile(pkPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
block, _ := pem.Decode(f)
|
||||||
|
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatePrivateKey(bitSize int) (*rsa.PrivateKey, error) {
|
||||||
|
// Private Key generation
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, bitSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Private Key
|
||||||
|
err = privateKey.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Private Key generated")
|
||||||
|
return privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodePrivateKeyToPEM encodes Private Key from RSA to PEM format
|
||||||
|
func encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte {
|
||||||
|
// Get ASN.1 DER format
|
||||||
|
privDER := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||||
|
|
||||||
|
// pem.Block
|
||||||
|
privBlock := pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Headers: nil,
|
||||||
|
Bytes: privDER,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private key in PEM format
|
||||||
|
privatePEM := pem.EncodeToMemory(&privBlock)
|
||||||
|
|
||||||
|
return privatePEM
|
||||||
|
}
|
||||||
|
|
||||||
|
// generatePublicKey take a rsa.PublicKey and return bytes suitable for writing to .pub file
|
||||||
|
// returns in the format "ssh-rsa ..."
|
||||||
|
func generatePublicKey(privatekey *rsa.PublicKey) ([]byte, error) {
|
||||||
|
publicRsaKey, err := ssh.NewPublicKey(privatekey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKeyBytes := ssh.MarshalAuthorizedKey(publicRsaKey)
|
||||||
|
|
||||||
|
log.Println("Public key generated")
|
||||||
|
return pubKeyBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writePemToFile writes keys to a file
|
||||||
|
func writeKeyToFile(keyBytes []byte, saveFileTo string) error {
|
||||||
|
err := os.WriteFile(saveFileTo, keyBytes, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Key saved to: %s", saveFileTo)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
const Version = "1.0.0"
|
const Version = "2.0.0"
|
||||||
|
|
||||||
const ApiVersion = 1
|
const ApiVersion = 2
|
||||||
|
|||||||
12
data/datasource/datasource.go
Normal file
12
data/datasource/datasource.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package datasource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Datasource interface {
|
||||||
|
Connect(dsn string) error
|
||||||
|
DB() *gorm.DB
|
||||||
|
}
|
||||||
|
)
|
||||||
40
data/datasource/pgsql/models/game/game.go
Normal file
40
data/datasource/pgsql/models/game/game.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"opensavecloudserver/data/datasource"
|
||||||
|
"opensavecloudserver/data/repository/game"
|
||||||
|
"opensavecloudserver/data/repository/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
GameDatasource struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g *GameDatasource) GameMetadataByID(ID game.ID) (game.GameMetadata, error) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GameDatasource) CreateGameEntry(game game.NewGameEntry) (game.GameMetadata, error) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GameDatasource) GameSavesHistory(gameID game.ID) ([]game.GameSaveVersion, error) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GameDatasource) UserGamesByUserID(userID user.ID) ([]game.GameMetadata, error) {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGameDatasource(dts datasource.Datasource) game.GameRepository {
|
||||||
|
g := new(GameDatasource)
|
||||||
|
g.db = dts.DB()
|
||||||
|
return g
|
||||||
|
}
|
||||||
31
data/datasource/pgsql/models/user/user.go
Normal file
31
data/datasource/pgsql/models/user/user.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"opensavecloudserver/data/datasource"
|
||||||
|
"opensavecloudserver/data/repository/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
UserDatasource struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewUserDatasource(dts datasource.Datasource) user.UserRepository {
|
||||||
|
repo := new(UserDatasource)
|
||||||
|
repo.db = dts.DB()
|
||||||
|
return repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ud *UserDatasource) UserByID(ID user.ID) (user.User, error) {
|
||||||
|
return nil, user.ErrUserNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ud *UserDatasource) UserByUsername(username string) (user.User, error) {
|
||||||
|
return nil, user.ErrUserNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ud *UserDatasource) CreateUser(u user.NewUserTemplate) (user.User, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
30
data/datasource/pgsql/pgsql.go
Normal file
30
data/datasource/pgsql/pgsql.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package pgsql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"opensavecloudserver/data/datasource"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
DatabaseDatasource struct {
|
||||||
|
conn *gorm.DB
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDatabaseDatasource() datasource.Datasource {
|
||||||
|
return new(DatabaseDatasource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dd *DatabaseDatasource) Connect(dsn string) error {
|
||||||
|
conn, err := gorm.Open(postgres.Open(dsn))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dd.conn = conn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dd *DatabaseDatasource) DB() *gorm.DB {
|
||||||
|
return dd.conn
|
||||||
|
}
|
||||||
40
data/repository/game/game.go
Normal file
40
data/repository/game/game.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"opensavecloudserver/data/repository/user"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
GameRepository interface {
|
||||||
|
GameMetadataByID(ID ID) (GameMetadata, error)
|
||||||
|
CreateGameEntry(game NewGameEntry) (GameMetadata, error)
|
||||||
|
GameSavesHistory(gameID ID) ([]GameSaveVersion, error)
|
||||||
|
UserGamesByUserID(userID user.ID) ([]GameMetadata, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
GameMetadata interface {
|
||||||
|
ID() uuid.UUID
|
||||||
|
Name() string
|
||||||
|
Path() string
|
||||||
|
Revision() string
|
||||||
|
}
|
||||||
|
|
||||||
|
GameSaveVersion interface {
|
||||||
|
ID() string
|
||||||
|
Date() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
NewGameEntry interface {
|
||||||
|
Path() string
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
ID uuid.UUID
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrGameNotFound = errors.New("game not found")
|
||||||
|
)
|
||||||
36
data/repository/user/user.go
Normal file
36
data/repository/user/user.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
UserRepository interface {
|
||||||
|
UserByID(ID ID) (User, error)
|
||||||
|
UserByUsername(username string) (User, error)
|
||||||
|
CreateUser(user NewUserTemplate) (User, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
User interface {
|
||||||
|
ID() uuid.UUID
|
||||||
|
Username() string
|
||||||
|
DisplayName() string
|
||||||
|
Roles() []Role
|
||||||
|
SetPassword() string
|
||||||
|
CheckPassword(password string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
NewUserTemplate interface {
|
||||||
|
Username() string
|
||||||
|
Password() string
|
||||||
|
DisplayName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
ID uuid.UUID
|
||||||
|
Role string
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUserNotFound = errors.New("user not found")
|
||||||
|
)
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
"gorm.io/driver/mysql"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
"log"
|
|
||||||
"opensavecloudserver/config"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var db *gorm.DB
|
|
||||||
|
|
||||||
const AdminRole string = "admin"
|
|
||||||
const UserRole string = "user"
|
|
||||||
|
|
||||||
func Init() {
|
|
||||||
dbConfig := config.Database()
|
|
||||||
var err error
|
|
||||||
connectionString := ""
|
|
||||||
if dbConfig.Password != nil {
|
|
||||||
connectionString = fmt.Sprintf("%s:%s@tcp(%s:%d)/osc?charset=utf8mb4&parseTime=True&loc=Local",
|
|
||||||
dbConfig.Username,
|
|
||||||
*dbConfig.Password,
|
|
||||||
dbConfig.Host,
|
|
||||||
dbConfig.Port)
|
|
||||||
} else {
|
|
||||||
connectionString = fmt.Sprintf("%s@tcp(%s:%d)/osc?charset=utf8mb4&parseTime=True&loc=Local",
|
|
||||||
dbConfig.Username,
|
|
||||||
dbConfig.Host,
|
|
||||||
dbConfig.Port)
|
|
||||||
}
|
|
||||||
db, err = gorm.Open(mysql.Open(connectionString), &gorm.Config{
|
|
||||||
Logger: logger.New(
|
|
||||||
log.New(os.Stdout, "", log.LstdFlags), // io writer
|
|
||||||
logger.Config{
|
|
||||||
SlowThreshold: time.Second, // Slow SQL threshold
|
|
||||||
LogLevel: logger.Error, // Log level
|
|
||||||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
|
||||||
Colorful: true, // Enable color
|
|
||||||
},
|
|
||||||
),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func AllUsers() ([]*User, error) {
|
|
||||||
var users []*User
|
|
||||||
err := db.Model(User{}).Find(&users).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, user := range users {
|
|
||||||
if user.Role == AdminRole {
|
|
||||||
user.IsAdmin = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserByUsername get a user by the username
|
|
||||||
func UserByUsername(username string) (*User, error) {
|
|
||||||
var user *User
|
|
||||||
err := db.Model(User{}).Where(User{Username: username}).First(&user).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if user.Role == AdminRole {
|
|
||||||
user.IsAdmin = true
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ChangeUsername(userId int, newUsername string) error {
|
|
||||||
user, err := UserById(userId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
user.Username = newUsername
|
|
||||||
return db.Save(user).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserById get a user
|
|
||||||
func UserById(userId int) (*User, error) {
|
|
||||||
var user *User
|
|
||||||
err := db.Model(User{}).Where(userId).First(&user).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if user.Role == AdminRole {
|
|
||||||
user.IsAdmin = true
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddUser register a user
|
|
||||||
func AddUser(username string, password []byte) error {
|
|
||||||
user := &User{
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
return db.Save(user).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveUser(user *User) error {
|
|
||||||
return db.Save(user).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveUser(user *User) error {
|
|
||||||
return db.Delete(User{}, user.ID).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveAllUserGameEntries(user *User) error {
|
|
||||||
return db.Delete(Game{}, Game{UserId: user.ID}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveGame(game *Game) error {
|
|
||||||
return db.Delete(Game{}, Game{UserId: game.UserId, ID: game.ID}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAdmin register a user and set his role to admin
|
|
||||||
/*func AddAdmin(username string, password []byte) error {
|
|
||||||
user := &User{
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
Role: AdminRole,
|
|
||||||
}
|
|
||||||
return db.Save(user).Error
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// GameInfoById return information of a game
|
|
||||||
func GameInfoById(userId, gameId int) (*Game, error) {
|
|
||||||
var game *Game
|
|
||||||
err := db.Model(Game{}).Where(Game{ID: gameId, UserId: userId}).First(&game).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return game, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GameInfosByUserId get all saved games for a user
|
|
||||||
func GameInfosByUserId(userId int) ([]*Game, error) {
|
|
||||||
var games []*Game
|
|
||||||
err := db.Model(Game{}).Where(Game{UserId: userId}).Find(&games).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return games, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateGame create an entry for a new game save, do this only for create a new entry
|
|
||||||
func CreateGame(userId int, name string) (*Game, error) {
|
|
||||||
gameUUID := uuid.New()
|
|
||||||
game := &Game{
|
|
||||||
Name: name,
|
|
||||||
Revision: 0,
|
|
||||||
PathStorage: gameUUID.String() + ".bin",
|
|
||||||
UserId: userId,
|
|
||||||
Available: false,
|
|
||||||
}
|
|
||||||
if err := db.Save(&game).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return game, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateGameRevision(game *Game, hash string) error {
|
|
||||||
game.Revision += 1
|
|
||||||
if game.Hash == nil {
|
|
||||||
game.Hash = new(string)
|
|
||||||
}
|
|
||||||
*game.Hash = hash
|
|
||||||
game.Available = true
|
|
||||||
if game.LastUpdate == nil {
|
|
||||||
game.LastUpdate = new(time.Time)
|
|
||||||
}
|
|
||||||
*game.LastUpdate = time.Now()
|
|
||||||
err := db.Save(game).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangePassword change the password of the user, the param 'password' must be the clear password
|
|
||||||
func ChangePassword(userId int, password []byte) error {
|
|
||||||
user, err := UserById(userId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword(password, *config.Features().PasswordHashCost)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
user.Password = hashedPassword
|
|
||||||
err = db.Save(user).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Role string `json:"role"`
|
|
||||||
Password []byte `json:"-"`
|
|
||||||
ID int `json:"id"`
|
|
||||||
IsAdmin bool `json:"is_admin" gorm:"-:all"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Game struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
PathStorage string `json:"-"`
|
|
||||||
ID int `json:"id"`
|
|
||||||
Revision int `json:"rev"`
|
|
||||||
UserId int `json:"-"`
|
|
||||||
Available bool `json:"available"`
|
|
||||||
Hash *string `json:"hash"`
|
|
||||||
LastUpdate *time.Time `json:"last_update"`
|
|
||||||
}
|
|
||||||
55
go.mod
55
go.mod
@@ -1,29 +1,50 @@
|
|||||||
module opensavecloudserver
|
module opensavecloudserver
|
||||||
|
|
||||||
go 1.18
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/getlantern/systray v1.2.1
|
github.com/getlantern/systray v1.2.1
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/chi/v5 v5.0.8
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
github.com/lestrrat-go/jwx/v2 v2.0.9
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
golang.org/x/crypto v0.8.0
|
||||||
gorm.io/driver/mysql v1.3.3
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/gorm v1.23.5
|
gorm.io/driver/postgres v1.5.2
|
||||||
|
gorm.io/gorm v1.25.0
|
||||||
|
tawesoft.co.uk/go/dialog v0.0.0-20201103210221-4175697d086f
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect
|
||||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
|
github.com/getlantern/errors v1.0.3 // indirect
|
||||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
|
github.com/getlantern/golog v0.0.0-20230206140254-6d0a2e0f79af // indirect
|
||||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
|
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect
|
||||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
github.com/getlantern/ops v0.0.0-20220713155959-1315d978fff7 // indirect
|
||||||
github.com/go-stack/stack v1.8.0 // indirect
|
github.com/go-logr/logr v1.2.4 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-stack/stack v1.8.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.3.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.4 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
|
||||||
|
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||||
|
github.com/lestrrat-go/httprc v1.0.4 // indirect
|
||||||
|
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||||
|
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||||
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
|
golang.org/x/sys v0.7.0 // indirect
|
||||||
|
golang.org/x/text v0.9.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
186
go.sum
186
go.sum
@@ -1,51 +1,183 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
|
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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
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/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
|
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA=
|
||||||
|
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo=
|
||||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
|
github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||||
|
github.com/getlantern/errors v1.0.3 h1:Ne4Ycj7NI1BtSyAfVeAT/DNoxz7/S2BUc3L2Ht1YSHE=
|
||||||
|
github.com/getlantern/errors v1.0.3/go.mod h1:m8C7H1qmouvsGpwQqk/6NUpIVMpfzUPn608aBZDYV04=
|
||||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
||||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
|
github.com/getlantern/golog v0.0.0-20230206140254-6d0a2e0f79af h1:cvD5qCZpH/Q32Ae0i1W1lRkVuM21czEZaJpTuRiJjc4=
|
||||||
|
github.com/getlantern/golog v0.0.0-20230206140254-6d0a2e0f79af/go.mod h1:+ZU1h+iOVqWReBpky6d5Y2WL0sF2Llxu+QcxJFs2+OU=
|
||||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
|
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc h1:sue+aeVx7JF5v36H1HfvcGFImLpSD5goj8d+MitovDU=
|
||||||
|
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc/go.mod h1:D9RWpXy/EFPYxiKUURo2TB8UBosbqkiLhttRrZYtvqM=
|
||||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 h1:cSrD9ryDfTV2yaur9Qk3rHYD414j3Q1rl7+L0AylxrE=
|
||||||
|
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770/go.mod h1:GOQsoDnEHl6ZmNIL+5uVo+JWRFWozMEp18Izcb++H+A=
|
||||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||||
|
github.com/getlantern/ops v0.0.0-20220713155959-1315d978fff7 h1:Od0xvR4iK3gZwhkIbxnHw4Teusv+n5G/F9dW7x+C2f0=
|
||||||
|
github.com/getlantern/ops v0.0.0-20220713155959-1315d978fff7/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||||
github.com/getlantern/systray v1.2.1 h1:udsC2k98v2hN359VTFShuQW6GGprRprw6kD6539JikI=
|
github.com/getlantern/systray v1.2.1 h1:udsC2k98v2hN359VTFShuQW6GGprRprw6kD6539JikI=
|
||||||
github.com/getlantern/systray v1.2.1/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
github.com/getlantern/systray v1.2.1/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
||||||
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
|
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
|
||||||
|
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
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/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
|
||||||
|
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||||
|
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||||
|
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||||
|
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
|
||||||
|
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
|
||||||
|
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||||
|
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||||
|
github.com/lestrrat-go/jwx/v2 v2.0.9 h1:TRX4Q630UXxPVLvP5vGaqVJO7S+0PE6msRZUsFSBoC8=
|
||||||
|
github.com/lestrrat-go/jwx/v2 v2.0.9/go.mod h1:K68euYaR95FnL0hIQB8VvzL70vB7pSifbJUydCTPmgM=
|
||||||
|
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
|
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||||
|
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
|
||||||
|
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
|
||||||
|
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
|
||||||
|
go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo=
|
||||||
|
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
|
||||||
|
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||||
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||||
|
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||||
|
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
|
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||||
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.3.3 h1:jXG9ANrwBc4+bMvBcSl8zCfPBaVoPyBEBshA8dA93X8=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gorm.io/driver/mysql v1.3.3/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
|
||||||
gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM=
|
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
|
||||||
gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
|
||||||
|
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
|
tawesoft.co.uk/go/dialog v0.0.0-20201103210221-4175697d086f h1:9LAzHynM3Jq3FZxP6rhoK3l96fSN+S47KABTOzE6gf4=
|
||||||
|
tawesoft.co.uk/go/dialog v0.0.0-20201103210221-4175697d086f/go.mod h1:h1pWmQQT/jWrxa6VT3M7lFUif/2F0+WVISAi447Punc=
|
||||||
|
|||||||
73
main.go
73
main.go
@@ -1,17 +1,82 @@
|
|||||||
//go:build !windows
|
//go:build !windows
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"opensavecloudserver/config"
|
||||||
|
"opensavecloudserver/config/security"
|
||||||
"opensavecloudserver/constant"
|
"opensavecloudserver/constant"
|
||||||
|
"opensavecloudserver/data/datasource/pgsql"
|
||||||
|
"opensavecloudserver/data/datasource/pgsql/models/game"
|
||||||
|
"opensavecloudserver/data/datasource/pgsql/models/user"
|
||||||
"opensavecloudserver/server"
|
"opensavecloudserver/server"
|
||||||
|
"opensavecloudserver/server/authentication/impl"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Printf("Open Save Cloud (Server) %s (%s %s)\n", constant.Version, runtime.GOOS, runtime.GOARCH)
|
fmt.Printf("Open Save Cloud (Server) v%s %s/%s\n", constant.Version, runtime.GOOS, runtime.GOARCH)
|
||||||
InitCommon()
|
|
||||||
server.Serve()
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
_, err := fmt.Fprintln(os.Stderr, "the server has encountered an error and must stop: ", err)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := initLogger(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := flag.String("config", "./config.yml", "Set the configuration file path")
|
||||||
|
generateRSAKey := flag.Bool("new-rsa-key", false, "Generate a new RSA key. This key will be written to the rsa_key path set in the configuration file")
|
||||||
|
flag.Parse()
|
||||||
|
appConfiguration, err := config.Load(*path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pk *rsa.PrivateKey
|
||||||
|
if *generateRSAKey {
|
||||||
|
pk, err = security.GenerateNewRSAKey(appConfiguration.Path.RSAKey, 2048)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pk, err = security.LoadPrivateKey(appConfiguration.Path.RSAKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dao := pgsql.NewDatabaseDatasource()
|
||||||
|
err = dao.Connect(appConfiguration.Database.URL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("failed to connect to the datasource: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userRepo := user.NewUserDatasource(dao)
|
||||||
|
jwtAuthenticator, err := impl.NewJWTAuthenticator(pk, userRepo)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
deps := server.DatasourceDependencies{
|
||||||
|
UserRepository: userRepo,
|
||||||
|
GameRepository: game.NewGameDatasource(dao),
|
||||||
|
Authenticator: jwtAuthenticator,
|
||||||
|
}
|
||||||
|
|
||||||
|
appServer := server.NewServer(appConfiguration, deps)
|
||||||
|
log.Println("the server is up and ready to listen on", appServer.Server.Addr)
|
||||||
|
err = appServer.Server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"github.com/getlantern/systray"
|
"github.com/getlantern/systray"
|
||||||
"opensavecloudserver/constant"
|
"log"
|
||||||
"opensavecloudserver/server"
|
"net/http"
|
||||||
"os"
|
|
||||||
|
"tawesoft.co.uk/go/dialog"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go-winres make
|
//go:generate go-winres make
|
||||||
@@ -16,32 +17,50 @@ import (
|
|||||||
var icon []byte
|
var icon []byte
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
go func() {
|
path := flag.String("config", "./config.yml", "Set the configuration file path")
|
||||||
InitCommon()
|
flag.Parse()
|
||||||
server.Serve()
|
appConfiguration, err := config.Load(*path)
|
||||||
}()
|
if err != nil {
|
||||||
systray.Run(onReady, onExit)
|
dialog.Alert("An error occured while starting the server: " + err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func onReady() {
|
if err := initLogger(); err != nil {
|
||||||
|
dialog.Alert("An error occured while starting the server: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appServer := server.NewServer(appConfiguration)
|
||||||
|
go func(s *http.Server) {
|
||||||
|
err := s.ListenAndServe()
|
||||||
|
defer systray.Quit()
|
||||||
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}(appServer.Server)
|
||||||
|
startWindowsApp(appServer.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startWindowsApp(appServer *http.Server) {
|
||||||
|
onReady := func() {
|
||||||
systray.SetIcon(icon)
|
systray.SetIcon(icon)
|
||||||
systray.SetTitle("Open Save Cloud Server")
|
systray.SetTitle("Open Save Cloud Server")
|
||||||
systray.SetTooltip("Open Save Cloud Server")
|
systray.SetTooltip("The server is up and ready")
|
||||||
systray.AddMenuItem("Open Save Cloud", "").Disable()
|
systray.AddMenuItem("Open Save Cloud "+constant.Version, "").Disable()
|
||||||
systray.AddMenuItem(constant.Version, "").Disable()
|
systray.AddMenuItem("Running on "+appServer.Addr, "").Disable()
|
||||||
systray.AddSeparator()
|
systray.AddSeparator()
|
||||||
mQuit := systray.AddMenuItem("Quit", "Quit the server")
|
mQuit := systray.AddMenuItem("Shutdown", "Quit the server")
|
||||||
|
for {
|
||||||
select {
|
select {
|
||||||
case <-mQuit.ClickedCh:
|
case <-mQuit.ClickedCh:
|
||||||
quit()
|
func(s *http.Server) {
|
||||||
|
mQuit.Disable()
|
||||||
|
systray.SetTooltip("Shutting down the server...")
|
||||||
|
s.Shutdown()
|
||||||
|
}(appServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func quit() {
|
|
||||||
systray.Quit()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
onExit := func() {}
|
||||||
func onExit() {
|
systray.Run(onReady, onExit)
|
||||||
systray.Quit()
|
|
||||||
}
|
}
|
||||||
|
|||||||
215
server/admin.go
215
server/admin.go
@@ -1,220 +1,33 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"opensavecloudserver/admin"
|
|
||||||
"opensavecloudserver/authentication"
|
|
||||||
"opensavecloudserver/database"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UpdateUsername struct {
|
func (s *HTTPServer) createUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Id int `json:"id"`
|
// TODO
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddUser(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) deleteUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := io.ReadAll(r.Body)
|
// TODO
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userInfo := new(authentication.Registration)
|
|
||||||
err = json.Unmarshal(body, userInfo)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = authentication.Register(userInfo)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := database.UserByUsername(userInfo.Username)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(user, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveUser(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) listAllServerUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
queryId := chi.URLParam(r, "id")
|
// TODO
|
||||||
id, err := strconv.Atoi(queryId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := database.UserById(id)
|
|
||||||
if err != nil {
|
|
||||||
notFound(err.Error(), w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = admin.RemoveUser(user)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(user, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AllUsers(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) userHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
users, err := database.AllUsers()
|
// TODO
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(users, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func User(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) updateUserRoleHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
queryId := chi.URLParam(r, "id")
|
// TODO
|
||||||
id, err := strconv.Atoi(queryId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := database.UserById(id)
|
|
||||||
if err != nil {
|
|
||||||
notFound(err.Error(), w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(user, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetAdmin(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) updateUserPasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
queryId := chi.URLParam(r, "id")
|
// TODO
|
||||||
id, err := strconv.Atoi(queryId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := database.UserById(id)
|
|
||||||
if err != nil {
|
|
||||||
notFound(err.Error(), w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = admin.SetAdmin(user)
|
|
||||||
if err != nil {
|
|
||||||
notFound(err.Error(), w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(user, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetNotAdmin(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) updateUsernameHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
queryId := chi.URLParam(r, "id")
|
// TODO
|
||||||
id, err := strconv.Atoi(queryId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := database.UserById(id)
|
|
||||||
if err != nil {
|
|
||||||
notFound(err.Error(), w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = admin.RemoveAdminRole(user)
|
|
||||||
if err != nil {
|
|
||||||
notFound(err.Error(), w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(user, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ChangeUserPassword(w http.ResponseWriter, r *http.Request) {
|
|
||||||
queryId := chi.URLParam(r, "id")
|
|
||||||
userId, err := strconv.Atoi(queryId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
body, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
newPassword := new(NewPassword)
|
|
||||||
err = json.Unmarshal(body, newPassword)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if newPassword.Password != newPassword.VerifyPassword {
|
|
||||||
badRequest("password are not the same", w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = database.ChangePassword(userId, []byte(newPassword.Password))
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
payload := &successMessage{
|
|
||||||
Message: "Password changed",
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
Status: 200,
|
|
||||||
}
|
|
||||||
ok(payload, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ChangeUsername(w http.ResponseWriter, r *http.Request) {
|
|
||||||
body, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
newUserInfo := new(UpdateUsername)
|
|
||||||
err = json.Unmarshal(body, newUserInfo)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(newUserInfo.Username) < 3 {
|
|
||||||
badRequest("username need at least 3 characters", w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = database.UserByUsername(newUserInfo.Username)
|
|
||||||
if err == nil {
|
|
||||||
badRequest("username already exist", w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = database.ChangeUsername(newUserInfo.Id, newUserInfo.Username)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
payload := &successMessage{
|
|
||||||
Message: "Username changed",
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
Status: 200,
|
|
||||||
}
|
|
||||||
ok(payload, w, r)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,94 +3,88 @@ package server
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"opensavecloudserver/authentication"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Credential struct {
|
type (
|
||||||
|
userLogin struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenValidation struct {
|
userRegistration struct {
|
||||||
Valid bool `json:"valid"`
|
UserUsername string `json:"username"`
|
||||||
|
UserPassword string `json:"password"`
|
||||||
|
UserDisplayName string `json:"displayName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Login(w http.ResponseWriter, r *http.Request) {
|
userPresenter struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtPresenter struct {
|
||||||
|
Token []byte `json:"token"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ur userRegistration) Username() string {
|
||||||
|
return ur.UserUsername
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ur userRegistration) Password() string {
|
||||||
|
return ur.UserPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ur userRegistration) DisplayName() string {
|
||||||
|
return ur.UserDisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) loginUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalServerError(w, r)
|
panic(err)
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
credential := new(Credential)
|
|
||||||
err = json.Unmarshal(body, credential)
|
var ul userLogin
|
||||||
|
err = json.Unmarshal(body, &ul)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalServerError(w, r)
|
panic(err)
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
token, err := authentication.Connect(credential.Username, credential.Password)
|
|
||||||
|
jwt, err := s.deps.Authenticator.Authenticate(ul.Username, ul.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unauthorized(w, r)
|
unauthorized(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ok(token, w, r)
|
|
||||||
|
payload := jwtPresenter{
|
||||||
|
Token: jwt,
|
||||||
|
}
|
||||||
|
ok(payload, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Register(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) registerUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalServerError(w, r)
|
panic(err)
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
registration := new(authentication.Registration)
|
|
||||||
err = json.Unmarshal(body, registration)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = authentication.Register(registration)
|
|
||||||
if err != nil {
|
|
||||||
badRequest(err.Error(), w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
payload := successMessage{
|
|
||||||
Message: "You are now registered",
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
Status: 200,
|
|
||||||
}
|
|
||||||
ok(payload, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckToken(w http.ResponseWriter, r *http.Request) {
|
var ur userRegistration
|
||||||
body, err := io.ReadAll(r.Body)
|
err = json.Unmarshal(body, &ur)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalServerError(w, r)
|
panic(err)
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
credential := new(authentication.AccessToken)
|
|
||||||
err = json.Unmarshal(body, credential)
|
u, err := s.deps.UserRepository.CreateUser(ur)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalServerError(w, r)
|
panic(err)
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
_, err = authentication.ParseToken(credential.Token)
|
payload := userPresenter{
|
||||||
if err != nil {
|
ID: u.ID().String(),
|
||||||
payload := TokenValidation{
|
Username: u.Username(),
|
||||||
Valid: false,
|
DisplayName: u.DisplayName(),
|
||||||
}
|
}
|
||||||
ok(payload, w, r)
|
ok(payload, w)
|
||||||
return
|
|
||||||
}
|
|
||||||
payload := TokenValidation{
|
|
||||||
Valid: true,
|
|
||||||
}
|
|
||||||
ok(payload, w, r)
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
server/authentication/authentication.go
Normal file
25
server/authentication/authentication.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package authentication
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"opensavecloudserver/data/repository/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Authenticator interface {
|
||||||
|
Authenticate(username, password string) ([]byte, error)
|
||||||
|
Validate(token string) (Session, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
Session interface {
|
||||||
|
UserID() user.ID
|
||||||
|
Scopes() []Scope
|
||||||
|
Roles() []user.Role
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope string
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadPassword = errors.New("failed to verify password")
|
||||||
|
)
|
||||||
128
server/authentication/impl/impl.go
Normal file
128
server/authentication/impl/impl.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"errors"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||||
|
"opensavecloudserver/data/repository/user"
|
||||||
|
"opensavecloudserver/server/authentication"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
JWTAuthenticator struct {
|
||||||
|
userRepo user.UserRepository
|
||||||
|
key jwk.Key
|
||||||
|
pubKey jwk.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
JWTSession struct {
|
||||||
|
id user.ID
|
||||||
|
scopes []authentication.Scope
|
||||||
|
roles []user.Role
|
||||||
|
}
|
||||||
|
|
||||||
|
claimKey string
|
||||||
|
)
|
||||||
|
|
||||||
|
func (J JWTSession) UserID() user.ID {
|
||||||
|
return J.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (J JWTSession) Scopes() []authentication.Scope {
|
||||||
|
return J.scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (J JWTSession) Roles() []user.Role {
|
||||||
|
return J.roles
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
userScopesClaimKey claimKey = "user.scopes"
|
||||||
|
userRolesClaimKey claimKey = "user.roles"
|
||||||
|
|
||||||
|
issuerName string = "oscs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrScopesNotFound = errors.New("scopes not found in JWT")
|
||||||
|
ErrRolesNotFound = errors.New("roles not found in JWT")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (J *JWTAuthenticator) Authenticate(username, password string) ([]byte, error) {
|
||||||
|
u, err := J.userRepo.UserByUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if u.CheckPassword(password) {
|
||||||
|
// Build a JWT!
|
||||||
|
tok, err := jwt.NewBuilder().
|
||||||
|
Issuer(issuerName).
|
||||||
|
IssuedAt(time.Now()).
|
||||||
|
Subject(u.ID().String()).
|
||||||
|
Claim(string(userScopesClaimKey), "").
|
||||||
|
Claim(string(userRolesClaimKey), u.Roles()).
|
||||||
|
Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign a JWT!
|
||||||
|
return jwt.Sign(tok, jwt.WithKey(jwa.RS256, J.key))
|
||||||
|
}
|
||||||
|
return nil, authentication.ErrBadPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
func (J *JWTAuthenticator) Validate(token string) (authentication.Session, error) {
|
||||||
|
verifiedToken, err := jwt.Parse([]byte(token), jwt.WithKey(jwa.RS256, J.pubKey), jwt.WithValidate(true), jwt.WithIssuer(issuerName))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
id, err := uuid.Parse(verifiedToken.Subject())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
value, ok := verifiedToken.Get(string(userScopesClaimKey))
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrScopesNotFound
|
||||||
|
}
|
||||||
|
scopes, ok := value.([]authentication.Scope)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrScopesNotFound
|
||||||
|
}
|
||||||
|
value, ok = verifiedToken.Get(string(userRolesClaimKey))
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrRolesNotFound
|
||||||
|
}
|
||||||
|
roles, ok := value.([]user.Role)
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrRolesNotFound
|
||||||
|
}
|
||||||
|
return JWTSession{
|
||||||
|
id: user.ID(id),
|
||||||
|
scopes: scopes,
|
||||||
|
roles: roles,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJWTAuthenticator(key *rsa.PrivateKey, userRepo user.UserRepository) (authentication.Authenticator, error) {
|
||||||
|
a := new(JWTAuthenticator)
|
||||||
|
a.userRepo = userRepo
|
||||||
|
// Parse, serialize, slice and dice JWKs!
|
||||||
|
privkey, err := jwk.FromRaw(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey, err := jwk.PublicKeyOf(privkey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.key = privkey
|
||||||
|
a.pubKey = pubkey
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
348
server/data.go
348
server/data.go
@@ -1,348 +1,42 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"opensavecloudserver/config"
|
|
||||||
"opensavecloudserver/database"
|
|
||||||
"opensavecloudserver/upload"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type NewGameInfo struct {
|
// createSaveEntryHandler create a game entry to the database
|
||||||
Name string `json:"name"`
|
func (s *HTTPServer) createSaveEntryHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
type UploadGameInfo struct {
|
// saveInformationHandler get the game save information from the database
|
||||||
GameId int `json:"game_id"`
|
func (s *HTTPServer) saveInformationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
type LockError struct {
|
// allUserSavesInformationHandler all game saves information for a user
|
||||||
Message string `json:"message"`
|
func (s *HTTPServer) allUserSavesInformationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewPassword struct {
|
// uploadDataHandler upload the game save archive to the storage folder
|
||||||
Password string `json:"password"`
|
func (s *HTTPServer) uploadDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
VerifyPassword string `json:"verify_password"`
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateGame create a game entry to the database
|
// downloadDataHandler send the game save archive to the client
|
||||||
func CreateGame(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) downloadDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
userId, err := userIdFromContext(r.Context())
|
// TODO
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
body, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gameInfo := new(NewGameInfo)
|
|
||||||
err = json.Unmarshal(body, gameInfo)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
game, err := database.CreateGame(userId, gameInfo.Name)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(game, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GameInfoByID get the game save information from the database
|
func (s *HTTPServer) currentUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func GameInfoByID(w http.ResponseWriter, r *http.Request) {
|
// TODO
|
||||||
userId, err := userIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
queryId := chi.URLParam(r, "id")
|
|
||||||
id, err := strconv.Atoi(queryId)
|
|
||||||
if err != nil {
|
|
||||||
badRequest("Game ID missing or not an int", w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
game, err := database.GameInfoById(userId, id)
|
|
||||||
if err != nil {
|
|
||||||
notFound(err.Error(), w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(game, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllGamesInformation all game saves information for a user
|
func (s *HTTPServer) updateCurrentUserPasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func AllGamesInformation(w http.ResponseWriter, r *http.Request) {
|
// TODO
|
||||||
userId, err := userIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
games, err := database.GameInfosByUserId(userId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(games, w, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AskForUpload check if the game save is not lock, then lock it and generate a token
|
func (s *HTTPServer) deleteSave(w http.ResponseWriter, r *http.Request) {
|
||||||
func AskForUpload(w http.ResponseWriter, r *http.Request) {
|
// TODO
|
||||||
userId, err := userIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
body, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gameInfo := new(UploadGameInfo)
|
|
||||||
err = json.Unmarshal(body, gameInfo)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
token, err := upload.AskForUpload(userId, gameInfo.GameId)
|
|
||||||
if err != nil {
|
|
||||||
ok(LockError{Message: err.Error()}, w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(token, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UploadSave upload the game save archive to the storage folder
|
|
||||||
func UploadSave(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userId, err := userIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gameId, err := gameIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer upload.UnlockGame(gameId)
|
|
||||||
hash := r.Header.Get("X-Game-Save-Hash")
|
|
||||||
if utf8.RuneCountInString(hash) == 0 {
|
|
||||||
badRequest("The header X-Game-Save-Hash is missing", w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
archiveHash := strings.ToLower(r.Header.Get("X-Hash"))
|
|
||||||
if utf8.RuneCountInString(hash) == 0 {
|
|
||||||
badRequest("The header X-Hash is missing", w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
game, err := database.GameInfoById(userId, gameId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file, _, err := r.FormFile("file")
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func(file multipart.File) {
|
|
||||||
err := file.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}(file)
|
|
||||||
err = upload.UploadToCache(file, game)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = upload.ValidateAndMove(game, archiveHash)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = database.UpdateGameRevision(game, hash)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
payload := &successMessage{
|
|
||||||
Message: "Game uploaded",
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
Status: 200,
|
|
||||||
}
|
|
||||||
ok(payload, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download send the game save archive to the client
|
|
||||||
func Download(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userId, err := userIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gameId, err := gameIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer upload.UnlockGame(gameId)
|
|
||||||
game, err := database.GameInfoById(userId, gameId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
savePath := filepath.Join(config.Path().Storage, strconv.Itoa(userId), game.PathStorage)
|
|
||||||
|
|
||||||
if _, err := os.Stat(savePath); err == nil {
|
|
||||||
hash, err := upload.FileHash(savePath)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file, err := os.Open(savePath)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func(file *os.File) {
|
|
||||||
err := file.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}(file)
|
|
||||||
w.Header().Add("X-Hash", strings.ToUpper(hash))
|
|
||||||
_, err = io.Copy(w, file)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UserInformation(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userId, err := userIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := database.UserById(userId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(user, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ChangePassword(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userId, err := userIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
body, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
newPassword := new(NewPassword)
|
|
||||||
err = json.Unmarshal(body, newPassword)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if newPassword.Password != newPassword.VerifyPassword {
|
|
||||||
badRequest("password are not the same", w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = database.ChangePassword(userId, []byte(newPassword.Password))
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
payload := &successMessage{
|
|
||||||
Message: "Password changed",
|
|
||||||
Timestamp: time.Now(),
|
|
||||||
Status: 200,
|
|
||||||
}
|
|
||||||
ok(payload, w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveGame(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userId, err := userIdFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
queryId := chi.URLParam(r, "id")
|
|
||||||
id, err := strconv.Atoi(queryId)
|
|
||||||
if err != nil {
|
|
||||||
badRequest("Game ID missing or not an int", w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
game, err := database.GameInfoById(userId, id)
|
|
||||||
if err != nil {
|
|
||||||
notFound(err.Error(), w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = upload.RemoveGame(userId, game)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = database.RemoveGame(game)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok(game, w, r)
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
server/middleware.go
Normal file
36
server/middleware.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// authMiddleware check the authentication token before accessing to the resource
|
||||||
|
func (s *HTTPServer) authMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// adminMiddleware check the role of the user before accessing to the resource
|
||||||
|
func (s *HTTPServer) adminMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// uploadMiddleware check the upload key before allowing to upload a file
|
||||||
|
func (s *HTTPServer) uploadMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func recoverMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -15,15 +15,9 @@ type httpError struct {
|
|||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type successMessage struct {
|
|
||||||
Status int `json:"status"`
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func internalServerError(w http.ResponseWriter, r *http.Request) {
|
func internalServerError(w http.ResponseWriter, r *http.Request) {
|
||||||
e := httpError{
|
e := httpError{
|
||||||
Status: 500,
|
Status: http.StatusInternalServerError,
|
||||||
Error: "Internal Server Error",
|
Error: "Internal Server Error",
|
||||||
Message: "The server encountered an unexpected condition that prevented it from fulfilling the request.",
|
Message: "The server encountered an unexpected condition that prevented it from fulfilling the request.",
|
||||||
Path: r.RequestURI,
|
Path: r.RequestURI,
|
||||||
@@ -35,7 +29,7 @@ func internalServerError(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
_, err = w.Write(payload)
|
_, err = w.Write(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@@ -44,7 +38,7 @@ func internalServerError(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func notFound(message string, w http.ResponseWriter, r *http.Request) {
|
func notFound(message string, w http.ResponseWriter, r *http.Request) {
|
||||||
e := httpError{
|
e := httpError{
|
||||||
Status: 404,
|
Status: http.StatusNotFound,
|
||||||
Error: "Not Found",
|
Error: "Not Found",
|
||||||
Message: message,
|
Message: message,
|
||||||
Path: r.RequestURI,
|
Path: r.RequestURI,
|
||||||
@@ -56,7 +50,28 @@ func notFound(message string, w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.WriteHeader(404)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
_, err = w.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
||||||
|
e := httpError{
|
||||||
|
Status: http.StatusMethodNotAllowed,
|
||||||
|
Error: "Method Not Allowed",
|
||||||
|
Message: "The server knows the request method, but the target resource doesn't support this method",
|
||||||
|
Path: r.RequestURI,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(e)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
_, err = w.Write(payload)
|
_, err = w.Write(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@@ -65,7 +80,7 @@ func notFound(message string, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func unauthorized(w http.ResponseWriter, r *http.Request) {
|
func unauthorized(w http.ResponseWriter, r *http.Request) {
|
||||||
e := httpError{
|
e := httpError{
|
||||||
Status: 401,
|
Status: http.StatusUnauthorized,
|
||||||
Error: "Unauthorized",
|
Error: "Unauthorized",
|
||||||
Message: "The request has not been completed because it lacks valid authentication credentials for the requested resource.",
|
Message: "The request has not been completed because it lacks valid authentication credentials for the requested resource.",
|
||||||
Path: r.RequestURI,
|
Path: r.RequestURI,
|
||||||
@@ -77,8 +92,8 @@ func unauthorized(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.Header().Add("WWW-Authenticate", "Custom realm=\"Login via /api/login\"")
|
w.Header().Add("WWW-Authenticate", "Custom realm=\"loginUserHandler via /api/login\"")
|
||||||
w.WriteHeader(401)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
_, err = w.Write(payload)
|
_, err = w.Write(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@@ -87,8 +102,8 @@ func unauthorized(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func forbidden(w http.ResponseWriter, r *http.Request) {
|
func forbidden(w http.ResponseWriter, r *http.Request) {
|
||||||
e := httpError{
|
e := httpError{
|
||||||
Status: 403,
|
Status: http.StatusForbidden,
|
||||||
Error: "Unauthorized",
|
Error: "Forbidden",
|
||||||
Message: "The access is permanently forbidden and tied to the application logic, such as insufficient rights to a resource.",
|
Message: "The access is permanently forbidden and tied to the application logic, such as insufficient rights to a resource.",
|
||||||
Path: r.RequestURI,
|
Path: r.RequestURI,
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
@@ -99,14 +114,14 @@ func forbidden(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.WriteHeader(403)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
_, err = w.Write(payload)
|
_, err = w.Write(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ok(obj interface{}, w http.ResponseWriter, _ *http.Request) {
|
func ok(obj interface{}, w http.ResponseWriter) {
|
||||||
payload, err := json.Marshal(obj)
|
payload, err := json.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@@ -120,7 +135,7 @@ func ok(obj interface{}, w http.ResponseWriter, _ *http.Request) {
|
|||||||
|
|
||||||
func badRequest(message string, w http.ResponseWriter, r *http.Request) {
|
func badRequest(message string, w http.ResponseWriter, r *http.Request) {
|
||||||
e := httpError{
|
e := httpError{
|
||||||
Status: 400,
|
Status: http.StatusBadRequest,
|
||||||
Error: "Bad Request",
|
Error: "Bad Request",
|
||||||
Message: message,
|
Message: message,
|
||||||
Path: r.RequestURI,
|
Path: r.RequestURI,
|
||||||
@@ -132,7 +147,7 @@ func badRequest(message string, w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.WriteHeader(400)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
_, err = w.Write(payload)
|
_, err = w.Write(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
|||||||
234
server/server.go
234
server/server.go
@@ -1,166 +1,138 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"opensavecloudserver/authentication"
|
|
||||||
"opensavecloudserver/config"
|
"opensavecloudserver/config"
|
||||||
"opensavecloudserver/database"
|
"opensavecloudserver/data/repository/game"
|
||||||
"opensavecloudserver/upload"
|
"opensavecloudserver/data/repository/user"
|
||||||
|
"opensavecloudserver/server/authentication"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContextKey string
|
type (
|
||||||
|
HTTPServer struct {
|
||||||
|
Server *http.Server
|
||||||
|
config config.Configuration
|
||||||
|
deps DatasourceDependencies
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
DatasourceDependencies struct {
|
||||||
UserIdKey ContextKey = "userId"
|
UserRepository user.UserRepository
|
||||||
GameIdKey ContextKey = "gameId"
|
GameRepository game.GameRepository
|
||||||
|
Authenticator authentication.Authenticator
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Serve start the http server
|
// NewServer start the http server
|
||||||
func Serve() {
|
func NewServer(config config.Configuration, deps DatasourceDependencies) *HTTPServer {
|
||||||
|
s := &HTTPServer{config: config, deps: deps}
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
|
router.NotFound(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
notFound("This route does not exist", writer, request)
|
||||||
|
})
|
||||||
|
router.MethodNotAllowed(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
methodNotAllowed(writer, request)
|
||||||
|
})
|
||||||
router.Use(middleware.Logger)
|
router.Use(middleware.Logger)
|
||||||
router.Use(recovery)
|
router.Use(recoverMiddleware)
|
||||||
router.Route("/api", func(rApi chi.Router) {
|
if config.Server.Compress {
|
||||||
rApi.Route("/v1", func(r chi.Router) {
|
router.Use(middleware.Compress(5, "application/json", "application/octet‑stream"))
|
||||||
r.Post("/login", Login)
|
|
||||||
r.Post("/check/token", CheckToken)
|
|
||||||
if config.Features().AllowRegister {
|
|
||||||
r.Post("/register", Register)
|
|
||||||
}
|
}
|
||||||
r.Route("/system", func(systemRouter chi.Router) {
|
if config.Server.PingEndPoint {
|
||||||
systemRouter.Get("/information", Information)
|
router.Use(middleware.Heartbeat("/heartbeat"))
|
||||||
})
|
}
|
||||||
r.Route("/admin", func(adminRouter chi.Router) {
|
router.Route("/api", func(routerAPI chi.Router) {
|
||||||
adminRouter.Use(adminMiddleware)
|
routerAPI.Route("/v1", func(r chi.Router) {
|
||||||
adminRouter.Post("/user", AddUser)
|
// Unsupported V1 because it was shitty, sorry about that
|
||||||
adminRouter.Post("/user/username", ChangeUsername)
|
r.HandleFunc("/*", unsupportedAPIHandler)
|
||||||
adminRouter.Post("/user/passwd/{id}", ChangeUserPassword)
|
|
||||||
adminRouter.Delete("/user/{id}", RemoveUser)
|
|
||||||
adminRouter.Get("/user/{id}", User)
|
|
||||||
adminRouter.Get("/users", AllUsers)
|
|
||||||
adminRouter.Get("/user/role/admin/{id}", SetAdmin)
|
|
||||||
adminRouter.Get("/user/role/user/{id}", SetNotAdmin)
|
|
||||||
})
|
})
|
||||||
|
routerAPI.Route("/v2", func(r chi.Router) {
|
||||||
|
// Get information about the server
|
||||||
|
r.Get("/version", s.Information)
|
||||||
|
// Authentication routes
|
||||||
|
// Login and get a token
|
||||||
|
r.Post("/login", s.loginUserHandler)
|
||||||
|
if config.Features.AllowRegister {
|
||||||
|
// Register a user
|
||||||
|
r.Post("/register", s.registerUserHandler)
|
||||||
|
}
|
||||||
|
// Secured routes
|
||||||
r.Group(func(secureRouter chi.Router) {
|
r.Group(func(secureRouter chi.Router) {
|
||||||
secureRouter.Use(authMiddleware)
|
secureRouter.Use(s.authMiddleware)
|
||||||
|
// Logged user routes
|
||||||
secureRouter.Route("/user", func(userRouter chi.Router) {
|
secureRouter.Route("/user", func(userRouter chi.Router) {
|
||||||
userRouter.Get("/information", UserInformation)
|
// Get information about the logged user
|
||||||
userRouter.Post("/passwd", ChangePassword)
|
userRouter.Get("/", s.currentUserHandler)
|
||||||
|
// Change the password of the current user
|
||||||
|
userRouter.Put("/password/update", s.updateCurrentUserPasswordHandler)
|
||||||
})
|
})
|
||||||
secureRouter.Route("/game", func(gameRouter chi.Router) {
|
// Save files routes
|
||||||
gameRouter.Post("/create", CreateGame)
|
secureRouter.Route("/saves", func(gameRouter chi.Router) {
|
||||||
gameRouter.Get("/all", AllGamesInformation)
|
// Create a save entry
|
||||||
gameRouter.Delete("/remove/{id}", RemoveGame)
|
gameRouter.Post("/create", s.createSaveEntryHandler)
|
||||||
gameRouter.Get("/info/{id}", GameInfoByID)
|
// List all available saves
|
||||||
gameRouter.Post("/upload/init", AskForUpload)
|
gameRouter.Get("/", s.allUserSavesInformationHandler)
|
||||||
|
// Remove a save
|
||||||
|
gameRouter.Delete("/{id}", s.deleteSave)
|
||||||
|
// Get the information about a save
|
||||||
|
gameRouter.Get("/{id}", s.saveInformationHandler)
|
||||||
|
// Data routes
|
||||||
gameRouter.Group(func(uploadRouter chi.Router) {
|
gameRouter.Group(func(uploadRouter chi.Router) {
|
||||||
uploadRouter.Use(uploadMiddleware)
|
uploadRouter.Use(s.uploadMiddleware)
|
||||||
uploadRouter.Post("/upload", UploadSave)
|
// Upload data
|
||||||
uploadRouter.Get("/download", Download)
|
uploadRouter.Put("/{id}/data", s.uploadDataHandler)
|
||||||
|
// downloadDataHandler data
|
||||||
|
uploadRouter.Get("/{id}/data", s.downloadDataHandler)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
secureRouter.Route("/admin", func(adminRouter chi.Router) {
|
||||||
|
adminRouter.Use(s.adminMiddleware)
|
||||||
|
// Create a user
|
||||||
|
adminRouter.Post("/user/create", s.createUserHandler)
|
||||||
|
// Update the username of a user
|
||||||
|
adminRouter.Post("/user/{id}/username", s.updateUsernameHandler)
|
||||||
|
// Update the password of a user
|
||||||
|
adminRouter.Post("/user/{id}/password/update", s.updateUserPasswordHandler)
|
||||||
|
// Remove a user
|
||||||
|
adminRouter.Delete("/user/{id}", s.deleteUserHandler)
|
||||||
|
// Get information about a user
|
||||||
|
adminRouter.Get("/user/{id}", s.userHandler)
|
||||||
|
// List all user registered on the server
|
||||||
|
adminRouter.Get("/users", s.listAllServerUsersHandler)
|
||||||
|
// Update role
|
||||||
|
adminRouter.Put("/user/{id}/role", s.updateUserRoleHandler)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
s.Server = &http.Server{
|
||||||
log.Println("Server is listening...")
|
Addr: fmt.Sprintf(":%d", config.Server.Port),
|
||||||
err := http.ListenAndServe(fmt.Sprintf(":%d", config.Server().Port), router)
|
Handler: router,
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// authMiddleware check the authentication token before accessing to the resource
|
func unsupportedAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func authMiddleware(next http.Handler) http.Handler {
|
e := httpError{
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
Status: http.StatusGone,
|
||||||
header := r.Header.Get("Authorization")
|
Error: "API Not supported anymore",
|
||||||
if len(header) > 7 {
|
Message: "This version of the server does not support the V1 version of the API.",
|
||||||
userId, err := authentication.ParseToken(header[7:])
|
Path: r.RequestURI,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
payload, err := json.Marshal(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unauthorized(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx := context.WithValue(r.Context(), UserIdKey, userId)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
unauthorized(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// adminMiddleware check the role of the user before accessing to the resource
|
|
||||||
func adminMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
header := r.Header.Get("Authorization")
|
|
||||||
if len(header) > 7 {
|
|
||||||
userId, err := authentication.ParseToken(header[7:])
|
|
||||||
if err != nil {
|
|
||||||
unauthorized(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := database.UserById(userId)
|
|
||||||
if err != nil {
|
|
||||||
internalServerError(w, r)
|
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if !user.IsAdmin {
|
w.Header().Add("Content-Type", "application/json")
|
||||||
forbidden(w, r)
|
w.WriteHeader(http.StatusGone)
|
||||||
return
|
_, err = w.Write(payload)
|
||||||
}
|
|
||||||
ctx := context.WithValue(r.Context(), UserIdKey, userId)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
unauthorized(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// uploadMiddleware check the upload key before allowing to upload a file
|
|
||||||
func uploadMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
header := r.Header.Get("X-Upload-Key")
|
|
||||||
if len(header) > 0 {
|
|
||||||
if gameId, ok := upload.CheckUploadToken(header); ok {
|
|
||||||
ctx := context.WithValue(r.Context(), GameIdKey, gameId)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unauthorized(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func userIdFromContext(ctx context.Context) (int, error) {
|
|
||||||
if userId, ok := ctx.Value(UserIdKey).(int); ok {
|
|
||||||
return userId, nil
|
|
||||||
}
|
|
||||||
return 0, errors.New("userId not found in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
func gameIdFromContext(ctx context.Context) (int, error) {
|
|
||||||
if gameId, ok := ctx.Value(GameIdKey).(int); ok {
|
|
||||||
return gameId, nil
|
|
||||||
}
|
|
||||||
return 0, errors.New("gameId not found in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
func recovery(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer func() {
|
|
||||||
err := recover()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalServerError(w, r)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"opensavecloudserver/config"
|
|
||||||
"opensavecloudserver/constant"
|
"opensavecloudserver/constant"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
@@ -10,20 +9,20 @@ import (
|
|||||||
type information struct {
|
type information struct {
|
||||||
AllowRegister bool `json:"allow_register"`
|
AllowRegister bool `json:"allow_register"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
ApiVersion int `json:"api_version"`
|
APIVersion int `json:"api_version"`
|
||||||
GoVersion string `json:"go_version"`
|
GoVersion string `json:"go_version"`
|
||||||
OsName string `json:"os_name"`
|
OSName string `json:"os_name"`
|
||||||
OsArchitecture string `json:"os_architecture"`
|
OSArchitecture string `json:"os_architecture"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Information(w http.ResponseWriter, r *http.Request) {
|
func (s *HTTPServer) Information(w http.ResponseWriter, r *http.Request) {
|
||||||
info := information{
|
info := information{
|
||||||
AllowRegister: config.Features().AllowRegister,
|
AllowRegister: s.config.Features.AllowRegister,
|
||||||
Version: constant.Version,
|
Version: constant.Version,
|
||||||
ApiVersion: constant.ApiVersion,
|
APIVersion: constant.ApiVersion,
|
||||||
GoVersion: runtime.Version(),
|
GoVersion: runtime.Version(),
|
||||||
OsName: runtime.GOOS,
|
OSName: runtime.GOOS,
|
||||||
OsArchitecture: runtime.GOARCH,
|
OSArchitecture: runtime.GOARCH,
|
||||||
}
|
}
|
||||||
ok(info, w, r)
|
ok(info, w)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"opensavecloudserver/config"
|
"opensavecloudserver/config"
|
||||||
|
database2 "opensavecloudserver/data/internal/database"
|
||||||
"opensavecloudserver/database"
|
"opensavecloudserver/database"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -108,7 +109,7 @@ func CheckUploadToken(uploadToken string) (int, bool) {
|
|||||||
return -1, false
|
return -1, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func UploadToCache(file multipart.File, game *database.Game) error {
|
func UploadToCache(file multipart.File, game *database2.Game) error {
|
||||||
filePath := path.Join(config.Path().Cache, strconv.Itoa(game.UserId))
|
filePath := path.Join(config.Path().Cache, strconv.Itoa(game.UserId))
|
||||||
if _, err := os.Stat(filePath); err != nil {
|
if _, err := os.Stat(filePath); err != nil {
|
||||||
err = os.Mkdir(filePath, 0766)
|
err = os.Mkdir(filePath, 0766)
|
||||||
@@ -133,7 +134,7 @@ func UploadToCache(file multipart.File, game *database.Game) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateAndMove(game *database.Game, hash string) error {
|
func ValidateAndMove(game *database2.Game, hash string) error {
|
||||||
filePath := path.Join(config.Path().Cache, strconv.Itoa(game.UserId), game.PathStorage)
|
filePath := path.Join(config.Path().Cache, strconv.Itoa(game.UserId), game.PathStorage)
|
||||||
if err := checkHash(filePath, hash); err != nil {
|
if err := checkHash(filePath, hash); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -155,7 +156,7 @@ func checkHash(path, hash string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveToStorage(cachePath string, game *database.Game) error {
|
func moveToStorage(cachePath string, game *database2.Game) error {
|
||||||
filePath := path.Join(config.Path().Storage, strconv.Itoa(game.UserId))
|
filePath := path.Join(config.Path().Storage, strconv.Itoa(game.UserId))
|
||||||
if _, err := os.Stat(filePath); err != nil {
|
if _, err := os.Stat(filePath); err != nil {
|
||||||
err = os.Mkdir(filePath, 0766)
|
err = os.Mkdir(filePath, 0766)
|
||||||
@@ -200,7 +201,7 @@ func RemoveFolders(userId int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveGame(userId int, game *database.Game) error {
|
func RemoveGame(userId int, game *database2.Game) error {
|
||||||
filePath := path.Join(config.Path().Storage, strconv.Itoa(userId), game.PathStorage)
|
filePath := path.Join(config.Path().Storage, strconv.Itoa(userId), game.PathStorage)
|
||||||
return os.Remove(filePath)
|
return os.Remove(filePath)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user