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
27 changes: 14 additions & 13 deletions cli/SimpleModule.Cli/Commands/Install/InstallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,24 @@ public override int Execute(CommandContext context, InstallSettings settings)
$"Installing [green]{Markup.Escape(settings.PackageId)}[/] into [blue]{Markup.Escape(Path.GetFileName(hostCsproj))}[/]..."
);

var args = $"add \"{hostCsproj}\" package {settings.PackageId}";
var psi = new ProcessStartInfo
{
FileName = "dotnet",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
};
psi.ArgumentList.Add("add");
psi.ArgumentList.Add(hostCsproj);
psi.ArgumentList.Add("package");
psi.ArgumentList.Add(settings.PackageId);
if (!string.IsNullOrWhiteSpace(settings.Version))
{
args += $" --version {settings.Version}";
psi.ArgumentList.Add("--version");
psi.ArgumentList.Add(settings.Version);
}

using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = args,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
},
};
using var process = new Process { StartInfo = psi };

process.Start();
var outputTask = process.StandardOutput.ReadToEndAsync();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<script>
@using SimpleModule.Core.Security
@inject ICspNonce CspNonce

<script nonce="@CspNonce.Value">
function applyTheme() {
var stored = localStorage.getItem('theme');
var shouldBeDark = stored === 'dark' || (!stored && window.matchMedia('(prefers-color-scheme: dark)').matches);
Expand Down
4 changes: 3 additions & 1 deletion framework/SimpleModule.Blazor/Components/InertiaShell.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
@using Microsoft.AspNetCore.Components.Web
@using SimpleModule.Core.Security
@inject ICspNonce CspNonce

<!DOCTYPE html>
<html lang="en">
Expand All @@ -12,7 +14,7 @@
@HeadContent
}
<DarkModeScript />
<script type="importmap">
<script type="importmap" nonce="@CspNonce.Value">
{
"imports": {
"react": "/js/vendor/react.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
@using SimpleModule.Core.Menu
@using SimpleModule.Core.Security
@inherits LayoutComponentBase
@inject IMenuRegistry MenuRegistry
@inject ICspNonce CspNonce

<div class="app-layout">
<!-- Mobile header -->
Expand Down Expand Up @@ -106,7 +108,7 @@
</div>

<script suppress-error="BL9992" src="/js/shell.js?v=@CacheBuster"></script>
<script suppress-error="BL9992">
<script suppress-error="BL9992" nonce="@CspNonce.Value">
// Sidebar collapse
(function() {
var sb = document.getElementById('app-sidebar');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
@using SimpleModule.Core.Menu
@using SimpleModule.Core.Security
@inherits LayoutComponentBase
@inject IPublicMenuProvider? PublicMenuProvider
@inject ICspNonce CspNonce

<nav class="sticky top-0 z-50 border-b border-border bg-surface-overlay" style="backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);">
<div class="max-w-7xl mx-auto flex items-center px-6 py-3">
Expand Down Expand Up @@ -173,7 +175,7 @@
}
}

<script suppress-error="BL9992">
<script suppress-error="BL9992" nonce="@CspNonce.Value">
(function() {
if (window.__publicOverlayInit) return;
window.__publicOverlayInit = true;
Expand Down
8 changes: 8 additions & 0 deletions framework/SimpleModule.Core/Security/CspNonce.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Security.Cryptography;

namespace SimpleModule.Core.Security;

public sealed class CspNonce : ICspNonce
{
public string Value { get; } = Convert.ToBase64String(RandomNumberGenerator.GetBytes(16));
}
6 changes: 6 additions & 0 deletions framework/SimpleModule.Core/Security/ICspNonce.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SimpleModule.Core.Security;

public interface ICspNonce
{
string Value { get; }
}
31 changes: 31 additions & 0 deletions framework/SimpleModule.Hosting/SimpleModuleHostExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using SimpleModule.Core.Inertia;
using SimpleModule.Core.Menu;
using SimpleModule.Core.RateLimiting;
using SimpleModule.Core.Security;
using SimpleModule.Database;
using SimpleModule.Database.Health;
using SimpleModule.Database.Interceptors;
Expand Down Expand Up @@ -85,6 +86,8 @@ public static WebApplicationBuilder AddSimpleModuleInfrastructure(
// Register default IPublicMenuProvider if no module provides one
builder.Services.TryAddScoped<IPublicMenuProvider, DefaultPublicMenuProvider>();

builder.Services.AddScoped<ICspNonce, CspNonce>();

if (options.EnableHealthChecks)
{
builder
Expand Down Expand Up @@ -143,6 +146,34 @@ public static async Task UseSimpleModuleInfrastructure(this WebApplication app)
}

app.UseHttpsRedirection();
app.Use(
async (context, next) =>
{
var nonce = context.RequestServices.GetRequiredService<ICspNonce>().Value;
context.Response.OnStarting(() =>
{
var headers = context.Response.Headers;
headers["X-Content-Type-Options"] = "nosniff";
headers["X-Frame-Options"] = "SAMEORIGIN";
headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
headers["X-Permitted-Cross-Domain-Policies"] = "none";
headers["Content-Security-Policy"] =
$"default-src 'none'; "
+ $"script-src 'self' 'nonce-{nonce}'; "
+ $"style-src 'self' 'unsafe-inline' fonts.googleapis.com; "
+ $"font-src 'self' fonts.gstatic.com; "
+ $"connect-src 'self'; "
+ $"img-src 'self' data:; "
+ $"object-src 'none'; "
+ $"base-uri 'self'; "
+ $"form-action 'self'; "
+ $"frame-ancestors 'none'; "
+ $"upgrade-insecure-requests;";
return Task.CompletedTask;
});
await next();
}
);
app.UseInertia();
UseStaticFileCaching(app);
app.MapStaticAssets();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ private HashSet<string> ReadInstalledPackages()
{
try
{
var doc = XDocument.Load(csproj);
var xmlSettings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
};
using var xmlReader = XmlReader.Create(csproj, xmlSettings);
var doc = XDocument.Load(xmlReader);
var packageRefs = doc.Descendants("PackageReference")
.Select(e => e.Attribute("Include")?.Value)
.Where(v => v is not null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
@page "/oauth-callback"
@using Microsoft.AspNetCore.Components.Web
@using SimpleModule.Core.Security
@inject ICspNonce CspNonce

<PageTitle>Authenticating... - SimpleModule</PageTitle>

<p class="text-center text-muted">Completing authentication...</p>
<script suppress-error="BL9992">
<script suppress-error="BL9992" nonce="@CspNonce.Value">
window.location.href = '/' + window.location.search;
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,7 @@ private static async Task<IResult> HandleAsync(HttpContext context)
identity.AddClaim("permission", permission);
}

identity.SetScopes(
Scopes.OpenId,
Scopes.Profile,
Scopes.Email,
AuthConstants.RolesScope
);
identity.SetScopes(Scopes.OpenId, Scopes.Profile, Scopes.Email, AuthConstants.RolesScope);

foreach (var claim in identity.Claims)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,7 @@ is not null
// Allow password grant in Development for load testing (k6, etc.)
if (configuration.GetValue<bool>("OpenIddict:AllowPasswordGrant"))
{
descriptor.Permissions.Add(
OpenIddictConstants.Permissions.GrantTypes.Password
);
descriptor.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Password);
}

// Allow additional redirect URIs from configuration
Expand Down
13 changes: 13 additions & 0 deletions modules/Users/src/SimpleModule.Users/Services/UserSeedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace SimpleModule.Users.Services;
public partial class UserSeedService(
IServiceProvider serviceProvider,
IConfiguration configuration,
IHostEnvironment environment,
ILogger<UserSeedService> logger
) : IHostedService
{
Expand Down Expand Up @@ -116,6 +117,8 @@ string role
};

var password = configuration[passwordConfigKey] ?? defaultPassword;
if (password == defaultPassword && !environment.IsDevelopment())
LogDefaultPasswordWarning(logger, email, passwordConfigKey);
var result = await userManager.CreateAsync(user, password);
if (result.Succeeded)
{
Expand All @@ -136,6 +139,16 @@ string role
[LoggerMessage(Level = LogLevel.Information, Message = "Seeding user: {Email}")]
private static partial void LogSeedingUser(ILogger logger, string email);

[LoggerMessage(
Level = LogLevel.Warning,
Message = "Seeding {Email} with default password. Set '{ConfigKey}' in configuration before deploying to production."
)]
private static partial void LogDefaultPasswordWarning(
ILogger logger,
string email,
string configKey
);

[LoggerMessage(Level = LogLevel.Error, Message = "Seed error: {ErrorDescription}")]
private static partial void LogSeedError(ILogger logger, string errorDescription);
}
3 changes: 2 additions & 1 deletion template/SimpleModule.Host/ClientApp/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,14 @@ function showErrorToast(message: string) {
<div class="flex items-start gap-3">
<div class="flex-1">
<p class="text-sm font-semibold">Error</p>
<p class="text-sm opacity-90">${message.replace(/[<>"'&]/g, (c) => `&#${c.charCodeAt(0)};`)}</p>
<p class="text-sm opacity-90"></p>
</div>
<button class="rounded-lg p-1 text-danger-text/60 hover:text-danger-text" aria-label="Close">
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M18 6 6 18M6 6l12 12" /></svg>
</button>
</div>
`;
container.querySelector('p.opacity-90')!.textContent = message;
container.querySelector('button')?.addEventListener('click', () => container.remove());
document.body.appendChild(container);
setTimeout(() => container.remove(), 8000);
Expand Down
Loading