Add root routing to control panel

This commit is contained in:
Jordan Brown
2018-11-13 15:24:48 -05:00
parent 6b695e6c49
commit 8fa9d93b64
6 changed files with 68 additions and 27 deletions
@@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Configuration
/// <summary>
/// Configuration options for the web control panel
/// </summary>
sealed class ControlPanelConfiguration
public sealed class ControlPanelConfiguration
{
/// <summary>
/// The key for the <see cref="Microsoft.Extensions.Configuration.IConfigurationSection"/> the <see cref="ControlPanelConfiguration"/> resides in
@@ -51,6 +51,11 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
readonly bool requireInstance;
/// <summary>
/// If <see cref="ApiHeaders"/> are required
/// </summary>
readonly bool requireHeaders;
/// <summary>
/// Construct an <see cref="ApiController"/>
/// </summary>
@@ -58,7 +63,8 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
/// <param name="logger">The value of <see cref="Logger"/></param>
/// <param name="requireInstance">The value of <see cref="requireInstance"/></param>
public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance)
/// <param name="requireHeaders">The value of <see cref="requireHeaders"/></param>
public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance, bool requireHeaders)
{
DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
if (authenticationContextFactory == null)
@@ -67,6 +73,7 @@ namespace Tgstation.Server.Host.Controllers
AuthenticationContext = authenticationContextFactory.CurrentAuthenticationContext;
Instance = AuthenticationContext?.InstanceUser?.Instance;
this.requireInstance = requireInstance;
this.requireHeaders = requireHeaders;
}
/// <inheritdoc />
@@ -113,8 +120,11 @@ namespace Tgstation.Server.Host.Controllers
}
catch (InvalidOperationException e)
{
await BadRequest(new ErrorMessage { Message = e.Message }).ExecuteResultAsync(context).ConfigureAwait(false);
return;
if (requireHeaders)
{
await BadRequest(new ErrorMessage { Message = e.Message }).ExecuteResultAsync(context).ConfigureAwait(false);
return;
}
}
if (ModelState?.IsValid == false)
@@ -138,7 +148,8 @@ namespace Tgstation.Server.Host.Controllers
}
}
Logger.LogDebug("Request made by User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", AuthenticationContext?.User.Id.ToString(CultureInfo.InvariantCulture), ApiHeaders.ApiVersion, ApiHeaders.UserAgent, Request.Method, Request.Path, Request.QueryString, ApiHeaders.InstanceId);
if (ApiHeaders != null)
Logger.LogDebug("Request made by User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}", AuthenticationContext?.User.Id.ToString(CultureInfo.InvariantCulture), ApiHeaders.ApiVersion, ApiHeaders.UserAgent, Request.Method, Request.Path, Request.QueryString, ApiHeaders.InstanceId);
try
{
@@ -1,14 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Wangkanai.Detection;
namespace Tgstation.Server.Host.Controllers
{
@@ -39,6 +44,16 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
readonly IIdentityCache identityCache;
/// <summary>
/// The <see cref="IBrowserResolver"/> for the <see cref="HomeController"/>
/// </summary>
readonly IBrowserResolver browserResolver;
/// <summary>
/// The <see cref="ControlPanelConfiguration"/> for the <see cref="HomeController"/>
/// </summary>
readonly ControlPanelConfiguration controlPanelConfiguration;
/// <summary>
/// Construct a <see cref="HomeController"/>
/// </summary>
@@ -49,27 +64,45 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/></param>
/// <param name="application">The value of <see cref="application"/></param>
/// <param name="identityCache">The value of <see cref="identityCache"/></param>
/// <param name="browserResolver">The value of <see cref="browserResolver"/></param>
/// <param name="controlPanelConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="controlPanelConfiguration"/></param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application, IIdentityCache identityCache, ILogger<HomeController> logger) : base(databaseContext, authenticationContextFactory, logger, false)
public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application, IIdentityCache identityCache, IBrowserResolver browserResolver, IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions, ILogger<HomeController> logger) : base(databaseContext, authenticationContextFactory, logger, false, false)
{
this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory));
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache));
this.browserResolver = browserResolver ?? throw new ArgumentNullException(nameof(browserResolver));
controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions));
}
/// <summary>
/// Returns the version of the <see cref="Application"/>
/// Main page of the <see cref="Application"/>
/// </summary>
/// <returns><see cref="Application.Version"/></returns>
/// <returns>The <see cref="Api.Models.ServerInformation"/> of the <see cref="Application"/> if a properly authenticated API request, the web control panel if on a browser and enabled, <see cref="UnauthorizedResult"/> otherwise</returns>
[TgsAuthorize]
[AllowAnonymous]
[HttpGet]
public JsonResult Home() => Json(new Api.Models.ServerInformation
public IActionResult Home(CancellationToken cancellationToken)
{
Version = application.Version,
ApiVersion = ApiHeaders.Version
});
if (AuthenticationContext != null)
return Json(new Api.Models.ServerInformation
{
Version = application.Version,
ApiVersion = ApiHeaders.Version
});
//if we are using a browser and the control panel, soft redirect to the app page
if (controlPanelConfiguration.Enable && browserResolver.Browser.Type != BrowserType.Generic)
{
Logger.LogDebug("Unauthorized browser request (User-Agent: \"{0}\"), loading control panel...", browserResolver.UserAgent);
return File("~/index.html", MediaTypeNames.Text.Html);
}
return Unauthorized();
}
/// <summary>
/// Attempt to authenticate a <see cref="User"/> using <see cref="ApiController.ApiHeaders"/>
@@ -79,6 +112,9 @@ namespace Tgstation.Server.Host.Controllers
[HttpPost]
public async Task<IActionResult> CreateToken(CancellationToken cancellationToken)
{
if (ApiHeaders == null)
return BadRequest(new Api.Models.ErrorMessage { Message = "Missing API headers!" });
if (ApiHeaders.IsTokenAuthentication)
return BadRequest(new Api.Models.ErrorMessage { Message = "Cannot create a token using another token!" });
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
/// <param name="requireInstance">If the <see cref="ModelController{TModel}"/> requires an <see cref="IAuthenticationContext.InstanceUser"/></param>
public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance) : base(databaseContext, authenticationContextFactory, logger, requireInstance) { }
public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger, bool requireInstance) : base(databaseContext, authenticationContextFactory, logger, requireInstance, true) { }
/// <summary>
/// Attempt to create a <paramref name="model"/>
@@ -227,6 +227,9 @@ namespace Tgstation.Server.Host.Core
options.SerializerSettings.Converters = new[] { new VersionConverter() };
});
//enable browser detection
services.AddDetectionCore().AddBrowser();
//enable CORS if necessary
if (controlPanelConfiguration.AllowAnyOrigin || controlPanelConfiguration.AllowedOrigins?.Count > 0)
services.AddCors();
@@ -26,9 +26,7 @@
<NpmInstallStampFile>ClientApp/node_modules/.install-stamp</NpmInstallStampFile>
</PropertyGroup>
<Target Name="NpmInstall"
Inputs="ClientApp/package-lock.json;ClientApp/package.json"
Outputs="$(NpmInstallStampFile)">
<Target Name="NpmInstall" Inputs="ClientApp/package-lock.json" Outputs="$(NpmInstallStampFile)">
<Exec WorkingDirectory="ClientApp" Command="npm ci" />
<Touch Files="$(NpmInstallStampFile)" AlwaysCreate="true" />
</Target>
@@ -38,17 +36,11 @@
<Content Remove="ClientApp\package.json" />
<Content Remove="ClientApp\public\manifest.json" />
<Content Remove="ClientApp\tsconfig.json" />
<Content Remove="ClientApp\tsconfig.prod.json" />
<Content Remove="ClientApp\tsconfig.test.json" />
<Content Remove="ClientApp\tslint.json" />
<ClientApp Include="ClientApp\**\*" />
<ClientApp Include="ClientApp\src;ClientApp\public;ClientApp\tsconfig.json;ClientApp\tslint.json;ClientApp\package-lock.json" />
</ItemGroup>
<Target Name="NpmBuild"
BeforeTargets="BeforeBuild"
DependsOnTargets="NpmInstall"
Inputs="@(ClientApp)"
Outputs="wwwroot\index.html">
<Target Name="NpmBuild" BeforeTargets="BeforeBuild" DependsOnTargets="NpmInstall" Inputs="@(ClientApp)" Outputs="wwwroot\index.html">
<Exec WorkingDirectory="ClientApp" Command="npm run build" />
</Target>
@@ -81,6 +73,7 @@
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="4.5.0" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="4.5.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.2.4" />
<PackageReference Include="Wangkanai.Detection.Browser" Version="2.0.0-beta9" />
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="1.8.10" />
</ItemGroup>
@@ -107,8 +100,6 @@
<None Include="ClientApp\package.json" />
<None Include="ClientApp\public\manifest.json" />
<None Include="ClientApp\tsconfig.json" />
<None Include="ClientApp\tsconfig.prod.json" />
<None Include="ClientApp\tsconfig.test.json" />
<None Include="ClientApp\tslint.json" />
</ItemGroup>