Ограничение доступа к маршрутам

Библиотека Universal Router предоставляет гибкий механизм маршрутизации, который можно использовать не только для обработки URL и переходов, но и для реализации контроля доступа к отдельным маршрутам. Ограничение доступа — это процесс, при котором маршруты становятся доступными только для определённых пользователей или ролей, в зависимости от условий, задаваемых в конфигурации.

В Universal Router доступ контролируется через свойства маршрута, функцию action и контекст маршрутизатора. Основная идея заключается в проверке условий до выполнения основной логики маршрута.

Использование контекста для проверки прав доступа

Контекст (context) передаётся в функцию resolve маршрутизатора и содержит любую дополнительную информацию о текущем состоянии приложения: данные пользователя, токены аутентификации, роли и другие параметры.

Пример структуры контекста:

const context = {
  user: {
    isAuthenticated: true,
    role: 'admin'
  }
};

Определение маршрутов с ограничением доступа

Каждый маршрут может содержать функцию action, которая возвращает результат выполнения маршрута или бросает ошибку, если доступ запрещён. В качестве ключевого механизма контроля доступа часто используется проверка состояния пользователя в контексте:

import UniversalRouter from 'universal-router';

const routes = [
  {
    path: '/dashboard',
    action: (context) => {
      if (!context.user || !context.user.isAuthenticated) {
        throw { status: 401, message: 'Доступ запрещён' };
      }
      return 'Добро пожаловать в панель управления';
    }
  },
  {
    path: '/admin',
    action: (context) => {
      if (context.user.role !== 'admin') {
        throw { status: 403, message: 'Нет прав администратора' };
      }
      return 'Административная панель';
    }
  }
];

const router = new UniversalRouter(routes);

router.resolve({ pathname: '/admin', user: { role: 'user' } })
  .catch(error => console.error(error.status, error.message));

В этом примере /dashboard доступен только аутентифицированным пользователям, а /admin — только пользователям с ролью admin.

Использование промежуточных функций (guards)

Для крупных приложений удобно создавать отдельные функции-посредники, которые будут проверять права доступа до выполнения основной логики маршрута. Это позволяет избежать дублирования кода и централизовать управление безопасностью.

function requireAuth(context) {
  if (!context.user || !context.user.isAuthenticated) {
    throw { status: 401, message: 'Требуется аутентификация' };
  }
}

function requireRole(role) {
  return (context) => {
    if (!context.user || context.user.role !== role) {
      throw { status: 403, message: 'Недостаточно прав' };
    }
  };
}

const routes = [
  {
    path: '/profile',
    action: (context) => {
      requireAuth(context);
      return 'Страница пользователя';
    }
  },
  {
    path: '/admin/settings',
    action: (context) => {
      requireAuth(context);
      requireRole('admin')(context);
      return 'Настройки администратора';
    }
  }
];

Такой подход делает маршруты более читаемыми и позволяет легко масштабировать правила доступа.

Асинхронная проверка доступа

В реальных приложениях информация о пользователе может загружаться с сервера или из хранилища, поэтому проверки часто выполняются асинхронно. Universal Router поддерживает асинхронные функции action:

const routes = [
  {
    path: '/premium',
    async action(context) {
      const user = await fetchUserData(context.token);
      if (!user.isPremium) {
        throw { status: 403, message: 'Только для премиум-пользователей' };
      }
      return 'Контент премиум-класса';
    }
  }
];

Асинхронные проверки позволяют интегрировать маршрутизацию с внешними API и сервисами авторизации, не блокируя основной поток выполнения.

Обработка ошибок и редиректы

Для корректного UX важно обрабатывать ошибки доступа и перенаправлять пользователя на соответствующие страницы:

router.resolve({ pathname: '/admin', user: { role: 'user' } })
  .then(result => console.log(result))
  .catch(error => {
    if (error.status === 401) {
      window.location.href = '/login';
    } else if (error.status === 403) {
      window.location.href = '/forbidden';
    } else {
      console.error(error);
    }
  });

Такой подход позволяет централизованно управлять сценариями недоступных маршрутов без изменения логики самих маршрутов.

Интеграция с ролями и правами

Для сложных систем часто создаётся отдельная матрица прав доступа:

const rolesPermissions = {
  user: ['/dashboard', '/profile'],
  admin: ['/dashboard', '/profile', '/admin', '/admin/settings']
};

function checkAccess(context, path) {
  const allowedPaths = rolesPermissions[context.user.role] || [];
  if (!allowedPaths.includes(path)) {
    throw { status: 403, message: 'Доступ к маршруту запрещён' };
  }
}

const routes = [
  {
    path: '/admin/settings',
    action: (context) => {
      checkAccess(context, '/admin/settings');
      return 'Страница настроек администратора';
    }
  }
];

Такой механизм позволяет централизованно управлять доступом по ролям и легко расширять систему при добавлении новых маршрутов или ролей.

Комбинирование разных уровней контроля

Universal Router позволяет комбинировать все перечисленные подходы: проверку в action, использование guard-функций, асинхронные проверки и централизованную матрицу прав. Это создаёт гибкую и безопасную систему маршрутизации с поддержкой любых требований по доступу.

Применение этих техник обеспечивает строгий контроль над доступом к маршрутам и позволяет интегрировать Universal Router в современные веб-приложения с различными сценариями авторизации и аутентификации.