Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817
This commit is contained in:
commit
ad8ad252a9
825 files changed
+124216
No files matched your search
@@ -0,0 +1,132 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using ItWorx.KnowledgeHub.Contracts.Auth;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Auth;
|
||||
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class LoginLogoutLockoutTests(PostgresContainerFixture postgres)
|
||||
: OwnerSetupTestBase(postgres)
|
||||
{
|
||||
[Fact]
|
||||
public async Task Login_with_correct_credentials_establishes_an_authenticated_session()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
var response = await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var session = await response.Content.ReadFromJsonAsync<SessionResponse>();
|
||||
Assert.NotNull(session);
|
||||
Assert.True(session.Authenticated);
|
||||
Assert.Equal(OwnerEmail, session.Email);
|
||||
Assert.Equal("Administrator", session.Role);
|
||||
|
||||
// The same client (with its session cookie) now sees itself as authenticated
|
||||
// on a fresh request too, not only in the login response body.
|
||||
var sessionAfter = await client.GetFromJsonAsync<SessionResponse>("/api/v1/auth/session");
|
||||
Assert.NotNull(sessionAfter);
|
||||
Assert.True(sessionAfter.Authenticated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_with_wrong_password_is_rejected_generically()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
var response = await SetupHelper.LoginAsync(client, OwnerEmail, "totally-wrong-password");
|
||||
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.DoesNotContain(OwnerPassword, body, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_for_an_unknown_account_returns_the_same_response_as_a_wrong_password()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
var response = await SetupHelper.LoginAsync(client, "does-not-exist@itworx.test", "whatever-password");
|
||||
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Login_rate_limit_is_per_remote_ip_and_ignores_untrusted_forwarded_headers()
|
||||
{
|
||||
using var clientA = Factory.CreateClient();
|
||||
clientA.DefaultRequestHeaders.Add("X-Test-Remote-Ip", "192.0.2.10");
|
||||
|
||||
// Without a configured trusted proxy, these caller-supplied values must not
|
||||
// create fresh rate-limit partitions. All attempts originate from client A's
|
||||
// effective remote address.
|
||||
for (var attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
clientA.DefaultRequestHeaders.Remove("X-Forwarded-For");
|
||||
clientA.DefaultRequestHeaders.Add("X-Forwarded-For", $"203.0.113.{attempt + 1}");
|
||||
|
||||
var response = await SetupHelper.LoginAsync(
|
||||
clientA,
|
||||
"unknown-rate-limit-user@itworx.test",
|
||||
"invalid-password");
|
||||
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
clientA.DefaultRequestHeaders.Remove("X-Forwarded-For");
|
||||
clientA.DefaultRequestHeaders.Add("X-Forwarded-For", "203.0.113.250");
|
||||
var exhaustedClientA = await SetupHelper.LoginAsync(
|
||||
clientA,
|
||||
"unknown-rate-limit-user@itworx.test",
|
||||
"invalid-password");
|
||||
Assert.Equal(HttpStatusCode.TooManyRequests, exhaustedClientA.StatusCode);
|
||||
|
||||
using var clientB = Factory.CreateClient();
|
||||
clientB.DefaultRequestHeaders.Add("X-Test-Remote-Ip", "192.0.2.11");
|
||||
clientB.DefaultRequestHeaders.Add("X-Forwarded-For", "203.0.113.251");
|
||||
var clientBLogin = await SetupHelper.LoginAsync(clientB, OwnerEmail, OwnerPassword);
|
||||
Assert.Equal(HttpStatusCode.OK, clientBLogin.StatusCode);
|
||||
|
||||
clientA.DefaultRequestHeaders.Remove("X-Forwarded-For");
|
||||
clientA.DefaultRequestHeaders.Add("X-Forwarded-For", "203.0.113.252");
|
||||
var stillExhaustedClientA = await SetupHelper.LoginAsync(
|
||||
clientA,
|
||||
"unknown-rate-limit-user@itworx.test",
|
||||
"invalid-password");
|
||||
Assert.Equal(HttpStatusCode.TooManyRequests, stillExhaustedClientA.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Repeated_failed_logins_lock_the_account()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
|
||||
// Configured lockout threshold is 5 failed attempts.
|
||||
HttpResponseMessage last = null!;
|
||||
for (var attempt = 0; attempt < 6; attempt++)
|
||||
{
|
||||
last = await SetupHelper.LoginAsync(client, OwnerEmail, "wrong-password-attempt");
|
||||
}
|
||||
|
||||
Assert.Equal(HttpStatusCode.TooManyRequests, last.StatusCode);
|
||||
|
||||
// Even the correct password is now rejected while locked out.
|
||||
var withCorrectPassword = await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
Assert.Equal(HttpStatusCode.TooManyRequests, withCorrectPassword.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Logout_ends_the_session()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
|
||||
var logoutResponse = await SetupHelper.PostWithCsrfAsync<object?>(client, "/api/v1/auth/logout", null);
|
||||
Assert.Equal(HttpStatusCode.OK, logoutResponse.StatusCode);
|
||||
|
||||
var session = await client.GetFromJsonAsync<SessionResponse>("/api/v1/auth/session");
|
||||
Assert.NotNull(session);
|
||||
Assert.False(session.Authenticated);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user