В 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); // Добро пожаловать, Иван!
Асинхронные проверки позволяют интегрировать защиту маршрутов с бэкендом и динамическими источниками данных.
Можно вынести общую логику проверки в функцию-мидлвар, чтобы не дублировать код проверки на каждом маршруте:
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, интегрировать их с авторизацией, ролями,
асинхронными запросами и централизованной обработкой ошибок.