Improved Wave startup logging

This commit is contained in:
Mia Rose Winter 2024-02-07 12:19:33 +01:00
parent eded7f08d5
commit 452c93fca0
Signed by: miawinter
GPG key ID: 4B6F6A83178F595E

View file

@ -6,8 +6,6 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Metrics;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using StackExchange.Redis; using StackExchange.Redis;
using Tomlyn.Extensions.Configuration; using Tomlyn.Extensions.Configuration;
@ -17,31 +15,33 @@
using Wave.Services; using Wave.Services;
using Wave.Utilities; using Wave.Utilities;
var logMessages = new List<string>();
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Configuration builder.Configuration
.AddJsonFile("/configuration/config.json", true, false) .AddJsonFile("/configuration/config.json", true, false)
.AddYamlFile("/configuration/config.yml", true, false) .AddYamlFile("/configuration/config.yml", true, false)
.AddTomlFile("/configuration/config.toml", true, false) .AddTomlFile("/configuration/config.toml", true, false)
.AddIniFile( "/configuration/config.ini", true, false) .AddIniFile( "/configuration/config.ini", true, false)
.AddXmlFile( "/configuration/config.xml", true, false) .AddXmlFile( "/configuration/config.xml", true, false)
.AddEnvironmentVariables("WAVE_"); .AddEnvironmentVariables("WAVE_");
builder.Services.AddRazorComponents().AddInteractiveServerComponents(); builder.Services.AddRazorComponents().AddInteractiveServerComponents();
builder.Services.AddControllers(options => { builder.Services.AddControllers(options => {
options.OutputFormatters.Add(new SyndicationFeedFormatter()); options.OutputFormatters.Add(new SyndicationFeedFormatter());
}); });
builder.Services.AddOutputCache(); builder.Services.AddOutputCache();
#region Data Protection & Redis #region Data Protection & Redis
if (builder.Configuration.GetConnectionString("Redis") is { } redisUri) { if (builder.Configuration.GetConnectionString("Redis") is { } redisUri) {
var redis = ConnectionMultiplexer.Connect(redisUri); var redis = ConnectionMultiplexer.Connect(redisUri);
builder.Services.AddDataProtection() builder.Services.AddDataProtection()
.PersistKeysToStackExchangeRedis(redis) .PersistKeysToStackExchangeRedis(redis)
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration() { .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration() {
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256 ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
}); });
builder.Services.AddStackExchangeRedisCache(options => { builder.Services.AddStackExchangeRedisCache(options => {
options.Configuration = redisUri; options.Configuration = redisUri;
options.InstanceName = "WaveDistributedCache"; options.InstanceName = "WaveDistributedCache";
@ -51,12 +51,12 @@
options.InstanceName = "WaveOutputCache"; options.InstanceName = "WaveOutputCache";
}); });
} else { } else {
builder.Services.AddDataProtection() builder.Services.AddDataProtection()
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration() { .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration() {
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256 ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
}); });
Console.WriteLine("No Redis connection string found."); logMessages.Add("No Redis connection string found.");
} }
#endregion #endregion
@ -73,56 +73,56 @@
// Moderators: Can delete Articles / take them Offline // Moderators: Can delete Articles / take them Offline
// Admins: Can do anything, and assign roles to other users // Admins: Can do anything, and assign roles to other users
builder.Services.AddAuthorizationBuilder() builder.Services.AddAuthorizationBuilder()
.AddPolicy("ArticleEditPermissions", p => p.RequireRole("Author", "Admin")) .AddPolicy("ArticleEditPermissions", p => p.RequireRole("Author", "Admin"))
.AddPolicy("ArticleReviewPermissions", p => p.RequireRole("Reviewer", "Admin")) .AddPolicy("ArticleReviewPermissions", p => p.RequireRole("Reviewer", "Admin"))
.AddPolicy("ArticleDeletePermissions", p => p.RequireRole("Moderator", "Admin")) .AddPolicy("ArticleDeletePermissions", p => p.RequireRole("Moderator", "Admin"))
.AddPolicy("CategoryManagePermissions", p => p.RequireRole("Admin")) .AddPolicy("CategoryManagePermissions", p => p.RequireRole("Admin"))
.AddPolicy("RoleAssignPermissions", p => p.RequireRole("Admin")) .AddPolicy("RoleAssignPermissions", p => p.RequireRole("Admin"))
.AddPolicy("ArticleEditOrReviewPermissions", p => p.RequireRole("Author", "Reviewer", "Admin")); .AddPolicy("ArticleEditOrReviewPermissions", p => p.RequireRole("Author", "Reviewer", "Admin"));
builder.Services.AddAuthentication(options => { builder.Services.AddAuthentication(options => {
options.DefaultScheme = IdentityConstants.ApplicationScheme; options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
}).AddIdentityCookies(); }).AddIdentityCookies();
#endregion #endregion
#region Identity #region Identity
string connectionString = builder.Configuration.GetConnectionString("DefaultConnection") string connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContextFactory<ApplicationDbContext>(options => builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseNpgsql(connectionString)); options.UseNpgsql(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>() .AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>() .AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager() .AddSignInManager()
.AddDefaultTokenProviders() .AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<UserClaimsFactory>(); .AddClaimsPrincipalFactory<UserClaimsFactory>();
#endregion #endregion
#region Services #region Services
builder.Services.AddLocalization(options => { builder.Services.AddLocalization(options => {
options.ResourcesPath = "Resources"; options.ResourcesPath = "Resources";
}); });
builder.Services.AddScoped<ImageService>(); builder.Services.AddScoped<ImageService>();
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
builder.Services.Configure<Customization>(builder.Configuration.GetSection(nameof(Customization))); builder.Services.Configure<Customization>(builder.Configuration.GetSection(nameof(Customization)));
builder.Services.AddCascadingValue("TitlePrefix", builder.Services.AddCascadingValue("TitlePrefix",
sf => (sf.GetService<IOptions<Customization>>()?.Value.AppName ?? "Wave") + " - "); sf => (sf.GetService<IOptions<Customization>>()?.Value.AppName ?? "Wave") + " - ");
var smtpConfig = builder.Configuration.GetSection("Email:Smtp"); var smtpConfig = builder.Configuration.GetSection("Email:Smtp");
if (smtpConfig.Exists()) { if (smtpConfig.Exists()) {
builder.Services.Configure<SmtpConfiguration>(smtpConfig); builder.Services.Configure<SmtpConfiguration>(smtpConfig);
builder.Services.AddScoped<IEmailSender<ApplicationUser>, SmtpEmailSender>(); builder.Services.AddScoped<IEmailSender<ApplicationUser>, SmtpEmailSender>();
} else { } else {
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>(); builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
Console.WriteLine("No Email provider configured."); logMessages.Add("No Email provider configured.");
} }
#endregion #endregion
@ -130,30 +130,30 @@
string[] cultures = ["en-US", "en-GB", "de-DE"]; string[] cultures = ["en-US", "en-GB", "de-DE"];
builder.Services.Configure<RequestLocalizationOptions>(options => { builder.Services.Configure<RequestLocalizationOptions>(options => {
options.ApplyCurrentCultureToResponseHeaders = true; options.ApplyCurrentCultureToResponseHeaders = true;
options.FallBackToParentCultures = true; options.FallBackToParentCultures = true;
options.FallBackToParentUICultures = true; options.FallBackToParentUICultures = true;
options.SetDefaultCulture(cultures[0]) options.SetDefaultCulture(cultures[0])
.AddSupportedCultures(cultures) .AddSupportedCultures(cultures)
.AddSupportedUICultures(cultures); .AddSupportedUICultures(cultures);
}); });
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) { if (app.Environment.IsDevelopment()) {
app.UseMigrationsEndPoint(); app.UseMigrationsEndPoint();
} else { } else {
app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseExceptionHandler("/Error", createScopeForErrors: true);
} }
app.UseStaticFiles(new StaticFileOptions { app.UseStaticFiles(new StaticFileOptions {
ContentTypeProvider = new FileExtensionContentTypeProvider { ContentTypeProvider = new FileExtensionContentTypeProvider {
Mappings = { Mappings = {
[".jxl"] = "image/jxl" [".jxl"] = "image/jxl"
} }
} }
}); });
app.UseAntiforgery(); app.UseAntiforgery();
@ -167,20 +167,22 @@
app.UseRequestLocalization(); app.UseRequestLocalization();
foreach (string message in logMessages) {
app.Logger.LogInformation("{message}", message);
}
{ {
using var scope = app.Services.CreateScope(); using var scope = app.Services.CreateScope();
using var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); using var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate(); context.Database.Migrate();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>(); var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
if (userManager.GetUsersInRoleAsync("Admin").Result.Any() is false) {
if (userManager.GetUsersInRoleAsync("Admin").Result.Any() is false) { string admin = Guid.NewGuid().ToString("N")[..16];
string admin = Guid.NewGuid().ToString("N")[..16]; app.Logger.LogWarning("There is currently no user in your installation with the admin role, " +
Console.WriteLine( "go to /Admin and use the following password to self promote your account: {admin}", admin);
"There is currently no user in your installation with the admin role, " + File.WriteAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "admin.txt"), admin);
"go to /Admin and use the following password to self promote your account: " + admin); }
File.WriteAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "admin.txt"), admin);
}
} }
app.Run(); app.Run();