Firebase Firestore — это популярная NoSQL база данных документов, широко используемая разработчиками для создания масштабируемых и гибких веб- и мобильных приложений.
Хотя Firestore предоставляет мощные возможности для управления данными, также важно обеспечить их безопасность, чтобы предотвратить несанкционированный доступ, утечку данных и другие угрозы.
В этой статье мы рассмотрим основные правила обеспечения безопасности базы данных Firestore.
Применяя эти меры безопасности, вы сможете обеспечить безопасность и конфиденциальность ваших данных и защитить ваше приложение от потенциальных уязвимостей.
Управление доступом на основе аутентификации
Доступ только аутентифицированных пользователей
Чтобы разрешить доступ только аутентифицированным пользователям, в правилах безопасности Firestore вы можете использовать следующее:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth != null; } } }
Это правило использует переменную request.auth, чтобы проверить, аутентифицирован ли пользователь, делающий запрос. Если пользователь аутентифицирован, он может читать/писать документ. Если пользователь не аутентифицирован, ему будет отказано в доступе.
Проверка доступа по электронной почте
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /collection_name/{documentId} { allow read: if request.auth != null && request.auth.token.email_verified; } } }
Управление доступом по владельцу
Доступ на основе владельца для одного документа
Если пользователь владеет только одним документом, и аутентифицированный идентификатор пользователя совпадает с идентификатором документа, то пользователь может писать в документ.
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /collectionName/{documentId} { // Allow the owner to write the document allow write: if request.auth.uid == documentId; } } }
Здесь мы проверяем, совпадает ли аутентифицированный идентификатор пользователя с идентификатором документа. Если UID совпадают, пользователю разрешается запись в документ. Если они не совпадают, в доступе на запись будет отказано.
Доступ на основе владельца для нескольких документов
Если пользователь владеет несколькими документами и каждый документ имеет аутентифицированный идентификатор пользователя, то пользователю должно быть разрешено писать во все принадлежащие ему документы.
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /collectionName/{documentId} { // Allow the owner to write their owned documents allow write: if request.auth.uid == resource.data.userId; } } }
В данном случае каждый документ в коллекции collectionName имеет поле userId, которое содержит UID владельца документа.
Правило проверяет, совпадает ли request.auth.uid с полем userId документа, в который производится запись. Если UID совпадают, пользователю разрешается запись в документ. Если они не совпадают, доступ к записи запрещен.
Контроль доступа на уровне документов
Предположим, у вас есть база данных Firestore, содержащая коллекцию с именем Workspaces. Каждый документ в этой коллекции представляет рабочее пространство и содержит такую информацию, как имя рабочего пространства, описание и members (члены) рабочего пространства.
Чтобы гарантировать, что только члены рабочего пространства могут читать его информацию, вы можете определить правило управления доступом на уровне документа следующим образом:
service cloud.firestore { match /databases/{database}/documents { // Allow read access to a workspace only if the user is a member of it match /workspaces/{workspaceId} { allow read: if exists(/databases/$(database)/documents/workspaces/$(workspaceId)/members/$(request.auth.uid)); } } }
В этом правиле мы используем функцию exists(), чтобы проверить, существует ли UID пользователя (который доступен в request.auth.uid) как идентификатор документа в подколлекции members документа рабочей области.
Используя это правило, только члены рабочей области могут читать ее информацию, в то время как нечленам будет отказано в доступе.
Управление доступом на основе ролей
Этот тип правил полезен в ситуациях, когда вы хотите ограничить доступ к определенным функциям или содержимому вашего приложения только определенным типам пользователей, например, редакторам или администраторам.
Пример 1. Чтобы разрешить пользователю писать документ, только если он имеет тип роли «редактор», вы можете использовать следующее правило безопасности Firestore:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Allow users with roleType of "editor" to write documents match /collectionName/{documentId} { allow read: if request.auth.token.roleType == 'editor'; allow write: if request.auth.token.roleType == 'editor'; } } }
Правило проверяет поле request.auth.token.roleType, чтобы убедиться, что пользователь имеет правильный тип роли, прежде чем разрешить ему чтение/запись документа.
Пример 2. Предположим, что сценарий предполагает предоставление администратору возможности изменять документ путем указания только определенных полей, в то время как другие пользователи должны предоставить все необходимые поля, чтобы обновить документ.
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { function isAdminUser() { return request.auth.uid in get(/databases/$(database)/documents/workspaces/$(workspaceId)/admins).data.uids; } function isOwner() { return request.auth.uid == resource.data.userId; } function hasRequiredFields() { return request.resource.data.keys().toSet().hasAll(["required_field_1", "required_field_2", "required_field_3"]); } function hasOverrideFields() { return request.resource.data.keys().toSet().hasAny(["status", "updatedAt"]); } match /workspaces/{workspaceId}/members/{memberId}/leaves/{leaveId} { allow update: if (isAdminUser() && hasOverrideFields()) || (!isAdminUser() && hasRequiredFields()); } } }
Обеспечивая такой контроль доступа, правило гарантирует, что только авторизованные пользователи могут изменять документы в коллекции и что любые внесенные изменения соответствуют требованиям. Это помогает поддерживать целостность и безопасность данных в коллекции.
Правила валидации на уровне полей
Проверка принадлежности значения к списку
Рассмотрим простой пример. Предположим, у вас есть коллекция рабочих пространств в базе данных Firestore с подколлекцией members. Каждый член имеет поле role_type, которое может быть admin, member, HR или guest.
Теперь, допустим, вы хотите разрешить доступ на запись только тем пользователям, которые имеют роль admin, member или hr. Пользователи вне этих ролей не смогут обновить подколлекцию members документа рабочей области. Для этого можно использовать следующее правило безопасности Firestore:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /workspaces/{workspaceId}/members/{memberId} { allow write: if request.auth != null && request.resource.data.role_type in ['admin', 'member', 'hr']; } } }
Правило разрешает доступ на запись к подколлекции members только в том случае, если пользователь, делающий запрос, аутентифицирован (request.auth != null) и если поле role_type запрашиваемого документа является либо admin, либо member, либо hr.
Требование всех полей для записи
Вот пример правила безопасности, которое разрешает доступ к созданию только в том случае, если запрос содержит все необходимые поля:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /collection/{documentId} { allow create: if request.resource.data.keys().hasAll(["required_field_1", "required_field_2", "required_field_3"]); } } }
Метод request.resource.data.keys().hasAll() проверяет, содержит ли объект запроса все необходимые поля. Если да, то правило разрешает доступ к созданию. Если нет, правило запрещает доступ.
Ограничение обновлений для определенных полей
Вот пример правила безопасности, которое ограничивает пользователя в обновлении определенных полей:
rules_version = '2'; service cloud.firestore { match /myCollection/{docId} { allow update: if !request.resource.data.diff(resource.data).affectedKeys().hasOnly(["specificField"]); } }
Ограничение доступа к созданию с обязательными и необязательными полями
Вот правило безопасности, которое разрешает операцию создания только в том случае, если запрос содержит все обязательные поля и некоторые необязательные поля:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /collection/{documentId} { allow create: if request.resource.data.keys().hasAll(["required_field_1", "required_field_2", "required_field_3"]) && (request.resource.data.keys().hasAny(["optional_field_1", "optional_field_2"]); } } }
Разрешение обновления только определенных полей
Это правило гарантирует, что только указанные поля могут быть изменены, а все остальные поля остаются неизменными.
rules_version = '2'; service cloud.firestore { match /myCollection/{documentId} { allow update: if request.resource.data.diff(resource.data).affectedKeys().hasOnly(['field1', 'field2']); } }
Контроль доступа на основе времени
Пример 1. Предположим, у вас есть мобильное приложение, позволяющее пользователям бронировать встречи с врачом. Вы хотите обеспечить, чтобы запись на прием к врачу осуществлялась только в рабочее время (т.е. с 9 утра до 5 вечера в будние дни).
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Only allow appointment booking during business hours match /appointments/{documentId} { allow create: if (date(request.time).getDay() >= 1 && date(request.time).getDay() <= 5) && (request.time.hour() >= 9 && request.time.hour() < 17) && request.auth != null; } } }
Пример 2. Разрешить запись в документ только один раз в день:
Это правило сравнивает дату запроса с датой из поля lastUpdated документа. Оно разрешает доступ к записи, только если даты не равны и пользователь аутентифицирован.
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /collection_name/{documentId} { allow write: if request.time.date() != resource.data.lastUpdated.date() && request.auth != null; } } }
Проверка типа данных
Правило проверки типа данных поля — это правило безопасности, которое проверяет, имеют ли данные в определенном поле документа ожидаемый тип данных.
Например, если поле должно содержать числовое значение, но вместо него вводится строковое значение, это может привести к ошибкам в вычислениях или запросах к базе данных. Аналогично, если поле должно содержать булево значение, а вместо него вводится строка или число, это может привести к неожиданному поведению.
Вот список всех различных типов данных, которые можно проверить:
value is bool value is string value is number value is int value is float value is timestamp value is duration value is path value is list value is map
Вот пример правил для документа со списком членов, которое разрешает создание только в том случае, если предоставленные данные имеют допустимые типы данных:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /workspaces/{workspaceId} { match /members/{memberId} { allow create: if ( request.resource.data.hasAll(['name', 'email', 'role_type', 'joining_date']) && request.resource.data.name is string && request.resource.data.email is string && request.resource.data.role_type is int && request.resource.data.joining_date is timestamp ); } } } }
Валидация типа данных необязательного поля
Для примера рассмотрим документ пользователя, в котором есть необязательные поля для номера телефона и адреса. Правило будет проверять, является ли поле номера телефона, если оно присутствует, типом number, а поле адреса, если оно присутствует, типом string.
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { function validateOptionalFields(data) { return (!('phone_number' in data.keys()) || data.phone_number is number) && (!('address' in data.keys()) || data.address is string); } match /users/{userId} { allow write: if validateOptionalFields(request.resource.data); } } }
Сложность пароля
В этом примере мы требуем, чтобы пароли:
- состояли не менее чем из 8 символов
- содержали как минимум одну букву и одну цифру
rules_version = '2'; service firebase.auth { match /users/{uid} { allow update: if request.auth.uid == uid && request.resource.password.matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/); } }
Проверка электронной почты
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { function isValidEmail(email) { // regular expression pattern to match valid email format const pattern = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,})+$/; // check if email matches pattern return email.matches(pattern); } match /collection/{documentId} { allow read, write: if isValidEmail(request.resource.data.email); } } }
Заключение
Внедрение правил безопасности для вашего Firebase Firestore — это важный шаг на пути к защите вашего приложения и пользовательских данных. Важно отметить, что эти правила безопасности не являются универсальным решением и должны быть адаптированы к требованиям вашего конкретного приложения.
Спасибо, что прочитали, и удачного кодинга! 👋