Node.jsМодуль 6: Безопасность
Авторизация
Роли, права доступа, middleware защиты
Цель урока
В этом уроке ты научишься:
- Реализовывать ролевую модель
- Создавать middleware для проверки прав
- Защищать маршруты
Ролевая модель (RBAC)
Модель пользователя
const userSchema = new mongoose.Schema({
email: String,
password: String,
role: {
type: String,
enum: ['user', 'moderator', 'admin'],
default: 'user'
}
});Middleware проверки роли
function authorize(...roles) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// Использование
app.get('/admin/users',
authenticate,
authorize('admin'),
async (req, res) => {
const users = await User.find();
res.json(users);
}
);
app.delete('/posts/:id',
authenticate,
authorize('admin', 'moderator'),
async (req, res) => {
await Post.findByIdAndDelete(req.params.id);
res.status(204).send();
}
);Проверка владельца ресурса
function isOwner(model, paramName = 'id') {
return async (req, res, next) => {
const resource = await model.findById(req.params[paramName]);
if (!resource) {
return res.status(404).json({ error: 'Not found' });
}
// Проверяем владельца или админа
const isOwner = resource.author?.toString() === req.user.id;
const isAdmin = req.user.role === 'admin';
if (!isOwner && !isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
req.resource = resource;
next();
};
}
// Использование
app.put('/posts/:id',
authenticate,
isOwner(Post),
async (req, res) => {
const post = await Post.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true }
);
res.json(post);
}
);Права доступа (Permissions)
Модель с правами
const rolePermissions = {
user: ['read:posts', 'create:posts', 'update:own-posts', 'delete:own-posts'],
moderator: ['read:posts', 'create:posts', 'update:posts', 'delete:posts'],
admin: ['*'] // Все права
};
function hasPermission(permission) {
return (req, res, next) => {
const userPermissions = rolePermissions[req.user.role] || [];
const hasAccess =
userPermissions.includes('*') ||
userPermissions.includes(permission);
if (!hasAccess) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// Использование
app.delete('/posts/:id',
authenticate,
hasPermission('delete:posts'),
async (req, res) => {
await Post.findByIdAndDelete(req.params.id);
res.status(204).send();
}
);Комбинированная проверка
function checkAccess(options) {
return async (req, res, next) => {
const { roles, permissions, ownerField } = options;
// Проверка роли
if (roles && !roles.includes(req.user.role)) {
// Проверка владельца
if (ownerField) {
const resource = await options.model.findById(req.params.id);
if (resource?.[ownerField]?.toString() === req.user.id) {
req.resource = resource;
return next();
}
}
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// Использование
app.put('/posts/:id',
authenticate,
checkAccess({
roles: ['admin', 'moderator'],
ownerField: 'author',
model: Post
}),
updatePost
);Практика
Задание: API с ролями
Задача: Создай API где:
- user может читать и создавать свои посты
- admin может всё
Решение:
// Middleware
const authenticate = require('./middleware/authenticate');
function authorize(...roles) {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// Routes
app.get('/posts', authenticate, async (req, res) => {
const posts = await Post.find();
res.json(posts);
});
app.post('/posts', authenticate, async (req, res) => {
const post = await Post.create({
...req.body,
author: req.user.id
});
res.status(201).json(post);
});
app.delete('/posts/:id',
authenticate,
async (req, res, next) => {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ error: 'Not found' });
}
const isOwner = post.author.toString() === req.user.id;
const isAdmin = req.user.role === 'admin';
if (!isOwner && !isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
await post.deleteOne();
res.status(204).send();
}
);Проверь себя
- Чем отличается 401 от 403?
- Что такое RBAC?
- Как проверить владельца ресурса?