update schema to support unique guid + game combinations

This commit is contained in:
RaidMax 2022-06-15 19:37:34 -05:00
parent deeb1dea87
commit 8ae6561f4e
46 changed files with 5449 additions and 181 deletions

View File

@ -634,7 +634,7 @@ namespace IW4MAdmin.Application
public EFClient FindActiveClient(EFClient client) => client.ClientNumber < 0 ?
GetActiveClients()
.FirstOrDefault(c => c.NetworkId == client.NetworkId) ?? client :
.FirstOrDefault(c => c.NetworkId == client.NetworkId && c.GameName == client.GameName) ?? client :
client;
public ClientService GetClientService()

View File

@ -75,7 +75,7 @@ namespace IW4MAdmin
{
ServerLogger.LogDebug("Client slot #{clientNumber} now reserved", clientFromLog.ClientNumber);
EFClient client = await Manager.GetClientService().GetUnique(clientFromLog.NetworkId);
var client = await Manager.GetClientService().GetUnique(clientFromLog.NetworkId, GameName);
// first time client is connecting to server
if (client == null)
@ -118,7 +118,7 @@ namespace IW4MAdmin
public override async Task OnClientDisconnected(EFClient client)
{
if (!GetClientsAsList().Any(_client => _client.NetworkId == client.NetworkId))
if (GetClientsAsList().All(eachClient => eachClient.NetworkId != client.NetworkId))
{
using (LogContext.PushProperty("Server", ToString()))
{
@ -449,7 +449,7 @@ namespace IW4MAdmin
Clients[E.Origin.ClientNumber] = E.Origin;
try
{
E.Origin.GameName = (Reference.Game?)GameName;
E.Origin.GameName = (Reference.Game)GameName;
E.Origin = await OnClientConnected(E.Origin);
E.Target = E.Origin;
}
@ -517,7 +517,7 @@ namespace IW4MAdmin
E.Target.SetLevel(Permission.User, E.Origin);
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId,
E.Target.CurrentAlias?.IPAddress);
E.Target.GameName, E.Target.CurrentAlias?.IPAddress);
await Manager.GetPenaltyService().Create(unflagPenalty);
}
@ -763,7 +763,7 @@ namespace IW4MAdmin
private async Task OnClientUpdate(EFClient origin)
{
var client = Manager.GetActiveClients().FirstOrDefault(c => c.NetworkId == origin.NetworkId);
var client = GetClientsAsList().FirstOrDefault(c => c.NetworkId == origin.NetworkId);
if (client == null)
{
@ -980,7 +980,7 @@ namespace IW4MAdmin
!string.IsNullOrEmpty(client.Name) && (client.Ping != 999 || client.IsBot)))
{
client.CurrentServer = this;
client.GameName = (Reference.Game?)GameName;
client.GameName = (Reference.Game)GameName;
var e = new GameEvent
{
@ -1530,7 +1530,7 @@ namespace IW4MAdmin
ServerLogger.LogDebug("Creating unban penalty for {targetClient}", targetClient.ToString());
targetClient.SetLevel(Permission.User, originClient);
await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId,
targetClient.NetworkId, targetClient.CurrentAlias?.IPAddress);
targetClient.NetworkId, targetClient.GameName, targetClient.CurrentAlias?.IPAddress);
await Manager.GetPenaltyService().Create(unbanPenalty);
}

View File

