Compare commits

...

5 Commits

Author SHA1 Message Date
Developer 02
09df86000b Bump version to 3.1.4 in project file
Updated package, assembly, and file versions from 3.1.3 to 3.1.4 to reflect the new release.
2025-05-10 03:49:46 +02:00
Developer 02
406ddfb226 Update package references and improve code readability
- Added `Microsoft.Data.SqlClient` v5.1.1 to `EnvelopeGenerator.Application.csproj` for enhanced database compatibility.
- Refactored `ReadByUuidSignatureAsync` in `EnvelopeReceiverRepository` to improve query construction and execution clarity.
- Simplified error handling in `HomeController` by replacing `ThenAsync` with a more direct approach for processing results.
- Removed `System.Data.SqlClient` v4.8.5 and added `System.Diagnostics.PerformanceCounter` v7.0.0 in `EnvelopeGenerator.Web.csproj` to focus on performance monitoring.
2025-05-10 03:47:55 +02:00
Developer 02
b01f13fb5f Add CommandDotNet and update logging configuration
- Included `CommandDotNet.Execution` namespace in `Program.cs`.
- Adjusted logging setup to clear providers and use NLog only in non-development environments.
- Added `AddHttpContextAccessor` to the service collection.
- Introduced a new `ConnectionStrings` section in `appsettings.Development.json` with a database connection string.
2025-05-10 02:39:32 +02:00
Developer 02
00fedc7c4c Enhance logging and refactor configuration in API
Updated `EnvelopeGenerator.GeneratorAPI.csproj` to include NLog packages for improved logging.
2025-05-10 01:25:32 +02:00
Developer 02
171de98552 Update version and enhance logging configuration
- Bump `EnvelopeGenerator.Web` version to `3.1.3`.
- Improve logging setup in `Program.cs` with NLog and Trace level.
- Modify `ServiceContainer` to accept `MSSQLServer` parameter.
- Update `ModelContainer` to utilize new database service.
- Add `warningLogs` section in `appsettings.json` for logging.
- Streamline logging rules by consolidating level properties.
2025-05-09 23:27:10 +02:00
10 changed files with 221 additions and 178 deletions

View File

@@ -21,6 +21,7 @@
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.0.0" />
<PackageReference Include="MediatR" Version="12.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.18" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
<PackageReference Include="Otp.NET" Version="1.4.0" />

View File

@@ -23,6 +23,8 @@
<PackageReference Include="DigitalData.Core.API" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
<PackageReference Include="NLog" Version="5.2.5" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
<PackageReference Include="Scalar.AspNetCore" Version="2.2.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
<PackageReference Include="DigitalData.Core.Abstractions" Version="3.6.0" />

View File

