From 53ee04c16370ea966309b2a29607440c2bf008f3 Mon Sep 17 00:00:00 2001 From: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com> Date: Wed, 3 Apr 2024 18:55:18 +0300 Subject: [PATCH] PAS-392 | Add rate limit bypass (#509) --- src/Api/Endpoints/Magic.cs | 14 ++- src/Api/Overrides/ApplicationOverrides.cs | 12 +++ .../ApplicationOverridesExtensions.cs | 17 ++++ .../AuthorizationTests.cs | 30 +++++- .../Endpoints/App/AppTests.cs | 99 +++++++++---------- .../Authenticators/AuthenticatorsTests.cs | 42 ++++---- .../Endpoints/Credentials/CredentialsTests.cs | 26 +++-- .../Endpoints/Events/EventsTests.cs | 89 ++++++++++------- .../Endpoints/Magic/MagicTests.cs | 73 ++++++++------ .../Register/RegisterAttestationTests.cs | 18 +++- .../Endpoints/Register/RegisterTests.cs | 18 +++- .../Endpoints/Register/RegisterTokenTests.cs | 30 +++++- .../Endpoints/SignIn/SignInTests.cs | 42 ++++++-- .../AuthorizationIntegrationTests.cs | 24 ++++- .../Middleware/RoutingIntegrationTests.cs | 12 ++- tests/Api.IntegrationTests/PasswordlessApi.cs | 66 +++++-------- .../PasswordlessApiFixture.cs | 15 ++- .../PasswordlessApiOptions.cs | 10 ++ 18 files changed, 411 insertions(+), 226 deletions(-) create mode 100644 src/Api/Overrides/ApplicationOverrides.cs create mode 100644 src/Api/Overrides/ApplicationOverridesExtensions.cs create mode 100644 tests/Api.IntegrationTests/PasswordlessApiOptions.cs diff --git a/src/Api/Endpoints/Magic.cs b/src/Api/Endpoints/Magic.cs index 894e1be9a..f7ce17af8 100644 --- a/src/Api/Endpoints/Magic.cs +++ b/src/Api/Endpoints/Magic.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.RateLimiting; using Passwordless.Api.Authorization; using Passwordless.Api.OpenApi; +using Passwordless.Api.Overrides; using Passwordless.Common.MagicLinks.Models; using Passwordless.Service.Features; using Passwordless.Service.Helpers; @@ -26,12 +27,20 @@ public static class MagicEndpoints /// /// Adds a rate limiter policy for the MagicEndpoints. Each tenant will have its own partition. /// - /// The RateLimiterOptions builder. public static void AddMagicRateLimiterPolicy(this RateLimiterOptions builder) => builder.AddPolicy(RateLimiterPolicy, context => { var tenant = context.User.FindFirstValue(CustomClaimTypes.AccountName) ?? ""; + var isRateLimitBypassed = context.RequestServices + .GetRequiredService() + .GetSection("ApplicationOverrides") + .GetApplicationOverrides(tenant) + .IsRateLimitBypassEnabled; + + if (isRateLimitBypassed) + return RateLimitPartition.GetNoLimiter(tenant); + return RateLimitPartition.GetFixedWindowLimiter(tenant, _ => new FixedWindowRateLimiterOptions { Window = TimeSpan.FromMinutes(5), PermitLimit = 10, QueueLimit = 0, AutoReplenishment = true } ); @@ -40,7 +49,6 @@ public static void AddMagicRateLimiterPolicy(this RateLimiterOptions builder) => /// /// Maps the magic link endpoints. /// - /// The object. public static void MapMagicEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/magic-links") @@ -55,8 +63,6 @@ public static void MapMagicEndpoints(this IEndpointRouteBuilder app) /// /// Sends an e-mail containing a magic link template allowing users to login. - /// - /// Warning: Verify the e-mail address matches the user identifier in your backend. /// [ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType(typeof(ValidationProblemDetails), (int)HttpStatusCode.BadRequest, MediaTypeNames.Application.ProblemJson)] diff --git a/src/Api/Overrides/ApplicationOverrides.cs b/src/Api/Overrides/ApplicationOverrides.cs new file mode 100644 index 000000000..bfc229011 --- /dev/null +++ b/src/Api/Overrides/ApplicationOverrides.cs @@ -0,0 +1,12 @@ +namespace Passwordless.Api.Overrides; + +/// +/// Configures behavior overrides for an application. +/// +public class ApplicationOverrides +{ + /// + /// Whether actions on behalf of this application bypass rate limiting. + /// + public bool IsRateLimitBypassEnabled { get; init; } +} \ No newline at end of file diff --git a/src/Api/Overrides/ApplicationOverridesExtensions.cs b/src/Api/Overrides/ApplicationOverridesExtensions.cs new file mode 100644 index 000000000..0f1f403e2 --- /dev/null +++ b/src/Api/Overrides/ApplicationOverridesExtensions.cs @@ -0,0 +1,17 @@ +namespace Passwordless.Api.Overrides; + +/// +/// Extensions for . +/// +public static class ApplicationOverridesExtensions +{ + /// + /// Gets overrides for the specified application from the configuration. + /// + public static ApplicationOverrides GetApplicationOverrides( + this IConfiguration configuration, + string applicationId) => + configuration + .GetSection(applicationId) + .Get() ?? new ApplicationOverrides(); +} \ No newline at end of file diff --git a/tests/Api.IntegrationTests/AuthorizationTests.cs b/tests/Api.IntegrationTests/AuthorizationTests.cs index 21e26166f..4c79a97f3 100644 --- a/tests/Api.IntegrationTests/AuthorizationTests.cs +++ b/tests/Api.IntegrationTests/AuthorizationTests.cs @@ -30,7 +30,11 @@ public class AuthorizationTests(ITestOutputHelper testOutput, PasswordlessApiFix public async Task ValidateThatEndpointsHaveProtectionAsync() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddAcceptApplicationJson(); // Act @@ -64,7 +68,11 @@ public async Task ValidateThatEndpointsHaveProtectionAsync() public async Task ValidateThatMissingApiSecretThrowsAsync() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddAcceptApplicationJson(); // Act @@ -88,7 +96,11 @@ public async Task ValidateThatMissingApiSecretThrowsAsync() public async Task ValidateThatInvalidApiSecretThrowsAsync() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddAcceptApplicationJson(); // Act @@ -121,7 +133,11 @@ public async Task ValidateThatInvalidApiSecretThrowsAsync() public async Task ApiSecretGivesHelpfulAdviceAsync(string input, string details) { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddAcceptApplicationJson(); using var request = new HttpRequestMessage(HttpMethod.Get, "/credentials/list?userId=1"); @@ -163,7 +179,11 @@ public async Task ApiSecretGivesHelpfulAdviceAsync(string input, string details) public async Task ApiPublicGivesHelpfulAdviceAsync(string input, string details) { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddAcceptApplicationJson(); using var request = new HttpRequestMessage(HttpMethod.Post, "/signin/begin"); diff --git a/tests/Api.IntegrationTests/Endpoints/App/AppTests.cs b/tests/Api.IntegrationTests/Endpoints/App/AppTests.cs index bbb75dec9..942121748 100644 --- a/tests/Api.IntegrationTests/Endpoints/App/AppTests.cs +++ b/tests/Api.IntegrationTests/Endpoints/App/AppTests.cs @@ -26,7 +26,7 @@ public class AppTests(ITestOutputHelper testOutput, PasswordlessApiFixture apiFi public async Task I_cannot_create_an_account_with_an_invalid_name(string name) { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); // Act @@ -45,7 +45,7 @@ public async Task I_cannot_create_an_account_with_an_invalid_name(string name) public async Task I_can_create_an_account_with_a_valid_name() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); const string accountName = "anders"; @@ -68,7 +68,7 @@ public async Task I_can_create_an_account_with_a_valid_name() public async Task I_can_create_an_app_and_its_features_will_be_set_correctly() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var name = GetApplicationName(); @@ -80,7 +80,8 @@ public async Task I_can_create_an_app_and_its_features_will_be_set_correctly() using var scope = api.Services.CreateScope(); - var appFeature = await scope.ServiceProvider.GetRequiredService().Create(name).GetAppFeaturesAsync(); + var appFeature = await scope.ServiceProvider.GetRequiredService().Create(name) + .GetAppFeaturesAsync(); appFeature.Should().NotBeNull(); appFeature!.Tenant.Should().Be(name); @@ -93,7 +94,7 @@ public async Task I_can_create_an_app_and_its_features_will_be_set_correctly() public async Task I_can_set_event_logging_retention_period() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); const int expectedEventLoggingRetentionPeriod = 30; @@ -114,7 +115,8 @@ public async Task I_can_set_event_logging_retention_period() setFeatureResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); using var scope = api.Services.CreateScope(); - var appFeature = await scope.ServiceProvider.GetRequiredService().Create(name).GetAppFeaturesAsync(); + var appFeature = await scope.ServiceProvider.GetRequiredService().Create(name) + .GetAppFeaturesAsync(); appFeature.Should().NotBeNull(); appFeature!.EventLoggingRetentionPeriod.Should().Be(expectedEventLoggingRetentionPeriod); } @@ -125,7 +127,7 @@ public async Task I_can_set_event_logging_retention_period() public async Task I_can_not_set_the_event_logging_retention_period_to_an_invalid_value(int invalidRetentionPeriod) { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var name = GetApplicationName(); @@ -134,11 +136,8 @@ public async Task I_can_not_set_the_event_logging_retention_period_to_an_invalid using var appHttpClient = api.CreateClient().AddSecretKey(appCreateDto!.ApiSecret1); // Act - using var setFeatureResponse = await appHttpClient.PostAsJsonAsync("/apps/features", new SetFeaturesRequest - { - PerformedBy = "a_user", - EventLoggingRetentionPeriod = invalidRetentionPeriod - }); + using var setFeatureResponse = await appHttpClient.PostAsJsonAsync("/apps/features", + new SetFeaturesRequest { PerformedBy = "a_user", EventLoggingRetentionPeriod = invalidRetentionPeriod }); // Assert setFeatureResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -151,13 +150,17 @@ public async Task I_can_not_set_the_event_logging_retention_period_to_an_invalid public async Task I_can_manage_an_apps_features() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); const int expectedEventLoggingRetentionPeriod = 30; var name = GetApplicationName(); _ = await client.CreateApplicationAsync(name); - var manageFeatureRequest = new ManageFeaturesRequest { EventLoggingRetentionPeriod = expectedEventLoggingRetentionPeriod, EventLoggingIsEnabled = true }; + var manageFeatureRequest = new ManageFeaturesRequest + { + EventLoggingRetentionPeriod = expectedEventLoggingRetentionPeriod, + EventLoggingIsEnabled = true + }; // Act var manageFeatureResponse = await client.PostAsJsonAsync($"/admin/apps/{name}/features", manageFeatureRequest); @@ -166,7 +169,8 @@ public async Task I_can_manage_an_apps_features() manageFeatureResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); using var scope = api.Services.CreateScope(); - var appFeature = await scope.ServiceProvider.GetRequiredService().Create(name).GetAppFeaturesAsync(); + var appFeature = await scope.ServiceProvider.GetRequiredService().Create(name) + .GetAppFeaturesAsync(); appFeature.Should().NotBeNull(); appFeature!.EventLoggingRetentionPeriod.Should().Be(expectedEventLoggingRetentionPeriod); appFeature.EventLoggingIsEnabled.Should().BeTrue(); @@ -177,13 +181,17 @@ public async Task I_can_manage_an_apps_features() public async Task I_can_get_an_apps_features() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); const int expectedEventLoggingRetentionPeriod = 30; var name = GetApplicationName(); _ = await client.CreateApplicationAsync(name); - var manageAppFeatureRequest = new ManageFeaturesRequest { EventLoggingRetentionPeriod = expectedEventLoggingRetentionPeriod, EventLoggingIsEnabled = true }; + var manageAppFeatureRequest = new ManageFeaturesRequest + { + EventLoggingRetentionPeriod = expectedEventLoggingRetentionPeriod, + EventLoggingIsEnabled = true + }; _ = await client.PostAsJsonAsync($"/admin/apps/{name}/features", manageAppFeatureRequest); // Act @@ -203,7 +211,7 @@ public async Task I_can_get_an_apps_features() public async Task I_can_get_all_api_keys_for_my_application() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); var applicationName = GetApplicationName(); using var client = api.CreateClient().AddManagementKey(); @@ -224,7 +232,7 @@ public async Task I_can_get_all_api_keys_for_my_application() public async Task I_can_create_a_new_public_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -250,7 +258,7 @@ public async Task I_can_create_a_new_public_key() public async Task I_can_create_a_new_secret_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -276,7 +284,7 @@ public async Task I_can_create_a_new_secret_key() public async Task I_can_lock_an_api_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -286,7 +294,8 @@ public async Task I_can_lock_an_api_key() var keyToLock = apiKeys!.First(x => x.Type == ApiKeyTypes.Public); // Act - using var response = await client.PostAsync($"/admin/apps/{applicationName}/api-keys/{keyToLock.Id}/lock", null); + using var response = + await client.PostAsync($"/admin/apps/{applicationName}/api-keys/{keyToLock.Id}/lock", null); // Assert response.StatusCode.Should().Be(HttpStatusCode.NoContent); @@ -307,7 +316,7 @@ public async Task I_can_lock_an_api_key() public async Task I_can_unlock_a_locked_api_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -339,7 +348,7 @@ public async Task I_can_unlock_a_locked_api_key() public async Task I_can_delete_an_api_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -349,12 +358,14 @@ public async Task I_can_delete_an_api_key() var keyToDelete = apiKeys!.First(); // Act - using var responseMessage = await client.DeleteAsync($"/admin/apps/{applicationName}/api-keys/{keyToDelete.Id}"); + using var responseMessage = + await client.DeleteAsync($"/admin/apps/{applicationName}/api-keys/{keyToDelete.Id}"); // Assert responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent); using var assertKeyIsDeletedResponse = await client.GetAsync($"/admin/apps/{applicationName}/api-keys"); - var assertKeyIsDeleted = await assertKeyIsDeletedResponse.Content.ReadFromJsonAsync>(); + var assertKeyIsDeleted = + await assertKeyIsDeletedResponse.Content.ReadFromJsonAsync>(); assertKeyIsDeleted.Should().NotContain(x => x.Id == keyToDelete.Id); } @@ -363,7 +374,7 @@ public async Task I_can_delete_an_api_key() public async Task I_can_enable_the_generate_sign_in_token_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -373,11 +384,7 @@ public async Task I_can_enable_the_generate_sign_in_token_endpoint() // Act using var enableResponse = await client.PostAsJsonAsync("apps/features", - new SetFeaturesRequest - { - PerformedBy = "a_user", - EnableManuallyGeneratedAuthenticationTokens = true - }); + new SetFeaturesRequest { PerformedBy = "a_user", EnableManuallyGeneratedAuthenticationTokens = true }); // Assert enableResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); @@ -396,7 +403,7 @@ public async Task I_can_enable_the_generate_sign_in_token_endpoint() public async Task I_can_disable_the_generate_sign_in_token_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -406,11 +413,7 @@ public async Task I_can_disable_the_generate_sign_in_token_endpoint() // Act using var enableResponse = await client.PostAsJsonAsync("apps/features", - new SetFeaturesRequest - { - PerformedBy = "a_user", - EnableManuallyGeneratedAuthenticationTokens = false - }); + new SetFeaturesRequest { PerformedBy = "a_user", EnableManuallyGeneratedAuthenticationTokens = false }); // Assert enableResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); @@ -429,7 +432,7 @@ public async Task I_can_disable_the_generate_sign_in_token_endpoint() public async Task I_can_enable_magic_links() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -439,11 +442,7 @@ public async Task I_can_enable_magic_links() // Act using var enableResponse = await client.PostAsJsonAsync("apps/features", - new SetFeaturesRequest - { - PerformedBy = "a_user", - EnableMagicLinks = true - }); + new SetFeaturesRequest { PerformedBy = "a_user", EnableMagicLinks = true }); // Assert enableResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); @@ -460,7 +459,7 @@ public async Task I_can_enable_magic_links() public async Task I_can_disable_magic_links() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -470,11 +469,7 @@ public async Task I_can_disable_magic_links() // Act using var enableResponse = await client.PostAsJsonAsync("apps/features", - new SetFeaturesRequest - { - PerformedBy = "a_user", - EnableMagicLinks = false - }); + new SetFeaturesRequest { PerformedBy = "a_user", EnableMagicLinks = false }); // Assert enableResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); @@ -489,7 +484,7 @@ public async Task I_can_disable_magic_links() public async Task I_can_check_whether_an_app_id_is_available() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); @@ -507,7 +502,7 @@ public async Task I_can_check_whether_an_app_id_is_available() public async Task I_can_check_whether_an_app_id_is_unavailable() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddManagementKey(); var applicationName = GetApplicationName(); diff --git a/tests/Api.IntegrationTests/Endpoints/Authenticators/AuthenticatorsTests.cs b/tests/Api.IntegrationTests/Endpoints/Authenticators/AuthenticatorsTests.cs index 27903ebc2..9608a34d1 100644 --- a/tests/Api.IntegrationTests/Endpoints/Authenticators/AuthenticatorsTests.cs +++ b/tests/Api.IntegrationTests/Endpoints/Authenticators/AuthenticatorsTests.cs @@ -31,7 +31,7 @@ public class AuthenticatorsTests(ITestOutputHelper testOutput, PasswordlessApiFi public async Task I_can_retrieve_configured_authenticators_when_attestation_is_allowed() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -52,7 +52,7 @@ public async Task I_can_retrieve_configured_authenticators_when_attestation_is_a public async Task I_can_retrieve_configured_authenticators_with_expected_result() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -61,14 +61,14 @@ public async Task I_can_retrieve_configured_authenticators_with_expected_result( client.AddSecretKey(accountKeysCreation!.ApiSecret1); client.AddPublicKey(accountKeysCreation!.ApiKey1); await client.EnableAttestation(applicationName); - var request = new AddAuthenticatorsRequest(new List - { - Guid.Parse("973446CA-E21C-9A9B-99F5-9B985A67AF0F") - }, true); + var request = + new AddAuthenticatorsRequest(new List { Guid.Parse("973446CA-E21C-9A9B-99F5-9B985A67AF0F") }, true); _ = await client.PostAsJsonAsync("authenticators/add", request); // Act - var actual = await client.GetFromJsonAsync>("authenticators/list?isAllowed=true"); + var actual = + await client.GetFromJsonAsync>( + "authenticators/list?isAllowed=true"); // Assert actual.Should().NotBeNull(); @@ -80,7 +80,7 @@ public async Task I_can_retrieve_configured_authenticators_with_expected_result( public async Task I_receive_forbidden_when_retrieving_configured_authenticators_when_attestation_is_not_allowed() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -100,7 +100,7 @@ public async Task I_receive_forbidden_when_retrieving_configured_authenticators_ public async Task I_can_whitelist_authenticators_when_attestation_is_allowed() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -110,10 +110,8 @@ public async Task I_can_whitelist_authenticators_when_attestation_is_allowed() client.AddPublicKey(accountKeysCreation!.ApiKey1); await client.EnableAttestation(applicationName); - var request = new AddAuthenticatorsRequest(new List - { - Guid.Parse("973446CA-E21C-9A9B-99F5-9B985A67AF0F") - }, true); + var request = + new AddAuthenticatorsRequest(new List { Guid.Parse("973446CA-E21C-9A9B-99F5-9B985A67AF0F") }, true); // Act var actual = await client.PostAsJsonAsync("authenticators/add", request); @@ -126,7 +124,7 @@ public async Task I_can_whitelist_authenticators_when_attestation_is_allowed() public async Task I_receive_forbidden_when_whitelisting_authenticators_when_attestation_is_not_allowed() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -135,10 +133,8 @@ public async Task I_receive_forbidden_when_whitelisting_authenticators_when_atte client.AddSecretKey(accountKeysCreation!.ApiSecret1); client.AddPublicKey(accountKeysCreation!.ApiKey1); - var request = new AddAuthenticatorsRequest(new List - { - Guid.Parse("973446CA-E21C-9A9B-99F5-9B985A67AF0F") - }, true); + var request = + new AddAuthenticatorsRequest(new List { Guid.Parse("973446CA-E21C-9A9B-99F5-9B985A67AF0F") }, true); // Act var actual = await client.PostAsJsonAsync("authenticators/add", request); @@ -151,7 +147,7 @@ public async Task I_receive_forbidden_when_whitelisting_authenticators_when_atte public async Task I_can_delist_authenticators_when_attestation_is_allowed() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -161,10 +157,8 @@ public async Task I_can_delist_authenticators_when_attestation_is_allowed() client.AddPublicKey(accountKeysCreation!.ApiKey1); await client.EnableAttestation(applicationName); - var whitelistRequest = new AddAuthenticatorsRequest(new List - { - Guid.Parse("973446CA-E21C-9A9B-99F5-9B985A67AF0F") - }, true); + var whitelistRequest = + new AddAuthenticatorsRequest(new List { Guid.Parse("973446CA-E21C-9A9B-99F5-9B985A67AF0F") }, true); _ = await client.PostAsJsonAsync("authenticators/add", whitelistRequest); var request = new RemoveAuthenticatorsRequest( new List { Guid.Parse("973446CA-E21C-9A9B-99F5-9B985A67AF0F") }); @@ -180,7 +174,7 @@ public async Task I_can_delist_authenticators_when_attestation_is_allowed() public async Task I_receive_forbidden_when_delisting_authenticators_when_attestation_is_not_allowed() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); diff --git a/tests/Api.IntegrationTests/Endpoints/Credentials/CredentialsTests.cs b/tests/Api.IntegrationTests/Endpoints/Credentials/CredentialsTests.cs index 7f2694fc8..9275f5689 100644 --- a/tests/Api.IntegrationTests/Endpoints/Credentials/CredentialsTests.cs +++ b/tests/Api.IntegrationTests/Endpoints/Credentials/CredentialsTests.cs @@ -24,22 +24,32 @@ public class CredentialsTests(ITestOutputHelper testOutput, PasswordlessApiFixtu public async Task I_can_view_a_list_of_registered_users_credentials() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); const string originUrl = PasswordlessApi.OriginUrl; const string rpId = PasswordlessApi.RpId; var tokenRequest = _tokenGenerator.Generate(); using var tokenResponse = await client.PostAsJsonAsync("register/token", tokenRequest); - var registerTokenResponse = await tokenResponse.Content.ReadFromJsonAsync(); - var registrationBeginRequest = new FidoRegistrationBeginDTO { Token = registerTokenResponse!.Token, Origin = originUrl, RPID = rpId }; + var registerTokenResponse = + await tokenResponse.Content.ReadFromJsonAsync(); + var registrationBeginRequest = + new FidoRegistrationBeginDTO { Token = registerTokenResponse!.Token, Origin = originUrl, RPID = rpId }; using var registrationBeginResponse = await client.PostAsJsonAsync("register/begin", registrationBeginRequest); - var sessionResponse = await registrationBeginResponse.Content.ReadFromJsonAsync>(); + var sessionResponse = await registrationBeginResponse.Content + .ReadFromJsonAsync>(); - var authenticatorAttestationRawResponse = await BrowserCredentialsHelper.CreateCredentialsAsync(sessionResponse!.Data, originUrl); + var authenticatorAttestationRawResponse = + await BrowserCredentialsHelper.CreateCredentialsAsync(sessionResponse!.Data, originUrl); _ = await client.PostAsJsonAsync("register/complete", - new RegistrationCompleteDTO { Origin = originUrl, RPID = rpId, Session = sessionResponse.Session, Response = authenticatorAttestationRawResponse }); + new RegistrationCompleteDTO + { + Origin = originUrl, + RPID = rpId, + Session = sessionResponse.Session, + Response = authenticatorAttestationRawResponse + }); // Act using var credentialsResponse = await client.GetAsync($"credentials/list?userId={tokenRequest.UserId}"); @@ -57,7 +67,7 @@ public async Task I_can_view_a_list_of_registered_users_credentials() public async Task I_am_told_to_pass_the_user_id_when_getting_credential_list_using_get_with_secret_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); // Act @@ -76,7 +86,7 @@ public async Task I_am_told_to_pass_the_user_id_when_getting_credential_list_usi public async Task I_am_told_to_pass_the_user_id_when_getting_credential_list_using_post_with_secret_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); var request = new GetCredentialsRequest(null!); diff --git a/tests/Api.IntegrationTests/Endpoints/Events/EventsTests.cs b/tests/Api.IntegrationTests/Endpoints/Events/EventsTests.cs index e449ceec1..061464eb4 100644 --- a/tests/Api.IntegrationTests/Endpoints/Events/EventsTests.cs +++ b/tests/Api.IntegrationTests/Endpoints/Events/EventsTests.cs @@ -21,7 +21,7 @@ public class EventsTests(ITestOutputHelper testOutput, PasswordlessApiFixture ap public async Task I_can_view_the_event_for_a_user_retrieving_the_api_keys() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -36,7 +36,8 @@ public async Task I_can_view_the_event_for_a_user_retrieving_the_api_keys() // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); applicationEvents.Events.Should().Contain(x => x.EventType == EventType.AdminApiKeysEnumerated.ToString()); @@ -46,7 +47,7 @@ public async Task I_can_view_the_event_for_a_user_retrieving_the_api_keys() public async Task I_can_view_the_event_for_a_user_creating_an_api_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -62,7 +63,8 @@ public async Task I_can_view_the_event_for_a_user_creating_an_api_key() // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); applicationEvents.Events.Should().Contain(x => x.EventType == EventType.AdminApiKeyCreated.ToString()); @@ -72,7 +74,7 @@ public async Task I_can_view_the_event_for_a_user_creating_an_api_key() public async Task I_can_view_the_event_for_locking_an_api_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -90,7 +92,8 @@ public async Task I_can_view_the_event_for_locking_an_api_key() // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); applicationEvents.Events.Should().Contain(x => x.EventType == EventType.AdminApiKeyLocked.ToString()); @@ -100,7 +103,7 @@ public async Task I_can_view_the_event_for_locking_an_api_key() public async Task I_can_view_the_event_for_unlocking_an_api_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -119,7 +122,8 @@ public async Task I_can_view_the_event_for_unlocking_an_api_key() // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); applicationEvents.Events.Should().Contain(x => x.EventType == EventType.AdminApiKeyUnlocked.ToString()); @@ -129,7 +133,7 @@ public async Task I_can_view_the_event_for_unlocking_an_api_key() public async Task I_can_view_the_event_for_deleting_an_api_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -147,7 +151,8 @@ public async Task I_can_view_the_event_for_deleting_an_api_key() // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); applicationEvents.Events.Should().Contain(x => x.EventType == EventType.AdminApiKeyDeleted.ToString()); @@ -157,7 +162,7 @@ public async Task I_can_view_the_event_for_deleting_an_api_key() public async Task I_can_view_the_event_for_enabling_the_generate_sign_in_token_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -173,10 +178,12 @@ public async Task I_can_view_the_event_for_enabling_the_generate_sign_in_token_e // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); - var enabledEvent = applicationEvents.Events.FirstOrDefault(x => x.EventType == EventType.AdminGenerateSignInTokenEndpointEnabled.ToString()); + var enabledEvent = applicationEvents.Events.FirstOrDefault(x => + x.EventType == EventType.AdminGenerateSignInTokenEndpointEnabled.ToString()); enabledEvent.Should().NotBeNull(); enabledEvent!.PerformedBy.Should().Be(user); } @@ -185,7 +192,7 @@ public async Task I_can_view_the_event_for_enabling_the_generate_sign_in_token_e public async Task I_can_view_the_event_for_disabling_the_generate_sign_in_token_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -201,10 +208,12 @@ public async Task I_can_view_the_event_for_disabling_the_generate_sign_in_token_ // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); - var enabledEvent = applicationEvents.Events.FirstOrDefault(x => x.EventType == EventType.AdminGenerateSignInTokenEndpointDisabled.ToString()); + var enabledEvent = applicationEvents.Events.FirstOrDefault(x => + x.EventType == EventType.AdminGenerateSignInTokenEndpointDisabled.ToString()); enabledEvent.Should().NotBeNull(); enabledEvent!.PerformedBy.Should().Be(user); } @@ -213,7 +222,7 @@ public async Task I_can_view_the_event_for_disabling_the_generate_sign_in_token_ public async Task I_can_view_the_event_for_enabling_magic_links() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -229,10 +238,12 @@ public async Task I_can_view_the_event_for_enabling_magic_links() // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); - var enabledEvent = applicationEvents.Events.FirstOrDefault(x => x.EventType == EventType.AdminMagicLinksEnabled.ToString()); + var enabledEvent = + applicationEvents.Events.FirstOrDefault(x => x.EventType == EventType.AdminMagicLinksEnabled.ToString()); enabledEvent.Should().NotBeNull(); enabledEvent!.PerformedBy.Should().Be(user); } @@ -241,7 +252,7 @@ public async Task I_can_view_the_event_for_enabling_magic_links() public async Task I_can_view_the_event_for_disabling_magic_links() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -257,10 +268,12 @@ public async Task I_can_view_the_event_for_disabling_magic_links() // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); - var enabledEvent = applicationEvents.Events.FirstOrDefault(x => x.EventType == EventType.AdminMagicLinksDisabled.ToString()); + var enabledEvent = + applicationEvents.Events.FirstOrDefault(x => x.EventType == EventType.AdminMagicLinksDisabled.ToString()); enabledEvent.Should().NotBeNull(); enabledEvent!.PerformedBy.Should().Be(user); } @@ -269,7 +282,7 @@ public async Task I_can_view_the_event_for_disabling_magic_links() public async Task I_can_view_the_event_for_using_a_disabled_api_secret() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -288,17 +301,19 @@ public async Task I_can_view_the_event_for_using_a_disabled_api_secret() using var getApplicationEventsResponse = await client.GetAsync("events?pageNumber=1"); // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); - applicationEvents.Events.Should().Contain(x => x.EventType == EventType.ApiAuthDisabledSecretKeyUsed.ToString()); + applicationEvents.Events.Should() + .Contain(x => x.EventType == EventType.ApiAuthDisabledSecretKeyUsed.ToString()); } [Fact] public async Task I_can_view_the_event_for_using_a_disabled_public_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -311,7 +326,8 @@ public async Task I_can_view_the_event_for_using_a_disabled_public_key() var apiKeys = await getApiKeysResponse.Content.ReadFromJsonAsync>(); var keyToLock = apiKeys!.First(x => x.ApiKey.EndsWith(accountKeysCreation.ApiKey1.GetLast(4))); _ = await client.PostAsync($"/admin/apps/{applicationName}/api-keys/{keyToLock.Id}/lock", null); - _ = await client.PostAsJsonAsync("/signin/begin", new SignInBeginDTO { Origin = PasswordlessApi.OriginUrl, RPID = PasswordlessApi.RpId }); + _ = await client.PostAsJsonAsync("/signin/begin", + new SignInBeginDTO { Origin = PasswordlessApi.OriginUrl, RPID = PasswordlessApi.RpId }); _ = await client.PostAsync($"/admin/apps/{applicationName}/api-keys/{keyToLock.Id}/unlock", null); // Act @@ -319,17 +335,19 @@ public async Task I_can_view_the_event_for_using_a_disabled_public_key() // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); - applicationEvents.Events.Should().Contain(x => x.EventType == EventType.ApiAuthDisabledPublicKeyUsed.ToString()); + applicationEvents.Events.Should() + .Contain(x => x.EventType == EventType.ApiAuthDisabledPublicKeyUsed.ToString()); } [Fact] public async Task I_can_view_the_event_for_using_a_non_existent_api_key() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -338,14 +356,16 @@ public async Task I_can_view_the_event_for_using_a_non_existent_api_key() client.AddSecretKey(accountKeysCreation!.ApiSecret1); client.AddPublicKey($"{applicationName}:public:invalid-public-key"); await client.EnableEventLogging(applicationName); - _ = await client.PostAsJsonAsync("/signin/begin", new SignInBeginDTO { Origin = PasswordlessApi.OriginUrl, RPID = PasswordlessApi.RpId }); + _ = await client.PostAsJsonAsync("/signin/begin", + new SignInBeginDTO { Origin = PasswordlessApi.OriginUrl, RPID = PasswordlessApi.RpId }); // Act using var getApplicationEventsResponse = await client.GetAsync("events?pageNumber=1"); // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); applicationEvents.Events.Should().Contain(x => x.EventType == EventType.ApiAuthInvalidPublicKeyUsed.ToString()); @@ -355,7 +375,7 @@ public async Task I_can_view_the_event_for_using_a_non_existent_api_key() public async Task I_can_view_the_event_for_using_a_non_existent_api_secret() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -372,7 +392,8 @@ public async Task I_can_view_the_event_for_using_a_non_existent_api_secret() // Assert getApplicationEventsResponse.StatusCode.Should().Be(HttpStatusCode.OK); - var applicationEvents = await getApplicationEventsResponse.Content.ReadFromJsonAsync(); + var applicationEvents = + await getApplicationEventsResponse.Content.ReadFromJsonAsync(); applicationEvents.Should().NotBeNull(); applicationEvents!.Events.Should().NotBeEmpty(); applicationEvents.Events.Should().Contain(x => x.EventType == EventType.ApiAuthInvalidSecretKeyUsed.ToString()); diff --git a/tests/Api.IntegrationTests/Endpoints/Magic/MagicTests.cs b/tests/Api.IntegrationTests/Endpoints/Magic/MagicTests.cs index c88273a9f..530d6ddca 100644 --- a/tests/Api.IntegrationTests/Endpoints/Magic/MagicTests.cs +++ b/tests/Api.IntegrationTests/Endpoints/Magic/MagicTests.cs @@ -21,7 +21,7 @@ public class MagicTests(ITestOutputHelper testOutput, PasswordlessApiFixture api public async Task I_can_send_a_magic_link_email() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -45,7 +45,7 @@ public async Task I_can_send_a_magic_link_email() public async Task I_cannot_send_a_magic_link_email_if_the_feature_is_disabled() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -69,7 +69,7 @@ public async Task I_cannot_send_a_magic_link_email_if_the_feature_is_disabled() public async Task I_receive_a_validation_error_when_the_url_does_not_contain_the_token_template() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -91,16 +91,18 @@ public async Task I_receive_a_validation_error_when_the_url_does_not_contain_the response.StatusCode.Should().Be(HttpStatusCode.BadRequest); var responseDetails = await response.Content.ReadFromJsonAsync(); responseDetails.Should().NotBeNull(); - var magicLinkUrlError = responseDetails!.Errors.FirstOrDefault(x => x.Key.Equals(nameof(request.UrlTemplate), StringComparison.CurrentCultureIgnoreCase)); + var magicLinkUrlError = responseDetails!.Errors.FirstOrDefault(x => + x.Key.Equals(nameof(request.UrlTemplate), StringComparison.CurrentCultureIgnoreCase)); magicLinkUrlError.Should().NotBeNull(); - magicLinkUrlError.Value.Should().Contain($"You have provided a {nameof(request.UrlTemplate)} without a {SendMagicLinkRequest.TokenTemplate} template. Please include it like so: https://www.example.com?token={SendMagicLinkRequest.TokenTemplate}"); + magicLinkUrlError.Value.Should().Contain( + $"You have provided a {nameof(request.UrlTemplate)} without a {SendMagicLinkRequest.TokenTemplate} template. Please include it like so: https://www.example.com?token={SendMagicLinkRequest.TokenTemplate}"); } [Fact] public async Task I_receive_a_validation_error_when_an_invalid_url_is_sent() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -122,23 +124,23 @@ public async Task I_receive_a_validation_error_when_an_invalid_url_is_sent() response.StatusCode.Should().Be(HttpStatusCode.BadRequest); var responseDetails = await response.Content.ReadFromJsonAsync(); responseDetails.Should().NotBeNull(); - var magicLinkUrlError = responseDetails!.Errors.FirstOrDefault(x => x.Key.Equals(nameof(request.UrlTemplate), StringComparison.CurrentCultureIgnoreCase)); + var magicLinkUrlError = responseDetails!.Errors.FirstOrDefault(x => + x.Key.Equals(nameof(request.UrlTemplate), StringComparison.CurrentCultureIgnoreCase)); magicLinkUrlError.Should().NotBeNull(); - magicLinkUrlError.Value.Should().Contain($"You have provided a {nameof(request.UrlTemplate)} that cannot be converted to a URL."); + magicLinkUrlError.Value.Should() + .Contain($"You have provided a {nameof(request.UrlTemplate)} that cannot be converted to a URL."); } [Fact] public async Task I_cannot_send_a_magic_link_email_to_a_non_admin_address_if_the_application_is_too_new() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); - using var appCreateResponse = await client.CreateApplicationAsync(applicationName, new CreateAppDto - { - AdminEmail = "admin@email.com" - }); + using var appCreateResponse = + await client.CreateApplicationAsync(applicationName, new CreateAppDto { AdminEmail = "admin@email.com" }); var appCreated = await appCreateResponse.Content.ReadFromJsonAsync(); client.AddSecretKey(appCreated!.ApiSecret1); await client.EnableMagicLinks("a_user"); @@ -159,15 +161,13 @@ public async Task I_cannot_send_a_magic_link_email_to_a_non_admin_address_if_the public async Task I_can_send_a_magic_link_email_to_an_admin_address_even_if_the_application_is_too_new() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); const string emailAddress = "admin@email.com"; var applicationName = CreateAppHelpers.GetApplicationName(); - using var appCreateResponse = await client.CreateApplicationAsync(applicationName, new CreateAppDto - { - AdminEmail = emailAddress - }); + using var appCreateResponse = + await client.CreateApplicationAsync(applicationName, new CreateAppDto { AdminEmail = emailAddress }); var appCreated = await appCreateResponse.Content.ReadFromJsonAsync(); client.AddSecretKey(appCreated!.ApiSecret1); await client.EnableMagicLinks("a_user"); @@ -188,7 +188,7 @@ public async Task I_can_send_a_magic_link_email_to_an_admin_address_even_if_the_ public async Task I_cannot_send_too_many_magic_link_emails_in_a_short_time() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput, false); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { TestOutput = testOutput }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -219,7 +219,7 @@ public async Task I_cannot_send_too_many_magic_link_emails_in_a_short_time() // Assert unsuccessfulResponse.Should().NotBeNull(); - unsuccessfulResponse!.StatusCode.Should().Be(HttpStatusCode.TooManyRequests); + unsuccessfulResponse.StatusCode.Should().Be(HttpStatusCode.TooManyRequests); unsuccessfulResponse.Dispose(); } @@ -230,14 +230,22 @@ public async Task I_cannot_send_too_many_magic_link_emails_in_a_short_time() public async Task I_cannot_send_too_many_magic_link_emails_in_a_month() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); - using var client = api.CreateClient(); - var applicationName = CreateAppHelpers.GetApplicationName(); - using var appCreateResponse = await client.CreateApplicationAsync(applicationName, new CreateAppDto + + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions { - MagicLinkEmailMonthlyQuota = 1000 + Settings = new Dictionary + { + [$"ApplicationOverrides:{applicationName}:IsRateLimitBypassEnabled"] = "true" + }, + TestOutput = testOutput }); + + using var client = api.CreateClient(); + + using var appCreateResponse = + await client.CreateApplicationAsync(applicationName, + new CreateAppDto { MagicLinkEmailMonthlyQuota = 1000 }); var appCreated = await appCreateResponse.Content.ReadFromJsonAsync(); client.AddSecretKey(appCreated!.ApiSecret1); await client.EnableMagicLinks("a_user"); @@ -269,7 +277,7 @@ public async Task I_cannot_send_too_many_magic_link_emails_in_a_month() // Assert successfulResponseCount.Should().Be(1000); unsuccessfulResponse.Should().NotBeNull(); - unsuccessfulResponse!.StatusCode.Should().Be(HttpStatusCode.TooManyRequests); + unsuccessfulResponse.StatusCode.Should().Be(HttpStatusCode.TooManyRequests); var details = await unsuccessfulResponse.Content.ReadFromJsonAsync(); details.Should().NotBeNull(); @@ -282,10 +290,19 @@ public async Task I_cannot_send_too_many_magic_link_emails_in_a_month() public async Task I_can_send_a_magic_link_email_after_enough_time_passed_since_the_monthly_quota_was_exceeded() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + var applicationName = CreateAppHelpers.GetApplicationName(); + + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + Settings = new Dictionary + { + [$"ApplicationOverrides:{applicationName}:IsRateLimitBypassEnabled"] = "true" + }, + TestOutput = testOutput + }); + using var client = api.CreateClient(); - var applicationName = CreateAppHelpers.GetApplicationName(); using var appCreateResponse = await client.CreateApplicationAsync(applicationName); var appCreated = await appCreateResponse.Content.ReadFromJsonAsync(); client.AddSecretKey(appCreated!.ApiSecret1); diff --git a/tests/Api.IntegrationTests/Endpoints/Register/RegisterAttestationTests.cs b/tests/Api.IntegrationTests/Endpoints/Register/RegisterAttestationTests.cs index 051e707e0..e65b893bf 100644 --- a/tests/Api.IntegrationTests/Endpoints/Register/RegisterAttestationTests.cs +++ b/tests/Api.IntegrationTests/Endpoints/Register/RegisterAttestationTests.cs @@ -39,7 +39,11 @@ public class RegisterAttestationTests(ITestOutputHelper testOutput, Passwordless public async Task I_can_use_supported_attestation_methods_to_register_a_new_user_when_attestation_is_allowed(string attestation, AttestationConveyancePreference expectedAttestation) { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient(); var tokenRequest = TokenGenerator @@ -87,7 +91,11 @@ public async Task I_can_use_supported_attestation_methods_to_register_a_new_user public async Task I_can_use_supported_none_attestation_method_to_register_a_new_user_when_attestation_is_disallowed(string attestation, AttestationConveyancePreference expectedAttestation) { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient(); var tokenRequest = TokenGenerator @@ -135,7 +143,11 @@ public async Task I_can_use_supported_none_attestation_method_to_register_a_new_ public async Task I_cannot_use_other_than_none_attestation_method_to_register_a_new_user_when_attestation_is_disallowed(string attestation) { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient(); var tokenRequest = TokenGenerator diff --git a/tests/Api.IntegrationTests/Endpoints/Register/RegisterTests.cs b/tests/Api.IntegrationTests/Endpoints/Register/RegisterTests.cs index 5a4caee3e..dc6eabbfe 100644 --- a/tests/Api.IntegrationTests/Endpoints/Register/RegisterTests.cs +++ b/tests/Api.IntegrationTests/Endpoints/Register/RegisterTests.cs @@ -22,7 +22,11 @@ public class RegisterTests(ITestOutputHelper testOutput, PasswordlessApiFixture public async Task I_can_retrieve_token_to_start_registration() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); var request = _tokenGenerator.Generate(); @@ -41,7 +45,11 @@ public async Task I_can_retrieve_token_to_start_registration() public async Task I_can_retrieve_the_credential_create_options_and_session_token_for_creating_a_new_user() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); var tokenRequest = _tokenGenerator.Generate(); @@ -70,7 +78,11 @@ public async Task I_can_retrieve_the_credential_create_options_and_session_token public async Task I_can_use_a_passkey_to_register_a_new_user() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); using var driver = WebDriverFactory.GetDriver(PasswordlessApi.OriginUrl); diff --git a/tests/Api.IntegrationTests/Endpoints/Register/RegisterTokenTests.cs b/tests/Api.IntegrationTests/Endpoints/Register/RegisterTokenTests.cs index 012aea6ba..69e912fb7 100644 --- a/tests/Api.IntegrationTests/Endpoints/Register/RegisterTokenTests.cs +++ b/tests/Api.IntegrationTests/Endpoints/Register/RegisterTokenTests.cs @@ -19,7 +19,11 @@ public async Task UserIdAndDisplayNameIsTheOnlyRequiredProperties() // Arrange var payload = new { UserId = "1", Username = "test" }; - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddSecretKey(); // Act @@ -40,7 +44,11 @@ public async Task InvalidUserIdReturnsError(string userid) registerTokenGenerator.RuleFor(x => x.UserId, userid); var registerToken = registerTokenGenerator.Generate(); - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddSecretKey(); // Act @@ -69,7 +77,11 @@ public async Task InvalidUsernameReturnsError(string input) registerTokenGenerator.RuleFor(x => x.Username, input); var registerToken = registerTokenGenerator.Generate(); - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddSecretKey(); // Act @@ -93,7 +105,11 @@ public async Task OtherAssertionIsNotAccepted(string attestation) var payload = new { UserId = "1", Username = "test", attestation }; // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddSecretKey(); // Act @@ -130,7 +146,11 @@ public async Task NoneAssertionIsAccepted(string attestation) Attestation = attestation }; - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddSecretKey(); // Act diff --git a/tests/Api.IntegrationTests/Endpoints/SignIn/SignInTests.cs b/tests/Api.IntegrationTests/Endpoints/SignIn/SignInTests.cs index 14a44541d..42fee3977 100644 --- a/tests/Api.IntegrationTests/Endpoints/SignIn/SignInTests.cs +++ b/tests/Api.IntegrationTests/Endpoints/SignIn/SignInTests.cs @@ -22,7 +22,11 @@ public class SignInTests(ITestOutputHelper testOutput, PasswordlessApiFixture ap public async Task I_can_retrieve_assertion_options_to_begin_sign_in() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); var request = new SignInBeginDTO { Origin = PasswordlessApi.OriginUrl, RPID = PasswordlessApi.RpId }; @@ -44,7 +48,11 @@ public async Task I_can_retrieve_assertion_options_to_begin_sign_in() public async Task I_can_retrieve_my_passkey_after_registering_and_receive_a_sign_in_token() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var httpClient = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); using var driver = WebDriverFactory.GetDriver(PasswordlessApi.OriginUrl); @@ -74,7 +82,11 @@ public async Task I_can_retrieve_my_passkey_after_registering_and_receive_a_sign public async Task I_can_retrieve_my_passkey_after_registering_and_receive_a_valid_sign_in_token() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var httpClient = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); using var driver = WebDriverFactory.GetDriver(PasswordlessApi.OriginUrl); @@ -108,7 +120,11 @@ public async Task I_can_retrieve_my_passkey_after_registering_and_receive_a_vali public async Task I_receive_an_error_message_when_sending_an_unrecognized_passkey() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var _httpClient = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); using var options = await _httpClient.PostAsJsonAsync("/signin/begin", new { Origin = PasswordlessApi.OriginUrl, RPID = PasswordlessApi.RpId }); @@ -157,7 +173,11 @@ public async Task I_receive_an_error_message_when_sending_an_unrecognized_passke public async Task An_expired_apps_token_keys_should_be_removed_when_a_request_is_made() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var httpClient = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); var applicationName = $"test{Guid.NewGuid():N}"; @@ -185,7 +205,11 @@ public async Task An_expired_apps_token_keys_should_be_removed_when_a_request_is public async Task I_receive_a_sign_in_token_for_a_valid_user_id() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var httpClient = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); using var client = api.CreateClient().AddManagementKey(); @@ -219,7 +243,11 @@ public async Task I_receive_an_api_exception_when_using_an_expired_token() { // Arrange const int timeToLive = 120; - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var httpClient = api.CreateClient().AddPublicKey().AddSecretKey().AddUserAgent(); using var client = api.CreateClient().AddManagementKey(); diff --git a/tests/Api.IntegrationTests/Middleware/AuthorizationIntegrationTests.cs b/tests/Api.IntegrationTests/Middleware/AuthorizationIntegrationTests.cs index 76b94125d..5acee30e6 100644 --- a/tests/Api.IntegrationTests/Middleware/AuthorizationIntegrationTests.cs +++ b/tests/Api.IntegrationTests/Middleware/AuthorizationIntegrationTests.cs @@ -16,7 +16,11 @@ public class AuthorizationIntegrationTests(ITestOutputHelper testOutput, Passwor public async Task I_receive_a_403_when_i_use_a_invalid_api_key_with_an_existing_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -35,7 +39,11 @@ public async Task I_receive_a_403_when_i_use_a_invalid_api_key_with_an_existing_ public async Task I_receive_a_403_when_i_use_a_badly_formatted_api_key_with_an_existing_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -54,7 +62,11 @@ public async Task I_receive_a_403_when_i_use_a_badly_formatted_api_key_with_an_e public async Task I_receive_a_403_when_i_use_a_invalid_api_secret_with_an_existing_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -73,7 +85,11 @@ public async Task I_receive_a_403_when_i_use_a_invalid_api_secret_with_an_existi public async Task I_receive_a_403_when_i_use_a_badly_formatted_api_secret_with_an_existing_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); diff --git a/tests/Api.IntegrationTests/Middleware/RoutingIntegrationTests.cs b/tests/Api.IntegrationTests/Middleware/RoutingIntegrationTests.cs index 0b9afc05a..9a9168cc6 100644 --- a/tests/Api.IntegrationTests/Middleware/RoutingIntegrationTests.cs +++ b/tests/Api.IntegrationTests/Middleware/RoutingIntegrationTests.cs @@ -15,7 +15,11 @@ public class RoutingIntegrationTests(ITestOutputHelper testOutput, PasswordlessA public async Task I_receive_a_404_when_i_use_a_badly_formatted_api_secret_with_a_non_existing_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); @@ -34,7 +38,11 @@ public async Task I_receive_a_404_when_i_use_a_badly_formatted_api_secret_with_a public async Task I_receive_a_404_when_i_use_a_badly_formatted_api_key_with_a_non_existing_endpoint() { // Arrange - await using var api = await apiFixture.CreateApiAsync(testOutput); + await using var api = await apiFixture.CreateApiAsync(new PasswordlessApiOptions + { + + TestOutput = testOutput + }); using var client = api.CreateClient(); var applicationName = CreateAppHelpers.GetApplicationName(); diff --git a/tests/Api.IntegrationTests/PasswordlessApi.cs b/tests/Api.IntegrationTests/PasswordlessApi.cs index 8ddf228b0..b7470863a 100644 --- a/tests/Api.IntegrationTests/PasswordlessApi.cs +++ b/tests/Api.IntegrationTests/PasswordlessApi.cs @@ -1,16 +1,13 @@ using MartinCostello.Logging.XUnit; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.AspNetCore.RateLimiting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Time.Testing; -using Passwordless.Api.Endpoints; using Passwordless.Api.IntegrationTests.Helpers; using Passwordless.Common.Services.Mail; using Xunit.Abstractions; @@ -24,49 +21,32 @@ public class PasswordlessApi : ITestOutputHelperAccessor, IDisposable, IAsyncDis private readonly WebApplicationFactory _factory; - public PasswordlessApi( - ITestOutputHelper? testOutput, - string databaseConnectionString, - bool disableRateLimiting) + public PasswordlessApi(PasswordlessApiOptions options) { _factory = new WebApplicationFactory() - .WithWebHostBuilder(host => host - .UseSetting("ConnectionStrings:sqlite:api", string.Empty) - .UseSetting("ConnectionStrings:mssql:api", databaseConnectionString) - .ConfigureLogging(logging => logging.ClearProviders().AddXUnit(this)) - .ConfigureTestServices(services => - { - // Disable background services - services.RemoveAll(); - - // Disable rate limiting - if (disableRateLimiting) + .WithWebHostBuilder(host => + { + foreach (var (key, value) in options.Settings) + host.UseSetting(key, value); + + host + .ConfigureLogging(logging => logging.ClearProviders().AddXUnit(this)) + .ConfigureTestServices(services => { - services.RemoveAll>(); - services.AddSingleton>(_ => - Options.Create( - // Have to re-add all the rate limiter policies, because they are referenced - // by the endpoints. - new RateLimiterOptions() - .AddFixedWindowLimiter(MagicEndpoints.RateLimiterPolicy, limiter => - { - limiter.PermitLimit = int.MaxValue; - limiter.Window = TimeSpan.FromSeconds(1); - }) - ) - ); - } - - // Replace time - services.RemoveAll(); - services.AddSingleton(Time); - - // Replace mail provider - services.RemoveAll(); - services.AddSingleton(); - })); - - TestOutput = testOutput; + // Disable background services + services.RemoveAll(); + + // Replace time + services.RemoveAll(); + services.AddSingleton(Time); + + // Replace mail provider + services.RemoveAll(); + services.AddSingleton(); + }); + }); + + TestOutput = options.TestOutput; Services = _factory.Services; } diff --git a/tests/Api.IntegrationTests/PasswordlessApiFixture.cs b/tests/Api.IntegrationTests/PasswordlessApiFixture.cs index e6eb7ce2b..75459af70 100644 --- a/tests/Api.IntegrationTests/PasswordlessApiFixture.cs +++ b/tests/Api.IntegrationTests/PasswordlessApiFixture.cs @@ -10,11 +10,18 @@ public class PasswordlessApiFixture : IAsyncDisposable, IAsyncLifetime public async Task InitializeAsync() => await _dbContainer.StartAsync(); - public async Task CreateApiAsync( - ITestOutputHelper? testOutput = null, - bool disableRateLimiting = true) + public async Task CreateApiAsync(PasswordlessApiOptions options) { - var api = new PasswordlessApi(testOutput, _dbContainer.GetConnectionString(), disableRateLimiting); + var api = new PasswordlessApi(new PasswordlessApiOptions + { + TestOutput = options.TestOutput, + // Add the connection string to the settings + Settings = new Dictionary(options.Settings) + { + ["ConnectionStrings:sqlite:api"] = null, + ["ConnectionStrings:mssql:api"] = _dbContainer.GetConnectionString() + } + }); // Perform migrations and make sure the API is ready to receive requests using var httpClient = api.CreateClient(); diff --git a/tests/Api.IntegrationTests/PasswordlessApiOptions.cs b/tests/Api.IntegrationTests/PasswordlessApiOptions.cs new file mode 100644 index 000000000..7c7578f83 --- /dev/null +++ b/tests/Api.IntegrationTests/PasswordlessApiOptions.cs @@ -0,0 +1,10 @@ +using Xunit.Abstractions; + +namespace Passwordless.Api.IntegrationTests; + +public class PasswordlessApiOptions +{ + public IReadOnlyDictionary Settings { get; init; } = new Dictionary(); + + public ITestOutputHelper? TestOutput { get; init; } +} \ No newline at end of file