Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions src/WebApp/Pages/Error.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,5 @@
ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h1 class="text-danger-emphasis">@ViewData["Title"]</h1>
<p class="lead">An error occurred while processing your request.</p>
9 changes: 1 addition & 8 deletions src/WebApp/Pages/Error.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;

namespace WebApp.Pages;

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet() => RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
public class ErrorModel : PageModel;
8 changes: 2 additions & 6 deletions src/WebApp/Pages/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,9 @@
<div class="mt-4">
<small class="text-body-secondary">
<a href="https://github.com/gaepdit/server-checkup" target="_blank" rel="noopener">Server Checkup</a>
@if (Model.Version is not null)
@if (!string.IsNullOrEmpty(AppSettings.Version))
{
@: | Version @Model.Version
}
@if (Model.Build is not null)
{
@: <em>(@Model.Build)</em>
@:| Version: <em>@AppSettings.Version</em>
}
</small>
</div>
Expand Down
12 changes: 0 additions & 12 deletions src/WebApp/Pages/Index.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using ServerCheckupLibrary;
using System.Reflection;
using WebApp.Platform;

namespace WebApp.Pages;
Expand All @@ -14,9 +13,6 @@ public class IndexModel : PageModel
public ResultMessage? ExternalServiceCheckMessage { get; private set; }
public ResultMessage? DotnetVersionCheckMessage { get; private set; }

public string? Version { get; private set; }
public string? Build { get; private set; }

public IActionResult OnGet()
{
if (User.Identity is not { IsAuthenticated: true })
Expand Down Expand Up @@ -47,14 +43,6 @@ public IActionResult OnGet()
DotnetVersionCheckMessage = new ResultMessage(Context.Info, ".NET version checks are disabled.");
}

// Get app version.
var entryAssembly = Assembly.GetEntryAssembly();
var segments = (entryAssembly?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
.InformationalVersion ?? entryAssembly?.GetName().Version?.ToString() ?? "").Split('+');

Version = segments[0];
if (segments.Length > 0) Build = segments[1][..Math.Min(7, segments[1].Length)];

return Page();
}
}
1 change: 1 addition & 0 deletions src/WebApp/Pages/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<link rel="icon" href="@Url.Content("~/icon.svg")" type="image/svg+xml">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
<partial name="Shared/_MonitoringScriptsPartial" />
</head>
<body>
<div class="container pt-3">
Expand Down
39 changes: 39 additions & 0 deletions src/WebApp/Pages/Shared/_MonitoringScriptsPartial.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@using WebApp.Platform
@{
// DataDog
var ddClientToken = AppSettings.DataDogSettings.ClientToken;
var ddApplicationId = AppSettings.DataDogSettings.ApplicationId;
const string service = "checkup";

if (!string.IsNullOrEmpty(ddClientToken) && !string.IsNullOrEmpty(ddApplicationId))
{
<script asp-add-nonce>
@* @formatter:off *@
(function(h,o,u,n,d) {
h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
d=o.createElement(u);d.async=1;d.src=n;d.crossOrigin=''
n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
})(window,document,'script','https://www.datadoghq-browser-agent.com/us3/v7/datadog-rum.js','DD_RUM');
@* @formatter:on *@

window.DD_RUM.onReady(function () {
window.DD_RUM.init({
clientToken: '@ddClientToken',
applicationId: '@ddApplicationId',
site: 'us3.datadoghq.com',
service: '@service',
env: '@AppSettings.ShortEnv',
version: '@AppSettings.SimpleVersion',
forwardErrorsToLogs: true,
forwardConsoleLogs: ['error', 'warn'],
sessionSampleRate: 100,
sessionReplaySampleRate: 20,
beforeSend: (log) => {
// discard debug logs
if (log.level === 'debug') return false;
},
});
})
</script>
}
}
42 changes: 41 additions & 1 deletion src/WebApp/Platform/AppSettings.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using JetBrains.Annotations;
using ServerCheckupLibrary.Checks;
using System.Reflection;

namespace WebApp.Platform;

public static class AppSettings
internal static class AppSettings
{
public static CheckEmailOptions CheckEmailOptions { get; } = new();
public static CheckDatabaseOptions CheckDatabaseOptions { get; } = new();
Expand All @@ -12,6 +13,45 @@ public static class AppSettings
public static CheckDotnetVersionOptions CheckDotnetVersionOptions { get; } = new();
public static string ServerName { get; set; } = string.Empty;
public static DevOptions DevOptions { get; } = new();

public static string? Version { get; private set; }
public static string? SimpleVersion => Version?.Split('+')[0];

private static string Env { get; } = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "unknown";
public static string ShortEnv => Env switch { "Production" => "prod", "Staging" => "uat", _ => "dev" };
public static DataDog DataDogSettings { get; } = new();

public record DataDog
{
public string? ClientToken { get; [UsedImplicitly] init; }
public string? ApplicationId { get; [UsedImplicitly] init; }
}

public static void BindAppSettings(this IHostApplicationBuilder builder)
{
// Set default timeout for regular expressions.
// https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices#use-time-out-values
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100));

Version = GetVersion();
ServerName = builder.Configuration.GetValue<string>(nameof(ServerName)) ?? "Unknown";