@@ -16,19 +16,30 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using DigitalData.Core.Abstractions.Security.Extensions;
using EnvelopeGenerator.GeneratorAPI.Middleware;
using NLog.Web;
using NLog;
var builder = WebApplication.CreateBuilder(args);
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized!");
var config = builder.Configuration;
try
{
var builder = WebApplication.CreateBuilder(args);
var deferredProvider = new DeferredServiceProvider();
builder.Logging.ClearProviders();
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
builder.Host.UseNLog();
builder.Services.AddControllers();
var config = builder.Configuration;
//CORS Policy
var allowedOrigins = config.GetSection("AllowedOrigins").Get<string[]>() ??
throw new InvalidOperationException("AllowedOrigins section is missing in the configuration.");
builder.Services.AddCors(options =>
var deferredProvider = new DeferredServiceProvider();
builder.Services.AddControllers();
//CORS Policy
var allowedOrigins = config.GetSection("AllowedOrigins").Get<string[]>() ??
throw new InvalidOperationException("AllowedOrigins section is missing in the configuration.");
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOriginsPolicy", builder =>
{
@@ -40,34 +51,34 @@ builder.Services.AddCors(options =>
});
});
// Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
// Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
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
options.SwaggerDoc("v1", new OpenApiInfo
{
Name = "Digital Data GmbH",
Url = new Uri("https://digitaldata.works/digitale-signatur#kontakt"),
Email = "info-flow@digitaldata.works"
},
});
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.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
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
@@ -82,130 +93,136 @@ builder.Services.AddSwaggerGen(options =>
}
});
var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
foreach (var xmlFile in xmlFiles)
{
options.IncludeXmlComments(xmlFile);
}
});
builder.Services.AddOpenApi();
// DbContext
var connStr = config.GetConnectionString("Default") ?? throw new InvalidOperationException("There is no default connection string in appsettings.json.");
builder.Services.Configure<ConnectionString>(cs => cs.Value = connStr);
builder.Services.AddDbContext<EGDbContext>(options => options.UseSqlServer(connStr));
builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams"));
var authTokenKeys = config.GetOrDefault<AuthTokenKeys>();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
foreach (var xmlFile in xmlFiles)
{
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
var clientParams = deferredProvider.GetOptions<ClientParams>();
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
return new List<SecurityKey>() { publicKey.SecurityKey };
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
options.IncludeXmlComments(xmlFile);
}
});
builder.Services.AddOpenApi();
// DbContext
var connStr = config.GetConnectionString("Default") ?? throw new InvalidOperationException("There is no default connection string in appsettings.json.");
opt.Events = new JwtBearerEvents
builder.Services.Configure<ConnectionString>(cs => cs.Value = connStr);
builder.Services.AddDbContext<EGDbContext>(options => options.UseSqlServer(connStr));
builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams"));
var authTokenKeys = config.GetOrDefault<AuthTokenKeys>();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
OnMessageReceived = context =>
opt.TokenValidationParameters = new TokenValidationParameters
{
// if there is no token read related cookie or query string
if (context.Token is null) // if there is no token
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
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;
var clientParams = deferredProvider.GetOptions<ClientParams>();
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
return new List<SecurityKey>() { publicKey.SecurityKey };
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// if there is no token read related cookie or query string
if (context.Token is null) // if there is no token
{
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;
}
return Task.CompletedTask;
}
};
};
});
// Authentication
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.LoginPath = "/api/auth/login";
options.LogoutPath = "/api/auth/logout";
options.SlidingExpiration = true;
});
// Authentication
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.LoginPath = "/api/auth/login";
options.LogoutPath = "/api/auth/logout";
options.SlidingExpiration = true;
});
// User manager
builder.Services.AddUserManager<EGDbContext>();
// User manager
builder.Services.AddUserManager<EGDbContext>();
// LDAP
builder.ConfigureBySection<DirectorySearchOptions>();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
// LDAP
builder.ConfigureBySection<DirectorySearchOptions>();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
// Localizer
builder.Services.AddCookieBasedLocalizer();
// Localizer
builder.Services.AddCookieBasedLocalizer() ;
// Envelope generator serives
builder.Services
.AddEnvelopeGeneratorInfrastructureServices(sqlExecutorConfigureOptions: executor => executor.ConnectionString = connStr)
.AddEnvelopeGeneratorServices(config);
// Envelope generator serives
builder.Services
.AddEnvelopeGeneratorInfrastructureServices(sqlExecutorConfigureOptions: executor => executor.ConnectionString = connStr)
.AddEnvelopeGeneratorServices(config);
var app = builder.Build();
var app = builder.Build();
deferredProvider.Factory = () => app.Services;
deferredProvider.Factory = () => app.Services;
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.MapOpenApi();
app.MapOpenApi();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || (app.IsDevOrDiP() && config.GetValue<bool>("UseSwagger")))
{
app.UseSwagger();
app.UseSwaggerUI();
app.MapScalarApiReference();
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || (app.IsDevOrDiP() && config.GetValue<bool>("UseSwagger")))
{
app.UseSwagger();
app.UseSwaggerUI();
app.MapScalarApiReference();
// Set CORS policy
app.UseCors("AllowSpecificOriginsPolicy");
// Localizer
string[] supportedCultureNames = { "de-DE", "en-US" };
IList<CultureInfo> list = supportedCultureNames.Select((string cn) => new CultureInfo(cn)).ToList();
CultureInfo cultureInfo = list.FirstOrDefault() ?? throw new ArgumentNullException("supportedCultureNames", "Supported cultures cannot be empty.");
RequestLocalizationOptions requestLocalizationOptions = new RequestLocalizationOptions
{
SupportedCultures = list,
SupportedUICultures = list
};
requestLocalizationOptions.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
app.UseRequestLocalization(requestLocalizationOptions);
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
// Set CORS policy
app.UseCors("AllowSpecificOriginsPolicy");
// Localizer
string[] supportedCultureNames = { "de-DE", "en-US" };
IList<CultureInfo> list = supportedCultureNames.Select((string cn) => new CultureInfo(cn)).ToList();
CultureInfo cultureInfo = list.FirstOrDefault() ?? throw new ArgumentNullException("supportedCultureNames", "Supported cultures cannot be empty.");
RequestLocalizationOptions requestLocalizationOptions = new RequestLocalizationOptions
catch (Exception ex)
{
SupportedCultures = list,
SupportedUICultures = list
};
requestLocalizationOptions.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
app.UseRequestLocalization(requestLocalizationOptions);
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
logger.Error(ex, "Stopped program because of exception");
throw;
}

View File

