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>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.1</RuntimeFrameworkVersion>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||
<Version>2.2.5.0</Version>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using IW4MAdmin.Application.EventParsers;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using IW4MAdmin.Application.RconParsers;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
@ -39,6 +40,10 @@ namespace IW4MAdmin.Application
|
||||
public IList<IRConParser> AdditionalRConParsers { get; }
|
||||
public IList<IEventParser> AdditionalEventParsers { get; }
|
||||
|
||||
public ITokenAuthentication TokenAuthenticator => Authenticator;
|
||||
|
||||
public ITokenAuthentication Authenticator => _authenticator;
|
||||
|
||||
static ApplicationManager Instance;
|
||||
readonly List<AsyncStatus> TaskStatuses;
|
||||
List<Command> Commands;
|
||||
@ -52,6 +57,7 @@ namespace IW4MAdmin.Application
|
||||
readonly IPageList PageList;
|
||||
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
|
||||
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
|
||||
readonly ITokenAuthentication _authenticator;
|
||||
|
||||
private ApplicationManager()
|
||||
{
|
||||
@ -70,6 +76,7 @@ namespace IW4MAdmin.Application
|
||||
AdditionalRConParsers = new List<IRConParser>();
|
||||
OnServerEvent += OnGameEvent;
|
||||
OnServerEvent += EventApi.OnGameEvent;
|
||||
_authenticator = new TokenAuthentication();
|
||||
}
|
||||
|
||||
private async void OnGameEvent(object sender, GameEventArgs args)
|
||||
@ -369,6 +376,7 @@ namespace IW4MAdmin.Application
|
||||
Commands.Add(new CPing());
|
||||
Commands.Add(new CSetGravatar());
|
||||
Commands.Add(new CNextMap());
|
||||
Commands.Add(new GenerateTokenCommand());
|
||||
|
||||
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>
|
||||
|
||||
<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;
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex.Set;
|
||||
@ -46,7 +46,7 @@
|
||||
</div>
|
||||
}
|
||||
<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>
|
||||
|
||||
<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;
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
@foreach (var snapshot in Model)
|
||||
{
|
||||
<!-- 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)
|
||||
{
|
||||
<!-- 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();
|
||||
IEventParser GenerateDynamicEventParser();
|
||||
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>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.1</RuntimeFrameworkVersion>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2.1.0</Version>
|
||||
<Version>2.2.0</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
|
@ -1,9 +1,10 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebfrontCore.Controllers
|
||||
{
|
||||
@ -60,5 +61,12 @@ namespace WebfrontCore.Controllers
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
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>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.2.1</RuntimeFrameworkVersion>
|
||||
<RazorCompileOnBuild>false</RazorCompileOnBuild>
|
||||
<RazorCompileOnPublish>false</RazorCompileOnPublish>
|
||||
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
|
||||
<RazorCompileOnBuild>true</RazorCompileOnBuild>
|
||||
<RazorCompileOnPublish>true</RazorCompileOnPublish>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<TypeScriptToolsVersion>2.6</TypeScriptToolsVersion>
|
||||
<PackageId>RaidMax.IW4MAdmin.WebfrontCore</PackageId>
|
||||
@ -13,7 +13,7 @@
|
||||
<Company>Forever None</Company>
|
||||
<Product>IW4MAdmin</Product>
|
||||
<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>
|
||||
<PackageProjectUrl>https://raidmax.org/IW4MAdmin</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin</RepositoryUrl>
|
||||
|
Loading…
Reference in New Issue
Block a user