automatically map the management endpoints (#2069)

This commit is contained in:
Erwin van der Valk 2025-06-20 13:27:18 +02:00 committed by GitHub
parent b24478a088
commit 6cd370ebc6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 65 additions and 17 deletions

View file

@ -96,8 +96,6 @@ app.UseBff();
app.UseAuthorization();
app.UseAntiforgery();
app.MapBffManagementEndpoints();
app.MapRemoteBffApiEndpoint("/remote-apis/user-token", new Uri("https://localhost:5010"))
.WithAccessToken(RequiredTokenType.User);

View file

@ -83,7 +83,6 @@ app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(Hosts.Bff.Blazor.WebAssembly.Client._Imports).Assembly);
app.MapBffManagementEndpoints();
WeatherEndpoints.Map(app);
app.Run();

View file

@ -100,9 +100,6 @@ internal static class Extensions
.RequireAuthorization()
.AsBffApiEndpoint();
// login, logout, user, backchannel logout...
app.MapBffManagementEndpoints();
// proxy endpoint for cross-site APIs
// all calls to /api/* will be forwarded to the remote API
// user or client access token will be attached in API call

View file

@ -76,9 +76,6 @@ app.MapControllers()
.RequireAuthorization()
.AsBffApiEndpoint();
// login, logout, user, backchannel logout...
app.MapBffManagementEndpoints();
//////////////////////////////////////
// proxy app for cross-site APIs
//////////////////////////////////////

View file

@ -182,8 +182,6 @@ app.MapGet("/local/invokes-external-api", async (SelectedFrontend frontend, IHtt
return data;
});
app.MapBffManagementEndpoints();
app.Run();
RouteConfig[] BuildYarpRoutes()

View file

@ -49,8 +49,6 @@ public class BffHost : Host
app.UseBff();
app.MapGet("/", () => "bff");
app.MapBffManagementEndpoints();
app.MapRemoteBffApiEndpoint("/allow_anon", apiUri);
app.MapRemoteBffApiEndpoint("/client_token", apiUri)
.WithAccessToken(RequiredTokenType.Client);

View file

@ -35,6 +35,12 @@ public static class BffEndpointRouteBuilderExtensions
/// <param name="endpoints"></param>
public static void MapBffManagementEndpoints(this IEndpointRouteBuilder endpoints)
{
var options = endpoints.ServiceProvider.GetRequiredService<IOptions<BffOptions>>().Value;
if (endpoints.AlreadyMappedManagementEndpoint(options.LoginPath, "Login"))
{
return;
}
endpoints.MapBffManagementLoginEndpoint();
#pragma warning disable CS0618 // Type or member is obsolete
endpoints.MapBffManagementSilentLoginEndpoints();
@ -60,6 +66,18 @@ public static class BffEndpointRouteBuilderExtensions
.AllowAnonymous();
}
internal static bool AlreadyMappedManagementEndpoint(this IEndpointRouteBuilder endpoints, PathString route, string name)
{
if (endpoints.DataSources.Any(x =>
x.Endpoints.OfType<RouteEndpoint>().Any(x => x.RoutePattern.RawText == route.ToString())))
{
endpoints.ServiceProvider.GetRequiredService<ILogger<BffBuilder>>().LogWarning("Already mapped {name} endpoint, so the call to MapBffManagementEndpoints will be ignored. If you're using BffOptions.AutomaticallyRegisterBffMiddleware, you don't need to call endpoints.MapBffManagementEndpoints()", name);
return true;
}
return false;
}
/// <summary>
/// Adds the silent login BFF management endpoints
/// </summary>
@ -72,6 +90,7 @@ public static class BffEndpointRouteBuilderExtensions
var options = endpoints.ServiceProvider.GetRequiredService<IOptions<BffOptions>>().Value;
endpoints.MapGet(options.SilentLoginPath.Value!, ProcessWith<ISilentLoginEndpoint>)
.WithName("SilentLogin")
.WithMetadata(new BffUiEndpointAttribute())
.AllowAnonymous();
@ -91,6 +110,7 @@ public static class BffEndpointRouteBuilderExtensions
var options = endpoints.ServiceProvider.GetRequiredService<IOptions<BffOptions>>().Value;
endpoints.MapGet(options.LogoutPath.Value!, ProcessWith<ILogoutEndpoint>)
.WithName("Logout")
.WithMetadata(new BffUiEndpointAttribute())
.AllowAnonymous();
}

View file

@ -34,7 +34,15 @@ internal class ConfigureBffStartupFilter : IStartupFilter
}
if (bffOptions.AutomaticallyRegisterBffMiddleware)
{
app.UseEndpoints(endpoints =>
{
if (!endpoints.AlreadyMappedManagementEndpoint(bffOptions.LoginPath, "Login"))
{
endpoints.MapBffManagementEndpoints();
}
});
app.UseBffIndexPages();
}
ConfigureOpenIdConfigurationCacheExpiration(app);

View file

@ -11,7 +11,7 @@ using ApiHost = Duende.Bff.Tests.TestInfra.ApiHost;
namespace Duende.Bff.Tests.Headers;
public class General(ITestOutputHelper output) : BffTestBase(output)
public class GeneralTests(ITestOutputHelper output) : BffTestBase(output)
{
[Theory, MemberData(nameof(AllSetups))]
public async Task local_endpoint_should_receive_standard_headers(BffSetupType setup)
@ -90,4 +90,31 @@ public class General(ITestOutputHelper output) : BffTestBase(output)
apiResult.RequestHeaders["Host"].Single().ShouldBe("api");
apiResult.RequestHeaders["x-custom"].Single().ShouldBe("custom");
}
[Theory, MemberData(nameof(AllSetups))]
public async Task Will_auto_register_login_endpoints(BffSetupType setup)
{
ConfigureBff(setup);
await InitializeAsync();
Context.LogMessages.ToString().ShouldNotContain("Already mapped Login endpoint");
// And we can log in, which means the login endpoint was registered
await Bff.BrowserClient.Login();
}
[Theory, MemberData(nameof(AllSetups))]
public async Task If_management_endpoints_are_mapped_manually_then_warning_is_written(BffSetupType setup)
{
Bff.OnConfigureEndpoints += endpoints => endpoints.MapBffManagementEndpoints();
ConfigureBff(setup);
await InitializeAsync();
// And we can log in, which means the login endpoint was registered
await Bff.BrowserClient.Login();
Context.LogMessages.ToString().ShouldContain("Already mapped Login endpoint");
}
}

View file

@ -66,8 +66,6 @@ public class BffTestHost(TestHostContext context, IdentityServerTestHost identit
endpoints.MapGet("/", () => DefaultRootResponse);
}
endpoints.MapBffManagementEndpoints();
};
}

View file

@ -1,6 +1,7 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using System.Text;
using Xunit.Abstractions;
namespace Duende.Bff.Tests.TestInfra;
@ -8,7 +9,14 @@ namespace Duende.Bff.Tests.TestInfra;
public record TestHostContext(ITestOutputHelper OutputHelper)
{
public readonly SimulatedInternet Internet = new SimulatedInternet(OutputHelper.WriteLine);
public WriteTestOutput WriteOutput => OutputHelper.WriteLine;
public WriteTestOutput WriteOutput => s =>
{
OutputHelper.WriteLine(s);
LogMessages.AppendLine(s);
};
public readonly TestData The = new TestData();
public TestDataBuilder Some => new TestDataBuilder(The);
public readonly StringBuilder LogMessages = new StringBuilder();
}