builder.Configuration.GetSection(nameof(CheckEmailOptions)).Bind(CheckEmailOptions);
builder.Configuration.GetSection(nameof(CheckDatabaseOptions)).Bind(CheckDatabaseOptions);
builder.Configuration.GetSection(nameof(CheckDatabaseEmailOptions)).Bind(CheckDatabaseEmailOptions);
builder.Configuration.GetSection(nameof(CheckExternalServiceOptions)).Bind(CheckExternalServiceOptions);
builder.Configuration.GetSection(nameof(CheckDotnetVersionOptions)).Bind(CheckDotnetVersionOptions);
builder.Configuration.GetSection(nameof(DevOptions)).Bind(DevOptions);
builder.Configuration.GetSection(nameof(DataDogSettings)).Bind(DataDogSettings);
}

private static string GetVersion()
{
var entryAssembly = Assembly.GetEntryAssembly();
var segments = (entryAssembly?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
.InformationalVersion ?? entryAssembly?.GetName().Version?.ToString() ?? "").Split('+');
return segments[0] + (segments.Length > 0 ? $"+{segments[1][..Math.Min(7, segments[1].Length)]}" : "");
}
}

[UsedImplicitly(ImplicitUseTargetFlags.Members)]
Expand Down
48 changes: 48 additions & 0 deletions src/WebApp/Platform/AuthenticationServices.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;

namespace WebApp.Platform;

public static class AuthenticationServices
{
public static void ConfigureAuthentication(this IHostApplicationBuilder builder)
{
if (AppSettings.DevOptions.UseLocalAuth)
{
// When running locally, use a built-in authenticated user.
builder.Services
.AddAuthentication(LocalAuthenticationHandler.BasicAuthenticationScheme)
.AddScheme<AuthenticationSchemeOptions, LocalAuthenticationHandler>(
LocalAuthenticationHandler.BasicAuthenticationScheme, null);
}
else
{
// When running on the server, require an OIDC login provider (configured in the app settings file).
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(configureOptions: options =>
{
var configSection = builder.Configuration.GetSection("OIDC");

options.Authority = configSection["Authority"];
options.ClientId = configSection["ClientId"];
options.ClientSecret = configSection["ClientSecret"];
options.CallbackPath = configSection["CallbackPath"];

options.Scope.Add("email");
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ResponseType = OpenIdConnectResponseType.Code;
options.MapInboundClaims = false;
options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "email" };
});
}
}
}
56 changes: 5 additions & 51 deletions src/WebApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,67 +1,21 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using ServerCheckupLibrary.Checks;
using ServerCheckupLibrary.Hubs;
using WebApp.Platform;
using ZLogger;

var builder = WebApplication.CreateBuilder(args);
var isDevelopment = builder.Environment.IsDevelopment();

// Configure logging
// Configure logging.
builder.Logging.ClearProviders().AddZLoggerConsole(options => options.UseJsonFormatter());

// Persist data protection keys
// Persist data protection keys.
builder.Services.AddDataProtection();

// Bind application settings.
builder.Configuration.GetSection(nameof(CheckEmailOptions)).Bind(AppSettings.CheckEmailOptions);
builder.Configuration.GetSection(nameof(CheckDatabaseOptions)).Bind(AppSettings.CheckDatabaseOptions);
builder.Configuration.GetSection(nameof(CheckDatabaseEmailOptions)).Bind(AppSettings.CheckDatabaseEmailOptions);
builder.Configuration.GetSection(nameof(CheckExternalServiceOptions)).Bind(AppSettings.CheckExternalServiceOptions);
builder.Configuration.GetSection(nameof(CheckDotnetVersionOptions)).Bind(AppSettings.CheckDotnetVersionOptions);
builder.Configuration.GetSection(nameof(DevOptions)).Bind(AppSettings.DevOptions);
AppSettings.ServerName = builder.Configuration.GetValue<string>(nameof(AppSettings.ServerName)) ?? "Unknown";

// Configure authentication.
if (AppSettings.DevOptions.UseLocalAuth)
{
// When running locally, use a built-in authenticated user.
builder.Services
.AddAuthentication(LocalAuthenticationHandler.BasicAuthenticationScheme)
.AddScheme<AuthenticationSchemeOptions, LocalAuthenticationHandler>(
LocalAuthenticationHandler.BasicAuthenticationScheme, null);
}
else
{
// When running on the server, require an OIDC login provider (configured in the appsettings file).
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(configureOptions: options =>
{
var configSection = builder.Configuration.GetSection("OIDC");

options.Authority = configSection["Authority"];
options.ClientId = configSection["ClientId"];
options.ClientSecret = configSection["ClientSecret"];
options.CallbackPath = configSection["CallbackPath"];

options.Scope.Add("email");
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ResponseType = OpenIdConnectResponseType.Code;
options.MapInboundClaims = false;
options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "email" };
});
}
builder.BindAppSettings();

// Configure authentication and authorization.
builder.ConfigureAuthentication();
builder.Services.AddAuthorization();

// Add SignalR
Expand Down
6 changes: 5 additions & 1 deletion src/WebApp/appsettings-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,12 @@
"Instance": "https://login.microsoftonline.com/",
"Domain": "qualified.domain.name",
"TenantId": "00000000-0000-0000-0000-000000000000",
"ClientId": "00000000-0000-0000-00000000000000000",
"ClientId": "00000000-0000-0000-0000-000000000000",
"CallbackPath": "/signin-oidc"
},
"DataDogSettings": {
"ClientToken": "pub0000000000000000000",
"ApplicationId": "00000000-0000-0000-0000-000000000000"
},
"AllowedHosts": "*"
}
4 changes: 4 additions & 0 deletions src/WebApp/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
"ClientId": "",
"CallbackPath": "/signin-oidc"
},
"DataDogSettings": {
"ClientToken": "",
"ApplicationId": ""
},
"Logging": {
"LogLevel": {
"Default": "Information",
Expand Down
Loading