Files
hr-appeals/server
gorbenko.pavel e9ae3d5bab fix: не отправлять в SharePoint поля с пустым значением
Состав полей зависит от вида обращения и анонимности, поэтому часть строк
заведомо пустая. Пустая строка в списке выглядит как заполненное значение,
а не как отсутствующее поле — теперь такие поля выпадают из payload.
Значения обрезаются, так что поле из одних пробелов тоже не уедет.

WantsFeedback остаётся всегда: false здесь осмысленно, а не пусто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 13:01:25 +05:00
..
2026-08-21 12:13:37 +00:00
2026-08-21 12:13:37 +00:00
2026-08-21 12:13:37 +00:00
2026-08-21 12:13:37 +00:00

hr-appeals-auth

Сервис получает app-only токен Azure AD (JWT client assertion) и используется для авторизованных запросов к внутреннему API api.tnsu.kz. Сам токен наружу не отдаётся — из HTTP-эндпоинтов сервиса доступны только /healthz и /diag/token (последний подтверждает, что токен получен, но не возвращает его значение). Детали получения токена — в graphAuth.ts.

Переменные окружения

Переменная Назначение
GRAPH_TENANT_ID Tenant ID Azure AD
GRAPH_CLIENT_ID Client ID App Registration, к которому привязан сертификат SPFxDeploy
GRAPH_CERT_PATH Путь к SPFxDeploy_cert.pem внутри контейнера (см. docker-compose.yml в корне репозитория)
GRAPH_KEY_PATH Путь к SPFxDeploy_key.pem внутри контейнера
GRAPH_SCOPE OAuth-scope запрашиваемого токена. По умолчанию — App ID URI самого api.tnsu.kz (api://9232d9e2-.../.default), задавать не нужно. Токен для https://graph.microsoft.com/.default тут не подойдёт: api.tnsu.kz проверяет aud входящего JWT и отвечает 401
PORT Порт HTTP-сервера сервиса, по умолчанию 3000 (локально — 3100, см. .env.example)

Локальный запуск

Из корня репозитория npm run dev поднимает сервис вместе с фронтендом. Отдельно:

cp .env.example .env   # подставить свои пути к SPFxDeploy_cert.pem / _key.pem
npm run dev            # tsc + запуск с чтением .env (--env-file, Node 20+)

Проверить, что авторизация работает: curl http://localhost:3100/diag/token → {"ok":true}. Если вернулась ошибка — в ней будет ответ Azure AD целиком.

Как обратиться к api.tnsu.kz с авторизацией

Токен запрашивается через getAppOnlyToken() и передаётся в заголовке Authorization: Bearer <token>. Функция сама кэширует токен до истечения, повторно вызывать её на каждый запрос не накладно — за сеть она сходит только когда кэш устарел.

import { getAppOnlyToken } from './graphAuth';

async function callApiTnsu(path: string, init: RequestInit = {}) {
  const token = await getAppOnlyToken();

  const response = await fetch(`https://api.tnsu.kz${path}`, {
    ...init,
    headers: {
      ...init.headers,
      Authorization: `Bearer ${token}`
    }
  });

  if (!response.ok) {
    throw new Error(`api.tnsu.kz ответил ${response.status}: ${await response.text()}`);
  }

  return response.json();
}

Перед тем как писать реальные вызовы, убедитесь, что обмен сертификата на токен вообще работает — на запущенном сервисе:

curl http://localhost:3000/diag/token
# {"ok":true} — сертификат подошёл, токен получен и закэширован
# {"ok":false,"error":"..."} — см. текст ошибки: неверный tenant/client id,
# истёкший сертификат, неверный scope и т.п.

Если /diag/token отвечает ok:true, но реальный вызов api.tnsu.kz падает с 401/403 — вероятная причина в GRAPH_SCOPE: токен получен, но выпущен не для этого API (Azure AD не проверяет аудиторию на этапе выдачи токена — она проверяется уже на стороне api.tnsu.kz).