using EnvelopeGenerator.Application.Services; using EnvelopeGenerator.Web.Services; using Microsoft.EntityFrameworkCore; using NLog; using Quartz; using NLog.Web; using DigitalData.Core.API; using Microsoft.AspNetCore.Authentication.Cookies; using EnvelopeGenerator.Web.Models; using System.Text.Encodings.Web; using Ganss.Xss; using Microsoft.Extensions.Options; using EnvelopeGenerator.Application; using DigitalData.EmailProfilerDispatcher; using EnvelopeGenerator.Infrastructure; using EnvelopeGenerator.Web.Sanitizers; using EnvelopeGenerator.Application.Contracts.Services; using EnvelopeGenerator.Web.Models.Annotation; using DigitalData.UserManager.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using System; var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); logger.Info("Logging initialized!"); try { var builder = WebApplication.CreateBuilder(args); builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); if (!builder.Environment.IsDevelopment()) { builder.Logging.ClearProviders(); builder.Host.UseNLog(); } var config = builder.Configuration; var allowedOrigins = config.GetSection("AllowedOrigins").Get() ?? throw new InvalidOperationException("AllowedOrigins section is missing in the configuration."); builder.Services.AddCors(options => { options.AddPolicy("SameOriginPolicy", builder => { builder.WithOrigins(allowedOrigins) .SetIsOriginAllowedToAllowWildcardSubdomains() .AllowCredentials() .AllowAnyMethod() .AllowAnyHeader(); }); }); // Add base services builder.Services.AddScoped(); // Add higher order services builder.Services.AddScoped(); builder.Services.AddHttpContextAccessor(); builder.ConfigureBySection(); // Add controllers and razor views builder.Services.AddControllersWithViews(options => { //remove option for Test*Controller options.Conventions.Add(new RemoveIfControllerConvention() .AndIf(c => c.ControllerName.StartsWith("Test")) .AndIf(_ => !builder.IsDevOrDiP() || !config.GetValue("EnableTestControllers"))); }).AddJsonOptions(q => { // Prevents serialization error when serializing SvgBitmap in EnvelopeReceiver q.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles; }); builder.Services.Configure(options => { // This lambda determines whether user consent for non-essential // cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); if (config.GetValue("EnableSwagger") && builder.IsDevOrDiP()) { builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); } //AddEF Core dbcontext var connStr = config.GetConnectionString(Key.Default) ?? throw new InvalidOperationException("There is no default connection string in appsettings.json."); builder.Services.AddDistributedSqlServerCache(options => { options.ConnectionString = connStr; options.SchemaName = "dbo"; options.TableName = "TBDD_CACHE"; }); // Add envelope generator services builder.Services.AddEnvelopeGeneratorInfrastructureServices((provider, options) => { var logger = provider.GetRequiredService>(); options.UseSqlServer(connStr) .LogTo(log => logger.LogInformation("{log}", log), Microsoft.Extensions.Logging.LogLevel.Trace) .EnableSensitiveDataLogging(); }); builder.Services.AddEnvelopeGeneratorServices(config); builder.Services.Configure(options => { options.CheckConsentNeeded = context => { var consentCookie = context.Request.Cookies["cookie-consent-settings"]; return consentCookie != "necessary=false"; }; options.MinimumSameSitePolicy = SameSiteMode.Strict; options.ConsentCookie.Name = "cookie-consent-settings"; }); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Ensures cookies are sent over HTTPS only options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites options.ExpireTimeSpan = TimeSpan.FromMinutes(30); options.Events = new CookieAuthenticationEvents { OnRedirectToLogin = context => { // Dynamically calculate the redirection path, for example: var envelopeReceiverId = context.HttpContext.Request.RouteValues["envelopeReceiverId"]; context.RedirectUri = $"/EnvelopeKey/{envelopeReceiverId}/Locked"; context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; }, OnRedirectToLogout = context => { // Apply a similar redirection logic for logout var envelopeReceiverId = context.HttpContext.Request.RouteValues["envelopeReceiverId"]; context.RedirectUri = $"/EnvelopeKey/{envelopeReceiverId}/Success"; context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; } }; }); builder.Services.AddSingleton(config.GetSection("ContactLink").Get() ?? new()); builder.Services.AddCookieBasedLocalizer(); builder.Services.AddSingleton(HtmlEncoder.Default); builder.Services.AddSingleton(UrlEncoder.Default); builder.Services.AddSanitizer(); builder.Services.AddSanitizer(s => { s.AllowedTags.Add("a"); s.AllowedAttributes.Add("href"); s.AllowedAttributes.Add("class"); s.AllowedClasses.Add("highlight"); s.AllowedClasses.Add("highlight-envelope-info-1"); s.AllowedClasses.Add("highlight-envelope-info-2"); }); // Register the FlagIconCssClass instance as a singleton builder.Services.Configure(config.GetSection("Cultures")); builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); // Register mail services builder.Services.AddScoped(); builder.Services.AddDispatcher(); builder.Services.AddMemoryCache(); builder.ConfigureBySection(); builder.ConfigureBySection(); builder.Services.AddUserManager(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } //Content-Security-Policy if (config.GetValue("UseCSPInDev") || !app.Environment.IsDevelopment()) { var csp_list = config.GetSection("Content-Security-Policy").Get(); if (csp_list is null) logger.Warn("There is no Content-Security-Policy"); else { var csp = string.Join("; ", csp_list?.Where(st => st is not null) ?? Array.Empty()); logger.Info($"Content-Security-Policy {csp}"); if (csp_list is not null) app.UseCSPMiddleware(csp); } } if (config.GetValue("EnableSwagger") && builder.IsDevOrDiP()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); var cultures = app.Services.GetRequiredService(); if(!cultures.Any()) throw new InvalidOperationException(@"Languages section is missing in the appsettings. Please configure like following. Language is both a name of the culture and the name of the resx file such as Resource.de-DE.resx FIClass is the css class (in wwwroot/lib/flag-icons-main) for the flag of country. ""Cultures"": [ { ""Language"": ""de-DE"", ""FIClass"": ""fi-de"" }, { ""Language"": ""en-US"", ""FIClass"": ""fi-us"" } ]"); if(!config.GetValue("DisableMultiLanguage")) app.UseCookieBasedLocalizer(cultures.Languages.ToArray()); app.UseCors("SameOriginPolicy"); app.MapControllers(); app.MapFallbackToController("Error404", "Home"); app.Run(); } catch(Exception ex) { logger.Error(ex, "Stopped program because of exception"); throw; }