разные правки по беку

This commit is contained in:
gorbenko.pavel
2026-08-17 15:33:54 +05:00
parent 2a7130c37f
commit b92897254d
23 changed files with 475 additions and 315 deletions
@@ -0,0 +1,69 @@
using System.Security.Cryptography;
using System.Text;
using Hangfire.Dashboard;
using Microsoft.Net.Http.Headers;
namespace Tnsu.ApprovalRoute.Api.Auth;
/// <summary>
/// Простая Basic Auth для дашборда Hangfire — единственный логин/пароль из
/// конфигурации (Hangfire:Dashboard), а не полноценная схема аутентификации: сам
/// дашборд не участвует в остальной авторизации API (это отдельная страница,
/// открываемая прямо в браузере, не по JWT).
///
/// Пустые логин/пароль (не заданы в конфигурации) означают «дашборд закрыт для
/// всех» — не «открыт для всех»: пустая строка в конфиге не должна случайно снять
/// защиту.
/// </summary>
public sealed class HangfireDashboardBasicAuthFilter(string username, string password) : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password) &&
TryGetBasicAuthCredentials(httpContext.Request.Headers.Authorization, out var user, out var pass) &&
FixedTimeEquals(user, username) && FixedTimeEquals(pass, password))
{
return true;
}
httpContext.Response.Headers[HeaderNames.WWWAuthenticate] = "Basic realm=\"Hangfire\"";
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
return false;
}
private static bool TryGetBasicAuthCredentials(
string? authorizationHeader, out string username, out string password)
{
username = string.Empty;
password = string.Empty;
if (string.IsNullOrEmpty(authorizationHeader) ||
!authorizationHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
return false;
try
{
var decoded = Encoding.UTF8.GetString(
Convert.FromBase64String(authorizationHeader["Basic ".Length..].Trim()));
var separatorIndex = decoded.IndexOf(':');
if (separatorIndex < 0) return false;
username = decoded[..separatorIndex];
password = decoded[(separatorIndex + 1)..];
return true;
}
catch (FormatException)
{
// Битый base64 в заголовке — не наша забота, просто не авторизуем.
return false;
}
}
// Сравнение за постоянное время: длина логина/пароля в Basic Auth и так видна
// по заголовку, но посимвольное сравнение через == утекало бы ЕЩЁ и через
// тайминг совпадения символов — дешёво закрыть и это.
private static bool FixedTimeEquals(string a, string b) =>
CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(a), Encoding.UTF8.GetBytes(b));
}