diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs new file mode 100644 index 000000000..af98ca5c5 --- /dev/null +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs @@ -0,0 +1,62 @@ +internal static class Extensions +{ + public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration) + { + services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy")); + return services; + } + + public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + services.AddHealthChecks() + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" }) + .AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" }); + + return services; + } + + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + // Register delegating handlers + services.AddTransient(); + + // Register http services + services.AddHttpClient() + .AddHttpMessageHandler(); + + return services; + } + + public static IServiceCollection AddGrpcServices(this IServiceCollection services) + { + services.AddTransient(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var basketApi = services.GetRequiredService>().Value.GrpcBasket; + options.Address = new Uri(basketApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; + options.Address = new Uri(catalogApi); + }).AddInterceptor(); + + services.AddScoped(); + + services.AddGrpcClient((services, options) => + { + var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; + options.Address = new Uri(orderingApi); + }).AddInterceptor(); + + return services; + } +} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs deleted file mode 100644 index 11473d1c1..000000000 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters -{ - namespace Basket.API.Infrastructure.Filters - { - public class AuthorizeCheckOperationFilter : IOperationFilter - { - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - // Check for authorize attribute - var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || - context.MethodInfo.GetCustomAttributes(true).OfType().Any(); - - if (!hasAuthorize) return; - - operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); - operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); - - var oAuthScheme = new OpenApiSecurityScheme - { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } - }; - - operation.Security = new List - { - new() - { - [ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" } - } - }; - } - } - } -} diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs index 6f18651cf..549873d8d 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs @@ -8,24 +8,18 @@ global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Diagnostics.HealthChecks; -global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; -global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; -global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Diagnostics.HealthChecks; -global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Options; global using Microsoft.OpenApi.Models; -global using Swashbuckle.AspNetCore.SwaggerGen; global using System.Collections.Generic; global using System.IdentityModel.Tokens.Jwt; global using System.Linq; @@ -37,3 +31,4 @@ global using System.Threading.Tasks; global using System.Threading; global using System; global using Microsoft.IdentityModel.Tokens; +global using Services.Common; diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj index dfa3275ba..35f37cb53 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj @@ -17,6 +17,7 @@ + @@ -31,6 +32,10 @@ + + + + diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs index 945124164..6bb3a0d3b 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs @@ -1,145 +1,38 @@ var builder = WebApplication.CreateBuilder(args); -builder.Services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddUrlGroup(new Uri(builder.Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" }) - .AddUrlGroup(new Uri(builder.Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" }) - .AddUrlGroup(new Uri(builder.Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" }) - .AddUrlGroup(new Uri(builder.Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" }) - .AddUrlGroup(new Uri(builder.Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" }); +builder.AddServiceDefaults(); -builder.Services.Configure(builder.Configuration.GetSection("urls")); - -builder.Services.AddControllers() - .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true); - -builder.Services.AddSwaggerGen(options => -{ - options.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Shopping Aggregator for Mobile Clients", - Version = "v1", - Description = "Shopping Aggregator for Mobile Clients" - }); - var identityUrl = builder.Configuration.GetSection("Identity").GetValue("ExternalUrl"); - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows() - { - Implicit = new OpenApiOAuthFlow() - { - AuthorizationUrl = new Uri($"{identityUrl}/connect/authorize"), - TokenUrl = new Uri($"{identityUrl}/connect/token"), - - Scopes = new Dictionary() - { - { "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" } - } - } - } - }); - - options.OperationFilter(); -}); +builder.Services.AddReverseProxy(builder.Configuration); +builder.Services.AddControllers(); +builder.Services.AddHealthChecks(builder.Configuration); builder.Services.AddCors(options => { + // TODO: Read allowed origins from configuration options.AddPolicy("CorsPolicy", builder => builder + .SetIsOriginAllowed((host) => true) .AllowAnyMethod() .AllowAnyHeader() - .SetIsOriginAllowed((host) => true) .AllowCredentials()); }); -JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); +builder.Services.AddApplicationServices(); +builder.Services.AddGrpcServices(); -var identityUrl = builder.Configuration.GetValue("urls:identity"); - -builder.Services.AddAuthentication(options => -{ - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - -}) -.AddJwtBearer(options => -{ - options.Authority = identityUrl; - options.RequireHttpsMetadata = false; - options.Audience = "mobileshoppingagg"; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateAudience = false - }; -}); - -builder.Services.AddAuthorization(options => -{ - options.AddPolicy("ApiScope", policy => - { - policy.RequireAuthenticatedUser(); - policy.RequireClaim("scope", "mobileshoppingagg"); - }); -}); - -builder.Services.AddTransient(); -builder.Services.AddSingleton(); -builder.Services.AddHttpClient(); - -builder.Services.AddTransient(); -builder.Services.AddScoped(); -builder.Services.AddGrpcClient((services, options) => -{ - var basketApi = services.GetRequiredService>().Value.GrpcBasket; - options.Address = new Uri(basketApi); -}).AddInterceptor(); -builder.Services.AddScoped(); -builder.Services.AddGrpcClient((services, options) => -{ - var catalogApi = services.GetRequiredService>().Value.GrpcCatalog; - options.Address = new Uri(catalogApi); -}).AddInterceptor(); -builder.Services.AddScoped(); -builder.Services.AddGrpcClient((services, options) => -{ - var orderingApi = services.GetRequiredService>().Value.GrpcOrdering; - options.Address = new Uri(orderingApi); -}).AddInterceptor(); +builder.Services.Configure(builder.Configuration.GetSection("urls")); var app = builder.Build(); -var pathBase = app.Configuration["PATH_BASE"]; - -if (!string.IsNullOrEmpty(pathBase)) -{ - app.UsePathBase(pathBase); -} - -app.UseSwagger().UseSwaggerUI(c => -{ - c.SwaggerEndpoint($"{(!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty)}/swagger/v1/swagger.json", "Purchase BFF V1"); +app.UseServiceDefaults(); - c.OAuthClientId("mobileshoppingaggswaggerui"); - c.OAuthClientSecret(string.Empty); - c.OAuthRealm(string.Empty); - c.OAuthAppName("Purchase BFF Swagger UI"); -}); +app.UseHttpsRedirection(); -app.UseRouting(); app.UseCors("CorsPolicy"); app.UseAuthentication(); app.UseAuthorization(); -app.MapDefaultControllerRoute(); + app.MapControllers(); -app.MapHealthChecks("/hc", new HealthCheckOptions() -{ - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); -app.MapHealthChecks("/liveness", new HealthCheckOptions -{ - Predicate = r => r.Name.Contains("self") -}); +app.MapReverseProxy(); await app.RunAsync(); diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json index b5a1dcc87..8526211d9 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json @@ -1,20 +1,138 @@ { + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "System.Net.Http": "Warning" + } + }, + "OpenApi": { + "Endpoint": { + "Name": "Purchase BFF V1" + }, + "Document": { + "Description": "Shopping Aggregator for Mobile Clients", + "Title": "Shopping Aggregator for Mobile Clients", + "Version": "v1" + }, + "Auth": { + "ClientId": "mobileshoppingaggswaggerui", + "AppName": "Mobile shopping BFF Swagger UI" + } + }, "Identity": { - "Url": "http://localhost:5105", - "Audience": "mobileshoppingagg" + "Url": "http://localhost:5223", + "Audience": "mobileshoppingagg", + "Scopes": { + "webshoppingagg": "Shopping Aggregator for Mobile Clients" + } }, - "Logging": { - "Debug": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" + "ReverseProxy": { + "Routes": { + "c-short": { + "ClusterId": "catalog", + "Match": { + "Path": "c/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/c" } + ] + }, + "c-long": { + "ClusterId": "catalog", + "Match": { + "Path": "catalog-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/catalog-api" } + ] + }, + "b-short": { + "ClusterId": "basket", + "Match": { + "Path": "b/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/b" } + ] + }, + "b-long": { + "ClusterId": "basket", + "Match": { + "Path": "basket-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/basket-api" } + ] + }, + "o-short": { + "ClusterId": "orders", + "Match": { + "Path": "o/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/o" } + ] + }, + "o-long": { + "ClusterId": "orders", + "Match": { + "Path": "ordering-api/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/ordering-api" } + ] + }, + "h-long": { + "ClusterId": "signalr", + "Match": { + "Path": "hub/notificationhub/{**catch-all}" + } } }, - "Console": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" + "Clusters": { + "basket": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5221" + } + } + }, + "catalog": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5222" + } + } + }, + "orders": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5224" + } + } + }, + "signalr": { + "Destinations": { + "destination0": { + "Address": "http://localhost:5225" + } + } } } - } + }, + "Urls": { + "Basket": "http://localhost:5221", + "Catalog": "http://localhost:5222", + "Orders": "http://localhost:5224", + "Identity": "http://localhost:5223", + "Signalr": "http://localhost:5225", + "GrpcBasket": "http://localhost:6221", + "GrpcCatalog": "http://localhost:6222", + "GrpcOrdering": "http://localhost:6224" + }, + "CatalogUrlHC": "http://localhost:5222/hc", + "OrderingUrlHC": "http://localhost:5224/hc", + "BasketUrlHC": "http://localhost:5221/hc", + "IdentityUrlHC": "http://localhost:5223/hc" } diff --git a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json index 233d14be5..711da6143 100644 --- a/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json +++ b/src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json @@ -17,7 +17,7 @@ }, "Auth": { "ClientId": "webshoppingaggswaggerui", - "AppName": "web shopping bff Swagger UI" + "AppName": "Web Shopping BFF Swagger UI" } }, "Identity": { @@ -27,7 +27,6 @@ "webshoppingagg": "Shopping Aggregator for Web Clients" } }, - "ReverseProxy": { "Routes": { "c-short": {