diff --git a/database/login.go b/database/login.go index 7693399..f326441 100644 --- a/database/login.go +++ b/database/login.go @@ -8,16 +8,17 @@ import ( "wwfc/logging" ) -func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string) (User, bool) { +func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string, profileId uint32) (User, bool) { var exists bool err := pool.QueryRow(ctx, DoesUserExist, userId, gsbrcd).Scan(&exists) if err != nil { - panic(err) + return User{}, false } uniqueNickname := common.Base32Encode(userId) + gsbrcd user := User{ + ProfileId: profileId, UserId: userId, GsbrCode: gsbrcd, Email: uniqueNickname + "@nds", @@ -26,13 +27,32 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb if !exists { // Create the GPCM account - user.CreateUser(pool, ctx) - - logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), "-", aurora.Cyan(user.ProfileId)) - } else { - err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId) + err := user.CreateUser(pool, ctx) if err != nil { - panic(err) + logging.Error("DATABASE", "Error creating user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId), "\nerror:", err.Error()) + return User{}, false + } + + logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId)) + } else { + err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName) + if err != nil { + return User{}, false + } + + if profileId != 0 && user.ProfileId != profileId { + err := user.UpdateProfileID(pool, ctx, profileId) + if err != nil { + logging.Warn("DATABASE", "Could not update", aurora.Cyan(userId), aurora.Cyan(gsbrcd), "profile ID from", aurora.Cyan(user.ProfileId), "to", aurora.Cyan(profileId)) + } else { + logging.Notice("DATABASE", "Updated GPCM user profile ID:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId)) + } + } + + if user.LastName == "" { + user = UpdateProfile(pool, ctx, profileId, map[string]string{ + "lastname": "000000000" + gsbrcd, + }) } logging.Notice("DATABASE", "Log in GPCM user:", aurora.Cyan(userId), aurora.Cyan(user.GsbrCode), "-", aurora.Cyan(user.ProfileId)) diff --git a/database/user.go b/database/user.go index a783863..45ba166 100644 --- a/database/user.go +++ b/database/user.go @@ -2,19 +2,21 @@ package database import ( "context" + "errors" "github.com/jackc/pgx/v4/pgxpool" "math/rand" ) const ( - InsertUser = `INSERT INTO users (user_id, gsbrcd, password, email, unique_nick) VALUES ($1, $2, $3, $4, $5) RETURNING profile_id` - UpdateUserTable = `UPDATE users SET firstname = CASE WHEN $3 THEN $2 ELSE firstname END, lastname = CASE WHEN $5 THEN $4 ELSE lastname END WHERE profile_id = $1 RETURNING user_id, gsbrcd, email, unique_nick, firstname, lastname` - GetUser = `SELECT user_id, gsbrcd, email, unique_nick, firstname, lastname FROM users WHERE profile_id = $1` - CreateUserSession = `INSERT INTO sessions (session_key, profile_id, login_ticket) VALUES ($1, $2, $3)` - GetTicketSession = `SELECT session_key, profile_id FROM sessions WHERE login_ticket = $1` - DoesUserExist = `SELECT EXISTS(SELECT 1 FROM users WHERE user_id = $1 AND gsbrcd = $2)` - DeleteUserSession = `DELETE FROM sessions WHERE profile_id = $1` - GetUserProfileID = `SELECT profile_id FROM users WHERE user_id = $1 AND gsbrcd = $2` + InsertUser = `INSERT INTO users (user_id, gsbrcd, password, email, unique_nick) VALUES ($1, $2, $3, $4, $5) RETURNING profile_id` + InsertUserWithProfileID = `INSERT INTO users (user_id, gsbrcd, password, email, unique_nick) VALUES ($1, $2, $3, $4, $5)` + UpdateUserTable = `UPDATE users SET firstname = CASE WHEN $3 THEN $2 ELSE firstname END, lastname = CASE WHEN $5 THEN $4 ELSE lastname END WHERE profile_id = $1 RETURNING user_id, gsbrcd, email, unique_nick, firstname, lastname` + UpdateUserProfileID = `UPDATE users SET profile_id = $3 WHERE user_id = $1 AND gsbrcd = $2` + GetUser = `SELECT user_id, gsbrcd, email, unique_nick, firstname, lastname FROM users WHERE profile_id = $1` + DoesUserExist = `SELECT EXISTS(SELECT 1 FROM users WHERE user_id = $1 AND gsbrcd = $2)` + IsProfileIDInUse = `SELECT EXISTS(SELECT 1 FROM users WHERE profile_id = $1)` + DeleteUserSession = `DELETE FROM sessions WHERE profile_id = $1` + GetUserProfileID = `SELECT profile_id, email, unique_nick, firstname, lastname FROM users WHERE user_id = $1 AND gsbrcd = $2` GetMKWFriendInfoQuery = `SELECT mariokartwii_friend_info FROM users WHERE profile_id = $1` UpdateMKWFriendInfoQuery = `UPDATE users SET mariokartwii_friend_info = $2 WHERE profile_id = $1` @@ -30,11 +32,55 @@ type User struct { LastName string } -func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) { - err := pool.QueryRow(ctx, InsertUser, user.UserId, user.GsbrCode, "", user.Email, user.UniqueNick).Scan(&user.ProfileId) - if err != nil { - panic(err) +var ( + ErrProfileIDInUse = errors.New("profile ID is already in use") + ErrReservedProfileIDRange = errors.New("profile ID is in reserved range") +) + +func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) error { + if user.ProfileId == 0 { + return pool.QueryRow(ctx, InsertUser, user.UserId, user.GsbrCode, "", user.Email, user.UniqueNick).Scan(&user.ProfileId) } + + if user.ProfileId >= 1000000000 { + return ErrReservedProfileIDRange + } + + var exists bool + err := pool.QueryRow(ctx, IsProfileIDInUse, user.ProfileId).Scan(&exists) + if err != nil { + return err + } + + if exists { + return ErrProfileIDInUse + } + + _, err = pool.Exec(ctx, InsertUserWithProfileID, user.UserId, user.GsbrCode, "", user.Email, user.UniqueNick, user.ProfileId) + return err +} + +func (user *User) UpdateProfileID(pool *pgxpool.Pool, ctx context.Context, newProfileId uint32) error { + if newProfileId >= 1000000000 { + return ErrReservedProfileIDRange + } + + var exists bool + err := pool.QueryRow(ctx, IsProfileIDInUse, newProfileId).Scan(&exists) + if err != nil { + return err + } + + if exists { + return ErrProfileIDInUse + } + + _, err = pool.Exec(ctx, UpdateUserProfileID, user.UserId, user.GsbrCode, newProfileId) + if err == nil { + user.ProfileId = newProfileId + } + + return err } func GetUniqueUserID() uint64 { diff --git a/gpcm/login.go b/gpcm/login.go index 29fcc8b..19dacfd 100644 --- a/gpcm/login.go +++ b/gpcm/login.go @@ -154,8 +154,23 @@ func (g *GameSpySession) login(command common.GameSpyCommand) { proof := generateProof(g.Challenge, challenge, command.OtherValues["authtoken"], command.OtherValues["challenge"]) + cmdProfileId := uint32(0) + if cmdProfileIdStr, exists := command.OtherValues["profileid"]; exists { + cmdProfileId2, err := strconv.ParseUint(cmdProfileIdStr, 10, 32) + if err != nil { + g.replyError(GPError{ + ErrorCode: ErrLogin.ErrorCode, + ErrorString: "The provided profile ID is invalid.", + Fatal: true, + }) + return + } + + cmdProfileId = uint32(cmdProfileId2) + } + // Perform the login with the database. - user, ok := database.LoginUserToGPCM(pool, ctx, userId, gsbrcd) + user, ok := database.LoginUserToGPCM(pool, ctx, userId, gsbrcd, cmdProfileId) if !ok { // There was an error logging in to the GP backend. g.replyError(ErrLogin) diff --git a/sake/storage.go b/sake/storage.go index 61a98bc..7995c1b 100644 --- a/sake/storage.go +++ b/sake/storage.go @@ -191,8 +191,6 @@ func getRequestIdentity(moduleName string, request StorageRequestData) (uint32, panic("Invalid secret key") } - logging.Notice(moduleName, request.LoginTicket) - err, profileId, _ := common.UnmarshalGPCMLoginTicket(request.LoginTicket) if err != nil { panic(err)