@ -9,40 +9,42 @@ namespace IW4MAdmin.Application.Misc
{
internal class TokenAuthentication : ITokenAuthentication
{
private readonly ConcurrentDictionary<long, TokenState> _tokens;
private readonly ConcurrentDictionary<string, TokenState> _tokens;
private readonly RandomNumberGenerator _random;
private static readonly TimeSpan TimeoutPeriod = new TimeSpan(0, 0, 120);
private static readonly TimeSpan TimeoutPeriod = new(0, 0, 120);
private const short TokenLength = 4;
public TokenAuthentication()
{
_tokens = new ConcurrentDictionary<long, TokenState>();
_tokens = new ConcurrentDictionary<string, TokenState>();
_random = RandomNumberGenerator.Create();
}
public bool AuthorizeToken(long networkId, string token)
public bool AuthorizeToken(ITokenIdentifier authInfo)
{
var authorizeSuccessful = _tokens.ContainsKey(networkId) && _tokens[networkId].Token == token;
var key = BuildKey(authInfo);
var authorizeSuccessful = _tokens.ContainsKey(key) && _tokens[key].Token == key;
if (authorizeSuccessful)
{
_tokens.TryRemove(networkId, out _);
_tokens.TryRemove(key, out _);
}
return authorizeSuccessful;
}
public TokenState GenerateNextToken(long networkId)
public TokenState GenerateNextToken(ITokenIdentifier authInfo)
{
TokenState state;
var genKey = BuildKey(authInfo);
if (_tokens.ContainsKey(networkId))
if (_tokens.ContainsKey(genKey))
{
state = _tokens[networkId];
state = _tokens[genKey];
if ((DateTime.Now - state.RequestTime) > TimeoutPeriod)
if (DateTime.Now - state.RequestTime > TimeoutPeriod)
{
_tokens.TryRemove(networkId, out _);
_tokens.TryRemove(genKey, out _);
}
else
@ -53,12 +55,11 @@ namespace IW4MAdmin.Application.Misc
state = new TokenState
{
NetworkId = networkId,
Token = _generateToken(),
TokenDuration = TimeoutPeriod
};
_tokens.TryAdd(networkId, state);
_tokens.TryAdd(genKey, state);
// perform some housekeeping so we don't have built up tokens if they're not ever used
foreach (var (key, value) in _tokens)
@ -96,5 +97,7 @@ namespace IW4MAdmin.Application.Misc
_random.Dispose();
return token.ToString();
}
private string BuildKey(ITokenIdentifier authInfo) => $"{authInfo.NetworkId}_${authInfo.Game}";
}
}

View File

@ -314,9 +314,9 @@ namespace IW4MAdmin.Application.RConParsers
continue;
}
var client = new EFClient()
var client = new EFClient
{
CurrentAlias = new EFAlias()
CurrentAlias = new EFAlias
{
Name = name,
IPAddress = ip

View File

@ -85,7 +85,15 @@ namespace Data.Context
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// make network id unique
modelBuilder.Entity<EFClient>(entity => { entity.HasIndex(e => e.NetworkId).IsUnique(); });
modelBuilder.Entity<EFClient>(entity =>
{
entity.HasIndex(e => e.NetworkId);
entity.HasAlternateKey(client => new
{
client.NetworkId,
client.GameName
});
});
modelBuilder.Entity<EFPenalty>(entity =>
{

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddAlternateKeyToEFClients : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients");
migrationBuilder.Sql("UPDATE `EFClients` set `GameName` = 0 WHERE `GameName` IS NULL");
migrationBuilder.AlterColumn<int>(
name: "GameName",
table: "EFClients",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AddUniqueConstraint(
name: "AK_EFClients_NetworkId_GameName",
table: "EFClients",
columns: new[] { "NetworkId", "GameName" });
migrationBuilder.CreateIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients",
column: "NetworkId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropUniqueConstraint(
name: "AK_EFClients_NetworkId_GameName",
table: "EFClients");
migrationBuilder.DropIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients");
migrationBuilder.AlterColumn<int>(
name: "GameName",
table: "EFClients",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.CreateIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients",
column: "NetworkId",
unique: true);
}
}
}

View File

@ -64,7 +64,7 @@ namespace Data.Migrations.MySql
b.Property<DateTime>("FirstConnection")
.HasColumnType("datetime(6)");
b.Property<int?>("GameName")
b.Property<int>("GameName")
.HasColumnType("int");
b.Property<DateTime>("LastConnection")
@ -90,12 +90,13 @@ namespace Data.Migrations.MySql
b.HasKey("ClientId");
b.HasAlternateKey("NetworkId", "GameName");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.HasIndex("NetworkId");
b.ToTable("EFClients", (string)null);
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddAlternateKeyToEFClients : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients");
migrationBuilder.Sql("UPDATE \"EFClients\" SET \"GameName\" = 0 WHERE \"GameName\" IS NULL");
migrationBuilder.AlterColumn<int>(
name: "GameName",
table: "EFClients",
type: "integer",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "integer",
oldNullable: true);
migrationBuilder.AddUniqueConstraint(
name: "AK_EFClients_NetworkId_GameName",
table: "EFClients",
columns: new[] { "NetworkId", "GameName" });
migrationBuilder.CreateIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients",
column: "NetworkId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropUniqueConstraint(
name: "AK_EFClients_NetworkId_GameName",
table: "EFClients");
migrationBuilder.DropIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients");
migrationBuilder.AlterColumn<int>(
name: "GameName",
table: "EFClients",
type: "integer",
nullable: true,
oldClrType: typeof(int),
oldType: "integer");
migrationBuilder.CreateIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients",
column: "NetworkId",
unique: true);
}
}
}

View File

@ -71,7 +71,7 @@ namespace Data.Migrations.Postgresql
b.Property<DateTime>("FirstConnection")
.HasColumnType("timestamp without time zone");
b.Property<int?>("GameName")
b.Property<int>("GameName")
.HasColumnType("integer");
b.Property<DateTime>("LastConnection")
@ -97,12 +97,13 @@ namespace Data.Migrations.Postgresql
b.HasKey("ClientId");
b.HasAlternateKey("NetworkId", "GameName");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.HasIndex("NetworkId");
b.ToTable("EFClients", (string)null);
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,61 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Sqlite
{
public partial class AddAlternateKeyToEFClients : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients");
migrationBuilder.AlterColumn<int>(
name: "GameName",
table: "EFClients",
type: "INTEGER",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "INTEGER",
oldNullable: true);
migrationBuilder.AddUniqueConstraint(
name: "AK_EFClients_NetworkId_GameName",
table: "EFClients",
columns: new[] { "NetworkId", "GameName" });
migrationBuilder.CreateIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients",
column: "NetworkId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropUniqueConstraint(
name: "AK_EFClients_NetworkId_GameName",
table: "EFClients");
migrationBuilder.DropIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients");
migrationBuilder.AlterColumn<int>(
name: "GameName",
table: "EFClients",
type: "INTEGER",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER");
migrationBuilder.CreateIndex(
name: "IX_EFClients_NetworkId",
table: "EFClients",
column: "NetworkId",
unique: true);
}
}
}

View File

@ -62,7 +62,7 @@ namespace Data.Migrations.Sqlite
b.Property<DateTime>("FirstConnection")
.HasColumnType("TEXT");
b.Property<int?>("GameName")
b.Property<int>("GameName")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastConnection")
@ -88,12 +88,13 @@ namespace Data.Migrations.Sqlite
b.HasKey("ClientId");
b.HasAlternateKey("NetworkId", "GameName");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.HasIndex("NetworkId");
b.ToTable("EFClients", (string)null);
});

View File

@ -63,7 +63,7 @@ namespace Data.Models.Client
public DateTime FirstConnection { get; set; }
[Required]
public DateTime LastConnection { get; set; }
public Reference.Game? GameName { get; set; } = Reference.Game.UKN;
public Reference.Game GameName { get; set; } = Reference.Game.UKN;
public bool Masked { get; set; }
[Required]
public int AliasLinkId { get; set; }

