Авторизация с Bouncer
Аутентификация отвечает на вопрос «кто это», а авторизация — «что ему можно». Например, редактировать статью может только её автор. В AdonisJS права проверяет пакет Bouncer: правила описываются функциями, а проверка в контроллере занимает одну строку. В Laravel то же самое делают Gate и политики.
Установка
node ace add @adonisjs/bouncerКоманда создаст файлы app/abilities/main.ts и app/policies/main.ts и подключит middleware, которое добавляет объект bouncer в контекст запроса. Для примера считаем, что у статьи есть столбец user_id и свойство userId — автор статьи.
Способность
Способность (ability) — функция, которая получает пользователя и запись и возвращает true или false:
// app/abilities/main.ts
import { Bouncer } from '@adonisjs/bouncer'
import User from '#models/user'
import Post from '#models/post'
export const editPost = Bouncer.ability((user: User, post: Post) => {
return user.id === post.userId
})Текущего пользователя Bouncer передаёт сам, вы передаёте только статью.
Проверка в контроллере
// app/controllers/posts_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'
import { editPost } from '#abilities/main'
export default class PostsController {
async edit({ params, bouncer, view }: HttpContext) {
const post = await Post.findOrFail(params.id)
await bouncer.authorize(editPost, post)
return view.render('pages/posts/edit', { post })
}
}Если способность вернула false, authorize выбросит исключение и посетитель получит ответ 403. Когда нужно не прерывать запрос, а просто узнать ответ, используйте await bouncer.allows(editPost, post) или denies.
Проверка в шаблоне
Кнопку «Редактировать» стоит показывать только тем, кому это разрешено:
{{-- resources/views/pages/posts/show.edge --}}
@can('editPost', post)
<a href="{{ route('posts.edit', { id: post.id }) }}">Редактировать</a>
@endТег @can принимает имя способности строкой. Для обратной проверки есть @cannot.
Политики
Когда правил для одной модели становится много, их собирают в политику — класс с методом на каждое действие:
node ace make:policy post// app/policies/post_policy.ts
import { BasePolicy } from '@adonisjs/bouncer'
import User from '#models/user'
import Post from '#models/post'
export default class PostPolicy extends BasePolicy {
edit(user: User, post: Post) {
return user.id === post.userId
}
delete(user: User, post: Post) {
return user.id === post.userId
}
}// app/controllers/posts_controller.ts
import PostPolicy from '#policies/post_policy'
await bouncer.with(PostPolicy).authorize('delete', post)В шаблоне политику указывают через точку: @can('PostPolicy.edit', post).
Частая ошибка
Кнопку спрятали тегом @can, а проверку в контроллере не написали. Тогда любой пользователь откроет /posts/5/edit вручную и изменит чужую статью. Шаблон только скрывает элементы, защищает же проверка в контроллере — она нужна всегда.