diff --git a/src/api/MicCheck.Api/Audit/DependencyRegistration.cs b/src/api/MicCheck.Api/Audit/DependencyRegistration.cs new file mode 100644 index 0000000..b159052 --- /dev/null +++ b/src/api/MicCheck.Api/Audit/DependencyRegistration.cs @@ -0,0 +1,12 @@ +namespace MicCheck.Api.Audit; + +public static class DependencyRegistration +{ + public static IServiceCollection AddAuditServices(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Common/DependencyRegistration.cs b/src/api/MicCheck.Api/Common/DependencyRegistration.cs new file mode 100644 index 0000000..44cb592 --- /dev/null +++ b/src/api/MicCheck.Api/Common/DependencyRegistration.cs @@ -0,0 +1,65 @@ +using System.Text; +using MicCheck.Api.Common.Security.ApiKeys; +using MicCheck.Api.Common.Security.Authentication; +using MicCheck.Api.Common.Security.Authorization; +using MicCheck.Api.Common.Validation; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; + +namespace MicCheck.Api.Common; + +public static class DependencyRegistration +{ + public static IServiceCollection AddCommonServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddAuthentication() + .AddScheme( + EnvironmentKeyAuthenticationHandler.SchemeName, _ => { }) + .AddScheme( + ApiKeyAuthenticationHandler.SchemeName, _ => { }) + .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateIssuerSigningKey = true, + ValidIssuer = configuration["Jwt:Issuer"], + ValidAudience = configuration["Jwt:Audience"], + IssuerSigningKey = new SymmetricSecurityKey( + Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"]!)) + }; + }); + + services.AddAuthorization(options => + { + options.AddPolicy(AuthorizationPolicies.FlagsApiAccess, policy => + policy.AddAuthenticationSchemes(EnvironmentKeyAuthenticationHandler.SchemeName) + .RequireClaim("EnvironmentId")); + + options.AddPolicy(AuthorizationPolicies.AdminApiAccess, policy => + policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme) + .RequireAuthenticatedUser()); + + options.AddPolicy(AuthorizationPolicies.OrganizationAdmin, policy => + policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme) + .RequireClaim("OrganizationRole", "Admin")); + }); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddModelValidatorsFromAssemblyContaining(); + services.Configure(options => + { + options.InvalidModelStateResponseFactory = context => ValidationProblemResponseFactory.Create(context.ModelState); + }); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Data/DependencyRegistration.cs b/src/api/MicCheck.Api/Data/DependencyRegistration.cs new file mode 100644 index 0000000..9721092 --- /dev/null +++ b/src/api/MicCheck.Api/Data/DependencyRegistration.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; + +namespace MicCheck.Api.Data; + +public static class DependencyRegistration +{ + public static IServiceCollection AddDataServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddScoped(); + + var connectionString = configuration.GetConnectionString("miccheck") + ?? (System.Environment.GetEnvironmentVariable("DATABASE_URL") is { } databaseUrl + ? DatabaseUrlParser.ToNpgsqlConnectionString(databaseUrl) + : configuration.GetConnectionString("DefaultConnection")!); + + services.AddDbContext(options => options.UseNpgsql(connectionString)); + services.AddScoped(sp => sp.GetRequiredService()); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Environments/DependencyRegistration.cs b/src/api/MicCheck.Api/Environments/DependencyRegistration.cs new file mode 100644 index 0000000..f5f5a8c --- /dev/null +++ b/src/api/MicCheck.Api/Environments/DependencyRegistration.cs @@ -0,0 +1,12 @@ +namespace MicCheck.Api.Environments; + +public static class DependencyRegistration +{ + public static IServiceCollection AddEnvironmentsServices(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Features/DependencyRegistration.cs b/src/api/MicCheck.Api/Features/DependencyRegistration.cs new file mode 100644 index 0000000..7e24f7c --- /dev/null +++ b/src/api/MicCheck.Api/Features/DependencyRegistration.cs @@ -0,0 +1,20 @@ +using MicCheck.Api.Features.Usage; + +namespace MicCheck.Api.Features; + +public static class DependencyRegistration +{ + public static IServiceCollection AddFeaturesServices(this IServiceCollection services) + { + services.AddScoped(); + services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddFeatureUsageServices(); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Features/FlagCache.cs b/src/api/MicCheck.Api/Features/FlagCache.cs index 536c9a5..db3303c 100755 --- a/src/api/MicCheck.Api/Features/FlagCache.cs +++ b/src/api/MicCheck.Api/Features/FlagCache.cs @@ -12,11 +12,9 @@ public class FlagCache(IMemoryCache cache) return flags; } - public void Set(int environmentId, IReadOnlyList flags) - => cache.Set(CacheKey(environmentId), flags, CacheDuration); + public void Set(int environmentId, IReadOnlyList flags) => cache.Set(CacheKey(environmentId), flags, CacheDuration); - public void Invalidate(int environmentId) - => cache.Remove(CacheKey(environmentId)); + public void Invalidate(int environmentId) => cache.Remove(CacheKey(environmentId)); private static string CacheKey(int environmentId) => $"flags:{environmentId}"; } diff --git a/src/api/MicCheck.Api/Features/Usage/DependencyRegistration.cs b/src/api/MicCheck.Api/Features/Usage/DependencyRegistration.cs new file mode 100644 index 0000000..68bb57d --- /dev/null +++ b/src/api/MicCheck.Api/Features/Usage/DependencyRegistration.cs @@ -0,0 +1,13 @@ +namespace MicCheck.Api.Features.Usage; + +public static class DependencyRegistration +{ + public static IServiceCollection AddFeatureUsageServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddScoped(); + services.AddHostedService(); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Identities/DependencyRegistration.cs b/src/api/MicCheck.Api/Identities/DependencyRegistration.cs new file mode 100644 index 0000000..4f1efea --- /dev/null +++ b/src/api/MicCheck.Api/Identities/DependencyRegistration.cs @@ -0,0 +1,12 @@ +namespace MicCheck.Api.Identities; + +public static class DependencyRegistration +{ + public static IServiceCollection AddIdentitiesServices(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Organizations/DependencyRegistration.cs b/src/api/MicCheck.Api/Organizations/DependencyRegistration.cs new file mode 100644 index 0000000..e474831 --- /dev/null +++ b/src/api/MicCheck.Api/Organizations/DependencyRegistration.cs @@ -0,0 +1,11 @@ +namespace MicCheck.Api.Organizations; + +public static class DependencyRegistration +{ + public static IServiceCollection AddOrganizationsServices(this IServiceCollection services) + { + services.AddScoped(); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Program.cs b/src/api/MicCheck.Api/Program.cs index bb5f1b8..fa7569a 100755 --- a/src/api/MicCheck.Api/Program.cs +++ b/src/api/MicCheck.Api/Program.cs @@ -1,28 +1,19 @@ -using System.Text; using System.Threading.RateLimiting; -using MicCheck.Api.Common.Security.ApiKeys; using MicCheck.Api.Audit; -using MicCheck.Api.Common.Security.Authentication; +using MicCheck.Api.Common; +using MicCheck.Api.Common.Security.ApiKeys; using MicCheck.Api.Common.Security.Authorization; using MicCheck.Api.Common.Validation; using MicCheck.Api.Data; using MicCheck.Api.Environments; using MicCheck.Api.Features; -using MicCheck.Api.Features.Usage; using MicCheck.Api.Identities; using MicCheck.Api.Organizations; using MicCheck.Api.Projects; using MicCheck.Api.Segments; using MicCheck.Api.Users; using MicCheck.Api.Webhooks; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; using Scalar.AspNetCore; using Serilog; @@ -47,39 +38,7 @@ try options.JsonSerializerOptions.Converters.Add( new System.Text.Json.Serialization.JsonStringEnumConverter())); - builder.Services.AddAuthentication() - .AddScheme( - EnvironmentKeyAuthenticationHandler.SchemeName, _ => { }) - .AddScheme( - ApiKeyAuthenticationHandler.SchemeName, _ => { }) - .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => - { - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateIssuerSigningKey = true, - ValidIssuer = builder.Configuration["Jwt:Issuer"], - ValidAudience = builder.Configuration["Jwt:Audience"], - IssuerSigningKey = new SymmetricSecurityKey( - Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]!)) - }; - }); - - builder.Services.AddAuthorization(options => - { - options.AddPolicy(AuthorizationPolicies.FlagsApiAccess, policy => - policy.AddAuthenticationSchemes(EnvironmentKeyAuthenticationHandler.SchemeName) - .RequireClaim("EnvironmentId")); - - options.AddPolicy(AuthorizationPolicies.AdminApiAccess, policy => - policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme) - .RequireAuthenticatedUser()); - - options.AddPolicy(AuthorizationPolicies.OrganizationAdmin, policy => - policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme) - .RequireClaim("OrganizationRole", "Admin")); - }); + builder.Services.AddCommonServices(builder.Configuration); builder.Services.AddHttpContextAccessor(); @@ -92,58 +51,20 @@ try limiter.QueueLimit = 0; })); - builder.Services.AddModelValidatorsFromAssemblyContaining(); - - builder.Services.Configure(options => - { - options.InvalidModelStateResponseFactory = context => ValidationProblemResponseFactory.Create(context.ModelState); - }); - builder.Services.AddMemoryCache(); builder.Services.AddMetrics(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped, PasswordHasher>(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddSingleton(); - builder.Services.AddHostedService(); - builder.Services.AddHostedService(); - builder.Services.AddSingleton(); - builder.Services.AddScoped(); - builder.Services.AddHostedService(); - builder.Services.AddHttpClient("Webhooks", client => - client.DefaultRequestHeaders.Add("User-Agent", "MicCheck-Webhook/1.0")); + builder.Services.AddAuditServices(); + builder.Services.AddIdentitiesServices(); + builder.Services.AddEnvironmentsServices(); + builder.Services.AddSegmentsServices(); + builder.Services.AddOrganizationsServices(); + builder.Services.AddProjectsServices(); + builder.Services.AddFeaturesServices(); + builder.Services.AddUsersServices(); + builder.Services.AddWebhooksServices(); - var connectionString = builder.Configuration.GetConnectionString("miccheck") - ?? (System.Environment.GetEnvironmentVariable("DATABASE_URL") is { } databaseUrl - ? DatabaseUrlParser.ToNpgsqlConnectionString(databaseUrl) - : builder.Configuration.GetConnectionString("DefaultConnection")!); - - builder.Services.AddDbContext(options => - options.UseNpgsql(connectionString)); - builder.Services.AddScoped(sp => sp.GetRequiredService()); + builder.Services.AddDataServices(builder.Configuration); var app = builder.Build(); diff --git a/src/api/MicCheck.Api/Projects/DependencyRegistration.cs b/src/api/MicCheck.Api/Projects/DependencyRegistration.cs new file mode 100644 index 0000000..81929fd --- /dev/null +++ b/src/api/MicCheck.Api/Projects/DependencyRegistration.cs @@ -0,0 +1,11 @@ +namespace MicCheck.Api.Projects; + +public static class DependencyRegistration +{ + public static IServiceCollection AddProjectsServices(this IServiceCollection services) + { + services.AddScoped(); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Segments/DependencyRegistration.cs b/src/api/MicCheck.Api/Segments/DependencyRegistration.cs new file mode 100644 index 0000000..df7c80f --- /dev/null +++ b/src/api/MicCheck.Api/Segments/DependencyRegistration.cs @@ -0,0 +1,12 @@ +namespace MicCheck.Api.Segments; + +public static class DependencyRegistration +{ + public static IServiceCollection AddSegmentsServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddScoped(); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Users/DependencyRegistration.cs b/src/api/MicCheck.Api/Users/DependencyRegistration.cs new file mode 100644 index 0000000..7229e37 --- /dev/null +++ b/src/api/MicCheck.Api/Users/DependencyRegistration.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Identity; + +namespace MicCheck.Api.Users; + +public static class DependencyRegistration +{ + public static IServiceCollection AddUsersServices(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped, PasswordHasher>(); + + return services; + } +} diff --git a/src/api/MicCheck.Api/Webhooks/DependencyRegistration.cs b/src/api/MicCheck.Api/Webhooks/DependencyRegistration.cs new file mode 100644 index 0000000..c240f4b --- /dev/null +++ b/src/api/MicCheck.Api/Webhooks/DependencyRegistration.cs @@ -0,0 +1,17 @@ +namespace MicCheck.Api.Webhooks; + +public static class DependencyRegistration +{ + public static IServiceCollection AddWebhooksServices(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddSingleton(); + services.AddHostedService(); + services.AddHostedService(); + services.AddHttpClient("Webhooks", client => + client.DefaultRequestHeaders.Add("User-Agent", "MicCheck-Webhook/1.0")); + + return services; + } +}