View File

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.15.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.15.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -4,6 +4,7 @@ using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using System.Threading.Tasks;
using SharedLibraryCore.Helpers;
namespace IW4MAdmin.Plugins.Login.Commands
{
@ -18,7 +19,7 @@ namespace IW4MAdmin.Plugins.Login.Commands
RequiresTarget = false;
Arguments = new CommandArgument[]
{
new CommandArgument()
new()
{
Name = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ARGS_PASSWORD"],
Required = true
@ -26,24 +27,29 @@ namespace IW4MAdmin.Plugins.Login.Commands
};
}
public override async Task ExecuteAsync(GameEvent E)
public override async Task ExecuteAsync(GameEvent gameEvent)
{
bool success = E.Owner.Manager.TokenAuthenticator.AuthorizeToken(E.Origin.NetworkId, E.Data);
var success = gameEvent.Owner.Manager.TokenAuthenticator.AuthorizeToken(new TokenIdentifier
{
NetworkId = gameEvent.Origin.NetworkId,
Game = gameEvent.Origin.GameName,
Token = gameEvent.Data
});
if (!success)
{
string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, E.Origin.PasswordSalt));
success = hashedPassword[0] == E.Origin.Password;
var hashedPassword = await Task.FromResult(Hashing.Hash(gameEvent.Data, gameEvent.Origin.PasswordSalt));
success = hashedPassword[0] == gameEvent.Origin.Password;
}
if (success)
{
Plugin.AuthorizedClients[E.Origin.ClientId] = true;
Plugin.AuthorizedClients[gameEvent.Origin.ClientId] = true;
}
_ = success ?
E.Origin.Tell(_translationLookup["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]) :
E.Origin.Tell(_translationLookup["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]);
gameEvent.Origin.Tell(_translationLookup["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]) :
gameEvent.Origin.Tell(_translationLookup["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]);
}
}
}

View File

@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.15.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.15.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -3,6 +3,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Models;
using Data.Models.Client;
using Data.Models.Client.Stats;
using IW4MAdmin.Plugins.Stats;
@ -12,7 +13,6 @@ using Microsoft.Extensions.Logging;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using Stats.Client.Abstractions;
using Stats.Dtos;
using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -50,7 +50,8 @@ namespace Stats.Helpers
{
client.ClientId,
client.CurrentAlias.Name,
client.Level
client.Level,
client.GameName
}).FirstOrDefaultAsync(client => client.ClientId == query.ClientId);
if (clientInfo == null)
@ -111,8 +112,9 @@ namespace Stats.Helpers
Rating = mostRecentRanking?.PerformanceMetric,
All = hitStats,
Servers = _manager.GetServers()
.Select(server => new ServerInfo()
{Name = server.Hostname, IPAddress = server.IP, Port = server.Port})
.Select(server => new ServerInfo
{Name = server.Hostname, IPAddress = server.IP, Port = server.Port, Game = (Reference.Game)server.GameName})
.Where(server => server.Game == clientInfo.GameName)
.ToList(),
Aggregate = hitStats.FirstOrDefault(hit =>
hit.HitLocationId == null && hit.ServerId == serverId && hit.WeaponId == null &&

View File

@ -17,7 +17,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.15.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -20,7 +20,7 @@
</Target>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.9.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.15.1" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -4,7 +4,6 @@ using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Data.Context;
using Data.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
@ -25,26 +24,32 @@ namespace SharedLibraryCore
/// <summary>
/// life span in months
/// </summary>
private const int COOKIE_LIFESPAN = 3;
private const int CookieLifespan = 3;
private static readonly byte[] LocalHost = { 127, 0, 0, 1 };
private static string SocialLink;
private static string SocialTitle;
protected readonly DatabaseContext Context;
private static string _socialLink;
private static string _socialTitle;
protected List<Page> Pages;
protected List<string> PermissionsSet;
protected bool Authorized { get; set; }
protected TranslationLookup Localization { get; }
protected EFClient Client { get; }
protected ApplicationConfiguration AppConfig { get; }
public IManager Manager { get; }
public BaseController(IManager manager)
{
AlertManager = manager.AlertManager;
Manager = manager;
Localization ??= Utilities.CurrentLocalization.LocalizationIndex;
Localization = Utilities.CurrentLocalization.LocalizationIndex;
AppConfig = Manager.GetApplicationSettings().Configuration();
if (AppConfig.EnableSocialLink && SocialLink == null)
if (AppConfig.EnableSocialLink && _socialLink == null)
{
SocialLink = AppConfig.SocialLinkAddress;
SocialTitle = AppConfig.SocialLinkTitle;
_socialLink = AppConfig.SocialLinkAddress;
_socialTitle = AppConfig.SocialLinkTitle;
}
Pages = Manager.GetPageList().Pages
@ -59,7 +64,7 @@ namespace SharedLibraryCore
ViewBag.EnableColorCodes = AppConfig.EnableColorCodes;
ViewBag.Language = Utilities.CurrentLocalization.Culture.TwoLetterISOLanguageName;
Client ??= new EFClient
Client = new EFClient
{
ClientId = -1,
Level = Data.Models.Client.EFClient.Permission.Banned,
@ -67,11 +72,7 @@ namespace SharedLibraryCore
};
}
public IManager Manager { get; }
protected bool Authorized { get; set; }
protected TranslationLookup Localization { get; }
protected EFClient Client { get; }
protected ApplicationConfiguration AppConfig { get; }
protected async Task SignInAsync(ClaimsPrincipal claimsPrinciple)
{
@ -79,7 +80,7 @@ namespace SharedLibraryCore
new AuthenticationProperties
{
AllowRefresh = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(COOKIE_LIFESPAN),
ExpiresUtc = DateTime.UtcNow.AddMonths(CookieLifespan),
IsPersistent = true,
IssuedUtc = DateTime.UtcNow
});
@ -99,7 +100,7 @@ namespace SharedLibraryCore
Client.ClientId = clientId;
Client.NetworkId = clientId == 1
? 0
: User.Claims.First(_claim => _claim.Type == ClaimTypes.PrimarySid).Value
: User.Claims.First(claim => claim.Type == ClaimTypes.PrimarySid).Value
.ConvertGuidToLong(NumberStyles.HexNumber);
Client.Level = (Data.Models.Client.EFClient.Permission)Enum.Parse(
typeof(Data.Models.Client.EFClient.Permission),
@ -107,6 +108,9 @@ namespace SharedLibraryCore
Client.CurrentAlias = new EFAlias
{ Name = User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value };
Authorized = Client.ClientId >= 0;
Client.GameName =
Enum.Parse<Reference.Game>(User.Claims
.First(claim => claim.Type == ClaimTypes.PrimaryGroupSid).Value);
}
}
@ -134,6 +138,7 @@ namespace SharedLibraryCore
new Claim(ClaimTypes.Role, Client.Level.ToString()),
new Claim(ClaimTypes.Sid, Client.ClientId.ToString()),
new Claim(ClaimTypes.PrimarySid, Client.NetworkId.ToString("X")),
new Claim(ClaimTypes.PrimaryGroupSid, Client.GameName.ToString())
};
var claimsIdentity = new ClaimsIdentity(claims, "login");
SignInAsync(new ClaimsPrincipal(claimsIdentity)).Wait();
@ -153,8 +158,8 @@ namespace SharedLibraryCore
ViewBag.Url = AppConfig.WebfrontUrl;
ViewBag.User = Client;
ViewBag.Version = Manager.Version;
ViewBag.SocialLink = SocialLink ?? "";
ViewBag.SocialTitle = SocialTitle;
ViewBag.SocialLink = _socialLink ?? "";
ViewBag.SocialTitle = _socialTitle;
ViewBag.Pages = Pages;
ViewBag.Localization = Utilities.CurrentLocalization.LocalizationIndex;
ViewBag.CustomBranding = shouldUseCommunityName

View File

@ -381,7 +381,7 @@ namespace SharedLibraryCore.Commands
{
// todo: don't do the lookup here
var penalties = await gameEvent.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(gameEvent.Target.AliasLinkId,
gameEvent.Target.CurrentAliasId, gameEvent.Target.NetworkId, gameEvent.Target.CurrentAlias.IPAddress);
gameEvent.Target.CurrentAliasId, gameEvent.Target.NetworkId, gameEvent.Target.GameName, gameEvent.Target.CurrentAlias.IPAddress);
if (penalties
.FirstOrDefault(p =>
@ -897,7 +897,7 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
var existingPenalties = await E.Owner.Manager.GetPenaltyService()
.GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.CurrentAliasId, E.Target.NetworkId, E.Target.IPAddress);
.GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.CurrentAliasId, E.Target.NetworkId, E.Target.GameName, E.Target.IPAddress);
var penalty = existingPenalties.FirstOrDefault(b => b.Type > EFPenalty.PenaltyType.Kick);
if (penalty == null)

View File

@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
namespace SharedLibraryCore.Commands
@ -19,11 +20,15 @@ namespace SharedLibraryCore.Commands
RequiresTarget = false;
}
public override Task ExecuteAsync(GameEvent E)
public override Task ExecuteAsync(GameEvent gameEvent)
{
var state = E.Owner.Manager.TokenAuthenticator.GenerateNextToken(E.Origin.NetworkId);
E.Origin.Tell(string.Format(_translationLookup["COMMANDS_GENERATETOKEN_SUCCESS"], state.Token,
$"{state.RemainingTime} {_translationLookup["GLOBAL_MINUTES"]}", E.Origin.ClientId));
var state = gameEvent.Owner.Manager.TokenAuthenticator.GenerateNextToken(new TokenIdentifier
{
Game = gameEvent.Origin.GameName,
NetworkId = gameEvent.Origin.NetworkId
});
gameEvent.Origin.Tell(string.Format(_translationLookup["COMMANDS_GENERATETOKEN_SUCCESS"], state.Token,
$"{state.RemainingTime} {_translationLookup["GLOBAL_MINUTES"]}", gameEvent.Origin.ClientId));
return Task.CompletedTask;
}

View File

@ -0,0 +1,11 @@
using Data.Models;
using SharedLibraryCore.Interfaces;
namespace SharedLibraryCore.Helpers;
public class TokenIdentifier : ITokenIdentifier
{
public long NetworkId { get; set; }
public Reference.Game Game { get; set; }
public string Token { get; set; }
}

View File

@ -4,7 +4,6 @@ namespace SharedLibraryCore.Helpers
{
public sealed class TokenState
{
public long NetworkId { get; set; }
public DateTime RequestTime { get; set; } = DateTime.Now;
public TimeSpan TokenDuration { get; set; }
public string Token { get; set; }

View File

@ -10,7 +10,7 @@ namespace SharedLibraryCore.Interfaces
Task<T> Delete(T entity);
Task<T> Update(T entity);
Task<T> Get(int entityID);
Task<T> GetUnique(long entityProperty);
Task<T> GetUnique(long entityProperty, object altKey);
Task<IList<T>> Find(Func<T, bool> expression);
}
}

View File

@ -7,16 +7,15 @@ namespace SharedLibraryCore.Interfaces
/// <summary>
/// generates and returns a token for the given network id
/// </summary>
/// <param name="networkId">network id of the players to generate the token for</param>
/// <param name="authInfo">auth information for next token generation</param>
/// <returns>4 character string token</returns>
TokenState GenerateNextToken(long networkId);
TokenState GenerateNextToken(ITokenIdentifier authInfo);
/// <summary>
/// authorizes given token
/// </summary>
/// <param name="networkId">network id of the client to authorize</param>
/// <param name="token">token to authorize</param>
/// <param name="authInfo">auth information</param>
/// <returns>true if token authorized successfully, false otherwise</returns>
bool AuthorizeToken(long networkId, string token);
bool AuthorizeToken(ITokenIdentifier authInfo);
}
}

