diff --git a/aid/config.go b/aid/config.go index ce9f9f5..97af250 100644 --- a/aid/config.go +++ b/aid/config.go @@ -15,6 +15,13 @@ type CS struct { Output struct { Level string } + API struct { + Host string + Port string + } + JWT struct { + Secret string + } } var ( @@ -52,4 +59,19 @@ func LoadConfig() { if Config.Output.Level != "dev" && Config.Output.Level != "prod" && Config.Output.Level != "time" && Config.Output.Level != "info" { panic("Output Level must be either dev or prod") } + + Config.API.Host = cfg.Section("api").Key("host").String() + if Config.API.Host == "" { + panic("API Host is empty") + } + + Config.API.Port = cfg.Section("api").Key("port").String() + if Config.API.Port == "" { + panic("API Port is empty") + } + + Config.JWT.Secret = cfg.Section("jwt").Key("secret").String() + if Config.JWT.Secret == "" { + panic("JWT Secret is empty") + } } \ No newline at end of file diff --git a/aid/errors.go b/aid/errors.go new file mode 100644 index 0000000..2dbe4ed --- /dev/null +++ b/aid/errors.go @@ -0,0 +1,29 @@ +package aid + +func ErrorBadRequest(errorMessage string) JSON { + return JSON{ + "errorCode": "errors.com.epicgames.bad_request", + "errorMessage": errorMessage, + "numericErrorCode": 1001, + "originatingService": "fortnite", + "intent": "prod-live", + } +} + +var ( + ErrorNotFound = JSON{ + "errorCode": "errors.com.epicgames.common.not_found", + "errorMessage": "Resource Not found", + "numericErrorCode": 1004, + "originatingService": "fortnite", + "intent": "prod-live", + } + + ErrorInternalServer = map[string]interface{}{ + "errorCode": "errors.com.epicgames.common.server_error", + "errorMessage": "Internal Server Error", + "numericErrorCode": 1000, + "originatingService": "fortnite", + "intent": "prod-live", + } +) \ No newline at end of file diff --git a/aid/fiber.go b/aid/fiber.go new file mode 100644 index 0000000..024413f --- /dev/null +++ b/aid/fiber.go @@ -0,0 +1,31 @@ +package aid + +import ( + "time" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/limiter" + "github.com/gofiber/fiber/v2/middleware/logger" +) + +func FiberLogger() fiber.Handler { + return logger.New(logger.Config{ + Format: "${status} ${path}\n", + }) +} + +func FiberLimiter() fiber.Handler { + return limiter.New(limiter.Config{ + Max: 100, + Expiration: 1 * time.Minute, + }) +} + +func FiberCors() fiber.Handler { + return func(c *fiber.Ctx) error { + c.Set("Access-Control-Allow-Origin", "*") + c.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Origin, Accept, X-Requested-With") + return c.Next() + } +} \ No newline at end of file diff --git a/aid/token.go b/aid/token.go new file mode 100644 index 0000000..9454b60 --- /dev/null +++ b/aid/token.go @@ -0,0 +1,44 @@ +package aid + +import ( + "fmt" + + "github.com/golang-jwt/jwt/v5" +) + +func JWTSign(m JSON) (string, error) { + claims := jwt.MapClaims{} + + for k, v := range m { + claims[k] = v + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString([]byte(Config.JWT.Secret)) +} + +func JWTVerify(tokenString string) (JSON, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return []byte(Config.JWT.Secret), nil + }) + + if err != nil { + return nil, err + } + + if !token.Valid { + return nil, fmt.Errorf("invalid token") + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return nil, fmt.Errorf("invalid claims") + } + + json := JSON{} + for k, v := range claims { + json[k] = v + } + + return json, nil +} \ No newline at end of file diff --git a/default.config.ini b/default.config.ini index e0d4252..0a402b2 100644 --- a/default.config.ini +++ b/default.config.ini @@ -11,4 +11,15 @@ drop=false ; info = everything ; time = only time taken ; prod = only errors -level="info" \ No newline at end of file +level="info" + +[api] +; port to listen on +port=80 +; host to listen on +; only change if you know what you're doing +host="0.0.0.0" + +[jwt] +; secret for jwt signing +secret="secret" \ No newline at end of file diff --git a/go.mod b/go.mod index 7cd0d53..a1c3a9b 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,28 @@ module github.com/ectrc/snow go 1.21.3 require ( + github.com/andybalholm/brotli v1.0.6 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/google/uuid v1.3.1 // indirect + github.com/gofiber/fiber/v2 v2.50.0 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect + github.com/google/uuid v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // 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.4.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/philhofer/fwd v1.1.2 // indirect github.com/r3labs/diff/v3 v3.0.1 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rs/zerolog v1.26.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -29,6 +36,10 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.6.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tinylib/msgp v1.1.8 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.50.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -36,7 +47,7 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 2e1a551..e046fb4 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ github.com/Vilsol/ue4pak v0.1.5 h1:XE17rYgieBh67vpB2lZI6ARv3KxMzAelWh/RDA9O7Ms= github.com/Vilsol/ue4pak v0.1.5/go.mod h1:e/4WiPI80PbsP2+IOWYoqwiZT+lKAcoEr0wmVY3ROLQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -85,8 +87,12 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw= +github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -142,6 +148,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -174,6 +182,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -190,6 +200,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -202,6 +216,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= @@ -218,6 +234,9 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg= github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -268,7 +287,15 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= +github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -282,6 +309,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -303,6 +331,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= @@ -342,6 +371,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -378,6 +409,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -398,6 +431,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -437,12 +472,20 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -451,6 +494,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -506,6 +550,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/handlers/auth.go b/handlers/auth.go new file mode 100644 index 0000000..2810a58 --- /dev/null +++ b/handlers/auth.go @@ -0,0 +1,164 @@ +package handlers + +import ( + "encoding/base64" + "math/rand" + "strings" + "time" + + "github.com/ectrc/snow/aid" + p "github.com/ectrc/snow/person" + "github.com/gofiber/fiber/v2" +) + +var ( + oatuhTokenGrantTypes = map[string]func(c *fiber.Ctx, body *OAuthTokenBody) error{ + "client_credentials": PostOAuthTokenClientCredentials, + "password": PostOAuthTokenPassword, + } +) + +type OAuthTokenBody struct { + GrantType string `form:"grant_type" binding:"required"` + Username string `form:"username"` + Password string `form:"password"` +} + +func PostOAuthToken(c *fiber.Ctx) error { + var body OAuthTokenBody + + if err := c.BodyParser(&body); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Request Body")) + } + + if action, ok := oatuhTokenGrantTypes[body.GrantType]; ok { + return action(c, &body) + } + + return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Grant Type")) +} + +func PostOAuthTokenClientCredentials(c *fiber.Ctx, body *OAuthTokenBody) error { + credentials, err := aid.JWTSign(aid.JSON{ + "snow_id": 0, // custom + "t": "s", + "am": "client_credentials", // authorization method + "ic": true, // internal client + "mver": false, // mobile version + "clsvc": "snow", // client service + "clid": c.IP(), // client id + "jti": rand.Int63(), // jwt id + "p": base64.StdEncoding.EncodeToString([]byte(c.IP())), // payload + "hours_expire": 1, + "creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"), + }) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer) + } + + return c.Status(fiber.StatusOK).JSON(aid.JSON{ + "access_token": "eg1~"+credentials, + "token_type": "bearer", + "client_id": c.IP(), + "client_service": "snow", + "internal_client": true, + "expires_in": 3600, + "expires_at": time.Now().Add(time.Hour).Format("2006-01-02T15:04:05.999Z"), + }) +} + +func PostOAuthTokenPassword(c *fiber.Ctx, body *OAuthTokenBody) error { + if body.Username == "" || body.Password == "" { + return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Username/Password is empty")) + } + + person := p.FindByDisplay(strings.Split(body.Username, "@")[0]) + if person == nil { + return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("No Account Found")) + } + + if person.AccessKey == "" { + return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Activation Required")) + } + + if person.AccessKey != body.Password { + return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Access Key")) + } + + access, err := aid.JWTSign(aid.JSON{ + "snow_id": person.ID, // custom + "iai": person.ID, // account id + "dn": person.DisplayName, // display name + "t": "s", + "am": "password", // authorization method + "ic": true, // internal client + "mver": false, // mobile version + "clsvc": "snow", // client service + "app": "com.epicgames.fortnite", // app name + "clid": c.IP(), // client id + "dvid": "default", // device id + "jti": rand.Int63(), // jwt id + "p": base64.StdEncoding.EncodeToString([]byte(c.IP())), // payload + "sec": 1, // security level + "hours_expire": 24, + "creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"), + }) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer) + } + + refresh, err := aid.JWTSign(aid.JSON{ + "snow_id": person.ID, // custom + "sub": person.ID, // account id + "clid": c.IP(), // client id + "jti": rand.Int63(), // jwt id + "t": "s", + "am": "refresh_token", // authorization method + "hours_expire": 24, + "creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"), + }) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer) + } + + return c.Status(fiber.StatusOK).JSON(aid.JSON{ + "access_token": "eg1~"+access, + "account_id": person.ID, + "client_id": c.IP(), + "client_service": "snow", + "device_id": "default", + "display_name": person.DisplayName, + "expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"), + "expires_in": 86400, + "internal_client": true, + "refresh_expires": 86400, + "refresh_expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"), + "refresh_token": "eg1~"+refresh, + "token_type": "bearer", + }) +} + +func DeleteOAuthSessions(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON(aid.JSON{}) +} + +func GetPublicAccount(c *fiber.Ctx) error { + person := p.Find(c.Params("accountId")) + if person == nil { + return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("No Account Found")) + } + + return c.Status(fiber.StatusOK).JSON(aid.JSON{ + "id": person.ID, + "displayName": person.DisplayName, + }) +} + +func GetPublicAccountExternalAuths(c *fiber.Ctx) error { + person := p.Find(c.Params("accountId")) + if person == nil { + return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("No Account Found")) + } + + return c.Status(fiber.StatusOK).JSON([]aid.JSON{}) +} \ No newline at end of file diff --git a/handlers/common.go b/handlers/common.go new file mode 100644 index 0000000..c493c50 --- /dev/null +++ b/handlers/common.go @@ -0,0 +1,36 @@ +package handlers + +import ( + "github.com/ectrc/snow/aid" + "github.com/gofiber/fiber/v2" +) + +func AnyNoContent(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusNoContent) +} + +func PostTryPlayOnPlatform(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).SendString("true") +} + +func GetEnabledFeatures(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON([]string{}) +} + +func PostGrantAccess(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).SendString("true") +} + +func GetAccountReceipts(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON([]string{}) +} + +func GetSessionFindPlayer(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON([]string{}) +} + +func GetVersionCheck(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON(aid.JSON{ + "type": "NO_UPDATE", + }) +} \ No newline at end of file diff --git a/handlers/lightswitch.go b/handlers/lightswitch.go new file mode 100644 index 0000000..5c5a5f7 --- /dev/null +++ b/handlers/lightswitch.go @@ -0,0 +1,14 @@ +package handlers + +import ( + "github.com/ectrc/snow/aid" + "github.com/gofiber/fiber/v2" +) + +func GetLightswitchBulkStatus(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON([]aid.JSON{{ + "serviceInstanceId": "fortnite", + "status": "UP", + "banned": false, + }}) +} \ No newline at end of file diff --git a/handlers/profile.go b/handlers/profile.go new file mode 100644 index 0000000..c348113 --- /dev/null +++ b/handlers/profile.go @@ -0,0 +1,57 @@ +package handlers + +import ( + "time" + + "github.com/ectrc/snow/aid" + p "github.com/ectrc/snow/person" + + "github.com/gofiber/fiber/v2" +) + +var ( + profileActions = map[string]func(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { + "QueryProfile": PostQueryProfileAction, + "ClientQuestLogin": PostQueryProfileAction, + } +) + +func PostProfileAction(c *fiber.Ctx) error { + person := p.Find(c.Params("accountId")) + if person == nil { + return c.Status(404).JSON(aid.ErrorBadRequest("No Account Found")) + } + + profile := person.GetProfileFromType(c.Query("profileId")) + if profile == nil { + return c.Status(404).JSON(aid.ErrorBadRequest("No Profile Found")) + } + + snapshot := profile.Snapshot() + if action, ok := profileActions[c.Params("action")]; ok { + err := action(c, person, profile) + if err != nil { + return err + } + } + profile.Diff(snapshot) + profile.Revision++ + + return c.Status(200).JSON(aid.JSON{ + "profileId": profile.Type, + "profileRevision": profile.Revision, + "profileCommandRevision": profile.Revision, + "profileChangesBaseRevision": profile.Revision - 1, + "profileChanges": profile.Changes, + "multiUpdate": []aid.JSON{}, + "notifications": []aid.JSON{}, + "responseVersion": 1, + "serverTime": time.Now().Format("2006-01-02T15:04:05.999Z"), + }) +} + +func PostQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { + profile.Changes = []interface{}{} + profile.CreateFullProfileUpdateChange() + return nil +} \ No newline at end of file diff --git a/handlers/storage.go b/handlers/storage.go new file mode 100644 index 0000000..6fec2bb --- /dev/null +++ b/handlers/storage.go @@ -0,0 +1,32 @@ +package handlers + +import ( + "github.com/ectrc/snow/aid" + "github.com/gofiber/fiber/v2" +) + +func GetCloudStorageFiles(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON([]aid.JSON{}) +} + +func GetCloudStorageConfig(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON(aid.JSON{ + "enumerateFilesPath": "/api/cloudstorage/system", + "enableMigration": true, + "enableWrites": true, + "epicAppName": "Live", + "isAuthenticated": true, + "disableV2": true, + "lastUpdated": "2021-01-01T00:00:00Z", + "transports": []string{}, + }) +} + +func GetCloudStorageFile(c *fiber.Ctx) error { + fileName := c.Params("fileName") + if fileName == "" { + return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest) + } + + return c.Status(fiber.StatusOK).JSON(aid.JSON{}) +} \ No newline at end of file diff --git a/main.go b/main.go index a9f6bf8..0b2b269 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,11 @@ package main import ( "github.com/ectrc/snow/aid" + "github.com/ectrc/snow/handlers" "github.com/ectrc/snow/person" "github.com/ectrc/snow/storage" + + "github.com/gofiber/fiber/v2" ) func init() { aid.LoadConfig() @@ -36,17 +39,55 @@ func init() { } func init() { + if aid.Config.Database.DropAllTables { + person.NewFortnitePerson("ac", "ket") + } + + aid.PrintTime("Loading all persons from database", func() { + for _, person := range person.AllFromDatabase() { + aid.Print("Loaded person: " + person.DisplayName) + } + }) + go storage.Cache.CacheKiller() } func main() { - aid.PrintTime("Fetching Persons", func() { - users := person.AllFromDatabase() - aid.Print("Found", len(users), "users") - for _, user := range users { - aid.Print(user.ID) - } - }) + r := fiber.New() - // aid.WaitForExit() + r.Use(aid.FiberLogger()) + r.Use(aid.FiberLimiter()) + r.Use(aid.FiberCors()) + + account := r.Group("/account/api") + account.Get("/public/account/:accountId", handlers.GetPublicAccount) + account.Get("/public/account/:accountId/externalAuths", handlers.GetPublicAccountExternalAuths) + account.Post("/oauth/token", handlers.PostOAuthToken) + account.Delete("/oauth/sessions/kill", handlers.DeleteOAuthSessions) + + fortnite := r.Group("/fortnite/api") + fortnite.Get("/receipts/v1/account/:accountId/receipts", handlers.GetAccountReceipts) + fortnite.Get("/versioncheck/*", handlers.GetVersionCheck) + + matchmaking := fortnite.Group("/matchmaking") + matchmaking.Get("/session/findPlayer/:accountId", handlers.GetSessionFindPlayer) + + storage := fortnite.Group("/cloudstorage") + storage.Get("/system", handlers.GetCloudStorageFiles) + storage.Get("/system/config", handlers.GetCloudStorageConfig) + storage.Get("/system/:fileName", handlers.GetCloudStorageFile) + + game := fortnite.Group("/game/v2") + game.Post("/tryPlayOnPlatform/account/:accountId", handlers.PostTryPlayOnPlatform) + game.Post("/grant_access/:accountId", handlers.PostGrantAccess) + game.Get("/enabled_features", handlers.GetEnabledFeatures) + + profile := game.Group("/profile/:accountId") + profile.Post("/client/:action", handlers.PostProfileAction) + + lightswitch := r.Group("/lightswitch/api") + lightswitch.Get("/service/bulk/status", handlers.GetLightswitchBulkStatus) + + r.All("*", func(c *fiber.Ctx) error { return c.Status(fiber.StatusNotFound).JSON(aid.ErrorNotFound) }) + r.Listen(aid.Config.API.Host + aid.Config.API.Port) } \ No newline at end of file diff --git a/person/fortnite.go b/person/fortnite.go new file mode 100644 index 0000000..efde58a --- /dev/null +++ b/person/fortnite.go @@ -0,0 +1,79 @@ +package person + +import "github.com/ectrc/snow/aid" + +func NewFortnitePerson(displayName string, key string) { + person := NewPerson() + person.DisplayName = displayName + person.AccessKey = key + + character := NewItem("AthenaCharacter:CID_001_Athena_Commando_F_Default", 1) + pickaxe := NewItem("AthenaPickaxe:DefaultPickaxe", 1) + glider := NewItem("AthenaGlider:DefaultGlider", 1) + default_dance := NewItem("AthenaDance:EID_DanceMoves", 1) + + person.AthenaProfile.Items.AddItem(character) + person.AthenaProfile.Items.AddItem(pickaxe) + person.AthenaProfile.Items.AddItem(glider) + person.AthenaProfile.Items.AddItem(default_dance) + person.CommonCoreProfile.Items.AddItem(NewItem("Currency:MtxPurchased", 0)) + + person.Loadout.Character = character.ID + person.Loadout.Pickaxe = pickaxe.ID + person.Loadout.Glider = glider.ID + person.Loadout.Dances[0] = default_dance.ID + + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("mfa_reward_claimed", true)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_overflow", 0)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("lifetime_wins", 0)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("party_assist_quest", "")) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("quest_manager", aid.JSON{})) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("inventory_limit_bonus", 0)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("daily_rewards", []aid.JSON{})) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("competitive_identity", aid.JSON{})) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_update", 0)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_num", 2)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("permissions", []aid.JSON{})) + + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("loadouts", []aid.JSON{})) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("last_applied_loadout", "")) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("active_loadout_index", 0)) + + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("accountLevel", 1)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("level", 1)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("xp", 0)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("xp_overflow", 0)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp", 0)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_mult", 0)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_exchange", 0)) + + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_purchased", false)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_level", 1)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_xp", 0)) + + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_character", person.Loadout.Character)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_backpack", person.Loadout.Backpack)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_pickaxe", person.Loadout.Pickaxe)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_glider", person.Loadout.Glider)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_skydivecontrail", person.Loadout.SkyDiveContrail)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_dance", person.Loadout.Dances)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_itemwraps", person.Loadout.ItemWraps)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_loadingscreen", person.Loadout.LoadingScreen)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_musicpack", person.Loadout.MusicPack)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_icon", person.Loadout.BannerIcon)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_color", person.Loadout.BannerColor)) + + person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mfa_enabled", true)) + person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mtx_affiliate", "")) + person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mtx_purchase_history", aid.JSON{ + "refundsUsed": 0, + "refundCredits": 3, + "purchases": []any{}, + })) + person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("current_mtx_platform", "EpicPC")) + person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_receive_gifts", true)) + person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("allowed_to_send_gifts", true)) + person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("gift_history", aid.JSON{})) + + person.Save() +} \ No newline at end of file diff --git a/person/person.go b/person/person.go index 46d262c..99fdc14 100644 --- a/person/person.go +++ b/person/person.go @@ -8,8 +8,11 @@ import ( type Person struct { ID string DisplayName string + AccessKey string AthenaProfile *Profile CommonCoreProfile *Profile + CommonPublicProfile *Profile + Profile0 *Profile Loadout *Loadout } @@ -22,13 +25,16 @@ func NewPerson() *Person { return &Person{ ID: uuid.New().String(), DisplayName: uuid.New().String(), + AccessKey: "", AthenaProfile: NewProfile("athena"), CommonCoreProfile: NewProfile("common_core"), + CommonPublicProfile: NewProfile("common_public"), + Profile0: NewProfile("profile0"), Loadout: NewLoadout(), } } -func FromDatabase(personId string) *Person { +func Find(personId string) *Person { person := storage.Repo.GetPerson(personId) if person == nil { return nil @@ -37,6 +43,8 @@ func FromDatabase(personId string) *Person { loadout := FromDatabaseLoadout(&person.Loadout) athenaProfile := NewProfile("athena") commonCoreProfile := NewProfile("common_core") + commonPublicProfile := NewProfile("common_public") + profile0 := NewProfile("profile0") for _, profile := range person.Profiles { if profile.Type == "athena" { @@ -48,13 +56,72 @@ func FromDatabase(personId string) *Person { commonCoreProfile.ID = profile.ID commonCoreProfile = FromDatabaseProfile(&profile) } + + if profile.Type == "common_public" { + commonPublicProfile.ID = profile.ID + commonPublicProfile = FromDatabaseProfile(&profile) + } + + if profile.Type == "profile0" { + profile0.ID = profile.ID + profile0 = FromDatabaseProfile(&profile) + } } return &Person{ ID: person.ID, DisplayName: person.DisplayName, + AccessKey: person.AccessKey, AthenaProfile: athenaProfile, CommonCoreProfile: commonCoreProfile, + CommonPublicProfile: commonPublicProfile, + Profile0: profile0, + Loadout: loadout, + } +} + +func FindByDisplay(displayName string) *Person { + person := storage.Repo.GetPersonByDisplay(displayName) + if person == nil { + return nil + } + + loadout := FromDatabaseLoadout(&person.Loadout) + athenaProfile := NewProfile("athena") + commonCoreProfile := NewProfile("common_core") + commonPublicProfile := NewProfile("common_public") + profile0 := NewProfile("profile0") + + for _, profile := range person.Profiles { + if profile.Type == "athena" { + athenaProfile.ID = profile.ID + athenaProfile = FromDatabaseProfile(&profile) + } + + if profile.Type == "common_core" { + commonCoreProfile.ID = profile.ID + commonCoreProfile = FromDatabaseProfile(&profile) + } + + if profile.Type == "common_public" { + commonPublicProfile.ID = profile.ID + commonPublicProfile = FromDatabaseProfile(&profile) + } + + if profile.Type == "profile0" { + profile0.ID = profile.ID + profile0 = FromDatabaseProfile(&profile) + } + } + + return &Person{ + ID: person.ID, + DisplayName: person.DisplayName, + AccessKey: person.AccessKey, + AthenaProfile: athenaProfile, + CommonCoreProfile: commonCoreProfile, + CommonPublicProfile: commonPublicProfile, + Profile0: profile0, Loadout: loadout, } } @@ -63,12 +130,27 @@ func AllFromDatabase() []*Person { var persons []*Person for _, person := range storage.Repo.GetAllPersons() { - persons = append(persons, FromDatabase(person.ID)) + persons = append(persons, Find(person.ID)) } return persons } +func (p *Person) GetProfileFromType(profileType string) *Profile { + switch profileType { + case "athena": + return p.AthenaProfile + case "common_core": + return p.CommonCoreProfile + case "common_public": + return p.CommonPublicProfile + case "profile0": + return p.Profile0 + } + + return nil +} + func (p *Person) Save() { storage.Repo.SavePerson(p.ToDatabase()) } @@ -79,11 +161,14 @@ func (p *Person) ToDatabase() *storage.DB_Person { DisplayName: p.DisplayName, Profiles: []storage.DB_Profile{}, Loadout: *p.Loadout.ToDatabase(), + AccessKey: p.AccessKey, } profilesToConvert := map[string]*Profile{ "common_core": p.CommonCoreProfile, - "athena": p.AthenaProfile, + "athena": p.AthenaProfile, + "common_public": p.CommonPublicProfile, + "profile0": p.Profile0, } for profileType, profile := range profilesToConvert { diff --git a/person/profile.go b/person/profile.go index af01a8f..60b3017 100644 --- a/person/profile.go +++ b/person/profile.go @@ -3,6 +3,7 @@ package person import ( "fmt" + "github.com/ectrc/snow/aid" "github.com/ectrc/snow/storage" "github.com/google/uuid" "github.com/r3labs/diff/v3" @@ -10,6 +11,7 @@ import ( type Profile struct { ID string + PersonID string Items *ItemMutex Gifts *GiftMutex Quests *QuestMutex @@ -22,6 +24,7 @@ type Profile struct { func NewProfile(profile string) *Profile { return &Profile{ ID: uuid.New().String(), + PersonID: "", Items: NewItemMutex(profile), Gifts: NewGiftMutex(), Quests: NewQuestMutex(), @@ -62,6 +65,7 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile { return &Profile{ ID: profile.ID, + PersonID: profile.PersonID, Items: items, Gifts: gifts, Quests: quests, @@ -71,6 +75,44 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile { } } +func (p *Profile) GenerateFortniteProfileEntry() aid.JSON { + items := aid.JSON{} + attributes := aid.JSON{} + + p.Items.RangeItems(func(id string, item *Item) bool { + items[id] = item.GenerateFortniteItemEntry() + return true + }) + + p.Quests.RangeQuests(func(id string, quest *Quest) bool { + items[id] = quest.GenerateFortniteQuestEntry() + return true + }) + + p.Gifts.RangeGifts(func(id string, gift *Gift) bool { + items[id] = gift.GenerateFortniteGiftEntry() + return true + }) + + p.Attributes.RangeAttributes(func(id string, attribute *Attribute) bool { + attributes[attribute.Key] = attribute.Value + return true + }) + + return aid.JSON{ + "profileId": p.Type, + "accountId": p.PersonID, + "rvn": p.Revision, + "commandRevision": p.Revision, + "wipeNumber": 0, + "version": "", + "items": items, + "stats": aid.JSON{ + "attributes": attributes, + }, + } +} + func (p *Profile) Save() { //storage.Repo.SaveProfile(p.ToDatabase()) } @@ -259,6 +301,13 @@ func (p *Profile) CreateItemAttributeChangedChange(item *Item, attribute string) }) } +func (p *Profile) CreateFullProfileUpdateChange() { + p.Changes = append(p.Changes, FullProfileUpdate{ + ChangeType: "fullProfileUpdate", + Profile: p.GenerateFortniteProfileEntry(), + }) +} + type Loadout struct { ID string Character string diff --git a/storage/cache.go b/storage/cache.go index 43c3c5a..64b1994 100644 --- a/storage/cache.go +++ b/storage/cache.go @@ -47,6 +47,20 @@ func (m *PersonsCache) GetPerson(id string) *DB_Person { return nil } +func (m *PersonsCache) GetPersonByDisplay(displayName string) *DB_Person { + var person *DB_Person + m.Range(func(key, value interface{}) bool { + if value.(*CacheEntry).Entry.(*DB_Person).DisplayName == displayName { + person = value.(*CacheEntry).Entry.(*DB_Person) + return false + } + + return true + }) + + return person +} + func (m *PersonsCache) SavePerson(p *DB_Person) { m.Store(p.ID, &CacheEntry{ Entry: p, diff --git a/storage/postgres.go b/storage/postgres.go index e332c20..d8ca9ad 100644 --- a/storage/postgres.go +++ b/storage/postgres.go @@ -48,6 +48,26 @@ func (s *PostgresStorage) GetPerson(personId string) *DB_Person { return &dbPerson } +func (s *PostgresStorage) GetPersonByDisplay(displayName string) *DB_Person { + var dbPerson DB_Person + s.Postgres. + Preload("Profiles"). + Preload("Profiles.Items.Variants"). + Preload("Profiles.Gifts.Loot"). + Preload("Profiles.Attributes"). + Preload("Profiles.Items"). + Preload("Profiles.Gifts"). + Preload("Profiles.Quests"). + Where("display_name = ?", displayName). + Find(&dbPerson) + + if dbPerson.ID == "" { + return nil + } + + return &dbPerson +} + func (s *PostgresStorage) GetAllPersons() []*DB_Person { var dbPersons []*DB_Person diff --git a/storage/storage.go b/storage/storage.go index 0cb7c78..a9ed148 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -9,6 +9,7 @@ type Storage interface { Migrate(table interface{}, tableName string) GetPerson(personId string) *DB_Person + GetPersonByDisplay(displayName string) *DB_Person GetAllPersons() []*DB_Person SavePerson(person *DB_Person) @@ -45,6 +46,21 @@ func (r *Repository) GetPerson(personId string) *DB_Person { return nil } +func (r *Repository) GetPersonByDisplay(displayName string) *DB_Person { + cachePerson := Cache.GetPersonByDisplay(displayName) + if cachePerson != nil { + return cachePerson + } + + storagePerson := r.Storage.GetPersonByDisplay(displayName) + if storagePerson != nil { + Cache.SavePerson(storagePerson) + return storagePerson + } + + return nil +} + func (r *Repository) GetAllPersons() []*DB_Person { return r.Storage.GetAllPersons() } diff --git a/storage/tables.go b/storage/tables.go index d85bda3..6a853d2 100644 --- a/storage/tables.go +++ b/storage/tables.go @@ -9,6 +9,7 @@ type Tabler interface { type DB_Person struct { ID string DisplayName string + AccessKey string Profiles []DB_Profile `gorm:"foreignkey:PersonID"` Loadout DB_Loadout `gorm:"foreignkey:PersonID"` }