Fixed access token issues causing errors logging in!
This commit is contained in:
parent
25c2e59b16
commit
9e983fff93
|
@ -1,7 +1,7 @@
|
|||
package aid
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
m "math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
|
@ -32,7 +32,7 @@ func RandomString(n int) string {
|
|||
|
||||
s := make([]rune, n)
|
||||
for i := range s {
|
||||
s[i] = letters[rand.Intn(len(letters))]
|
||||
s[i] = letters[m.Intn(len(letters))]
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
|
74
aid/crypto.go
Normal file
74
aid/crypto.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package aid
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
type keyPair struct {
|
||||
PrivateKey rsa.PrivateKey
|
||||
PublicKey rsa.PublicKey
|
||||
}
|
||||
|
||||
var KeyPair = GeneratePublicPrivateKeyPair()
|
||||
|
||||
func GeneratePublicPrivateKeyPair() keyPair {
|
||||
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||
publicKey := privateKey.PublicKey
|
||||
|
||||
return keyPair{
|
||||
PrivateKey: *privateKey,
|
||||
PublicKey: publicKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *keyPair) EncryptAndSign(message []byte) ([]byte, []byte) {
|
||||
encryptedMessage, _ := rsa.EncryptPKCS1v15(rand.Reader, &k.PublicKey, message)
|
||||
signature, _ := rsa.SignPKCS1v15(rand.Reader, &k.PrivateKey, 0, encryptedMessage)
|
||||
|
||||
return encryptedMessage, signature
|
||||
}
|
||||
|
||||
func (k *keyPair) EncryptAndSignB64(message []byte) (string, string) {
|
||||
encryptedMessage, signature := k.EncryptAndSign(message)
|
||||
|
||||
return Base64Encode(encryptedMessage), Base64Encode(signature)
|
||||
}
|
||||
|
||||
func (k *keyPair) DecryptAndVerify(encryptedMessage []byte, signature []byte) []byte {
|
||||
decryptedMessage, _ := rsa.DecryptPKCS1v15(rand.Reader, &k.PrivateKey, encryptedMessage)
|
||||
_ = rsa.VerifyPKCS1v15(&k.PublicKey, 0, encryptedMessage, signature)
|
||||
|
||||
return decryptedMessage
|
||||
}
|
||||
|
||||
func (k *keyPair) ExportPrivateKey() []byte {
|
||||
privateKey := x509.MarshalPKCS1PrivateKey(&k.PrivateKey)
|
||||
return privateKey
|
||||
}
|
||||
|
||||
func (k *keyPair) ExportPublicKey() []byte {
|
||||
publicKey := x509.MarshalPKCS1PublicKey(&k.PublicKey)
|
||||
return publicKey
|
||||
}
|
||||
|
||||
func Base64Encode(input []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(input)
|
||||
}
|
||||
|
||||
func Base64Decode(input string) ([]byte, bool) {
|
||||
data, err := base64.StdEncoding.DecodeString(input)
|
||||
if err != nil {
|
||||
return []byte{}, true
|
||||
}
|
||||
return data, false
|
||||
}
|
||||
|
||||
func Hash(input []byte) string {
|
||||
shaBytes := sha256.Sum256(input)
|
||||
return hex.EncodeToString(shaBytes[:])
|
||||
}
|
44
aid/token.go
44
aid/token.go
|
@ -1,44 +0,0 @@
|
|||
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
|
||||
}
|
15
aid/type.go
15
aid/type.go
|
@ -1,3 +1,16 @@
|
|||
package aid
|
||||
|
||||
type JSON map[string]interface{}
|
||||
import "github.com/goccy/go-json"
|
||||
|
||||
type JSON map[string]interface{}
|
||||
|
||||
func JSONFromBytes(input []byte) JSON {
|
||||
var output JSON
|
||||
json.Unmarshal(input, &output)
|
||||
return output
|
||||
}
|
||||
|
||||
func (j *JSON) ToBytes() []byte {
|
||||
json, _ := json.Marshal(j)
|
||||
return json
|
||||
}
|
|
@ -145,4 +145,6 @@ func GiveEverything(person *p.Person) {
|
|||
}
|
||||
|
||||
storage.Repo.BulkCreateItems(&items)
|
||||
aid.Print("Gave everything to " + person.DisplayName)
|
||||
person.Save()
|
||||
}
|
|
@ -366,7 +366,7 @@ func GenerateRandomStorefront() {
|
|||
}
|
||||
|
||||
minimumItems := 8
|
||||
if aid.Config.Fortnite.Season < 14 {
|
||||
if aid.Config.Fortnite.Season < 11 {
|
||||
minimumItems = 3
|
||||
}
|
||||
|
||||
|
|
108
handlers/auth.go
108
handlers/auth.go
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/ectrc/snow/aid"
|
||||
p "github.com/ectrc/snow/person"
|
||||
"github.com/ectrc/snow/storage"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
|
@ -37,22 +38,19 @@ func PostFortniteToken(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func PostTokenClientCredentials(c *fiber.Ctx, body *FortniteTokenBody) error {
|
||||
credentials, err := aid.JWTSign(aid.JSON{
|
||||
"snow_id": 0, // custom
|
||||
"creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"),
|
||||
})
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer)
|
||||
}
|
||||
client, sig := aid.KeyPair.EncryptAndSignB64([]byte(c.IP()))
|
||||
hash := aid.Hash([]byte(client + "." + sig))
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(aid.JSON{
|
||||
"access_token": "eg1~"+credentials,
|
||||
"access_token": hash,
|
||||
"token_type": "bearer",
|
||||
"client_id": c.IP(),
|
||||
"client_service": "fortnite",
|
||||
"internal_client": true,
|
||||
"expires_in": 3600,
|
||||
"expires_at": time.Now().Add(time.Hour).Format("2006-01-02T15:04:05.999Z"),
|
||||
"product_id": "prod-fn",
|
||||
"sandbox_id": "fn",
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -66,24 +64,31 @@ func PostTokenPassword(c *fiber.Ctx, body *FortniteTokenBody) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("No Account Found"))
|
||||
}
|
||||
|
||||
access, err := aid.JWTSign(aid.JSON{
|
||||
"snow_id": person.ID, // custom
|
||||
"creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"),
|
||||
})
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer)
|
||||
}
|
||||
access, ac_sig := aid.KeyPair.EncryptAndSignB64([]byte(person.ID))
|
||||
ac_hash := aid.Hash([]byte(access + "." + ac_sig))
|
||||
|
||||
refresh, err := aid.JWTSign(aid.JSON{
|
||||
"snow_id": person.ID,
|
||||
"creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"),
|
||||
})
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer)
|
||||
ac_token := &storage.DB_GameToken{
|
||||
ID: ac_hash,
|
||||
PersonID: person.ID,
|
||||
AccessToken: access + "." + ac_sig,
|
||||
Type: "access",
|
||||
}
|
||||
storage.Repo.SaveToken(ac_token)
|
||||
|
||||
refresh, re_sig := aid.KeyPair.EncryptAndSignB64([]byte(person.ID))
|
||||
re_hash := aid.Hash([]byte(refresh + "." + re_sig))
|
||||
|
||||
re_token := &storage.DB_GameToken{
|
||||
ID: re_hash,
|
||||
PersonID: person.ID,
|
||||
AccessToken: refresh + "." + re_sig,
|
||||
Type: "refresh",
|
||||
}
|
||||
storage.Repo.SaveToken(re_token)
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(aid.JSON{
|
||||
"access_token": "eg1~"+access,
|
||||
// "access_token": access + "." + ac_sig,
|
||||
"access_token": ac_hash,
|
||||
"account_id": person.ID,
|
||||
"client_id": c.IP(),
|
||||
"client_service": "fortnite",
|
||||
|
@ -95,8 +100,11 @@ func PostTokenPassword(c *fiber.Ctx, body *FortniteTokenBody) error {
|
|||
"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,
|
||||
// "refresh_token": refresh + "." + re_sig,
|
||||
"refresh_token": re_hash,
|
||||
"token_type": "bearer",
|
||||
"product_id": "prod-fn",
|
||||
"sandbox_id": "fn",
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -105,21 +113,13 @@ func GetOAuthVerify(c *fiber.Ctx) error {
|
|||
if auth == "" {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Authorization Header is empty"))
|
||||
}
|
||||
real := strings.ReplaceAll(auth, "bearer eg1~", "")
|
||||
real := strings.ReplaceAll(auth, "bearer ", "")
|
||||
|
||||
claims, err := aid.JWTVerify(real)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
|
||||
}
|
||||
|
||||
if claims["snow_id"] == nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
|
||||
}
|
||||
|
||||
snowId, ok := claims["snow_id"].(string)
|
||||
if !ok {
|
||||
found := storage.Repo.GetToken(real)
|
||||
if found == nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
|
||||
}
|
||||
snowId := found.PersonID
|
||||
|
||||
person := p.Find(snowId)
|
||||
if person == nil {
|
||||
|
@ -128,7 +128,7 @@ func GetOAuthVerify(c *fiber.Ctx) error {
|
|||
|
||||
return c.Status(fiber.StatusOK).JSON(aid.JSON{
|
||||
"app": "fortnite",
|
||||
"token": "eg1~"+real,
|
||||
"token": real,
|
||||
"token_type": "bearer",
|
||||
"expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"),
|
||||
"expires_in": 86400,
|
||||
|
@ -140,6 +140,8 @@ func GetOAuthVerify(c *fiber.Ctx) error {
|
|||
"in_app_id": person.ID,
|
||||
"account_id": person.ID,
|
||||
"displayName": person.DisplayName,
|
||||
"product_id": "prod-fn",
|
||||
"sandbox_id": "fn",
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -148,21 +150,13 @@ func FortniteMiddleware(c *fiber.Ctx) error {
|
|||
if auth == "" {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Authorization Header is empty"))
|
||||
}
|
||||
real := strings.ReplaceAll(auth, "bearer eg1~", "")
|
||||
real := strings.ReplaceAll(auth, "bearer ", "")
|
||||
|
||||
claims, err := aid.JWTVerify(real)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
|
||||
}
|
||||
|
||||
if claims["snow_id"] == nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
|
||||
}
|
||||
|
||||
snowId, ok := claims["snow_id"].(string)
|
||||
if !ok {
|
||||
found := storage.Repo.GetToken(real)
|
||||
if found == nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
|
||||
}
|
||||
snowId := found.PersonID
|
||||
|
||||
person := p.Find(snowId)
|
||||
if person == nil {
|
||||
|
@ -179,23 +173,11 @@ func FrontendMiddleware(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Authorization Header is empty"))
|
||||
}
|
||||
|
||||
claims, err := aid.JWTVerify(auth)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
|
||||
}
|
||||
|
||||
if claims["snow_id"] == nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
|
||||
}
|
||||
|
||||
if claims["frontend"] == nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Claims"))
|
||||
}
|
||||
|
||||
snowId, ok := claims["snow_id"].(string)
|
||||
if !ok {
|
||||
found := storage.Repo.GetToken(auth)
|
||||
if found == nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
|
||||
}
|
||||
snowId := found.PersonID
|
||||
|
||||
person := p.Find(snowId)
|
||||
if person == nil {
|
||||
|
|
|
@ -3,7 +3,6 @@ package handlers
|
|||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ectrc/snow/aid"
|
||||
p "github.com/ectrc/snow/person"
|
||||
|
@ -75,18 +74,14 @@ func GetDiscordOAuthURL(c *fiber.Ctx) error {
|
|||
person.Discord.Banner = user.Banner
|
||||
storage.Repo.SaveDiscordPerson(person.Discord)
|
||||
|
||||
access, err := aid.JWTSign(aid.JSON{
|
||||
"snow_id": person.ID, // custom
|
||||
"frontend": true,
|
||||
"creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"),
|
||||
})
|
||||
access, sig := aid.KeyPair.EncryptAndSignB64([]byte(person.ID + ".frontend"))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer)
|
||||
}
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "access_token",
|
||||
Value: access,
|
||||
Value: access + "." + sig,
|
||||
})
|
||||
return c.Redirect(aid.Config.API.Host + aid.Config.API.FrontendPort + "/attempt")
|
||||
}
|
6
main.go
6
main.go
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/ectrc/snow/discord"
|
||||
"github.com/ectrc/snow/fortnite"
|
||||
"github.com/ectrc/snow/handlers"
|
||||
"github.com/ectrc/snow/person"
|
||||
"github.com/ectrc/snow/storage"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -42,8 +43,11 @@ func init() {
|
|||
fortnite.PreloadCosmetics(aid.Config.Fortnite.Season)
|
||||
fortnite.GenerateRandomStorefront()
|
||||
fortnite.GeneratePlaylistImages()
|
||||
}
|
||||
|
||||
if found := person.FindByDisplay("god"); found == nil {
|
||||
fortnite.NewFortnitePerson("god", true)
|
||||
}
|
||||
}
|
||||
func main() {
|
||||
r := fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
|
|
|
@ -21,15 +21,12 @@ Performance first, universal Fortnite private server backend written in Go.
|
|||
### Supported
|
||||
|
||||
- **_Chapter 1 Season 2_** `Fortnite+Release-2.5-CL-3889387-Windows` I started with this build of the game as it requires more work to get working, this means snow can support _most_ versions of the game.
|
||||
- **_Chapter 1 Season 4_** `Fortnite+Release-4.5-CL-4159770-Windows` Fixed the access token issue causing errors logging in!
|
||||
- **_Chapter 1 Season 5_** `Fortnite+Release-5.41-CL-4363240-Windows` This build was used to make sure challenges, variants and lobby backgrounds work.
|
||||
- **_Chapter 1 Season 8_** `Fortnite+Release-8.51-CL-6165369-Windows` Fixed the invisible player bug caused by invalid account responses. Also fixed the issue with the item shop spamming the api.
|
||||
- **_Chapter 2 Season 2_** `Fortnite+Release-12.41-CL-12905909-Windows` Item Shop length is correct, also Creative profile stopping login has also been fixed.
|
||||
- **_Chapter 3 Season 1_** `Fortnite+Release-19.10-CL-Unknown-Windows` This is a very new build of fortnite that introfuces alot of different methods e.g. locker data is now stored as an item. Every MCP action is now fully working and tested. You need to start using easy anticheat otherwise this will not work.
|
||||
|
||||
### Broken
|
||||
|
||||
- **_Chapter 1 Season 4_** `Fortnite+Release-4.5-CL-4159770-Windows` Does not accept the Access Token for user authentication. I have some ideas why however not planned for a fix.
|
||||
|
||||
## How do I use this?
|
||||
|
||||
- **[Discord OAuth Setup Guide](oauth.md)** How to setup Discord OAuth for your backend. This enabled the ability to login to the web app with Discord.
|
||||
|
|
|
@ -43,9 +43,10 @@ func (s *PostgresStorage) MigrateAll() {
|
|||
s.Migrate(&DB_Loot{}, "Loot")
|
||||
s.Migrate(&DB_VariantChannel{}, "Variants")
|
||||
s.Migrate(&DB_PAttribute{}, "Attributes")
|
||||
s.Migrate(&DB_TemporaryCode{}, "Exchanges")
|
||||
s.Migrate(&DB_TemporaryCode{}, "ExchangeCodes")
|
||||
s.Migrate(&DB_DiscordPerson{}, "Discords")
|
||||
s.Migrate(&DB_SeasonStat{}, "Stats")
|
||||
s.Migrate(&DB_GameToken{}, "GameTokens")
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DropTables() {
|
||||
|
@ -241,4 +242,23 @@ func (s *PostgresStorage) SaveDiscordPerson(discordPerson *DB_DiscordPerson) {
|
|||
|
||||
func (s *PostgresStorage) DeleteDiscordPerson(discordPersonId string) {
|
||||
s.Postgres.Delete(&DB_DiscordPerson{}, "id = ?", discordPersonId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) SaveToken(token *DB_GameToken) {
|
||||
s.Postgres.Save(token)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) DeleteToken(tokenId string) {
|
||||
s.Postgres.Delete(&DB_GameToken{}, "id = ?", tokenId)
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) GetToken(tokenId string) *DB_GameToken {
|
||||
var token DB_GameToken
|
||||
s.Postgres.Model(&DB_GameToken{}).Where("id = ?", tokenId).Find(&token)
|
||||
|
||||
if token.ID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &token
|
||||
}
|
|
@ -48,6 +48,10 @@ type Storage interface {
|
|||
|
||||
SaveDiscordPerson(person *DB_DiscordPerson)
|
||||
DeleteDiscordPerson(personId string)
|
||||
|
||||
SaveToken(token *DB_GameToken)
|
||||
GetToken(tokenId string) *DB_GameToken
|
||||
DeleteToken(tokenId string)
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
|
@ -189,4 +193,16 @@ func (r *Repository) SaveDiscordPerson(person *DB_DiscordPerson) {
|
|||
|
||||
func (r *Repository) DeleteDiscordPerson(personId string) {
|
||||
r.Storage.DeleteDiscordPerson(personId)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveToken(token *DB_GameToken) {
|
||||
r.Storage.SaveToken(token)
|
||||
}
|
||||
|
||||
func (r *Repository) GetToken(tokenId string) *DB_GameToken {
|
||||
return r.Storage.GetToken(tokenId)
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteToken(tokenId string) {
|
||||
r.Storage.DeleteToken(tokenId)
|
||||
}
|
|
@ -146,7 +146,7 @@ type DB_TemporaryCode struct {
|
|||
}
|
||||
|
||||
func (DB_TemporaryCode) TableName() string {
|
||||
return "Exchanges"
|
||||
return "ExchangeCodes"
|
||||
}
|
||||
|
||||
type DB_DiscordPerson struct {
|
||||
|
@ -177,4 +177,16 @@ type DB_SeasonStat struct {
|
|||
|
||||
func (DB_SeasonStat) TableName() string {
|
||||
return "Stats"
|
||||
}
|
||||
|
||||
type DB_GameToken struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
PersonID string
|
||||
AccessToken string
|
||||
Type string
|
||||
ExpiresAt int64
|
||||
}
|
||||
|
||||
func (DB_GameToken) TableName() string {
|
||||
return "GameTokens"
|
||||
}
|
Loading…
Reference in New Issue
Block a user