Implement rate limiting and enhance authentication flow
CI / Backend (build + test) (push) Successful in 1m17s
CI / Frontend (lint + typecheck + build) (push) Successful in 35s

- Added rate limiting configuration for authentication endpoints, allowing customizable request limits via environment variables.
- Updated authentication flow to utilize HttpRequest for cookie management, ensuring secure handling of refresh tokens.
- Introduced a new endpoint to retrieve user subscription details.
- Enhanced the handling of Telegram bot token validation to prevent errors with empty tokens.
- Updated the application to serialize enums as strings for better documentation and compatibility with TypeScript.
- Improved test coverage for new features and adjustments in command handlers.
This commit is contained in:
Leonid Pershin
2026-07-02 12:40:23 +03:00
parent ed07221ca5
commit 8067be3c35
106 changed files with 8823 additions and 172 deletions
@@ -8,13 +8,13 @@ namespace PnvPanel.IntegrationTests.Admin;
[Collection(IntegrationTestCollection.Name)]
public class NodeInboundCrudTests(PnvPanelWebApplicationFactory factory)
{
private sealed record NodeResponse(Guid Id, string Name, string BaseAddress, string Username, string? Location, int Status, bool IsEnabled);
private sealed record NodeResponse(Guid Id, string Name, string BaseAddress, string Username, string? Location, string Status, bool IsEnabled);
private sealed record InboundResponse(
Guid Id, Guid NodeId, string RemoteInboundId, int Protocol, string Remark, int Port,
Guid Id, Guid NodeId, string RemoteInboundId, string Protocol, string Remark, int Port,
bool IsPublished, string? DisplayName, int? MaxClients, IReadOnlyList<Guid> AllowedRoleIds);
private sealed record SyncNodeResponse(int InboundsSynced, int Status);
private sealed record SyncNodeResponse(int InboundsSynced, string Status);
[Fact]
public async Task RegisterSyncListPublish_FullNodeInboundLifecycle_Succeeds()
@@ -77,7 +77,7 @@ public class AuthFlowTests(PnvPanelWebApplicationFactory factory)
var first = await client.PostJsonAsync("/api/auth/register", new { userName, password = "P@ssw0rd123" });
Assert.Equal(HttpStatusCode.OK, first.StatusCode);
var second = await client.PostJsonAsync("/api/auth/register", new { userName, password = "AnotherPass123" });
var second = await client.PostJsonAsync("/api/auth/register", new { userName, password = "AnotherPass123!" });
Assert.Equal(HttpStatusCode.Conflict, second.StatusCode);
}
@@ -15,12 +15,14 @@ public class ConfigQuotaTests(PnvPanelWebApplicationFactory factory)
private sealed record NodeResponse(Guid Id, string Name);
private sealed record SyncNodeResponse(int InboundsSynced, int Status);
private sealed record SyncNodeResponse(int InboundsSynced, string Status);
private sealed record InboundResponse(Guid Id, Guid NodeId, string RemoteInboundId, int Protocol, string Remark, int Port, bool IsPublished);
private sealed record InboundResponse(Guid Id, Guid NodeId, string RemoteInboundId, string Protocol, string Remark, int Port, bool IsPublished);
private sealed record ActivationRequestResponse(Guid Id, string? Comment, DateTimeOffset CreatedAt);
private sealed record MyConfigsResponse(List<object> Configs, int MaxConfigs);
/// <summary>
/// Доказывает, что pg_advisory_xact_lock в CreateVpnConfigCommandHandler реально защищает
/// от гонки: при параллельных запросах ровно Quota проходят, остальные — 409 QuotaExceeded.
@@ -98,7 +100,7 @@ public class ConfigQuotaTests(PnvPanelWebApplicationFactory factory)
Assert.Equal(ConcurrentAttempts - Quota, quotaExceeded);
var myConfigsResponse = await userClient.GetAsync("/api/configs");
var myConfigs = await myConfigsResponse.ReadAsAsync<List<object>>();
Assert.Equal(Quota, myConfigs!.Count);
var myConfigs = await myConfigsResponse.ReadAsAsync<MyConfigsResponse>();
Assert.Equal(Quota, myConfigs!.Configs.Count);
}
}
@@ -27,7 +27,10 @@ public sealed class PnvPanelWebApplicationFactory : WebApplicationFactory<Progra
.WithPassword("pnvpanel")
.Build();
public async Task InitializeAsync() => await _postgres.StartAsync();
public async Task InitializeAsync()
{
await _postgres.StartAsync();
}
async Task IAsyncLifetime.DisposeAsync()
{
@@ -48,6 +51,9 @@ public sealed class PnvPanelWebApplicationFactory : WebApplicationFactory<Progra
["AdminSeed:Password"] = AdminPassword,
// Пусто — TelegramBotHostedService при пустом токене не стартует (см. Api/Telegram/TelegramBotHostedService.cs).
["Telegram:BotToken"] = "",
// Весь collection делит один TestServer/host — все запросы идут от одного "клиента",
// дефолтный лимит 20/мин быстро исчерпывается. Поднимаем для тестового окружения.
["RateLimiting:AuthPermitLimit"] = "10000",
});
});