View File

@ -0,0 +1,11 @@

using Data.Models;
namespace SharedLibraryCore.Interfaces;
public interface ITokenIdentifier
{
long NetworkId { get; }
Reference.Game Game { get; set; }
string Token { get; set; }
}

View File

@ -682,7 +682,7 @@ namespace SharedLibraryCore.Database.Models
// we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID)
var activePenalties = await CurrentServer.Manager.GetPenaltyService()
.GetActivePenaltiesAsync(AliasLinkId, CurrentAliasId, NetworkId, ipAddress);
.GetActivePenaltiesAsync(AliasLinkId, CurrentAliasId, NetworkId, GameName, ipAddress);
var banPenalty = activePenalties.FirstOrDefault(_penalty => _penalty.Type == EFPenalty.PenaltyType.Ban);
var tempbanPenalty =
activePenalties.FirstOrDefault(_penalty => _penalty.Type == EFPenalty.PenaltyType.TempBan);

View File

@ -23,25 +23,25 @@ namespace SharedLibraryCore.Services
{
public class ClientService : IEntityService<EFClient>, IResourceQueryHelper<FindClientRequest, FindClientResult>
{
private static readonly Func<DatabaseContext, long, Task<EFClient>> _getUniqueQuery =
EF.CompileAsyncQuery((DatabaseContext context, long networkId) =>
private static readonly Func<DatabaseContext, long, Reference.Game, Task<EFClient>> GetUniqueQuery =
EF.CompileAsyncQuery((DatabaseContext context, long networkId, Reference.Game game) =>
context.Clients
.Select(_client => new EFClient
.Select(client => new EFClient
{
ClientId = _client.ClientId,
AliasLinkId = _client.AliasLinkId,
Level = _client.Level,
Connections = _client.Connections,
FirstConnection = _client.FirstConnection,
LastConnection = _client.LastConnection,
Masked = _client.Masked,
NetworkId = _client.NetworkId,
TotalConnectionTime = _client.TotalConnectionTime,
AliasLink = _client.AliasLink,
Password = _client.Password,
PasswordSalt = _client.PasswordSalt
ClientId = client.ClientId,
AliasLinkId = client.AliasLinkId,
Level = client.Level,
Connections = client.Connections,
FirstConnection = client.FirstConnection,
LastConnection = client.LastConnection,
Masked = client.Masked,
NetworkId = client.NetworkId,
TotalConnectionTime = client.TotalConnectionTime,
AliasLink = client.AliasLink,
Password = client.Password,
PasswordSalt = client.PasswordSalt
})
.FirstOrDefault(c => c.NetworkId == networkId)
.FirstOrDefault(client => client.NetworkId == networkId && client.GameName == game)
);
private readonly ApplicationConfiguration _appConfig;
@ -235,10 +235,14 @@ namespace SharedLibraryCore.Services
return foundClient.Client;
}
public virtual async Task<EFClient> GetUnique(long entityAttribute)
public virtual async Task<EFClient> GetUnique(long entityAttribute, object altKey = null)
{
if (altKey is not Reference.Game game)
{
throw new ArgumentException($"Alternate key must be of type {nameof(Reference.Game)}");
}
await using var context = _contextFactory.CreateContext(false);
return await _getUniqueQuery(context, entityAttribute);
return await GetUniqueQuery(context, entityAttribute, game);
}
public async Task<EFClient> Update(EFClient temporalClient)
@ -285,7 +289,7 @@ namespace SharedLibraryCore.Services
entity.PasswordSalt = temporalClient.PasswordSalt;
}
entity.GameName ??= temporalClient.GameName;
entity.GameName = temporalClient.GameName;
// update in database
await context.SaveChangesAsync();
@ -758,19 +762,20 @@ namespace SharedLibraryCore.Services
{
await using var context = _contextFactory.CreateContext(false);
return await context.Clients
.Select(_client => new EFClient
.Select(client => new EFClient
{
NetworkId = _client.NetworkId,
ClientId = _client.ClientId,
NetworkId = client.NetworkId,
ClientId = client.ClientId,
CurrentAlias = new EFAlias
{
Name = _client.CurrentAlias.Name
Name = client.CurrentAlias.Name
},
Password = _client.Password,
PasswordSalt = _client.PasswordSalt,
Level = _client.Level
Password = client.Password,
PasswordSalt = client.PasswordSalt,
GameName = client.GameName,
Level = client.Level
})
.FirstAsync(_client => _client.ClientId == clientId);
.FirstAsync(client => client.ClientId == clientId);
}
public async Task<List<EFClient>> GetPrivilegedClients(bool includeName = true)
@ -860,15 +865,16 @@ namespace SharedLibraryCore.Services
// we want to project our results
var iqClientProjection = iqClients.OrderByDescending(_client => _client.LastConnection)
.Select(_client => new PlayerInfo
.Select(client => new PlayerInfo
{
Name = _client.CurrentAlias.Name,
LevelInt = (int)_client.Level,
LastConnection = _client.LastConnection,
ClientId = _client.ClientId,
IPAddress = _client.CurrentAlias.IPAddress.HasValue
? _client.CurrentAlias.SearchableIPAddress
: ""
Name = client.CurrentAlias.Name,
LevelInt = (int)client.Level,
LastConnection = client.LastConnection,
ClientId = client.ClientId,
IPAddress = client.CurrentAlias.IPAddress.HasValue
? client.CurrentAlias.SearchableIPAddress
: "",
Game = client.GameName
});
var clients = await iqClientProjection.ToListAsync();

View File

@ -88,7 +88,7 @@ namespace SharedLibraryCore.Services
throw new NotImplementedException();
}
public Task<EFPenalty> GetUnique(long entityProperty)
public Task<EFPenalty> GetUnique(long entityProperty, object altKey)
{
throw new NotImplementedException();
}
@ -139,10 +139,10 @@ namespace SharedLibraryCore.Services
LinkedPenalties.Contains(pi.Penalty.Type) && pi.Penalty.Active &&
(pi.Penalty.Expires == null || pi.Penalty.Expires > DateTime.UtcNow);
public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int currentAliasId, long networkId,
public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int currentAliasId, long networkId, Reference.Game game,
int? ip = null)
{
var penaltiesByIdentifier = await GetActivePenaltiesByIdentifier(ip, networkId);
var penaltiesByIdentifier = await GetActivePenaltiesByIdentifier(ip, networkId, game);
if (penaltiesByIdentifier.Any())
{
@ -183,12 +183,12 @@ namespace SharedLibraryCore.Services
return activePenalties.OrderByDescending(p => p.When).ToList();
}
public async Task<List<EFPenalty>> GetActivePenaltiesByIdentifier(int? ip, long networkId)
public async Task<List<EFPenalty>> GetActivePenaltiesByIdentifier(int? ip, long networkId, Reference.Game game)
{
await using var context = _contextFactory.CreateContext(false);
var activePenaltiesIds = context.PenaltyIdentifiers.Where(identifier =>
identifier.IPv4Address != null && identifier.IPv4Address == ip || identifier.NetworkId == networkId)
identifier.IPv4Address != null && identifier.IPv4Address == ip || identifier.NetworkId == networkId && identifier.Penalty.Offender.GameName == game)
.Where(FilterById);
return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync();
}
@ -214,12 +214,12 @@ namespace SharedLibraryCore.Services
return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync();
}
public virtual async Task RemoveActivePenalties(int aliasLinkId, long networkId, int? ipAddress = null)
public virtual async Task RemoveActivePenalties(int aliasLinkId, long networkId, Reference.Game game, int? ipAddress = null)
{
await using var context = _contextFactory.CreateContext();
var now = DateTime.UtcNow;
var activePenalties = await GetActivePenaltiesByIdentifier(ipAddress, networkId);
var activePenalties = await GetActivePenaltiesByIdentifier(ipAddress, networkId, game);
if (activePenalties.Any())
{

View File

@ -4,7 +4,7 @@
<OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework>
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2022.6.9.1</Version>
<Version>2022.6.15.1</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Configurations>Debug;Release;Prerelease</Configurations>
@ -19,7 +19,7 @@
<IsPackable>true</IsPackable>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Shared Library for IW4MAdmin</Description>
<PackageVersion>2022.6.9.1</PackageVersion>
<PackageVersion>2022.6.15.1</PackageVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>

View File

@ -1181,7 +1181,8 @@ namespace SharedLibraryCore
Meta = client.Meta,
ReceivedPenalties = client.ReceivedPenalties,
AdministeredPenalties = client.AdministeredPenalties,
Active = client.Active
Active = client.Active,
GameName = client.GameName
};
}
@ -1264,5 +1265,8 @@ namespace SharedLibraryCore
return allRules[index];
}
public static string MakeAbbreviation(string gameName) => string.Join("",
gameName.Split(' ').Select(word => char.ToUpper(word.First())).ToArray());
}
}

