Защищенные маршруты

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

Пример базовой структуры защищённого маршрута:

import UniversalRouter from 'universal-router';

const routes = [
  {
    path: '/dashboard',
    action: async (context) => {
      if (!context.user || !context.user.isAuthenticated) {
        throw new Error('Unauthorized');
      }
      return 'Добро пожаловать на дашборд!';
    }
  }
];

const router = new UniversalRouter(routes, {
  context: { user: { isAuthenticated: false } }
});

router.resolve('/dashboard')
  .then(console.log)
  .catch(err => console.error(err.message));

Здесь проверка авторизации встроена прямо в action, что делает маршрут защищённым от неавторизованных пользователей.


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

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

Пример с контекстом и ролями:

const routes = [
  {
    path: '/admin',
    action: ({ user }) => {
      if (!user || user.role !== 'admin') {
        throw new Error('Forbidden: требуется роль администратора');
      }
      return 'Страница администратора';
    }
  }
];

const router = new UniversalRouter(routes, {
  context: { user: { role: 'editor' } }
});

router.resolve('/admin')
  .then(console.log)
  .catch(err => console.error(err.message)); // Forbidden: требуется роль администратора

Ключевой момент: проверка ролей выполняется внутри action, что делает маршруты гибко настраиваемыми и безопасными.


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

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

const routes = [
  {
    path: '/profile',
    action: ({ user }) => {
      if (!user || !user.isAuthenticated) {
        return { redirect: '/login' };
      }
      return 'Профиль пользователя';
    }
  },
  {
    path: '/login',
    action: () => 'Страница логина'
  }
];

const router = new UniversalRouter(routes, {
  context: { user: null }
});

router.resolve('/profile')
  .then(result => {
    if (result.redirect) {
      console.log(`Перенаправление на ${result.redirect}`);
    } else {
      console.log(result);
    }
  });

Использование объекта с ключом redirect позволяет разделять логику проверки и обработку перехода и избегать прямого выброса ошибок.


Защита вложенных маршрутов

В Universal Router маршруты могут быть вложенными, что удобно для организации секций приложения. Защита вложенных маршрутов строится аналогично — через проверку контекста на каждом уровне:

const routes = [
  {
    path: '/app',
    children: [
      {
        path: '/settings',
        action: ({ user }) => {
          if (!user || !user.isAuthenticated) {
            throw new Error('Unauthorized');
          }
          return 'Настройки приложения';
        }
      },
      {
        path: '/reports',
        action: ({ user }) => {
          if (!user || user.role !== 'manager') {
            throw new Error('Forbidden');
          }
          return 'Отчёты';
        }
      }
    ]
  }
];

Каждый дочерний маршрут самостоятельно проверяет права доступа, что позволяет комбинировать разные уровни защиты для разных частей приложения.


Асинхронные проверки и загрузка данных

Маршруты могут использовать асинхронные проверки, например, запросы к API для подтверждения токена или загрузки профиля пользователя:

const routes = [
  {
    path: '/dashboard',
    action: async ({ fetchUser }) => {
      const user = await fetchUser();
      if (!user || !user.isAuthenticated) {
        throw new Error('Unauthorized');
      }
      return `Добро пожаловать, ${user.name}!`;
    }
  }
];

const router = new UniversalRouter(routes, {
  context: {
    fetchUser: async () => {
      return new Promise(resolve => setTimeout(() => resolve({ name: 'Иван', isAuthenticated: true }), 200));
    }
  }
});

router.resolve('/dashboard').then(console.log); // Добро пожаловать, Иван!

Асинхронные проверки позволяют интегрировать защиту маршрутов с бэкендом и динамическими источниками данных.


Многоуровневая защита с middleware-подходом

Можно вынести общую логику проверки в функцию-мидлвар, чтобы не дублировать код проверки на каждом маршруте:

function requireAuth(action) {
  return (context) => {
    if (!context.user || !context.user.isAuthenticated) {
      return { redirect: '/login' };
    }
    return action(context);
  };
}

const routes = [
  {
    path: '/dashboard',
    action: requireAuth(() => 'Дашборд защищён')
  },
  {
    path: '/login',
    action: () => 'Страница логина'
  }
];

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


Встроенные ошибки и обработка статусов

Universal Router позволяет использовать кастомные ошибки для обозначения статусов доступа. Например, можно определять собственные классы ошибок UnauthorizedError и ForbiddenError и обрабатывать их централизованно:

class UnauthorizedError extends Error {}
class ForbiddenError extends Error {}

const routes = [
  {
    path: '/admin',
    action: ({ user }) => {
      if (!user) throw new UnauthorizedError();
      if (user.role !== 'admin') throw new ForbiddenError();
      return 'Админка';
    }
  }
];

router.resolve('/admin').catch(err => {
  if (err instanceof UnauthorizedError) console.log('Нужна авторизация');
  if (err instanceof ForbiddenError) console.log('Нет прав доступа');
});

Использование специализированных ошибок улучшает обработку маршрутов и UX, так как можно различать разные причины отказа доступа и реагировать соответствующим образом.


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