В главе 1 мы создали базу данных, используя Фауна И следующие учебные пособия вместе, мы создали некоторых пользователей и вошли в систему, используя проект от Next.js. В главе 2 мы добавили функциональность, чтобы следовать и публиковать, а также необходимые разрешения для этого. Теперь у вас есть репозиторий, чтобы клонировать и использовать его для этой цели.
В этой главе мы собираемся использовать Фауна Контроль доступа, ABAC (контроль доступа на основе атрибутов), чтобы ограничить то, что разрешено делать пользователям. Например, они смогут создавать, редактировать или удалять посты, только если они являются владельцами. Мы создадим частный контент и отклоним доступ к VIP. Кроме того, верхние роли и настраивают разрешения Post.
Мы уже упоминали, что означает ABAC, но что это значит? Фауна Может получить доступ к конкретному документу и пользователю, пытающемуся получить к нему доступ, с помощью этой информации он может определить, есть ли пользователь, пытающийся получить доступ/изменить документ, действительно есть разрешения для этого. Это может очень помочь, чтобы сохранить часть информации пользователя в частном порядке или предотвратить изменения в документе (например, сообщение), который не принадлежит пользователю, пытающемуся его изменить.
ABAC состоит из двух разделов: Первый – это членство, мы уже что -то сделали с членством во второй главе: вы создали Lambda
функция, которая проверяет документы в коллекции, Если Lambda
Функция возвращает Верно
, документ имеет эту роль.
Давайте использовать панель панели фауны и перейти к безопасности/управлению Роли/базовый орган/членство
Если вы следили за второй главой, вы можете увидеть коллекцию пользователей и сможете расширить ее, чтобы увидеть Lambda
функция Эта функция имеет «ref» в качестве параметра и возвращает истину каждый раз. Это означает, что все пользователи из коллекции пользователей будут иметь эту схему разрешений. Эта функция Lambda может быть изменена, чтобы включить любой атрибут, соответствующий коллекции пользователей. Например, функция Lambda по умолчанию, когда вы создаете членство, идет так:
Lambda("ref", Select(["data", "vip"], Get(Var("ref"))))
Эта функция использует Получить
(читает) на «Ref» и Тогда Выберите
(s) Путь документа Данные/VIP Анкет Здесь, VIP должен содержать логическое заявление, если у пользователя есть статус VIP (премиум). Вы также можете проверить, находится ли пользователь во вторичном списке, например, в другую коллекцию, где вы можете найти ссылки администраторов. Здесь мы проведем несколько примеров и применим их.
Пример 1: Премиум содержание:
Давайте подумаем об этом сценарии: у вас нет социальной сети, но какой -то премиальный контент, который ваш пользователи смогут увидеть после оплаты платы, пожизненной подписки на услугу.
- Создайте коллекцию под названием PAYVIDEOS Анкет
CreateCollection({name:'PaidVideos'})
- Создайте несколько документов на нем с полевым полем: Video:
Map( [ {video:"dQw4w9WgXcQ",text:"Old but gold"}, {video:"XcgoZO-p9tI",text:"Secret of the forest"}, {video:"D5q094yRsbA",text:"Tyrano lair"}, {video:"re0A23CSvpw",text:"Imp's song"}, {video:"cM4kqL13jGM",text:"Rebirth of slick"} ], Lambda( "docPayload", Create(Collection('PaidVideos'),{data:Var('docPayload')}) ) )
- Создайте новую функцию с именем PremiumContent со следующим телом
Query( Lambda( [], Map( Paginate(Documents(Collection("PaidVideos"))), Lambda("videoRef", Select("data",Get(Var("videoRef")))) ) ) )
Вы можете видеть, что есть новый метод под названием Документы
Анкет Этот метод возвращает набор, содержащий все документы коллекции в аргументе.
Теперь давайте создадим новую роль, отправляйтесь в Безопасность/управление ролями и нажмите на Новая роль Анкет
Эта новая роль потребует коллекции PAYVIDEOS , мы предоставим Просмотр Разрешения также, функция Премиум -контент
, мы предоставим Позвоните разрешения.
Почему только эти два разрешения? Как вы можете помнить, любой документ в Пользователи будет иметь разрешения BasicUser. Их предикатная функция всегда возвращает Верно
Анкет Любой документ в Пользователи У кого также есть поле под названием VIP со значением Верно
будет иметь разрешения BasicUser, а также премиум -организатор.
Теперь перейдите на вкладку «Членство», добавьте пользователей коллекции и используйте функцию, предоставленную Fauna.
Поместите имя на вашу новую роль, я использовал Премиум Вы можете выбрать любое имя, которое вам нравится, но мы будем использовать это имя здесь, когда ссылается на эту роль.
Возьмите одного или двух ваших существующих пользователей и Обновление
им иметь новое поле VIP: true
, это позволит им как Премиум
Если вы клонировали это хранилище в начале урока, вы можете переключиться на ветвь, называемую Третья чаповая конец Чтобы обновить свой репозиторий, чтобы проверить эту функциональность.
Войдите в систему с любым пользователем, действительным для PremiumUser (те, которые мы обновили, чтобы иметь VIP: true), нажмите на новую вкладку с надписью Премиум Анкет
Если у пользователя есть Attribute VIP, установленная на True, он сможет получить доступ к контенту внутри PAYVIDEOS Коллекция, в противном случае, функция бросит ошибку, в которой указывается, что у пользователя нет разрешений на доступ к этим документам.
В этом случае мы обработали ошибку от API, мы также можем обработать ее с передней части, чтобы получить статус 403, что указывает на дальнейшую оплату.
Пример 2: Ежемесячная подписка
Этот сценарий очень похож на предыдущий, за исключением того, что ваша подписка истекает через некоторое время.
Создайте новую роль с теми же разрешениями, что и PremiumUser , в этом случае, мы будем называть это Подписках -инсуляр Анкет Отправляйтесь в Членство Вкладка, добавьте Пользователи Сбор и добавьте эту функцию в функцию предиката:
Lambda( "ref", Let( { subExpiration: Select( ["data", "expiration"], Get(Var("ref")), TimeSubtract(Now(),1,"day") ), remainingTime: TimeDiff(Var("subExpiration"), Now(), "seconds") }, GTE(Var("remainingTime"),0) ) )
Эта функция кажется немного сложнее, но не паникует. Мы используем метод Пусть
Для создания 2 переменных: субэкспирация который Получить
(s) Пользователь
Информация, Выберите
(s) Путь данные/истечение которая будет временной меткой с датой истечения срока действия подписки (третий аргумент означает, что если нет данных в этом значении, верните текущее время, но вчера); а также оставшееся время , который вычитает (используя timeediff
) текущее время до времени, хранящегося в субэкспирация и возвращает значение за секунды. Теперь, GTE
Возвращает true, если переменная Остальное время больше или равен 0, что означает, что у пользователя все еще есть время на подписке.
Как вы можете видеть, этот статус может измениться, если подписка пользователя истекает в течение дня. Когда истекает дата истечения срока действия пользователя, у него больше не будет атрибутов, чтобы быть Подписках -инсуляр Анкет Таким образом, когда пользователь запрашивает PAYVIDEOS
, это получит ответ «разрешение отказано».
Давайте обновим любого пользователя, не имеющего VIP, чтобы получить дату истечения срока действия на завтра.
При обновлении файла на панели инструментов, если вы используете метод Timeadd
(или любой другой метод), Фауна обновит поле до результата такого метода вместо фиксированного значения.
Давайте войдем с нашим обновленным пользователем и перейдем к вкладке Premium нашего проекта Next.js. Вы должны увидеть контент, который мы определили как PAYVIDEOS Анкет
Если вы попробуете с пользователем без VIP, не подписываемого, вы должны увидеть сообщение, в котором говорится, что вы не премиум
Пример 3: только администраторы
Предположим, что ваш веб -сайт имеет раздел для администраторов, менеджеров или любой другой роли, которая предоставляется только некоторым пользователям, отобранным вручную.
Создать новую коллекцию и назвать ее Апперры Анкет Создайте новый индекс под названием roles_by_user_id Используя следующую команду на оболочке фауны:
CreateIndex( { name: "roles_by_user_id", unique: true, serialized: true, source: Collection("UpperRoles"), terms: [ { field: ["data", "userId"] } ], values: [ { field: ["data", "roles"] } ] } )
Мы отмечаем уникальные как Верно
иметь простого пользователя в коллекции со всеми ролями, назначенными ему.
Создайте новую роль с теми же разрешениями, которые мы имели на Премиум и Подписках -инсуляр , теперь давайте снова направимся к Членство Вкладка, выберите Пользователи Сбор и добавьте это в качестве функции предиката:
Lambda( "ref", Let( { rolesPage:Paginate(Match(Index('roles_by_user_id'),Var("ref"))), roles:Select(["data"], Var("rolesPage"),[]), }, IsNonEmpty(Intersection(["admin"],Var("roles"))) ) )
Мы приносим результаты индекса, который мы только что создали, ожидается, что он принесет один результат, поскольку он имеет уникальный Флаг отмечен как истинный
, на роли Мы приносим первый результат страницы и устанавливаем по умолчанию пустой массив, мы ожидаем Роли Чтобы быть массивом всех ролей, которые есть у пользователя. Наконец, мы получаем Пересечение
из наших ролей массива и массива, содержащего роль «администратор». Если это непустые массивы, пользователь будет иметь привилегии этой роли.
Теперь давайте предоставим пользователю эти привилегии администратора:
Скопируйте ссылку любого не-премиального, не подписанного пользователя.
Создайте новый документ на Апперры Сбор со следующими данными:
Create( Collection("UpperRoles"), {data:{ userId:Ref(Collection("Users"), "277425124024517138"), //The reference you just copied Roles:["admin","accountant","manager"] //additional roles as reference }} )
Как вы можете видеть, этот пользователь будет иметь некоторые роли, в том числе «администратор», который является той ценностью, которую мы будем искать.
Войдите в систему с пользователем, которого вы настроили, и попробуйте получить доступ к вкладке Premium. Этот пользователь теперь администратор.
Мы исследовали некоторые сценарии, чтобы определить роль пользователя на основе его атрибутов. Далее мы собираемся определить, имеет ли пользователь доступ к чтению/обновлению/созданию/удалению документа.
Во второй главе мы устанавливаем доступ к сообщениям таким образом, чтобы позволить любому BasicUser Чтобы изменить любой документ в коллекции Сообщения , чтобы создать и удалить любой документ в коллекции Последователи а также многие другие привилегии, которые дают слишком много свободы и могут вызвать нежелательное поведение.
Давайте направимся к Безопасность Раздел, нажмите на Управление ролями , найти BasicUser и нажмите на капля на правой стороне. Давайте нажмите на коллекцию пользователей, чтобы расширить ее. Посмотрите на символы под каждым действием. При нажатии это позволяет нам создавать предикатую функцию или сценарий для предоставления привилегий.
Когда вы нажимаете на любой из них, Фауна Предоставляет простой сценарий шаблона, чтобы намекнуть вас на полезную функцию.
Если вы не хотите использовать скрипт, но вы уже нажали на кнопку, просто найдите ясно Опция в нижней правой части области сценария.
Давайте расширим Сообщения Сбор и посмотрите, что мы можем сделать в отношении разрешений на запись.
При написании документа, Фауна ABAC вызывает эту функцию с 3 аргументами: предыдущий документ (Oldata), будущее государство документа (Newdata) и идентификатор документа (обычно REF). Давайте проверим, что здесь нового, Равно
Метод сравнивает аргументы внутри него и возвращает Верно
Если все они равны. И
Метод возвращает истинный
Если все аргументы истины, точно так же, как обычный И
логические ворота.
В этом примере мы проверяем, принадлежит ли документ пользователю, пытающемуся его изменить, используя равные:
Equals(Identity(), Select(["data", "owner"], Var("oldData")))
Как видите, он проверяет данные/владельца пути в предыдущем документе и сравнивает его с Идентичность
из зарегистрированного пользователя, что означает, что вы можете редактировать только посты, которые у вас есть. Кроме того, мы хотим, чтобы данные оставались одним и тем же пользователем, поэтому мы проверяем поле в данных/владельце как в предыдущих, так и новых документах, чтобы проверить, останется ли владелец.
Как оба Равно
Методы внутри И
Метод, оба должны вернуть Верно
Чтобы подтвердить запись документа. Вы также можете добавить еще одно поле, например, дата создания предыдущего документа должна быть равна новой дате.
Equals( Select(["data", "date"], Var("oldData")), Select(["data", "date"], Var("newData")) )
Если функция возвращает Верно
Документ будет обновлен, как если бы у пользователя были полные разрешения, чтобы сделать это, в противном случае он принесет ошибку, а документ останется неизменным.
Примечание: Newdata содержит новое состояние всего документа. Если вы измените одно поле, Newdata будет содержать весь документ с изменением в модифицированном поле. Нет необходимости отправлять поля, которые вы хотите сравнить, чтобы сравнить.
Это очень полезно, чтобы сохранить некоторые поля базы данных, например, владельца сообщения. На данный момент, не понаблюдайте функциональную часть сценария, поэтому мы его используем, нажмите на Символ под Создать действие.
Вы можете увидеть функцию здесь очень похожа на другую, за исключением того, что у нас есть только один аргумент на Lambda
функция, которая является ценности Это значения, которые должны быть записаны в базе данных. Путь Данные/Владелец должен быть равен Идентичность
Чтобы позволить пользователю создать сообщение, в противном случае документ не создан вообще. Давайте также не позаботимся об этой функции и проверим Удалить действие.
Эта функция получает идентификатор документа как аргумент и называет его рефери , Он выполняет Получить
Метод аргумента и проверяет данные пути/владельца, чтобы сравнить его с идентичностью. Если это владелец, который удаляет пост, действие выполняется. Давайте также расстроим эту функциональную часть и прокрутите вниз, чтобы сохранить наши изменения.
Давайте попробуем создать пост под обычным методом. Перейдите к Chrome, создайте пост, и вы должны увидеть, что ничего не изменилось, так как мы сделали это в предыдущей главе.
Теперь давайте нарушим наше заявление: Скопируйте ссылку любого пользователя, отличающегося от того, что вы вошли в систему, точно так же, как мы это сделали для верхних ролей. Перейдите в раздел функций, выберите CreatePost функционируйте и измените поле на данных о пути/владельца, чтобы выглядеть следующим образом:
Query( Lambda( "description", Create(Collection("Posts"), { data: { description: Var("description"), date: Now(), owner: Ref(Collection("Users"), "277945843461390867"), // The ref you just copied likes: 0, comments: 0 } }) ) )
Как ссылка в поле Владелец отличается от нашего зарегистрированного пользователя, наше разрешение будет отказано. Сохраните сломанную функцию и попробуйте снова создать пост.
Это сообщение об ошибке довольно большое, но изюминка находится в Ответроу Field (также вы можете поймать поле Это ошибка, которую вы найдете каждый раз, когда пытаетесь выполнить действие, которое вам не разрешено. Это не ожидаемое поведение вашего приложения, а отказ в случае, если кто -то попытается сломать хаос в вашем приложении. Теперь вы можете отремонтировать сломанную функцию, мы проверили то, что хотели.
Что ж, мы настроили некоторые разрешения, и мы хотим знать, действительно ли определяемые нами функции делали то, что нам нужно. Мы будем использовать Фауна Оболочка, чтобы сравнить наши результаты с нашими ожиданиями.
Например, давайте принесем нашу предикатую функцию для PremiumUser:
Lambda( "ref", Select( ["data", "vip"], Get(Var("ref")) ) )
Переменная ref
будет ссылка пользователя. Итак, давайте направимся к оболочке фауны, используем Пусть
Метод, чтобы принести переменную с именем ref
Анкет
Let( { ref:Ref(Collection("Users"),"277945843461390867") }, Select( ["data", "vip"], Get(Var("ref")) ) )
Мы изменили Lambda
Метод для Пусть
и создал переменную ref со ссылкой на пользователя. В этом случае это документ пользователя:
{ "ref": Ref(Collection("Users"), "277945843461390867"), "ts": 1603515727810000, "data": { "email": "testmail5@mail.com", "posts": 0, "activeSince": Time("2020-09-28T21:31:02.124870Z"), "vip": true } }
Когда вы выполняете на оболочке, вы поймете, что получение документа и выберете значение в данных Path Data/VIP вернется Верно
Анкет
Когда вы попробуете с другим пользователем, например:
{ "ref": Ref(Collection("Users"), "280324497574199812"), "ts": 1603600132565000, "data": { "email": "testmail4@mail.com", "posts": 0, "activeSince": Time("2020-10-25T03:38:43.365515Z"), "expiration": Time("2020-10-26T04:28:52.453007Z"), "vip":false } }
Функция вернется ложный
Анкет Это означает, что пользователь не будет включен в PremiumUser роль.
Единственный способ, которым функция ABAC предоставляет привилегии или включает документ в роли, – это иметь возвращение функции предиката Верно
, наличие функции, которая возвращает ошибку, будет отрицать привилегии или роль. Это означает, что у вас могут быть пользователи, которые не содержат полевого VIP И это не сломает функциональность ABAC.
Теперь давайте попробуем с предикатами, чтобы обновить сообщение:
Lambda( ["oldData", "newData"], And( Equals(Identity(), Select(["data", "owner"], Var("oldData"))), Equals( Select(["data", "owner"], Var("oldData")), Select(["data", "owner"], Var("newData")) ) ) )
Это требует определения 3 переменных: OldData, Newdata и идентификатор пользователя, которые заменит Идентичность
Метод, это потому, что Фауна Shell не имеет ни личности, ни связанных документов.
Скопируйте и вставьте весь существующий документ для OldData, сделайте то же самое для Newdata, но измените владельца на какой -то другой идентификатор пользователя (или просто что -то случайное, это не имеет значения). При выполнении на оболочке фауны вы увидите, что это возвращает ложный
Потому что новое значение для владельца не равно предыдущему.
Let( { oldData:{ "ref": Ref(Collection("Posts"), "280597810560107014"), "ts": 1603857775247000, "data": { "description": "I like turtles", "date": Time("2020-10-28T04:02:55.038172Z"), "owner": Ref(Collection("Users"), "277425124024517138"), "likes": 0, "comments": 0 } }, newData:{ "ref": Ref(Collection("Posts"), "280597810560107014"), "ts": 1603857775247000, "data": { "description": "I like turtles", "date": Time("2020-10-28T04:02:55.038172Z"), "owner": Ref(Collection("Users"), "280324497574199812"), "likes": 0, "comments": 0 } }, userId:Ref(Collection("Users"), "277425124024517138") }, And( Equals(Var("userId"), Select(["data", "owner"], Var("oldData"))), Equals( Select(["data", "owner"], Var("oldData")), Select(["data", "owner"], Var("newData")) ) ) )
Причина, по которой мы скопировали весь документ, вместо того, чтобы просто нам нужно было показать вам, как ABAC увидит информацию, когда вы пытаетесь выполнить написать действие на документ. Что -то подобное произойдет, когда вы попытаетесь прочитать/создать/удалить документ в этой коллекции из -за предикатов.
Это в основном, скопируйте функциональную часть Lambda
В пределах Пусть
и установить ожидаемые (и некоторые неожиданные) значения как Пусть
Определения, с этим, вы сможете предсказать поведение любой функции предиката, которую вы объявляете.
Давайте подумаем об этом общем сценарии: один из ваших пользователей не помнит пароль, используемый для регистрации. Как это восстановить? Фауна Не покажет вам пароль и не позволит вам увидеть клавиши входа в систему пользователя. Даже если вы администратор. Однако Фауна Позволяет администраторам создавать токены для входа в систему для любого пользователя, не требуется пароли. Таким образом, вы можете попытаться отправить токен пользователя по электронной почте или любой другой метод подтверждения, определенный до потери пароля.
Мы собираемся создать функцию на Фауна Чтобы выполнить это действие. Мы собираемся получить электронную почту пользователя, поищите его в нашей базе данных, чтобы получить идентификатор пользователя, создать токен и вернуть его в API, мы ожидаем, что этот API не вернет токен напрямую пользователю, вместо этого, в API отправит электронное письмо пользователю.
Query( Lambda( "email", Let( { userId: Select( ["data", 0], Paginate( Match(Index("users_by_email"), Var("email")), ) ), returnData: Create(Tokens(), { instance: Var("userId"), data: { message: "you can add some information here" }, ttl: TimeAdd(Now(), 5, "minutes") // add time to live }) }, Var("returnData") ) ) )
Мы используем эту функцию для создания нового документа в Tokens Collection (), это коллекция, где Фауна хранит токены входа для всех пользователей, эта информация частично видна, мы не сможем увидеть ни текущий ключ, ни используемый пароль, но мы можем увидеть экземпляр, который должен быть идентификатором пользователя, и поле данных, которое мы используется для хранения сообщения. Мы также добавили TTL или время для жизни, это работает как дата истечения срока действия, поэтому у пользователя ограниченное время для сброса пароля с этим токеном.
Последняя функция на Фауна это сбросить пароль Эта функция обновит пароль пользователя до того, что предоставлено на параметрах.
Query( Lambda( "password", Do( Update(Identity(), { credentials: { password: Var("password") } }), Logout(false) ) ) )
Поскольку это обновит собственного пользователя, нам нужно добавить привилегии к Пользователи Коллекция, чтобы обновить себя. Добавьте это в качестве функции предиката под действием записи.
Lambda( ["oldData"], Equals(Identity(), Select("ref", Var("oldData"))) )
Также добавьте ResetPassword Функция на привилегии и проверьте привилегию к Позвоните функция.
В нашем репозитории мы добавили вкладку под названием «Восстановить», зарегистрируйтесь с достижимым адресом электронной почты и попытаемся сбросить пароль.
Вы получите электронное письмо, похожее на это:
И когда вы нажмете на ссылку, вы будете здесь
Добавьте новый пароль, и вы сможете войти с ним.
При настройке переменных среды на фактическом сервере рекомендуется не использовать ключ с привилегиями администратора или сервера. Использование ключа с минимальными привилегиями может сохранить функциональность нетронутыми, и ваше приложение будет безопаснее.
В нашем случае мы можем иметь разрешения на создание и чтение на Пользователи Коллекция, добавьте привилегии для чтения в индекс users_by_email , функция подписчики и recordPassword будут иметь разрешения на вызов.
При этом вы будете играть публичную роль с ограниченной функциональностью, создайте ключ для этой роли, вам не нужно добавлять коллекцию или функцию предиката, просто добавьте ключ из меню безопасности.
Вот и все. Добавьте несколько стилей, чтобы он выглядел причудливым, добавьте некоторые функции, чтобы сделать его более интересным. Тебе решать.
Большое спасибо за то, что следили за этой серией блога, я надеюсь, что это полезно для ваших проектов или ваших новых интересов.
Оригинал: “https://dev.to/sertge/chapter-3-let-s-get-into-fauna-a-guide-to-understanding-fauna-while-creating-a-social-media-database-1gc”