Integrated YARP by adding the Yarp.ReverseProxy package, including yarp.json for proxy configuration, and updating Program.cs to load and map reverse proxy routes. This enables the API to forward requests based on yarp.json settings.
257 lines
9.4 KiB
C#
257 lines
9.4 KiB
C#
using DigitalData.Core.API;
|
|
using DigitalData.Core.Application;
|
|
using EnvelopeGenerator.Infrastructure;
|
|
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 EnvelopeGenerator.API.Models;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using DigitalData.Core.Abstractions.Security.Extensions;
|
|
using EnvelopeGenerator.API.Middleware;
|
|
using NLog.Web;
|
|
using NLog;
|
|
|
|
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
|
logger.Info("Logging initialized!");
|
|
|
|
try
|
|
{
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Configuration.AddJsonFile("yarp.json", optional: true, reloadOnChange: true);
|
|
|
|
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
|
|
|
|
if (!builder.Environment.IsDevelopment())
|
|
{
|
|
builder.Logging.ClearProviders();
|
|
builder.Host.UseNLog();
|
|
}
|
|
|
|
var config = builder.Configuration;
|
|
|
|
var deferredProvider = new DeferredServiceProvider();
|
|
|
|
builder.Services.AddControllers();
|
|
builder.Services.AddHttpClient();
|
|
builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
|
|
|
|
// 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 =>
|
|
{
|
|
builder.WithOrigins(allowedOrigins)
|
|
.SetIsOriginAllowedToAllowWildcardSubdomains()
|
|
.AllowAnyMethod()
|
|
.AllowAnyHeader()
|
|
.AllowCredentials();
|
|
});
|
|
});
|
|
|
|
// Swagger
|
|
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 = ReferenceType.SecurityScheme,
|
|
Id = "Bearer"
|
|
}
|
|
},
|
|
Array.Empty<string>()
|
|
}
|
|
});
|
|
|
|
var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
|
|
foreach (var xmlFile in xmlFiles)
|
|
{
|
|
options.IncludeXmlComments(xmlFile);
|
|
}
|
|
});
|
|
builder.Services.AddOpenApi();
|
|
|
|
//AddEF Core dbcontext
|
|
var useDbMigration = Environment.GetEnvironmentVariable("MIGRATION_TEST_MODE") == true.ToString() || config.GetValue<bool>("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<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,
|
|
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
|
|
{
|
|
var clientParams = deferredProvider.GetOptions<ClientParams>();
|
|
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 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;
|
|
}
|
|
};
|
|
});
|
|
|
|
// 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
|
|
#pragma warning disable CS0618 // Type or member is obsolete
|
|
builder.Services.AddUserManager<EGDbContext>();
|
|
#pragma warning restore CS0618 // Type or member is obsolete
|
|
|
|
// LDAP
|
|
builder.ConfigureBySection<DirectorySearchOptions>();
|
|
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
|
|
|
|
// Localizer
|
|
builder.Services.AddCookieBasedLocalizer();
|
|
|
|
// Envelope generator serives
|
|
#pragma warning disable CS0618 // Type or member is obsolete
|
|
builder.Services
|
|
.AddEnvelopeGeneratorInfrastructureServices(opt =>
|
|
{
|
|
opt.AddDbTriggerParams(config);
|
|
opt.AddDbContext((provider, options) =>
|
|
{
|
|
var logger = provider.GetRequiredService<ILogger<EGDbContext>>();
|
|
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 // Type or member is obsolete
|
|
|
|
var app = builder.Build();
|
|
|
|
deferredProvider.Factory = () => app.Services;
|
|
|
|
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
|
|
|
app.MapOpenApi();
|
|
|
|
// 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(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.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
app.MapReverseProxy();
|
|
app.MapControllers();
|
|
|
|
app.Run();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, "Stopped program because of exception");
|
|
throw;
|
|
} |