diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/AuthScheme.cs b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/AuthScheme.cs new file mode 100644 index 00000000..84efbb5d --- /dev/null +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/AuthScheme.cs @@ -0,0 +1,17 @@ +namespace EnvelopeGenerator.WebUI; + +/// +/// Authentication scheme names for envelope generator. +/// +public static class AuthScheme +{ + /// + /// Scheme name used for per-envelope receiver JWT authentication. + /// + public const string Receiver = "EnvelopeGenerator.WebUI.ReceiverJWT"; + + /// + /// Scheme name used for per-envelope sender JWT authentication. + /// + public const string Sender = "EnvelopeGenerator.WebUI.SenderJWT"; +} diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI.csproj b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI.csproj index 7674d95b..b05029cf 100644 --- a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI.csproj +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI.csproj @@ -8,11 +8,40 @@ + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + @@ -22,9 +51,6 @@ Always - - PreserveNewest - diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Middleware/ExceptionHandlingMiddleware.cs b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Middleware/ExceptionHandlingMiddleware.cs new file mode 100644 index 00000000..807d240e --- /dev/null +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Middleware/ExceptionHandlingMiddleware.cs @@ -0,0 +1,84 @@ +namespace EnvelopeGenerator.WebUI.Middleware; + +using DigitalData.Core.Exceptions; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System.Net; +using System.Text.Json; + +/// +/// Middleware for handling exceptions globally in the application. +/// Captures exceptions thrown during the request pipeline execution, +/// logs them, and returns an appropriate HTTP response with a JSON error message. +/// +public class ExceptionHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The next middleware in the request pipeline. + /// The logger instance for logging exceptions. + public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + /// + /// Invokes the middleware to handle the HTTP request. + /// + /// The HTTP context of the current request. + /// A task that represents the asynchronous operation. + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); // Continue down the pipeline + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex, _logger); + } + } + + /// + /// Handles exceptions by logging them and writing an appropriate JSON response. + /// + /// The HTTP context of the current request. + /// The exception that occurred. + /// The logger instance for logging the exception. + /// A task that represents the asynchronous operation. + private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger) + { + context.Response.ContentType = "application/json"; + + string message; + + switch (exception) + { + case BadRequestException badRequestEx: + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + message = badRequestEx.Message; + break; + + case NotFoundException notFoundEx: + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + message = notFoundEx.Message; + break; + + default: + logger.LogError(exception, "Unhandled exception occurred."); + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + message = "An unexpected error occurred."; + break; + } + + await context.Response.WriteAsync(JsonSerializer.Serialize(new + { + message + })); + } +} diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Models/AuthTokenKeys.cs b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Models/AuthTokenKeys.cs new file mode 100644 index 00000000..0d296df7 --- /dev/null +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Models/AuthTokenKeys.cs @@ -0,0 +1,28 @@ +namespace EnvelopeGenerator.WebUI.Models; + +/// +/// Represents the keys and default values used for authentication token handling +/// within the Envelope Generator WebUI. +/// +public class AuthTokenKeys +{ + /// + /// Gets the name of the cookie used to store the authentication token. + /// + public string Cookie { get; init; } = "AuthToken"; + + /// + /// Gets the name of the query string parameter used to pass the authentication token. + /// + public string QueryString { get; init; } = "AuthToken"; + + /// + /// Gets the expected issuer value for the authentication token. + /// + public string Issuer { get; init; } = "auth.digitaldata.works"; + + /// + /// Gets the expected audience value for the authentication token. + /// + public string Audience { get; init; } = "sign-flow.digitaldata.works"; +} diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Models/ConnectionString.cs b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Models/ConnectionString.cs new file mode 100644 index 00000000..553360fd --- /dev/null +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Models/ConnectionString.cs @@ -0,0 +1,12 @@ +namespace EnvelopeGenerator.WebUI.Models; + +/// +/// Represents the database connection string for dependency injection. +/// +public class ConnectionString +{ + /// + /// The database connection string value. + /// + public string Value { get; set; } = string.Empty; +} diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Options/CacheOptions.cs b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Options/CacheOptions.cs new file mode 100644 index 00000000..a5ebb277 --- /dev/null +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Options/CacheOptions.cs @@ -0,0 +1,18 @@ +namespace EnvelopeGenerator.WebUI.Options; + +/// +/// Configuration options for distributed caching. +/// +public sealed class CacheOptions +{ + /// + /// Configuration section name in appsettings.json. + /// + public const string SectionName = "Cache"; + + /// + /// Signature cache expiration time. + /// If null, signatures will not expire automatically. + /// + public TimeSpan? SignatureCacheExpiration { get; set; } +} diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Program.cs b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Program.cs index 0c0153d6..331ee357 100644 --- a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Program.cs +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Program.cs @@ -1,83 +1,386 @@ using EnvelopeGenerator.WebUI.Components; +using EnvelopeGenerator.WebUI.Models; +using EnvelopeGenerator.WebUI.Options; using DevExpress.Blazor; using EnvelopeGenerator.WebUI.Client.Services; +using DigitalData.Core.API; +using DigitalData.Core.Application; +using EnvelopeGenerator.Infrastructure; +using EnvelopeGenerator.Domain.Constants; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Localization; +using Microsoft.EntityFrameworkCore; +using System.Globalization; +using Scalar.AspNetCore; +using Microsoft.OpenApi.Models; +using DigitalData.UserManager.DependencyInjection; +using EnvelopeGenerator.Application; +using DigitalData.Auth.Client; +using DigitalData.Core.Abstractions; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using DigitalData.Core.Abstractions.Security.Extensions; +using NLog.Web; +using NLog; +using DigitalData.Auth.Claims; +using EnvelopeGenerator.WebUI; -var builder = WebApplication.CreateBuilder(args); +var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); +logger.Info("EnvelopeGenerator.WebUI logging initialized!"); -// Load YARP configuration -builder.Configuration.AddJsonFile("yarp.json", optional: false, reloadOnChange: true); +try +{ + var builder = WebApplication.CreateBuilder(args); -// Add services to the container. -builder.Services.AddRazorComponents() - .AddInteractiveServerComponents() - .AddInteractiveWebAssemblyComponents(); + builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); -// HttpClient for server-side components (e.g., MainLayout with FontLoader) -builder.Services.AddHttpContextAccessor(); -builder.Services.AddScoped(sp => { - var httpContextAccessor = sp.GetRequiredService(); - var request = httpContextAccessor.HttpContext?.Request; - - var httpClient = sp.GetRequiredService().CreateClient(); - - if (request != null) { - // Set base address to current host (e.g., https://localhost:5131) - httpClient.BaseAddress = new Uri($"{request.Scheme}://{request.Host}"); + if (!builder.Environment.IsDevelopment()) + { + builder.Logging.ClearProviders(); + builder.Host.UseNLog(); } - - return httpClient; -}); -builder.Services.AddHttpClient(); -// Business Services -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddSingleton(); + var config = builder.Configuration; -// YARP Reverse Proxy -builder.Services.AddReverseProxy() - .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); + var deferredProvider = new DeferredServiceProvider(); -// DevExpress Server-Side Services (CRITICAL for DxPdfViewer) -builder.Services.AddDevExpressBlazor(); -builder.Services.AddDevExpressServerSideBlazorPdfViewer(); + // Add Blazor services + builder.Services.AddRazorComponents() + .AddInteractiveServerComponents() + .AddInteractiveWebAssemblyComponents(); -// Configuration Options -builder.Services.Configure( - builder.Configuration.GetSection("ApiOptions")); -builder.Services.Configure( - builder.Configuration.GetSection("PdfViewerOptions")); + // Add API Controllers + builder.Services.AddControllers(); + builder.Services.AddHttpClient(); -var app = builder.Build(); + // CORS Policy + var allowedOrigins = config.GetSection("AllowedOrigins").Get() ?? + throw new InvalidOperationException("AllowedOrigins section is missing in the configuration."); + builder.Services.AddCors(options => + { + options.AddPolicy("AllowSpecificOriginsPolicy", builder => + { + builder.WithOrigins(allowedOrigins) + .SetIsOriginAllowedToAllowWildcardSubdomains() + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + }); + }); -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseWebAssemblyDebugging(); + // Swagger/OpenAPI + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(options => + { + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "signFLOW Absender-API", + Description = "Eine API zur Verwaltung der Erstellung, des Versands und der Nachverfolgung von Umschlägen in der signFLOW-Anwendung.", + Contact = new OpenApiContact + { + Name = "Digital Data GmbH", + Url = new Uri("https://digitaldata.works/digitale-signatur#kontakt"), + Email = "info-flow@digitaldata.works" + }, + }); + + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Name = "Authorization", + Type = SecuritySchemeType.Http, + Scheme = "bearer", + BearerFormat = "JWT", + In = ParameterLocation.Header, + Description = "JWT-Autorisierungs-Header unter Verwendung des Bearer-Schemas.", + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + }); + + var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml"); + foreach (var xmlFile in xmlFiles) + { + options.IncludeXmlComments(xmlFile); + } + }); + + // Database Context + var useDbMigration = Environment.GetEnvironmentVariable("MIGRATION_TEST_MODE") == true.ToString() || config.GetValue("UseDbMigration"); + var cnnStrName = useDbMigration ? "DbMigrationTest" : "Default"; + var connStr = config.GetConnectionString(cnnStrName) + ?? throw new InvalidOperationException($"Connection string '{cnnStrName}' is missing in the application configuration."); + + builder.Services.Configure(cs => cs.Value = connStr); + + builder.Services.AddDbContext(options => options.UseSqlServer(connStr)); + + // Authentication - AuthHub + builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams")); + + var authTokenKeys = config.GetOrDefault(); + + builder.Services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(AuthScheme.Sender, opt => + { + opt.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) => + { + var clientParams = deferredProvider.GetOptions(); + var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience); + return [publicKey.SecurityKey]; + }, + ValidateIssuer = true, + ValidIssuer = authTokenKeys.Issuer, + ValidateAudience = true, + ValidAudience = authTokenKeys.Audience, + }; + + opt.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + if (context.Token is null) + { + if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null) + context.Token = cookieToken; + else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken)) + context.Token = queryStrToken; + } + return Task.CompletedTask; + } + }; + }) + .AddJwtBearer(AuthScheme.Receiver, opt => + { + opt.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) => + { + var clientParams = deferredProvider.GetOptions(); + var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience); + return [publicKey.SecurityKey]; + }, + ValidateIssuer = true, + ValidIssuer = authTokenKeys.Issuer, + ValidateAudience = true, + ValidAudience = authTokenKeys.Audience, + }; + + opt.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + var paths = context.Request.Path.Value?.Split('/', StringSplitOptions.RemoveEmptyEntries); + var envelopeKey = paths?.LastOrDefault(); + + if (envelopeKey is not null) + { + var cookieName = CookieNames.GetEnvelopeReceiverCookieName(authTokenKeys.Cookie, envelopeKey); + if (context.Request.Cookies.TryGetValue(cookieName, out var cookieToken) && cookieToken is not null) + context.Token = cookieToken; + } + + return Task.CompletedTask; + }, + OnTokenValidated = context => + { + var paths = context.Request.Path.Value?.Split('/', StringSplitOptions.RemoveEmptyEntries); + var envelopeKey = paths?.LastOrDefault(); + + var sub = context.Principal?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value + ?? context.Principal?.FindFirst("sub")?.Value; + + if (envelopeKey is null || sub != envelopeKey) + context.Fail("Envelope key in the path does not match the token subject."); + + return Task.CompletedTask; + } + }; + }); + + // Cookie Authentication + builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.Cookie.HttpOnly = true; + options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + options.Cookie.SameSite = SameSiteMode.Strict; + options.LoginPath = "/api/auth/login"; + options.LogoutPath = "/api/auth/logout"; + options.SlidingExpiration = true; + }); + + // Authorization Policies + builder.Services.AddAuthorizationBuilder() + .AddPolicy(AuthPolicy.SenderOrReceiver, policy => policy.RequireRole(Role.Sender, Role.Receiver.Full)) + .AddPolicy(AuthPolicy.Sender, policy => policy + .RequireRole(Role.Sender) + .AddAuthenticationSchemes(AuthScheme.Sender)) + .AddPolicy(AuthPolicy.Receiver, policy => policy + .AddAuthenticationSchemes(AuthScheme.Receiver) + .RequireAuthenticatedUser() + .RequireRole(Role.Receiver.Full, "receiver")) + .AddPolicy(AuthPolicy.ReceiverTFA, policy => policy.RequireRole(Role.Receiver.TFA)); + + // User Manager +#pragma warning disable CS0618 + builder.Services.AddUserManager(); +#pragma warning restore CS0618 + + // LDAP Directory Search + builder.ConfigureBySection(); + builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions")); + + // Localization + builder.Services.AddCookieBasedLocalizer(); + + // Cache options + builder.Services.Configure(config.GetSection(CacheOptions.SectionName)); + + // Distributed Cache - SQL Server + builder.Services.AddDistributedSqlServerCache(options => + { + config.GetSection("Cache:SqlServer").Bind(options); + + if (string.IsNullOrWhiteSpace(options.ConnectionString)) + { + options.ConnectionString = connStr; + } + }); + + // Envelope Generator Infrastructure & Application Services +#pragma warning disable CS0618 + builder.Services + .AddEnvelopeGeneratorInfrastructureServices(opt => + { + opt.AddDbTriggerParams(config); + opt.AddDbContext((provider, options) => + { + var logger = provider.GetRequiredService>(); + options.UseSqlServer(connStr) + .LogTo(log => logger.LogInformation("{log}", log), Microsoft.Extensions.Logging.LogLevel.Trace) + .EnableSensitiveDataLogging() + .EnableDetailedErrors(); + }); + opt.AddSQLExecutor(executor => executor.ConnectionString = connStr); + }) + .AddEnvelopeGeneratorServices(config); +#pragma warning restore CS0618 + + // HttpClient for server-side components (e.g., MainLayout with FontLoader) + builder.Services.AddHttpContextAccessor(); + builder.Services.AddScoped(sp => + { + var httpContextAccessor = sp.GetRequiredService(); + var request = httpContextAccessor.HttpContext?.Request; + + var httpClient = sp.GetRequiredService().CreateClient(); + + if (request != null) + { + httpClient.BaseAddress = new Uri($"{request.Scheme}://{request.Host}"); + } + + return httpClient; + }); + + // Business Services (WebUI specific) + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + + // DevExpress Server-Side Services (CRITICAL for DxPdfViewer) + builder.Services.AddDevExpressBlazor(); + builder.Services.AddDevExpressServerSideBlazorPdfViewer(); + + // Configuration Options + builder.Services.Configure( + builder.Configuration.GetSection("ApiOptions")); + builder.Services.Configure( + builder.Configuration.GetSection("PdfViewerOptions")); + + var app = builder.Build(); + + deferredProvider.Factory = () => app.Services; + + // Exception handling middleware for API controllers + app.UseMiddleware(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseWebAssemblyDebugging(); + app.UseSwagger(); + app.UseSwaggerUI(); + app.MapScalarApiReference(); + } + else + { + app.UseExceptionHandler("/Error", createScopeForErrors: true); + app.UseHsts(); + } + + // Set CORS policy + app.UseCors("AllowSpecificOriginsPolicy"); + + // Localization + string[] supportedCultureNames = ["de-DE", "en-US"]; + IList list = [.. supportedCultureNames.Select(cn => new CultureInfo(cn))]; + var cultureInfo = list.FirstOrDefault() ?? throw new InvalidOperationException("There is no supported culture."); + var requestLocalizationOptions = new RequestLocalizationOptions + { + SupportedCultures = list, + SupportedUICultures = list + }; + requestLocalizationOptions.RequestCultureProviders.Add(new QueryStringRequestCultureProvider()); + app.UseRequestLocalization(requestLocalizationOptions); + + app.UseHttpsRedirection(); + + app.UseDefaultFiles(); + app.UseStaticFiles(); + app.UseAntiforgery(); + + app.UseAuthentication(); + app.UseAuthorization(); + + // API Controllers (map before Blazor routing) + app.MapControllers(); + + // Blazor routing + app.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddInteractiveWebAssemblyRenderMode() + .AddAdditionalAssemblies(typeof(EnvelopeGenerator.WebUI.Client._Imports).Assembly); + + app.Run(); } -else +catch (Exception ex) { - app.UseExceptionHandler("/Error", createScopeForErrors: true); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); + logger.Error(ex, "Stopped program because of exception"); + throw; } - -app.UseHttpsRedirection(); - -app.UseStaticFiles(); -app.UseAntiforgery(); - -// Blazor routing (BEFORE YARP - important order!) -app.MapRazorComponents() - .AddInteractiveServerRenderMode() - .AddInteractiveWebAssemblyRenderMode() - .AddAdditionalAssemblies(typeof(EnvelopeGenerator.WebUI.Client._Imports).Assembly); - -// YARP proxy (AFTER Blazor - catch-all for /api/*) -app.MapReverseProxy(); - -app.Run(); diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/appsettings.Development.json b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/appsettings.Development.json index 0c208ae9..f3350f57 100644 --- a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/appsettings.Development.json +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/appsettings.Development.json @@ -4,5 +4,15 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "AuthClientParams": { + "Url": "http://172.24.12.39:9090/auth-hub", + "PublicKeys": [ + { + "Issuer": "auth.digitaldata.works", + "Audience": "sign-flow.digitaldata.works" + } + ], + "RetryDelay": "00:00:05" } } diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/appsettings.json b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/appsettings.json index 46430479..561aa0d4 100644 --- a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/appsettings.json +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/appsettings.json @@ -1,4 +1,7 @@ { + "UseSwagger": true, + "UseDbMigration": false, + "DiPMode": true, "Logging": { "LogLevel": { "Default": "Information", @@ -6,6 +9,42 @@ } }, "AllowedHosts": "*", + "AllowedOrigins": [ + "http://localhost:4200", + "http://172.24.12.39:9090", + "https://localhost:8088", + "http://localhost:5131", + "http://localhost:7192" + ], + "ConnectionStrings": { + "Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;", + "DbMigrationTest": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM_DATA_MIGR_TEST;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;" + }, + "DirectorySearchOptions": { + "ServerName": "DD-VMP01-DC01", + "Root": "DC=dd-gan,DC=local,DC=digitaldata,DC=works", + "UserCacheExpirationDays": 1, + "CustomSearchFilters": { + "User": "(&(objectClass=user)(sAMAccountName=*))", + "Group": "(&(objectClass=group)(samAccountName=*))" + } + }, + "AuthClientParams": { + "Url": "http://172.24.12.39:9090/auth-hub", + "PublicKeys": [ + { + "Issuer": "auth.digitaldata.works", + "Audience": "sign-flow.digitaldata.works" + } + ], + "RetryDelay": "00:00:05" + }, + "AuthTokenKeys": { + "Cookie": "AuthToken", + "QueryString": "AuthToken", + "Issuer": "auth.digitaldata.works", + "Audience": "sign-flow.digitaldata.works" + }, "ApiOptions": { "BaseUrl": "" }, @@ -14,5 +53,215 @@ "ThumbnailEnableHiDPI": true, "MainCanvasEnableHiDPI": true, "ZoomStepPercentage": 5 + }, + "PSPDFKitLicenseKey": "SXCtGGY9XA-31OGUXQK-r7c6AkdLGPm2ljuyDr1qu0kkhLvydg-Do-fxpNUF4Rq3fS_xAnZRNFRHbXpE6sQ2BMcCSVTcXVJO6tPviexjpiT-HnrDEySlUERJnnvh-tmeOWprxS6BySPnSILkmaVQtUfOIUS-cUbvvEYHTvQBKbSF8di4XHQFyfv49ihr51axm3NVV3AXwh2EiKL5C5XdqBZ4sQ4O7vXBjM2zvxdPxlxdcNYmiU83uAzw7B83O_jubPzya4CdUHh_YH7Nlp2gP56MeG1Sw2JhMtfG3Rj14Sg4ctaeL9p6AEWca5dDjJ2li5tFIV2fQSsw6A_cowLu0gtMm5i8IfJXeIcQbMC2-0wGv1oe9hZYJvFMdzhTM_FiejM0agemxt3lJyzuyP8zbBSOgp7Si6A85krLWPZptyZBTG7pp7IHboUHfPMxCXqi-zMsqewOJtQBE2mjntU-lPryKnssOpMPfswwQX7QSkJYV5EMqNmEhQX6mEkp2wcqFzMC7bJQew1aO4pOpvChUaMvb1vgRek0HxLag0nwQYX2YrYGh7F_xXJs-8HNwJe8H0-eW4x4faayCgM5rB5772CCCsD9ThZcvXFrjNHHLGJ8WuBUFm6LArvSfFQdii_7j-_sqHMpeKZt26NFgivj1A==", + "Content-Security-Policy": [ + "default-src 'self'", + "script-src 'self' 'nonce-{0}' 'unsafe-eval'", + "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com:*", + "img-src 'self' data: https: blob:", + "font-src 'self' https://fonts.gstatic.com:*", + "connect-src 'self' https://nominatim.openstreetmap.org:* http://localhost:* https://localhost:* ws://localhost:* wss://localhost:* blob:", + "frame-src 'self'", + "media-src 'self'", + "object-src 'self'" + ], + "NLog": { + "throwConfigExceptions": true, + "variables": { + "logDirectory": "E:\\LogFiles\\Digital Data\\signFlow", + "logFileNamePrefix": "${shortdate}-ECM.EnvelopeGenerator.WebUI" + }, + "targets": { + "infoLogs": { + "type": "File", + "fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log", + "maxArchiveDays": 30 + }, + "errorLogs": { + "type": "File", + "fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log", + "maxArchiveDays": 30 + }, + "criticalLogs": { + "type": "File", + "fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log", + "maxArchiveDays": 30 + } + }, + "rules": [ + { + "logger": "*", + "minLevel": "Info", + "maxLevel": "Warn", + "writeTo": "infoLogs" + }, + { + "logger": "*", + "level": "Error", + "writeTo": "errorLogs" + }, + { + "logger": "*", + "level": "Fatal", + "writeTo": "criticalLogs" + } + ] + }, + "ContactLink": { + "Label": "Kontakt", + "Href": "https://digitaldata.works/", + "HrefLang": "de", + "Target": "_blank", + "Title": "Digital Data GmbH" + }, + "Cultures": [ + { + "Language": "de-DE", + "FIClass": "fi-de" + }, + { + "Language": "en-US", + "FIClass": "fi-us" + } + ], + "DisableMultiLanguage": false, + "Regexes": [ + { + "Pattern": "/^\\p{L}+(?:([\\ \\-\\']|(\\.\\ ))\\p{L}+)*$/u", + "Name": "City", + "Platforms": [ ".NET" ] + }, + { + "Pattern": "/^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$/", + "Name": "City", + "Platforms": [ "javascript" ] + } + ], + "CustomImages": { + "App": { + "Src": "/img/DD_signFLOW_LOGO.png", + "Classes": { + "Main": "signFlow-logo" + } + }, + "Company": { + "Src": "/img/digital_data.svg", + "Classes": { + "Show": "dd-show-logo", + "Locked": "dd-locked-logo" + } + } + }, + "DispatcherParams": { + "SendingProfile": 1, + "AddedWho": "DDEnvelopGenerator", + "ReminderTypeId": 202377, + "EmailAttmt1": "" + }, + "MailParams": { + "Placeholders": { + "[NAME_PORTAL]": "signFlow", + "[SIGNATURE_TYPE]": "signieren", + "[REASON]": "" + } + }, + "GtxMessagingParams": { + "Uri": "https://rest.gtx-messaging.net", + "Path": "smsc/sendsms/f566f7e5-bdf2-4a9a-bf52-ed88215a432e/json", + "Headers": {}, + "QueryParams": { + "from": "signFlow" + } + }, + "TFARegParams": { + "TimeLimit": "00:30:00" + }, + "DbTriggerParams": { + "Envelope": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ], + "EnvelopeHistory": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ], + "EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ], + "EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ], + "Receiver": [], + "EmailTemplate": [ "TBSIG_EMAIL_TEMPLATE_AFT_UPD" ] + }, + "Cache": { + "SignatureCacheExpiration": null, + "SqlServer": { + "ConnectionString": null, + "SchemaName": "dbo", + "TableName": "TBDD_CACHE" + } + }, + "MainPageTitle": null, + "AnnotationParams": { + "Background": { + "Margin": 0.20, + "BackgroundColor": { + "R": 222, + "G": 220, + "B": 215 + }, + "BorderColor": { + "R": 204, + "G": 202, + "B": 198 + }, + "BorderStyle": "underline", + "BorderWidth": 4 + }, + "DefaultAnnotation": { + "Width": 1, + "Height": 0.5, + "MarginTop": 1 + }, + "Annotations": [ + { + "Name": "Signature", + "MarginTop": 0 + }, + { + "Name": "PositionLabel", + "VerBoundAnnotName": "Signature", + "WidthRatio": 1.2, + "HeightRatio": 0.5, + "MarginTopRatio": 0.22 + }, + { + "Name": "Position", + "VerBoundAnnotName": "PositionLabel", + "WidthRatio": 1.2, + "HeightRatio": 0.5, + "MarginTopRatio": -0.05 + }, + { + "Name": "CityLabel", + "VerBoundAnnotName": "Position", + "WidthRatio": 1.2, + "HeightRatio": 0.5, + "MarginTopRatio": 0.05 + }, + { + "Name": "City", + "VerBoundAnnotName": "CityLabel", + "WidthRatio": 1.2, + "HeightRatio": 0.5, + "MarginTopRatio": -0.05 + }, + { + "Name": "DateLabel", + "VerBoundAnnotName": "City", + "WidthRatio": 1.55, + "HeightRatio": 0.5, + "MarginTopRatio": 0.05 + }, + { + "Name": "Date", + "VerBoundAnnotName": "DateLabel", + "WidthRatio": 1.55, + "HeightRatio": 0.5, + "MarginTopRatio": -0.1 + } + ] } }