View File

@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Logging;
using SharedLibraryCore;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Services;
using WebfrontCore.Controllers.API.Dtos;
using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -100,9 +101,16 @@ namespace WebfrontCore.Controllers.API
if (!Authorized)
{
var tokenData = new TokenIdentifier
{
Game = privilegedClient.GameName,
Token = request.Password,
NetworkId = privilegedClient.NetworkId
};
loginSuccess =
Manager.TokenAuthenticator.AuthorizeToken(privilegedClient.NetworkId, request.Password) ||
(await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(request.Password,
Manager.TokenAuthenticator.AuthorizeToken(tokenData) ||
(await Task.FromResult(Hashing.Hash(request.Password,
privilegedClient.PasswordSalt)))[0] == privilegedClient.Password;
}
@ -120,7 +128,7 @@ namespace WebfrontCore.Controllers.API
var claimsPrinciple = new ClaimsPrincipal(claimsIdentity);
await SignInAsync(claimsPrinciple);
Manager.AddEvent(new GameEvent()
Manager.AddEvent(new GameEvent
{
Origin = privilegedClient,
Type = GameEvent.EventType.Login,
@ -149,7 +157,7 @@ namespace WebfrontCore.Controllers.API
{
if (Authorized)
{
Manager.AddEvent(new GameEvent()
Manager.AddEvent(new GameEvent
{
Origin = Client,
Type = GameEvent.EventType.Logout,

View File

@ -7,7 +7,7 @@ using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using SharedLibraryCore.Helpers;
namespace WebfrontCore.Controllers
{
@ -19,6 +19,7 @@ namespace WebfrontCore.Controllers
}
[HttpGet]
[Obsolete]
public async Task<IActionResult> Login(int clientId, string password)
{
if (clientId == 0 || string.IsNullOrEmpty(password))
@ -29,14 +30,23 @@ namespace WebfrontCore.Controllers
try
{
var privilegedClient = await Manager.GetClientService().GetClientForLogin(clientId);
bool loginSuccess = false;
#if DEBUG
var loginSuccess = false;
if (Utilities.IsDevelopment)
{
loginSuccess = clientId == 1;
#endif
}
if (!Authorized && !loginSuccess)
{
loginSuccess = Manager.TokenAuthenticator.AuthorizeToken(privilegedClient.NetworkId, password) ||
(await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(password, privilegedClient.PasswordSalt)))[0] == privilegedClient.Password;
loginSuccess = Manager.TokenAuthenticator.AuthorizeToken(new TokenIdentifier
{
NetworkId = privilegedClient.NetworkId,
Game = privilegedClient.GameName,
Token = password
}) ||
(await Task.FromResult(Hashing.Hash(password, privilegedClient.PasswordSalt)))[0] ==
privilegedClient.Password;
}
if (loginSuccess)
@ -46,21 +56,22 @@ namespace WebfrontCore.Controllers
new Claim(ClaimTypes.NameIdentifier, privilegedClient.Name),
new Claim(ClaimTypes.Role, privilegedClient.Level.ToString()),
new Claim(ClaimTypes.Sid, privilegedClient.ClientId.ToString()),
new Claim(ClaimTypes.PrimarySid, privilegedClient.NetworkId.ToString("X"))
new Claim(ClaimTypes.PrimarySid, privilegedClient.NetworkId.ToString("X")),
new Claim(ClaimTypes.PrimaryGroupSid, privilegedClient.GameName.ToString())
};
var claimsIdentity = new ClaimsIdentity(claims, "login");
var claimsPrinciple = new ClaimsPrincipal(claimsIdentity);
await SignInAsync(claimsPrinciple);
Manager.AddEvent(new GameEvent()
Manager.AddEvent(new GameEvent
{
Origin = privilegedClient,
Type = GameEvent.EventType.Login,
Owner = Manager.GetServers().First(),
Data = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For")
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
: HttpContext.Connection.RemoteIpAddress.ToString()
: HttpContext.Connection.RemoteIpAddress?.ToString()
});
return Ok($"Welcome {privilegedClient.Name}. You are now logged in");
@ -80,14 +91,14 @@ namespace WebfrontCore.Controllers
{
if (Authorized)
{
Manager.AddEvent(new GameEvent()
Manager.AddEvent(new GameEvent
{
Origin = Client,
Type = GameEvent.EventType.Logout,
Owner = Manager.GetServers().First(),
Data = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For")
? HttpContext.Request.Headers["X-Forwarded-For"].ToString()
: HttpContext.Connection.RemoteIpAddress.ToString()
: HttpContext.Connection.RemoteIpAddress?.ToString()
});
}

View File

@ -10,6 +10,7 @@ using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using WebfrontCore.Permissions;
using WebfrontCore.ViewModels;
@ -274,7 +275,12 @@ namespace WebfrontCore.Controllers
[Authorize]
public string GenerateLoginTokenAsync()
{
var state = Manager.TokenAuthenticator.GenerateNextToken(Client.NetworkId);
var state = Manager.TokenAuthenticator.GenerateNextToken(new TokenIdentifier
{
NetworkId = Client.NetworkId,
Game = Client.GameName
});
return string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_SUCCESS"],
state.Token,
$"{state.RemainingTime} {Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_MINUTES"]}",

View File

@ -47,7 +47,7 @@ namespace WebfrontCore.Controllers
}
var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId,
client.CurrentAliasId, client.NetworkId, client.IPAddress);
client.CurrentAliasId, client.NetworkId, client.GameName, client.IPAddress);
var persistentMetaTask = new[]
{
@ -88,7 +88,7 @@ namespace WebfrontCore.Controllers
var clientDto = new PlayerInfo
{
Name = client.Name,
Game = client.GameName ?? Reference.Game.UKN,
Game = client.GameName,
Level = displayLevel,
LevelInt = displayLevelInt,
ClientId = client.ClientId,
@ -183,7 +183,7 @@ namespace WebfrontCore.Controllers
ClientId = admin.ClientId,
LastConnection = admin.LastConnection,
IsMasked = admin.Masked,
Game = admin.GameName ?? Reference.Game.UKN
Game = admin.GameName
});
}

View File

@ -34,7 +34,12 @@ namespace WebfrontCore.Controllers
{
ClientId = id,
ServerEndpoint = serverId
})).Results.First();
}))?.Results?.First();
if (hitInfo is null)
{
return NotFound();
}
var server = Manager.GetServers().FirstOrDefault(server => server.ToString() == serverId);
long? matchedServerId = null;

View File

@ -36,24 +36,26 @@ namespace WebfrontCore.Middleware
/// <param name="gameEvent"></param>
private void OnGameEvent(object sender, GameEvent gameEvent)
{
if (gameEvent.Type == EventType.ChangePermission &&
gameEvent.Extra is EFClient.Permission perm)
if (gameEvent.Type != EventType.ChangePermission || gameEvent.Extra is not EFClient.Permission perm)
{
return;
}
lock (_privilegedClientIds)
{
switch (perm)
{
// we want to remove the claims when the client is demoted
if (perm < EFClient.Permission.Trusted)
{
lock (_privilegedClientIds)
case < EFClient.Permission.Trusted:
{
_privilegedClientIds.RemoveAll(id => id == gameEvent.Target.ClientId);
}
break;
}
// and add if promoted
else if (perm > EFClient.Permission.Trusted &&
!_privilegedClientIds.Contains(gameEvent.Target.ClientId))
{
lock (_privilegedClientIds)
case > EFClient.Permission.Trusted when !_privilegedClientIds.Contains(gameEvent.Target.ClientId):
{
_privilegedClientIds.Add(gameEvent.Target.ClientId);
break;
}
}
}
@ -62,10 +64,16 @@ namespace WebfrontCore.Middleware
public async Task Invoke(HttpContext context)
{
// we want to load the initial list of privileged clients
if (_privilegedClientIds.Count == 0)
bool hasAny;
lock (_privilegedClientIds)
{
hasAny = _privilegedClientIds.Any();
}
if (hasAny)
{
var ids = (await _manager.GetClientService().GetPrivilegedClients())
.Select(_client => _client.ClientId);
.Select(client => client.ClientId);
lock (_privilegedClientIds)
{
@ -74,13 +82,19 @@ namespace WebfrontCore.Middleware
}
// sid stores the clientId
string claimsId = context.User.Claims.FirstOrDefault(_claim => _claim.Type == ClaimTypes.Sid)?.Value;
var claimsId = context.User.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Sid)?.Value;
if (!string.IsNullOrEmpty(claimsId))
{
int clientId = int.Parse(claimsId);
var clientId = int.Parse(claimsId);
bool hasKey;
lock (_privilegedClientIds)
{
hasKey = _privilegedClientIds.Contains(clientId);
}
// they've been removed
if (!_privilegedClientIds.Contains(clientId) && clientId != 1)
if (!hasKey && clientId != 1)
{
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}

View File

@ -54,7 +54,8 @@ public class BanInfoResourceQueryHelper : IResourceQueryHelper<BanInfoRequest, B
client.NetworkId,
client.AliasLinkId,
client.ClientId,
client.CurrentAlias.IPAddress
client.CurrentAlias.IPAddress,
client.GameName
}).ToListAsync();
var results = new List<BanInfo>();
@ -101,7 +102,6 @@ public class BanInfoResourceQueryHelper : IResourceQueryHelper<BanInfoRequest, B
.Select(alias => alias.LinkId)
.ToListAsync()).Distinct();
matchedPenalties = await context.Penalties.Where(penalty => penalty.Type == EFPenalty.PenaltyType.Ban)
.Where(penalty => penalty.Expires == null || penalty.Expires > lateDateTime)
.Where(penalty => penalty.LinkId != null && linkIds.Contains(penalty.LinkId.Value))
@ -158,6 +158,7 @@ public class BanInfoResourceQueryHelper : IResourceQueryHelper<BanInfoRequest, B
ClientId = matchingClient.ClientId,
NetworkId = matchingClient.NetworkId,
IPAddress = matchingClient.IPAddress,
Game = matchingClient.GameName,
AssociatedPenalties = relatedEntities,
AttachedPenalty = allPenalties.FirstOrDefault(penalty =>

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Data.Models;
namespace WebfrontCore.QueryHelpers.Models;
@ -9,6 +10,7 @@ public class BanInfo
public int ClientId { get; set; }
public int? IPAddress { get; set; }
public long NetworkId { get; set; }
public Reference.Game Game { get; set; }
public PenaltyInfo AttachedPenalty { get; set; }
public IEnumerable<PenaltyInfo> AssociatedPenalties { get; set; }
}

View File

@ -12,22 +12,23 @@
<div class="d-flex p-15 mr-md-10 w-full w-md-200 bg-very-dark-dm bg-light-ex-lm rounded">
<div class="align-self-center ">
<a asp-controller="Client" asp-action="Profile" asp-route-id="@ban.ClientId" class="font-size-18 no-decoration">@ban.ClientName</a>
<br/>
<div class="badge">@Utilities.MakeAbbreviation(ViewBag.Localization[$"GAME_{ban.Game}"])</div>
<has-permission entity="ClientGuid" required-permission="Read">
<div class="text-muted">@ban.NetworkId.ToString("X")</div>
</has-permission>
<has-permission entity="ClientIPAddress" required-permission="Read">
<div class="text-muted">@ban.IPAddress.ConvertIPtoString()</div>
</has-permission>
<br/>
@if (ban.AttachedPenalty is not null)
{
<br/>
<div class="text-muted font-weight-light">@ban.AttachedPenalty.Offense.CapClientName(30)</div>
<div class="text-danger font-weight-light">@ban.AttachedPenalty.DateTime.ToStandardFormat()</div>
<div class="btn profile-action mt-10 w-100" data-action="unban" data-action-id="@ban.ClientId">Unban</div>
<div class="btn profile-action w-100" data-action="unban" data-action-id="@ban.ClientId">Unban</div>
}
else
{
<br/>
<div class="align-self-end text-muted font-weight-light">
<span class="oi oi-warning font-size-12"></span>
<span>Link-Only Ban</span>
@ -56,6 +57,7 @@
<div class="text-muted font-weight-light">@associatedEntity.Offense.CapClientName(30)</div>
<div class="text-danger font-weight-light">@associatedEntity.DateTime.ToStandardFormat()</div>
<div class="btn profile-action mt-10 w-100" data-action="unban" data-action-id="@associatedEntity.OffenderInfo.ClientId">Unban</div>
<div class="badge">@Utilities.MakeAbbreviation(ViewBag.Localization[$"GAME_{ban.Game}"])</div>
</div>
}
</div>

View File

@ -15,9 +15,7 @@
foreach (var snapshot in Model.ClientHistory.ClientCounts)
{
snapshot.MapAlias = GetMapName(snapshot.Map);
};
string MakeAbbreviation(string gameName) => string.Join("", gameName.Split(' ').Select(word => char.ToUpper(word.First())).ToArray());
}
}
<div class="card mt-20 mb-20 ml-0 mr-0 p-0">
@ -44,7 +42,7 @@
class="text-light align-self-center">
<i class="oi oi-spreadsheet ml-5 mr-5"></i>
</a>
<span class="ml-5 mr-5 text-light badge font-weight-light" data-toggle="tooltip" data-title="@ViewBag.Localization[$"GAME_{Model.Game}"]">@MakeAbbreviation(ViewBag.Localization[$"GAME_{Model.Game}"])</span>
<span class="ml-5 mr-5 text-light badge font-weight-light" data-toggle="tooltip" data-title="@ViewBag.Localization[$"GAME_{Model.Game}"]">@Utilities.MakeAbbreviation(ViewBag.Localization[$"GAME_{Model.Game}"])</span>
</div>
</div>
<!-- second column -->