diff --git a/backend/src/Tnsu.ApprovalRoute.Api/appsettings.json b/backend/src/Tnsu.ApprovalRoute.Api/appsettings.json index 3205e20..bb23562 100644 --- a/backend/src/Tnsu.ApprovalRoute.Api/appsettings.json +++ b/backend/src/Tnsu.ApprovalRoute.Api/appsettings.json @@ -9,8 +9,8 @@ "Postgres": "Host=localhost;Port=5435;Database=tnsu_approval_route;Username=tnsu_approval_route;Password=tnsu_approval_route" }, "Entra": { - "TenantId": "", - "Audience": "" + "TenantId": "b4c2af35-bfd6-4cff-9af9-556efb53e65a", + "Audience": "9232d9e2-9807-4753-808f-07250e5474b0" }, "Dev": { "DefaultUserEmail": "dev@tnsu.kz" diff --git a/backend/src/Tnsu.ApprovalRoute.Infrastructure/Tnsu.ApprovalRoute.Infrastructure.csproj b/backend/src/Tnsu.ApprovalRoute.Infrastructure/Tnsu.ApprovalRoute.Infrastructure.csproj index 5ea6939..33b62bc 100644 --- a/backend/src/Tnsu.ApprovalRoute.Infrastructure/Tnsu.ApprovalRoute.Infrastructure.csproj +++ b/backend/src/Tnsu.ApprovalRoute.Infrastructure/Tnsu.ApprovalRoute.Infrastructure.csproj @@ -13,5 +13,7 @@ + + diff --git a/backend/src/Tnsu.ApprovalRoute.Infrastructure/TnsuPortal/HttpTnsuPortalClient.cs b/backend/src/Tnsu.ApprovalRoute.Infrastructure/TnsuPortal/HttpTnsuPortalClient.cs index 621693d..d1b387e 100644 --- a/backend/src/Tnsu.ApprovalRoute.Infrastructure/TnsuPortal/HttpTnsuPortalClient.cs +++ b/backend/src/Tnsu.ApprovalRoute.Infrastructure/TnsuPortal/HttpTnsuPortalClient.cs @@ -1,14 +1,23 @@ using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Text.Json; using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Http; using Tnsu.ApprovalRoute.Domain.Enums; namespace Tnsu.ApprovalRoute.Infrastructure.TnsuPortal; -// Клиент api.tnsu.kz (api-tnsu-portal). Сервисный токен прокидывается через TnsuPortalTokenProvider. -public sealed class HttpTnsuPortalClient(HttpClient httpClient, TnsuPortalTokenProvider tokenProvider) : ITnsuPortalClient +// Клиент api.tnsu.kz (api-tnsu-portal). +public sealed class HttpTnsuPortalClient( + HttpClient httpClient, + TnsuPortalTokenProvider tokenProvider, + IHttpContextAccessor httpContextAccessor) : ITnsuPortalClient { + // Разные эндпоинты api-tnsu-portal отдают JSON в разном регистре (Dapper-результаты — PascalCase, + // строго типизированные модели — camelCase), поэтому сопоставляем поля без учёта регистра. + private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; + public async Task GetEmployeeByEmailAsync(string email, CancellationToken cancellationToken) { using var request = new HttpRequestMessage(HttpMethod.Get, $"/Employee/ByEmail?email={Uri.EscapeDataString(email)}"); @@ -19,7 +28,7 @@ public sealed class HttpTnsuPortalClient(HttpClient httpClient, TnsuPortalTokenP return null; response.EnsureSuccessStatusCode(); - var employee = await response.Content.ReadFromJsonAsync(cancellationToken); + var employee = await response.Content.ReadFromJsonAsync(JsonOptions, cancellationToken); if (employee is null) return null; @@ -36,7 +45,7 @@ public sealed class HttpTnsuPortalClient(HttpClient httpClient, TnsuPortalTokenP return []; response.EnsureSuccessStatusCode(); - var project = await response.Content.ReadFromJsonAsync(cancellationToken); + var project = await response.Content.ReadFromJsonAsync(JsonOptions, cancellationToken); if (project is null) return []; @@ -56,8 +65,18 @@ public sealed class HttpTnsuPortalClient(HttpClient httpClient, TnsuPortalTokenP executors.Add(new ProjectExecutorInfo(role, email)); } + // ВРЕМЕННО: для TnsuPortal ещё нет своего client-credentials приложения в Entra, поэтому прокидываем + // токен текущего пользователя дальше — его aud совпадает с ClientId api-tnsu-portal, так что подходит. + // Когда появится своё приложение — вернуть на tokenProvider.GetAccessTokenAsync(...). private async Task AuthorizeAsync(HttpRequestMessage request, CancellationToken cancellationToken) { + var incoming = httpContextAccessor.HttpContext?.Request.Headers.Authorization.ToString(); + if (!string.IsNullOrWhiteSpace(incoming)) + { + request.Headers.TryAddWithoutValidation("Authorization", incoming); + return; + } + var token = await tokenProvider.GetAccessTokenAsync(cancellationToken); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); }