С системой прав всегда сложно работать и всегда нужно смотреть на конкретную реализацию и учитывать как она будет изменяться в будущем. В любом случае, я сейчас слегка включен в работу над подобной задачей в нашей компании и попробую дать какой-то полезный ответ.
В контроллере действительно проверять стоит только простые кейсы и недостаток проверки в контроллере как раз в том, что если, например, реализовали проверку в одном HTTP-контроллере и вдруг появляется CLI-контроллер, то хочется чтобы вызовы доменной логики были одинаковыми с любого места, включая валидацию. Соответственно, попробуем унести проверку доступа в другое место.
В модель все тоже уносить не хочется. Соблюдая принцип SoC и слоистости приложения, доменные модели в общем случае ничего не должны знать про пользователя, который производит над ними действие. Соответственно, есть предложение ввести некий третий компонент, который отвечает именно за проверку есть ли у пользователя доступ к определенному действию. Плюс уверен, что иногда захочется произвести какое-то действие над моделью без проверки прав.
Как вы написали, у вас есть некий набор прав и некие действия (модерация, редактирование) с некоторыми параметрами (поля для редактирования), которые можно производить над моделью. Соответственно, сформулируем какое-то решение. Оно может вам совершенно не подойти, это лишь простой вариант пальцем в небо, который иллюстрирует подход. Введем PolicyChecker с методами в духе canCreatePost(user, createPostRequest) bool, где каждый метод соответствует проверке конкретного действия с конкретными параметрами запроса у конкретного пользователя. Этот PolicyChecker все знает про авторизованных пользователей, про роли, группы и что бы то ни было у них есть, а если ему нужны дополнительные зависимости для проверки, они передаются ему в конструкторе или какой там у вас подход к управлению зависимостями.
Таким образом, в каждом контроллере вы на основе PolicyChecker'а сможете проверить, что данный пользователь авторизован на данное действие. Если появится еще один контроллер, то придется эту проверку писать заново и я считаю, что это правильно, т.е. PolicyChecker'ы могут быть опять же разные для разных контроллеров и правила проверки в них могут отличаться. Либо может быть необходимость вообще выполнить действие над моделью без авторизованного пользователя, как уже писал выше, и тогда никакие права можно и не проверять и просто забыть про PolicyChecker.
Конкретная реализация опять же зависит от вашего конкретного юз кейса и систему прав нужно хорошенько проектировать в связи с нуждами бизнеса потому что она имеет свойство лавинообразно разрастаться и приводить к сложностям в поддержке кода и вообще уязвимостям.
Надеюсь, мои рассуждения оказались вам полезны.