# hr-appeals-auth Сервис получает app-only токен Azure AD (JWT client assertion) и используется для авторизованных запросов к внутреннему API `api.tnsu.kz`. Сам токен наружу не отдаётся — из HTTP-эндпоинтов сервиса доступны только `/healthz` и `/diag/token` (последний подтверждает, что токен получен, но не возвращает его значение). Детали получения токена — в [graphAuth.ts](src/graphAuth.ts). ## Переменные окружения | Переменная | Назначение | |---|---| | `GRAPH_TENANT_ID` | Tenant ID Azure AD | | `GRAPH_CLIENT_ID` | Client ID App Registration, к которому привязан сертификат `SPFxDeploy` | | `SECRETS_DIR` | Каталог с `SPFxDeploy_cert.pem` и `SPFxDeploy_key.pem` (имена файлов фиксированы). По умолчанию — [`secrets/`](../secrets/README.md) в корне репозитория; в контейнере `/certs`, куда смонтирован каталог с хоста. Задавать нужно только чтобы подменить каталог целиком | | `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` поднимает сервис вместе с фронтендом. Отдельно: ```bash cp .env.example .env # tenant/client id и порт npm run dev # tsc + запуск с чтением .env (--env-file, Node 20+) ``` Сертификат и ключ при этом должны лежать в [`secrets/`](../secrets/README.md) — в git они не хранятся. Проверить, что авторизация работает: `curl http://localhost:3100/diag/token` → `{"ok":true}`. Если вернулась ошибка — в ней будет ответ Azure AD целиком. ## Как обратиться к api.tnsu.kz с авторизацией Токен запрашивается через `getAppOnlyToken()` и передаётся в заголовке `Authorization: Bearer `. Функция сама кэширует токен до истечения, повторно вызывать её на каждый запрос не накладно — за сеть она сходит только когда кэш устарел. ```ts 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(); } ``` Перед тем как писать реальные вызовы, убедитесь, что обмен сертификата на токен вообще работает — на запущенном сервисе: ```bash 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`).