diff --git a/samples/WebSample/Extensions/ServiceCollectionExtensions.cs b/samples/WebSample/Extensions/ServiceCollectionExtensions.cs index 2bf27fd..2578390 100644 --- a/samples/WebSample/Extensions/ServiceCollectionExtensions.cs +++ b/samples/WebSample/Extensions/ServiceCollectionExtensions.cs @@ -1,3 +1,8 @@ +using PowerCSharp.Feature.Cache; +using PowerCSharp.Feature.Cache.BitFaster; +using PowerCSharp.Feature.Sanitization; +using PowerCSharp.Features; + namespace WebSample.Extensions; /// @@ -15,7 +20,7 @@ public static IServiceCollection AddSwaggerServices(this IServiceCollection serv services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new() { Title = "PowerCSharp Web Sample API", Version = "v1" }); - + // Include XML comments var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); @@ -24,4 +29,25 @@ public static IServiceCollection AddSwaggerServices(this IServiceCollection serv return services; } + + /// + /// Registers the PowerCSharp Features Framework via auto-discovery, opting in the Cache and + /// Sanitization feature modules. Also registers the BitFaster cache provider so the Cache demo + /// endpoint has an active backend rather than the NoOp floor. + /// + /// The IServiceCollection instance + /// The application configuration + public static IServiceCollection AddPowerCSharpFeatures(this IServiceCollection services, IConfiguration configuration) + { + services.AddPowerFeatures(configuration, options => + { + options.ScanAssemblies( + typeof(CacheFeatureModule).Assembly, + typeof(SanitizationFeatureModule).Assembly); + }); + + services.AddCacheBitFaster(configuration); + + return services; + } } diff --git a/samples/WebSample/Extensions/WebApplicationExtensions.cs b/samples/WebSample/Extensions/WebApplicationExtensions.cs index f19b965..5dd4b2a 100644 --- a/samples/WebSample/Extensions/WebApplicationExtensions.cs +++ b/samples/WebSample/Extensions/WebApplicationExtensions.cs @@ -1,5 +1,7 @@ +using PowerCSharp.Features; using WebSample.Samples.Core; using WebSample.Samples.Extensions; +using WebSample.Samples.Features; using WebSample.Samples.Helpers; using WebSample.Samples.Utilities; @@ -24,6 +26,17 @@ public static void UseSwaggerUI(this WebApplication app) }); } + /// + /// Runs the PowerCSharp Features Framework pipeline hook — resolves each enabled feature's + /// middleware (none, for Cache/Sanitization) and bridges the Sanitization engine's + /// configuration for non-DI call sites. + /// + /// The WebApplication instance + public static void UsePowerCSharpFeatures(this WebApplication app) + { + app.UsePowerFeatures(); + } + /// /// Maps all PowerCSharp demo endpoints using dedicated endpoint classes /// @@ -55,5 +68,9 @@ public static void MapPowerCSharpDemoEndpoints(this WebApplication app) app.MapGet("/demo/collection", CollectionSampleEndpoints.GetDemoData); app.MapGet("/demo/dictionary", DictionarySampleEndpoints.GetDemoData); app.MapGet("/demo/unix-timestamp", UnixTimestampSampleEndpoints.GetDemoData); + + // Features Framework Samples + app.MapGet("/demo/cache", CacheSampleEndpoints.GetDemoDataAsync); + app.MapGet("/demo/sanitization", SanitizationSampleEndpoints.GetDemoData); } } diff --git a/samples/WebSample/Program.cs b/samples/WebSample/Program.cs index 1a5b17c..56679e9 100644 --- a/samples/WebSample/Program.cs +++ b/samples/WebSample/Program.cs @@ -5,8 +5,14 @@ // Add Swagger services builder.Services.AddSwaggerServices(); +// Add the PowerCSharp Features Framework (Cache + Sanitization feature modules) +builder.Services.AddPowerCSharpFeatures(builder.Configuration); + var app = builder.Build(); +// Run the Features Framework pipeline hook (bridges the Sanitization engine's configuration) +app.UsePowerCSharpFeatures(); + // Map all PowerCSharp demo endpoints app.MapPowerCSharpDemoEndpoints(); diff --git a/samples/WebSample/Samples/Features/CacheSampleEndpoints.cs b/samples/WebSample/Samples/Features/CacheSampleEndpoints.cs new file mode 100644 index 0000000..d6cd91b --- /dev/null +++ b/samples/WebSample/Samples/Features/CacheSampleEndpoints.cs @@ -0,0 +1,39 @@ +using PowerCSharp.Feature.Cache.Abstractions; + +namespace WebSample.Samples.Features; + +/// +/// Sample endpoint demonstrating the Cache feature, resolved via the Features Framework +/// (PowerCSharp.Feature.Cache + PowerCSharp.Feature.Cache.BitFaster). +/// +public static class CacheSampleEndpoints +{ + private const string DemoKey = "websample:cache:demo"; + + /// + /// Gets cache demo data: a cache miss, a write, and the resulting hit, using the + /// DI-resolved (the active BitFaster provider, or NoOp if the + /// Cache feature is disabled via configuration). + /// + /// The cache service, injected by the Features Framework. + /// Demo results showing a cache miss followed by a set/hit round-trip. + public static async Task GetDemoDataAsync(ICacheService cache) + { + // Start from a clean slate so this endpoint is idempotent across repeated calls. + await cache.RemoveAsync(DemoKey); + + var missResult = await cache.GetWithResultAsync(DemoKey); + + var value = $"cached at {DateTimeOffset.UtcNow:O}"; + await cache.SetAsync(DemoKey, value, TimeSpan.FromMinutes(1)); + + var hitResult = await cache.GetWithResultAsync(DemoKey); + + return new + { + providerNote = "Backed by PowerCSharp.Feature.Cache.BitFaster; falls back to a NoOp cache if the Cache feature is disabled.", + beforeSet = new { hit = missResult.IsSuccess, value = missResult.Value }, + afterSet = new { hit = hitResult.IsSuccess, value = hitResult.Value, provider = hitResult.ProviderName } + }; + } +} diff --git a/samples/WebSample/Samples/Features/SanitizationSampleEndpoints.cs b/samples/WebSample/Samples/Features/SanitizationSampleEndpoints.cs new file mode 100644 index 0000000..7009a7d --- /dev/null +++ b/samples/WebSample/Samples/Features/SanitizationSampleEndpoints.cs @@ -0,0 +1,57 @@ +using PowerCSharp.Feature.Sanitization.Abstractions; + +namespace WebSample.Samples.Features; + +/// +/// Sample endpoint demonstrating the Sanitization feature, resolved via the Features Framework +/// (PowerCSharp.Feature.Sanitization). +/// +public static class SanitizationSampleEndpoints +{ + /// + /// Gets sanitization demo data covering all four concerns: log injection (CWE-117), + /// file-path traversal (CWE-22), sensitive-data masking (CWE-200), and regex-injection/ReDoS + /// validation (CWE-400/CWE-730), using the DI-resolved . + /// + /// The sanitization service, injected by the Features Framework. + /// Demo results for each sanitization concern. + public static object GetDemoData(ISanitizationService sanitizer) + { + const string maliciousLogInput = "user logged in\r\nADMIN: fake audit entry injected"; + const string maliciousPath = "../../etc/passwd"; + const string secretPayload = "password=SuperSecret123, token=abcdef0123456789"; + const string unsafeRegexPattern = "(a+)+$"; // classic catastrophic-backtracking shape + + var logResult = sanitizer.SanitizeForLogInjection(maliciousLogInput); + var pathResult = sanitizer.SanitizeForFilePath(maliciousPath); + var sensitiveResult = sanitizer.SanitizeForSensitiveData(secretPayload); + var regexResult = sanitizer.SanitizeForRegexInjection(unsafeRegexPattern); + + return new + { + note = "Falls back to a NoOp sanitizer (inputs pass through unchanged) if the Sanitization feature is disabled.", + logInjection = new + { + original = maliciousLogInput, + sanitized = logResult.SanitizedValue, + wasModified = logResult.WasModified + }, + filePathTraversal = new + { + original = maliciousPath, + wasRejected = pathResult.IsRejected + }, + sensitiveDataMasking = new + { + original = secretPayload, + masked = sensitiveResult.SanitizedValue, + wasModified = sensitiveResult.WasModified + }, + regexInjection = new + { + original = unsafeRegexPattern, + wasRejected = regexResult.IsRejected + } + }; + } +} diff --git a/samples/WebSample/WebSample.csproj b/samples/WebSample/WebSample.csproj index ed3757b..01327e7 100644 --- a/samples/WebSample/WebSample.csproj +++ b/samples/WebSample/WebSample.csproj @@ -6,6 +6,13 @@ + + + + + + + diff --git a/samples/WebSample/appsettings.json b/samples/WebSample/appsettings.json index 4d56694..e9990ae 100644 --- a/samples/WebSample/appsettings.json +++ b/samples/WebSample/appsettings.json @@ -5,5 +5,15 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "PowerFeatures": { + "Cache": { + "Enabled": true, + "Provider": "BitFaster", + "Capacity": 1000 + }, + "Sanitization": { + "Enabled": true + } + } }