@@ -39,7 +39,11 @@ public class EnvelopeReceiverRepository : CRUDRepository<EnvelopeReceiver, (int
=> await ReadWhere(signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver, readOnly: readOnly).ToListAsync();
public async Task<EnvelopeReceiver?> ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true, bool readOnly = true)
=> await ReadWhere(uuid: uuid, signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver, readOnly: readOnly).FirstOrDefaultAsync();
{
var query = ReadWhere(uuid: uuid, signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver, readOnly: readOnly);
return await query.FirstOrDefaultAsync();
}
public async Task<string?> ReadAccessCodeAsync(string uuid, string signature, bool readOnly = true)
=> await ReadWhere(uuid: uuid, signature: signature, readOnly: readOnly)

View File

@@ -96,31 +96,32 @@ public class HomeController : ViewControllerBase
ViewData["EnvelopeKey"] = envelopeReceiverId;
return await _envRcvService.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId: envelopeReceiverId).ThenAsync<EnvelopeReceiverDto, IActionResult>(
SuccessAsync: async er =>
var er_res = await _envRcvService.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId: envelopeReceiverId);
if (er_res.IsFailed)
{
_logger.LogNotice(er_res.Notices);
return this.ViewEnvelopeNotFound();
}
var er = er_res.Data;
EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId);
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress);
if (!accessCodeAlreadyRequested)
{
await _historyService.RecordAsync(er.EnvelopeId, er.Receiver.EmailAddress, EnvelopeStatus.AccessCodeRequested);
var mailRes = await _mailService.SendAccessCodeAsync(envelopeReceiverDto: er);
if (mailRes.IsFailed)
{
EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId);
_logger.LogNotice(mailRes);
return this.ViewAccessCodeNotSent();
}
}
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress);
if (!accessCodeAlreadyRequested)
{
await _historyService.RecordAsync(er.EnvelopeId, er.Receiver.EmailAddress, EnvelopeStatus.AccessCodeRequested);
var mailRes = await _mailService.SendAccessCodeAsync(envelopeReceiverDto: er);
if (mailRes.IsFailed)
{
_logger.LogNotice(mailRes);
return this.ViewAccessCodeNotSent();
}
}
return Redirect($"{envelopeReceiverId}/Locked");
},
Fail: (messages, notices) =>
{
_logger.LogNotice(notices);
return this.ViewEnvelopeNotFound();
});
return Redirect($"{envelopeReceiverId}/Locked");
}
catch(Exception ex)
{

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>EnvelopeGenerator.Web</PackageId>
<Version>3.1.2</Version>
<Version>3.1.4</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>EnvelopeGenerator.Web</Product>
@@ -13,8 +13,8 @@
<PackageTags>digital data envelope generator web</PackageTags>
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
<AssemblyVersion>3.1.2</AssemblyVersion>
<FileVersion>3.1.2</FileVersion>
<AssemblyVersion>3.1.4</AssemblyVersion>
<FileVersion>3.1.4</FileVersion>
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
</PropertyGroup>
@@ -2126,7 +2126,7 @@
<PackageReference Include="Quartz.Serialization.Json" Version="3.8.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
<PackageReference Include="System.DirectoryServices" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="7.0.1" />

View File

@@ -24,6 +24,14 @@ 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<string[]>() ??
@@ -41,15 +49,14 @@ try
});
});
builder.Logging.ClearProviders();
builder.Host.UseNLog();
// Add base services
builder.Services.AddScoped<DatabaseService>();
// Add higher order services
builder.Services.AddScoped<EnvelopeOldService>();
builder.Services.AddHttpContextAccessor();
builder.ConfigureBySection<TFARegParams>();
// Add controllers and razor views

View File

@@ -18,9 +18,9 @@ namespace EnvelopeGenerator.Web.Services
public ActionService actionService;
public EmailService emailService;
public ServiceContainer(State state)
public ServiceContainer(State state, MSSQLServer MSSQL)
{
actionService = new(state);
actionService = new(state, MSSQL);
emailService = new(state);
}
}
@@ -82,7 +82,7 @@ namespace EnvelopeGenerator.Web.Services
State.DbConfig = configModel.LoadConfiguration();
Models = new(State);
Services = new(State);
Services = new(State, MSSQL);
}
else
{

View File

@@ -7,5 +7,8 @@
}
},
"AdminPassword": "dd",
"UseCSPInDev": false
"UseCSPInDev": false,
"ConnectionStrings": {
"Default": "Server=sDD-VMP04-SQL19\\CURSORAG;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
}
}

View File

@@ -39,6 +39,11 @@
"fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
"maxArchiveDays": 30
},
"warningLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Warning.log",
"maxArchiveDays": 30
},
"errorLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
@@ -50,14 +55,17 @@
"maxArchiveDays": 30
}
},
// Trace, Debug, Info, Warn, Error and *Fatal*
"rules": [
{
"logger": "*",
"minLevel": "Info",
"maxLevel": "Warn",
"level": "Info",
"writeTo": "infoLogs"
},
{
"logger": "*",
"level": "Warn",
"writeTo": "warningLogs"
},
{
"logger": "*",
"level": "Error",