begin implementation of token authentication
replacing password authentication ingame precompile views for webfront issue #66 issue #52
This commit is contained in:
parent
a362caebac
commit
5e36bf4316
@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.2.1</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||||
<Version>2.2.5.0</Version>
|
<Version>2.2.5.0</Version>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using IW4MAdmin.Application.API.Master;
|
using IW4MAdmin.Application.API.Master;
|
||||||
using IW4MAdmin.Application.EventParsers;
|
using IW4MAdmin.Application.EventParsers;
|
||||||
|
using IW4MAdmin.Application.Misc;
|
||||||
using IW4MAdmin.Application.RconParsers;
|
using IW4MAdmin.Application.RconParsers;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Commands;
|
using SharedLibraryCore.Commands;
|
||||||
@ -39,6 +40,10 @@ namespace IW4MAdmin.Application
|
|||||||
public IList<IRConParser> AdditionalRConParsers { get; }
|
public IList<IRConParser> AdditionalRConParsers { get; }
|
||||||
public IList<IEventParser> AdditionalEventParsers { get; }
|
public IList<IEventParser> AdditionalEventParsers { get; }
|
||||||
|
|
||||||
|
public ITokenAuthentication TokenAuthenticator => Authenticator;
|
||||||
|
|
||||||
|
public ITokenAuthentication Authenticator => _authenticator;
|
||||||
|
|
||||||
static ApplicationManager Instance;
|
static ApplicationManager Instance;
|
||||||
readonly List<AsyncStatus> TaskStatuses;
|
readonly List<AsyncStatus> TaskStatuses;
|
||||||
List<Command> Commands;
|
List<Command> Commands;
|
||||||
@ -52,6 +57,7 @@ namespace IW4MAdmin.Application
|
|||||||
readonly IPageList PageList;
|
readonly IPageList PageList;
|
||||||
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
|
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
|
||||||
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
|
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
|
||||||
|
readonly ITokenAuthentication _authenticator;
|
||||||
|
|
||||||
private ApplicationManager()
|
private ApplicationManager()
|
||||||
{
|
{
|
||||||
@ -70,6 +76,7 @@ namespace IW4MAdmin.Application
|
|||||||
AdditionalRConParsers = new List<IRConParser>();
|
AdditionalRConParsers = new List<IRConParser>();
|
||||||
OnServerEvent += OnGameEvent;
|
OnServerEvent += OnGameEvent;
|
||||||
OnServerEvent += EventApi.OnGameEvent;
|
OnServerEvent += EventApi.OnGameEvent;
|
||||||
|
_authenticator = new TokenAuthentication();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnGameEvent(object sender, GameEventArgs args)
|
private async void OnGameEvent(object sender, GameEventArgs args)
|
||||||
@ -369,6 +376,7 @@ namespace IW4MAdmin.Application
|
|||||||
Commands.Add(new CPing());
|
Commands.Add(new CPing());
|
||||||
Commands.Add(new CSetGravatar());
|
Commands.Add(new CSetGravatar());
|
||||||
Commands.Add(new CNextMap());
|
Commands.Add(new CNextMap());
|
||||||
|
Commands.Add(new GenerateTokenCommand());
|
||||||
|
|
||||||
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
|
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
|
||||||
{
|
{
|
||||||
|
94
Application/Misc/TokenAuthentication.cs
Normal file
94
Application/Misc/TokenAuthentication.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc
|
||||||
|
{
|
||||||
|
class TokenAuthentication : ITokenAuthentication
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<long, TokenState> _tokens;
|
||||||
|
private readonly RNGCryptoServiceProvider _random;
|
||||||
|
private readonly static TimeSpan _timeoutPeriod = new TimeSpan(0, 0, 30);
|
||||||
|
private const short TOKEN_LENGTH = 4;
|
||||||
|
|
||||||
|
private class TokenState
|
||||||
|
{
|
||||||
|
public long NetworkId { get; set; }
|
||||||
|
public DateTime RequestTime { get; set; } = DateTime.Now;
|
||||||
|
public string Token { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenAuthentication()
|
||||||
|
{
|
||||||
|
_tokens = new ConcurrentDictionary<long, TokenState>();
|
||||||
|
_random = new RNGCryptoServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AuthorizeToken(long networkId, string token)
|
||||||
|
{
|
||||||
|
bool authorizeSuccessful = _tokens.ContainsKey(networkId) && _tokens[networkId].Token == token;
|
||||||
|
|
||||||
|
if (authorizeSuccessful)
|
||||||
|
{
|
||||||
|
_tokens.TryRemove(networkId, out TokenState _);
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizeSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateNextToken(long networkId)
|
||||||
|
{
|
||||||
|
TokenState state = null;
|
||||||
|
if (_tokens.ContainsKey(networkId))
|
||||||
|
{
|
||||||
|
state = _tokens[networkId];
|
||||||
|
|
||||||
|
if ((DateTime.Now - state.RequestTime) < _timeoutPeriod)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_tokens.TryRemove(networkId, out TokenState _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state = new TokenState()
|
||||||
|
{
|
||||||
|
NetworkId = networkId,
|
||||||
|
Token = _generateToken()
|
||||||
|
};
|
||||||
|
|
||||||
|
_tokens.TryAdd(networkId, state);
|
||||||
|
return state.Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string _generateToken()
|
||||||
|
{
|
||||||
|
bool validCharacter(char c)
|
||||||
|
{
|
||||||
|
// this ensure that the characters are 0-9, A-Z, a-z
|
||||||
|
return (c > 47 && c < 58) || (c > 64 && c < 91) || (c > 96 && c < 123);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder token = new StringBuilder();
|
||||||
|
|
||||||
|
while (token.Length < TOKEN_LENGTH)
|
||||||
|
{
|
||||||
|
byte[] charSet = new byte[1];
|
||||||
|
_random.GetBytes(charSet);
|
||||||
|
|
||||||
|
if (validCharacter((char)charSet[0]))
|
||||||
|
{
|
||||||
|
token.Append((char)charSet[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_random.Dispose();
|
||||||
|
return token.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
@model List<IW4MAdmin.Plugins.Stats.Web.Dtos.TopStatsInfo>
|
@model List<dynamic>
|
||||||
<h4 class="pb-2 text-center ">@ViewBag.Title</h4>
|
<h4 class="pb-2 text-center ">@ViewBag.Title</h4>
|
||||||
|
|
||||||
<div id="stats_top_players" class="striped border-top border-bottom">
|
<div id="stats_top_players" class="striped border-top border-bottom">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@model List<IW4MAdmin.Plugins.Stats.Web.Dtos.TopStatsInfo>
|
@model List<dynamic>
|
||||||
@{
|
@{
|
||||||
Layout = null;
|
Layout = null;
|
||||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex.Set;
|
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex.Set;
|
||||||
@ -46,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<span class="text-muted pl-1 pr-1" style="font-size: 1.25rem;">—</span>
|
<span class="text-muted pl-1 pr-1" style="font-size: 1.25rem;">—</span>
|
||||||
@Html.ActionLink(stat.Name, "ProfileAsync", "Client", new { id = stat.ClientId })
|
@Html.ActionLink((string)stat.Name, "ProfileAsync", "Client", new { id = stat.ClientId })
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="text-primary">@stat.Performance</span><span class="text-muted"> @loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"]</span><br />
|
<span class="text-primary">@stat.Performance</span><span class="text-muted"> @loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"]</span><br />
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@model IEnumerable<IW4MAdmin.Plugins.Stats.Models.EFACSnapshot>
|
@model IEnumerable<dynamic>
|
||||||
@{
|
@{
|
||||||
Layout = null;
|
Layout = null;
|
||||||
}
|
}
|
||||||
@ -7,7 +7,7 @@
|
|||||||
@foreach (var snapshot in Model)
|
@foreach (var snapshot in Model)
|
||||||
{
|
{
|
||||||
<!-- this is not ideal, but I didn't want to manually write out all the properties-->
|
<!-- this is not ideal, but I didn't want to manually write out all the properties-->
|
||||||
var snapProperties = typeof(IW4MAdmin.Plugins.Stats.Models.EFACSnapshot).GetProperties();
|
var snapProperties = Model.First().GetType().GetProperties();
|
||||||
foreach (var prop in snapProperties)
|
foreach (var prop in snapProperties)
|
||||||
{
|
{
|
||||||
<!-- this is another ugly hack-->
|
<!-- this is another ugly hack-->
|
||||||
|
22
SharedLibraryCore/Commands/GenerateTokenCommand.cs
Normal file
22
SharedLibraryCore/Commands/GenerateTokenCommand.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Commands
|
||||||
|
{
|
||||||
|
public class GenerateTokenCommand : Command
|
||||||
|
{
|
||||||
|
public GenerateTokenCommand() :
|
||||||
|
base("generatetoken", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_DESC"], "gt", EFClient.Permission.Trusted, false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public override Task ExecuteAsync(GameEvent E)
|
||||||
|
{
|
||||||
|
string token = E.Owner.Manager.TokenAuthenticator.GenerateNextToken(E.Origin.NetworkId);
|
||||||
|
var _event = token == null ?
|
||||||
|
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_FAIL"]) :
|
||||||
|
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_SUCCESS"]} {token}");
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,5 +45,6 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
IRConParser GenerateDynamicRConParser();
|
IRConParser GenerateDynamicRConParser();
|
||||||
IEventParser GenerateDynamicEventParser();
|
IEventParser GenerateDynamicEventParser();
|
||||||
string Version { get;}
|
string Version { get;}
|
||||||
|
ITokenAuthentication TokenAuthenticator { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
SharedLibraryCore/Interfaces/ITokenAuthentication.cs
Normal file
23
SharedLibraryCore/Interfaces/ITokenAuthentication.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces
|
||||||
|
{
|
||||||
|
public interface ITokenAuthentication
|
||||||
|
{
|
||||||
|
/// <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>
|
||||||
|
/// <returns>4 character string token</returns>
|
||||||
|
string GenerateNextToken(long networkId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// authorizes given token
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">token to authorize</param>
|
||||||
|
/// <returns>true if token authorized successfully, false otherwise</returns>
|
||||||
|
bool AuthorizeToken(long networkId, string token);
|
||||||
|
}
|
||||||
|
}
|
@ -3,11 +3,11 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.2.1</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||||
<ApplicationIcon />
|
<ApplicationIcon />
|
||||||
<StartupObject />
|
<StartupObject />
|
||||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||||
<Version>2.1.0</Version>
|
<Version>2.2.0</Version>
|
||||||
<Authors>RaidMax</Authors>
|
<Authors>RaidMax</Authors>
|
||||||
<Company>Forever None</Company>
|
<Company>Forever None</Company>
|
||||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using System.Security.Claims;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace WebfrontCore.Controllers
|
namespace WebfrontCore.Controllers
|
||||||
{
|
{
|
||||||
@ -60,5 +61,12 @@ namespace WebfrontCore.Controllers
|
|||||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
return RedirectToAction("Index", "Home");
|
return RedirectToAction("Index", "Home");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> GenerateLoginTokenAsync()
|
||||||
|
{
|
||||||
|
return Json(new { token = Manager.TokenAuthenticator.GenerateNextToken(Client.NetworkId) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.2.1</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||||
<RazorCompileOnBuild>false</RazorCompileOnBuild>
|
<RazorCompileOnBuild>true</RazorCompileOnBuild>
|
||||||
<RazorCompileOnPublish>false</RazorCompileOnPublish>
|
<RazorCompileOnPublish>true</RazorCompileOnPublish>
|
||||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||||
<TypeScriptToolsVersion>2.6</TypeScriptToolsVersion>
|
<TypeScriptToolsVersion>2.6</TypeScriptToolsVersion>
|
||||||
<PackageId>RaidMax.IW4MAdmin.WebfrontCore</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.WebfrontCore</PackageId>
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<Company>Forever None</Company>
|
<Company>Forever None</Company>
|
||||||
<Product>IW4MAdmin</Product>
|
<Product>IW4MAdmin</Product>
|
||||||
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server</Description>
|
<Description>IW4MAdmin is a complete server administration tool for IW4x and most Call of Duty® dedicated server</Description>
|
||||||
<Copyright>2018</Copyright>
|
<Copyright>2019</Copyright>
|
||||||
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
|
<PackageLicenseUrl>https://github.com/RaidMax/IW4M-Admin/blob/master/LICENSE</PackageLicenseUrl>
|
||||||
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>
|
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin</RepositoryUrl>
|
||||||
|
Loading…
Reference in New Issue
Block a user