Enhance logging and refactor configuration in API

Updated `EnvelopeGenerator.GeneratorAPI.csproj` to include NLog packages for improved logging.
This commit is contained in:
Developer 02 2025-05-10 01:24:24 +02:00
parent 171de98552
commit 00fedc7c4c
2 changed files with 159 additions and 140 deletions

View File

@ -23,6 +23,8 @@
<PackageReference Include="DigitalData.Core.API" Version="2.1.1" /> <PackageReference Include="DigitalData.Core.API" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" 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="Scalar.AspNetCore" Version="2.2.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
<PackageReference Include="DigitalData.Core.Abstractions" Version="3.6.0" /> <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 Microsoft.IdentityModel.Tokens;
using DigitalData.Core.Abstractions.Security.Extensions; using DigitalData.Core.Abstractions.Security.Extensions;
using EnvelopeGenerator.GeneratorAPI.Middleware; 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 deferredProvider = new DeferredServiceProvider();
var allowedOrigins = config.GetSection("AllowedOrigins").Get<string[]>() ??
throw new InvalidOperationException("AllowedOrigins section is missing in the configuration."); builder.Services.AddControllers();
builder.Services.AddCors(options =>
//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 => options.AddPolicy("AllowSpecificOriginsPolicy", builder =>
{ {
@ -40,34 +51,34 @@ builder.Services.AddCors(options =>
}); });
}); });
// Swagger // Swagger
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options => builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{ {
Version = "v1", options.SwaggerDoc("v1", new OpenApiInfo
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", Version = "v1",
Url = new Uri("https://digitaldata.works/digitale-signatur#kontakt"), Title = "signFLOW Absender-API",
Email = "info-flow@digitaldata.works" 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 options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{ {
Name = "Authorization", Name = "Authorization",
Type = SecuritySchemeType.Http, Type = SecuritySchemeType.Http,
Scheme = "bearer", Scheme = "bearer",
BearerFormat = "JWT", BearerFormat = "JWT",
In = ParameterLocation.Header, In = ParameterLocation.Header,
Description = "JWT-Autorisierungs-Header unter Verwendung des Bearer-Schemas.", Description = "JWT-Autorisierungs-Header unter Verwendung des Bearer-Schemas.",
}); });
options.AddSecurityRequirement(new OpenApiSecurityRequirement options.AddSecurityRequirement(new OpenApiSecurityRequirement
{ {
{ {
new OpenApiSecurityScheme new OpenApiSecurityScheme
@ -82,130 +93,136 @@ builder.Services.AddSwaggerGen(options =>
} }
}); });
var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml"); var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
foreach (var xmlFile in xmlFiles) 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
{ {
ValidateIssuerSigningKey = true, options.IncludeXmlComments(xmlFile);
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) => }
{ });
var clientParams = deferredProvider.GetOptions<ClientParams>(); builder.Services.AddOpenApi();
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience); // DbContext
return new List<SecurityKey>() { publicKey.SecurityKey }; var connStr = config.GetConnectionString("Default") ?? throw new InvalidOperationException("There is no default connection string in appsettings.json.");
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
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 ValidateIssuerSigningKey = true,
if (context.Token is null) // if there is no token IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{ {
if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null) var clientParams = deferredProvider.GetOptions<ClientParams>();
context.Token = cookieToken; var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken)) return new List<SecurityKey>() { publicKey.SecurityKey };
context.Token = queryStrToken; },
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 // User manager
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) builder.Services.AddUserManager<EGDbContext>();
.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 // LDAP
builder.Services.AddUserManager<EGDbContext>(); builder.ConfigureBySection<DirectorySearchOptions>();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
// LDAP // Localizer
builder.ConfigureBySection<DirectorySearchOptions>(); builder.Services.AddCookieBasedLocalizer();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
// Localizer // Envelope generator serives
builder.Services.AddCookieBasedLocalizer() ; builder.Services
.AddEnvelopeGeneratorInfrastructureServices(sqlExecutorConfigureOptions: executor => executor.ConnectionString = connStr)
.AddEnvelopeGeneratorServices(config);
// Envelope generator serives var app = builder.Build();
builder.Services
.AddEnvelopeGeneratorInfrastructureServices(sqlExecutorConfigureOptions: executor => executor.ConnectionString = connStr)
.AddEnvelopeGeneratorServices(config);
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. // Set CORS policy
if (app.Environment.IsDevelopment() || (app.IsDevOrDiP() && config.GetValue<bool>("UseSwagger"))) app.UseCors("AllowSpecificOriginsPolicy");
{
app.UseSwagger(); // Localizer
app.UseSwaggerUI(); string[] supportedCultureNames = { "de-DE", "en-US" };
app.MapScalarApiReference(); 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();
} }
catch (Exception ex)
// 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, logger.Error(ex, "Stopped program because of exception");
SupportedUICultures = list throw;
}; }
requestLocalizationOptions.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
app.UseRequestLocalization(requestLocalizationOptions);
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();