ProjectLighthouse/ProjectLighthouse.Servers.GameServer/Controllers/LoginController.cs
Josh 64b95e807d
Refactor Database class (#616)
Refactor Database into DatabaseContext
Moved into separate folder so it actually has a namespace instead sitting in the root
2023-02-15 23:54:30 -06:00

207 lines
No EOL
8 KiB
C#

#nullable enable
using System.Net;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Tickets;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Users;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController]
[Route("LITTLEBIGPLANETPS3_XML/login")]
[Produces("text/xml")]
public class LoginController : ControllerBase
{
private readonly DatabaseContext database;
public LoginController(DatabaseContext database)
{
this.database = database;
}
[HttpPost]
public async Task<IActionResult> Login()
{
MemoryStream ms = new();
await this.Request.Body.CopyToAsync(ms);
byte[] loginData = ms.ToArray();
NPTicket? npTicket;
try
{
npTicket = NPTicket.CreateFromBytes(loginData);
}
catch
{
npTicket = null;
}
if (npTicket == null)
{
Logger.Warn("npTicket was null, rejecting login", LogArea.Login);
return this.BadRequest();
}
IPAddress? remoteIpAddress = this.HttpContext.Connection.RemoteIpAddress;
if (remoteIpAddress == null)
{
Logger.Warn("unable to determine ip, rejecting login", LogArea.Login);
return this.BadRequest();
}
string ipAddress = remoteIpAddress.ToString();
string? username = npTicket.Username;
if (username == null)
{
Logger.Warn("Unable to determine username, rejecting login", LogArea.Login);
return this.StatusCode(403, "");
}
await this.database.RemoveExpiredTokens();
User? user;
switch (npTicket.Platform)
{
case Platform.RPCS3:
user = await this.database.Users.FirstOrDefaultAsync(u => u.LinkedRpcnId == npTicket.UserId);
break;
case Platform.PS3:
case Platform.Vita:
case Platform.UnitTest:
user = await this.database.Users.FirstOrDefaultAsync(u => u.LinkedPsnId == npTicket.UserId);
break;
case Platform.PSP:
case Platform.Unknown:
default:
throw new ArgumentOutOfRangeException();
}
// If this user id hasn't been linked to any accounts
if (user == null)
{
// Check if there is an account with that username already
User? targetUsername = await this.database.Users.FirstOrDefaultAsync(u => u.Username == npTicket.Username);
if (targetUsername != null)
{
ulong targetPlatform = npTicket.Platform == Platform.RPCS3
? targetUsername.LinkedRpcnId
: targetUsername.LinkedPsnId;
// only make a link request if the user doesn't already have an account linked for that platform
if (targetPlatform != 0)
{
Logger.Warn($"New user tried to login but their name is already taken, username={username}", LogArea.Login);
return this.StatusCode(403, "");
}
// if there is already a pending link request don't create another
bool linkAttemptExists = await this.database.PlatformLinkAttempts.AnyAsync(p =>
p.Platform == npTicket.Platform &&
p.PlatformId == npTicket.UserId &&
p.UserId == targetUsername.UserId);
if (linkAttemptExists) return this.StatusCode(403, "");
PlatformLinkAttempt linkAttempt = new()
{
Platform = npTicket.Platform,
UserId = targetUsername.UserId,
IPAddress = ipAddress,
Timestamp = TimeHelper.TimestampMillis,
PlatformId = npTicket.UserId,
};
this.database.PlatformLinkAttempts.Add(linkAttempt);
await this.database.SaveChangesAsync();
Logger.Success($"User '{npTicket.Username}' tried to login but platform isn't linked, platform={npTicket.Platform}", LogArea.Login);
return this.StatusCode(403, "");
}
if (!ServerConfiguration.Instance.Authentication.AutomaticAccountCreation)
{
Logger.Warn($"Unknown user tried to connect username={username}", LogArea.Login);
return this.StatusCode(403, "");
}
// create account for user if they don't exist
user = await this.database.CreateUser(username, "$");
user.Password = null;
user.LinkedRpcnId = npTicket.Platform == Platform.RPCS3 ? npTicket.UserId : 0;
user.LinkedPsnId = npTicket.Platform != Platform.RPCS3 ? npTicket.UserId : 0;
await this.database.SaveChangesAsync();
Logger.Success($"Created new user for {username}, platform={npTicket.Platform}", LogArea.Login);
}
// automatically change username if it doesn't match
else if (user.Username != npTicket.Username)
{
bool usernameExists = await this.database.Users.AnyAsync(u => u.Username == npTicket.Username);
if (usernameExists)
{
Logger.Warn($"{npTicket.Platform} user changed their name to a name that is already taken," +
$" oldName='{user.Username}', newName='{npTicket.Username}'", LogArea.Login);
return this.StatusCode(403, "");
}
Logger.Info($"User's username has changed, old='{user.Username}', new='{npTicket.Username}', platform={npTicket.Platform}", LogArea.Login);
user.Username = username;
this.database.PlatformLinkAttempts.RemoveWhere(p => p.UserId == user.UserId);
// unlink other platforms because the names no longer match
if (npTicket.Platform == Platform.RPCS3)
user.LinkedPsnId = 0;
else
user.LinkedRpcnId = 0;
await this.database.SaveChangesAsync();
}
GameToken? token = await this.database.GameTokens.Include(t => t.User)
.FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == npTicket.Username && t.TicketHash == npTicket.TicketHash);
if (token != null)
{
Logger.Warn($"Rejecting duplicate ticket from {username}", LogArea.Login);
return this.StatusCode(403, "");
}
token = await this.database.AuthenticateUser(user, npTicket, ipAddress);
if (token == null)
{
Logger.Warn($"Unable to find/generate a token for username {npTicket.Username}", LogArea.Login);
return this.StatusCode(403, "");
}
if (user.IsBanned)
{
Logger.Error($"User {npTicket.Username} tried to login but is banned", LogArea.Login);
return this.StatusCode(403, "");
}
Logger.Success($"Successfully logged in user {user.Username} as {token.GameVersion} client", LogArea.Login);
user.LastLogin = TimeHelper.TimestampMillis;
await this.database.SaveChangesAsync();
// Create a new room on LBP2/3/Vita
if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user.UserId, token.GameVersion, token.Platform);
return this.Ok
(
new LoginResult
{
AuthTicket = "MM_AUTH=" + token.UserToken,
ServerBrand = VersionHelper.EnvVer,
TitleStorageUrl = ServerConfiguration.Instance.GameApiExternalUrl,
}.Serialize()
);
}
}