From 7f6f42a96b58690df520db7cd62cccfb1d495597 Mon Sep 17 00:00:00 2001 From: Mariya Trishina Date: Fri, 21 Aug 2026 17:52:23 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D1=8F=D0=B5=D1=82=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=86=D0=B8=D1=8E=20=D0=B4=D0=BB=D1=8F=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B0=20=D0=B0=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20hr-appeals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 server/README.md diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..2178c07 --- /dev/null +++ b/server/README.md @@ -0,0 +1,63 @@ +# 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` | +| `GRAPH_CERT_PATH` | Путь к `SPFxDeploy_cert.pem` внутри контейнера (см. `docker-compose.yml` в корне репозитория) | +| `GRAPH_KEY_PATH` | Путь к `SPFxDeploy_key.pem` внутри контейнера | +| `GRAPH_SCOPE` | OAuth-scope запрашиваемого токена. **По умолчанию `https://graph.microsoft.com/.default` — для api.tnsu.kz это неверное значение.** Нужно явно указать App ID URI, под которым `api.tnsu.kz` зарегистрирован в Azure AD, например `api:///.default`. Точное значение уточнить у того, кто регистрировал API в Azure AD | +| `PORT` | Порт HTTP-сервера сервиса, по умолчанию `3000` | + +## Как обратиться к 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`).