mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-03-21 17:44:58 -05:00
247 lines
7.3 KiB
Go
247 lines
7.3 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
"wwfc/common"
|
|
"wwfc/logging"
|
|
|
|
"github.com/jackc/pgx/v4"
|
|
"github.com/jackc/pgx/v4/pgxpool"
|
|
"github.com/logrusorgru/aurora/v3"
|
|
)
|
|
|
|
const (
|
|
SearchUserBan = `WITH known_ng_device_ids AS (
|
|
WITH RECURSIVE device_tree AS (
|
|
SELECT unnest(ng_device_id) AS device_id
|
|
FROM users
|
|
WHERE ng_device_id && $1
|
|
UNION
|
|
SELECT unnest(ng_device_id)
|
|
FROM users
|
|
JOIN device_tree dt
|
|
ON ng_device_id && array[dt.device_id]
|
|
) SELECT array_agg(DISTINCT device_id) FROM device_tree
|
|
)
|
|
SELECT has_ban, ban_tos, ng_device_id, ban_reason
|
|
FROM users
|
|
WHERE has_ban = true
|
|
AND ((profile_id = $2 OR allow_default_keys = FALSE)
|
|
OR ng_device_id && (SELECT * FROM known_ng_device_ids)
|
|
OR last_ip_address = $3
|
|
OR ($4 != '' AND last_ip_address = $4))
|
|
AND (ban_expires IS NULL OR ban_expires > $5)
|
|
ORDER BY ban_tos DESC LIMIT 1`
|
|
)
|
|
|
|
var (
|
|
ErrDeviceIDMismatch = errors.New("NG device ID mismatch")
|
|
ErrProhibitedDeviceID = errors.New("used prohibited NG device ID in request")
|
|
ErrProfileBannedTOS = errors.New("profile is banned for violating the Terms of Service")
|
|
)
|
|
|
|
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string, profileId uint32, defaultKey bool, ngDeviceId uint32, ipAddress string, ingamesn string, deviceAuth bool) (User, error) {
|
|
var exists bool
|
|
err := pool.QueryRow(ctx, DoesUserExist, userId, gsbrcd).Scan(&exists)
|
|
if err != nil {
|
|
return User{}, err
|
|
}
|
|
|
|
user := User{
|
|
UserId: userId,
|
|
GsbrCode: gsbrcd,
|
|
}
|
|
|
|
var lastIPAddress *string
|
|
|
|
if !exists {
|
|
user.ProfileId = profileId
|
|
user.NgDeviceId = []uint32{ngDeviceId}
|
|
if ngDeviceId == 0 {
|
|
user.NgDeviceId = []uint32{}
|
|
}
|
|
user.UniqueNick = common.Base32Encode(userId) + gsbrcd
|
|
user.Email = user.UniqueNick + "@nds"
|
|
|
|
// Create the GPCM account
|
|
err := user.CreateUser(pool, ctx)
|
|
if err != nil {
|
|
logging.Error("DATABASE", "Error creating user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId), "\nerror:", err.Error())
|
|
return User{}, err
|
|
}
|
|
|
|
logging.Notice("DATABASE", "Created new GPCM user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId))
|
|
} else {
|
|
var firstName *string
|
|
var lastName *string
|
|
var allowDefaultKeys bool
|
|
|
|
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &user.NgDeviceId, &user.Email, &user.UniqueNick, &firstName, &lastName, &user.OpenHost, &lastIPAddress, &allowDefaultKeys)
|
|
if err != nil {
|
|
return User{}, err
|
|
}
|
|
|
|
if defaultKey && !allowDefaultKeys && !common.GetConfig().AllowDefaultDolphinKeys {
|
|
return User{}, ErrProhibitedDeviceID
|
|
}
|
|
|
|
if firstName != nil {
|
|
user.FirstName = *firstName
|
|
}
|
|
|
|
if lastName != nil {
|
|
user.LastName = *lastName
|
|
}
|
|
|
|
validDeviceId := false
|
|
deviceIdList := ""
|
|
for index, id := range user.NgDeviceId {
|
|
if id == ngDeviceId {
|
|
validDeviceId = true
|
|
}
|
|
|
|
if !validDeviceId && id == 0 {
|
|
// Replace the 0 with the actual device ID
|
|
user.NgDeviceId[index] = ngDeviceId
|
|
_, err = pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, user.NgDeviceId)
|
|
validDeviceId = true
|
|
}
|
|
|
|
deviceIdList += aurora.Cyan(fmt.Sprintf("%08x", id)).String() + ", "
|
|
}
|
|
|
|
if !validDeviceId && ngDeviceId != 0 {
|
|
if len(user.NgDeviceId) > 0 && common.GetConfig().AllowMultipleDeviceIDs != "always" {
|
|
if common.GetConfig().AllowMultipleDeviceIDs == "SameIPAddress" && (lastIPAddress == nil || ipAddress != *lastIPAddress) {
|
|
logging.Error("DATABASE", "NG device ID mismatch for profile", aurora.Cyan(user.ProfileId), "- expected one of {", deviceIdList[:len(deviceIdList)-2], "} but got", aurora.Cyan(fmt.Sprintf("%08x", ngDeviceId)))
|
|
return User{}, ErrDeviceIDMismatch
|
|
}
|
|
}
|
|
|
|
if len(user.NgDeviceId) > 0 {
|
|
logging.Warn("DATABASE", "Adding NG device ID", aurora.Cyan(fmt.Sprintf("%08x", ngDeviceId)), "to profile", aurora.Cyan(user.ProfileId))
|
|
}
|
|
|
|
user.NgDeviceId = append(user.NgDeviceId, ngDeviceId)
|
|
_, err = pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, user.NgDeviceId)
|
|
} else if deviceAuth && !validDeviceId && ngDeviceId == 0 {
|
|
if len(user.NgDeviceId) > 0 && !common.GetConfig().AllowConnectWithoutDeviceID {
|
|
logging.Error("DATABASE", "NG device ID not provided for profile", aurora.Cyan(user.ProfileId), "- expected one of {", deviceIdList[:len(deviceIdList)-2], "} but got", aurora.Cyan("00000000"))
|
|
return User{}, ErrDeviceIDMismatch
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return User{}, err
|
|
}
|
|
|
|
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))
|
|
}
|
|
}
|
|
|
|
logging.Notice("DATABASE", "Log in GPCM user:", aurora.Cyan(userId), aurora.Cyan(user.GsbrCode), "-", aurora.Cyan(user.ProfileId))
|
|
}
|
|
|
|
// This should be set if the user already knows its own profile ID
|
|
if profileId != 0 && user.LastName == "" {
|
|
user.UpdateProfile(pool, ctx, map[string]string{
|
|
"lastname": "000000000" + gsbrcd,
|
|
})
|
|
}
|
|
|
|
// Update the user's last IP address and ingamesn
|
|
if deviceAuth {
|
|
_, err = pool.Exec(ctx, UpdateUserLastIPAddress, user.ProfileId, ipAddress, ingamesn)
|
|
if err != nil {
|
|
return User{}, err
|
|
}
|
|
}
|
|
|
|
emptyString := ""
|
|
if lastIPAddress == nil {
|
|
lastIPAddress = &emptyString
|
|
}
|
|
|
|
// Find ban from device ID or IP address
|
|
var banExists bool
|
|
var banTOS bool
|
|
var bannedDeviceIdList []uint32
|
|
var banReason string
|
|
timeNow := time.Now().UTC()
|
|
err = pool.QueryRow(ctx, SearchUserBan, user.NgDeviceId, user.ProfileId, ipAddress, *lastIPAddress, timeNow).Scan(&banExists, &banTOS, &bannedDeviceIdList, &banReason)
|
|
|
|
if err != nil {
|
|
if err != pgx.ErrNoRows {
|
|
return User{}, err
|
|
}
|
|
|
|
banExists = false
|
|
}
|
|
|
|
if banExists {
|
|
// Find first device ID in common
|
|
bannedDeviceId := uint32(0)
|
|
for _, id := range bannedDeviceIdList {
|
|
for _, id2 := range user.NgDeviceId {
|
|
if id == id2 {
|
|
bannedDeviceId = id
|
|
break
|
|
}
|
|
}
|
|
|
|
if bannedDeviceId != 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
if bannedDeviceId == 0 && len(bannedDeviceIdList) > 0 {
|
|
bannedDeviceId = bannedDeviceIdList[len(bannedDeviceIdList)-1]
|
|
}
|
|
|
|
if banTOS {
|
|
logging.Warn("DATABASE", "Profile", aurora.Cyan(user.ProfileId), "is banned")
|
|
return User{RestrictedDeviceId: bannedDeviceId, BanReason: banReason}, ErrProfileBannedTOS
|
|
}
|
|
|
|
logging.Warn("DATABASE", "Profile", aurora.Cyan(user.ProfileId), "is restricted")
|
|
user.Restricted = true
|
|
user.RestrictedDeviceId = bannedDeviceId
|
|
user.BanReason = banReason
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func LoginUserToGameStats(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string) (User, error) {
|
|
user := User{
|
|
UserId: userId,
|
|
GsbrCode: gsbrcd,
|
|
}
|
|
|
|
var firstName *string
|
|
var lastName *string
|
|
var lastIPAddress *string
|
|
err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &user.NgDeviceId, &user.Email, &user.UniqueNick, &firstName, &lastName, &user.OpenHost, &lastIPAddress)
|
|
if err != nil {
|
|
return User{}, err
|
|
}
|
|
|
|
if firstName != nil {
|
|
user.FirstName = *firstName
|
|
}
|
|
|
|
if lastName != nil {
|
|
user.LastName = *lastName
|
|
}
|
|
|
|
return user, nil
|
|
}
|