Table of Contents

Авторизация в TypeScript/Angular

Клиентские приложения ПОЛИНОМ:MDM авторизуются на web-сервере с помощью JWT-токенов. В SDK авторизация реализована библиотекой @ascon/polynom-api (сервисы LoginService, PolynomApiConfig, AuthStore и фабрика SignInRequestFactory), а готовое окно входа — библиотекой @ascon/polynom-login.

ВНИМАНИЕ: срок действия токена доступа истекает через 10 минут после его создания. Для продолжения работы используется токен обновления, который применяется автоматически методом LoginService.updateToken().

Подключение библиотек

Рекомендуемые зависимости:

{
  "dependencies": {
    "@ascon/core": "24.1.126",
    "@ascon/polynom-api": "~24.1.14404",
    "@ascon/polynom-api-client": "~24.1.14404",
    "@ascon/polynom-login": "~24.1.14404",
    "@ascon/primeng": "24.0.2",
    "@ascon/qwebchannel-bridge": "^6.8.3",
    "@ascon/ui": "24.1.126"
  }
}

Библиотеки @ascon/* можно получить из каталога поставки SDK/Web/Samples/Client/libs/@ascon или из npm-регистра.

Настройка адреса web-сервера и типа клиента

Адрес web-сервера и тип клиента задаются через сервис PolynomApiConfig:

import { inject } from "@angular/core";
import { ClientTypes } from "@ascon/polynom-api-client";
import { PolynomApiConfig } from "@ascon/polynom-api";

const polynomApiConfig: PolynomApiConfig = inject(PolynomApiConfig);

// Адрес web-сервера ПОЛИНОМ:MDM
polynomApiConfig.setHost("http://localhost:5000");

// Тип клиента влияет на получение лицензии
polynomApiConfig.setClientType(ClientTypes.Client);

Инициализация приложения

При старте приложения токен доступа обновляется до первого обращения к API. Для этого используется APP_INITIALIZER:

// app.config.ts
import { APP_INITIALIZER, ApplicationConfig } from "@angular/core";
import { AuthStore, LoginService, PolynomApiConfig } from "@ascon/polynom-api";
import { appInitializer } from "./helpers/app.initializer";

export const appConfig: ApplicationConfig = {
  providers: [
    // ... прочие провайдеры
    {
      provide: APP_INITIALIZER,
      useFactory: appInitializer,
      deps: [LoginService, PolynomApiConfig, AuthStore],
      multi: true
    }
  ]
};
// helpers/app.initializer.ts
import { LoginService, PolynomApiConfig } from "@ascon/polynom-api";
import { ClientTypes } from "@ascon/polynom-api-client";
import { delay, lastValueFrom, take, TeardownLogic } from "rxjs";
import { environment } from "../environments/environment";

export async function initializeClient(loginService: LoginService): Promise<TeardownLogic> {
  // Преобразуем Observable в Promise, чтобы ошибка автоматически превратилась в rejected promise
  await lastValueFrom(loginService.updateToken().pipe(take(1), delay(500)));
}

export function appInitializer(loginService: LoginService, config: PolynomApiConfig): () => Promise<TeardownLogic> {
  config.setClientType(ClientTypes.Client);
  config.setHost(environment.apiRoute);

  return async () => {
    try {
      return await initializeClient(loginService);
    } catch (error) {
      console.error("App initialization failed:", error);
    }
  };
}

Использование токена в запросах к API

Токен доступа добавляется в заголовок Authorization HTTP-интерцептором. Отдельный заголовок с идентификатором хранилища не требуется: сессия однозначно определяется токеном доступа.

import { HttpHeaders, HttpInterceptorFn } from "@angular/common/http";
import { inject } from "@angular/core";
import { LoginService } from "@ascon/polynom-api";

export const jwtInterceptor: HttpInterceptorFn = (request, next) => {
  const loginService = inject(LoginService);

  // Получаем токен авторизации из локального хранилища
  const polynomAccessToken = loginService.accessToken;
  if (polynomAccessToken) {
    const clonedRequest = request.clone({
      headers: new HttpHeaders({
        Authorization: `Bearer ${polynomAccessToken}`
      })
    });

    return next(clonedRequest);
  }

  return next(request);
};

Регистрация интерцептора:

import { provideHttpClient, withInterceptors } from "@angular/common/http";

// в providers
provideHttpClient(withInterceptors([jwtInterceptor]));

Готовое окно авторизации

Библиотека @ascon/polynom-login предоставляет готовое окно входа AuthenticationPageComponent. Оно выполняет выбор хранилища, вход по логину и паролю, вход от системной учётной записи, вход по внешнему токену доступа (в том числе через общий сервис авторизации) и сохраняет последние использованные данные.

Размещение окна на отдельном маршруте:

import { Route } from "@angular/router";
import { AuthenticationPageComponent } from "@ascon/polynom-login";

export const appRoutes: Route[] = [
  { path: "sign-in", component: AuthenticationPageComponent }
];

При интеграции в веб-клиент ПОЛИНОМ:MDM окно автоматически получает переданные идентификатор хранилища и токен доступа через IntegrationHostService из @ascon/polynom-integration.

Программная авторизация

Если готовое окно не используется, авторизацию можно выполнить сервисом LoginService.

Получение списка зарегистрированных хранилищ

import { inject } from "@angular/core";
import { take } from "rxjs";
import { IStorageDefinition, LoginApiClient } from "@ascon/polynom-api-client";

private readonly _loginApiClient: LoginApiClient = inject(LoginApiClient);

requestStorageDefinition(): void {
  this._loginApiClient
    .storageDefinitions()
    .pipe(take(1))
    .subscribe({
      next: (storageDefinitions: IStorageDefinition[]) => {
        // Выбрать нужное хранилище из списка
      },
      error: (err) => {
        console.error(err);
      }
    });
}

Определение IStorageDefinition

export type IStorageDefinition = {
  // Отображаемое имя хранилища.
  displayName: string;

  // Идентификатор хранилища.
  storageId: string;
};

Вход по имени входа и паролю

Тело запроса формируется фабрикой SignInRequestFactory, которая проставляет тип клиента и имя модуля:

import { inject } from "@angular/core";
import { take } from "rxjs";
import { ClientTypes } from "@ascon/polynom-api-client";
import { LoginService, SignInRequestFactory } from "@ascon/polynom-api";

private readonly _loginService: LoginService = inject(LoginService);

signInByPassword(storageId: string, login: string, password: string): void {
  const request = SignInRequestFactory.createByPassword(
    storageId,
    login,
    password,
    ClientTypes.Client
  );

  this._loginService
    .signIn(request)
    .pipe(take(1))
    .subscribe({
      next: () => {
        // Сохранять токены отдельно не нужно: LoginService сделает это автоматически
      },
      error: (err) => {
        console.error(err);
      }
    });
}

Вход по имеющемуся токену доступа

Метод применяется при интеграции, когда токен доступа получен извне (например, от общего сервиса авторизации):

import { ClientTypes } from "@ascon/polynom-api-client";
import { SignInRequestFactory } from "@ascon/polynom-api";

signInByAccessToken(accessToken: string): void {
  const request = SignInRequestFactory.createByAccessToken(ClientTypes.Client);

  this._loginService
    .signInByAccessToken(accessToken, request)
    .pipe(take(1))
    .subscribe({
      next: () => {
        // Действия после успешной авторизации
      },
      error: (err) => console.error(err)
    });
}

Проверка состояния авторизации

import { Observable } from "rxjs";

// Поток состояния авторизации
get authorized$(): Observable<boolean> {
  return this._loginService.authenticated$;
}

// Текущее значение
const authorized: boolean = this._loginService.authenticated;

Обновление токена доступа

this._loginService
  .updateToken()
  .pipe(take(1))
  .subscribe({
    next: () => {
      // Токены доступа и обновления обновлены
    },
    error: (err) => console.error(err)
  });

Выход

this._loginService
  .signOut()
  .pipe(take(1))
  .subscribe({
    next: () => {
      // Сессия закрыта, сохранённые токены удалены
    },
    error: (err) => console.error(err)
  });

См. пример SDK/Web/Samples/Client/polynom-select-demo.

Прямые запросы к REST API

Если библиотека не используется, авторизацию можно выполнить прямыми запросами к REST API web-сервера. Описание методов /login/storage-definitions, /login/sign-in, /login/update-token и /login/sign-out приведено в статье Авторизация в WEB API ПОЛИНОМ:MDM. В заголовках последующих запросов нужно передавать Authorization: Bearer <access_token>.