Ограничение скорости в Firebase в правилах безопасности?
Я запустил свой первый открытый проект репозитория, EphChat , и люди быстро начали наводнять его запросами.
У Firebase есть способ оценить лимитные запросы в правилах безопасности? Я предполагаю, что есть способ сделать это, используя время запроса и время ранее записанных данных, но не могу найти что-либо в документации о том, как я это сделаю.
Существующие правила безопасности заключаются в следующем.
- Есть ли способ сделать подтверждение по электронной почте для создания пользователей Firebase и / или сброса пароля?
- Когда я обновляю свой сайт, я получаю 404. Это с Angular2 и firebase
- Какое правило Firebase предотвратит дубликаты в коллекции на основе других полей?
- Как защитить Firebase Cloud Function HTTP-конечную точку, чтобы разрешать только проверенные пользователями Firebase?
- Аутентификация Firebase не отменяется при удалении пользователя?
{ "rules": { "rooms": { "$RoomId": { "connections": { ".read": true, ".write": "auth.username == newData.child('FBUserId').val()" }, "messages": { "$any": { ".write": "!newData.exists() || root.child('rooms').child(newData.child('RoomId').val()).child('connections').hasChild(newData.child('FBUserId').val())", ".validate": "newData.hasChildren(['RoomId','FBUserId','userName','userId','message']) && newData.child('message').val().length >= 1", ".read": "root.child('rooms').child(data.child('RoomId').val()).child('connections').hasChild(data.child('FBUserId').val())" } }, "poll": { ".write": "auth.username == newData.child('FBUserId').val()", ".read": true } } } } }
Я бы хотел ограничить запись (и читать?) В db для всего объекта «Комнаты», поэтому только один запрос может быть сделан в секунду (например).
Благодаря!
- Есть ли способ ограничить регистрацию в firebase
- Как создать общеansible / частный профиль пользователя с правилами безопасности Firebase?
- Ограничить количество записей, которые могут быть записаны в путь (ссылаться на другие пути в правилах безопасности)
- Как предотвратить несанкционированный доступ к моей базе данных Firebase?
- Разрешение Firebase при чтении данных после аутентификации
- как реализовать контроль доступа на основе роли в firebase
- Как я могу войти в систему с несколькими социальными службами с Firebase?
- Firebase: установить правила безопасности в зависимости от пользовательских ролей
Хитрость заключается в том, чтобы провести аудит в последний раз, когда пользователь отправил сообщение. Затем вы можете обеспечить соблюдение времени, в течение которого каждое сообщение отправляется на основе значения аудита:
{ "rules": { // this stores the last message I sent so I can throttle them by timestamp "last_message": { "$user": { // timestamp can't be deleted or I could just recreate it to bypass our throttle ".write": "newData.exists() && auth.uid === $user", // the new value must be at least 5000 milliseconds after the last (no more than one message every five seconds) // the new value must be before now (it will be since `now` is when it reaches the server unless I try to cheat) ".validate": "newData.isNumber() && newData.val() === now && (!data.exists() || newData.val() > data.val()+5000)" } }, "messages": { "$message_id": { // message must have a timestamp attribute and a sender attribute ".write": "newData.hasChildren(['timestamp', 'sender', 'message'])", "sender": { ".validate": "newData.val() === auth.uid" }, "timestamp": { // in order to write a message, I must first make an entry in timestamp_index // additionally, that message must be within 500ms of now, which means I can't // just re-use the same one over and over, thus, we've effectively required messages // to be 5 seconds apart ".validate": "newData.val() >= now - 500 && newData.val() === data.parent().parent().parent().child('last_message/'+auth.uid).val()" }, "message": { ".validate": "newData.isString() && newData.val().length < 500" }, "$other": { ".validate": false } } } } }
Смотрите на это в этой скрипке . Вот суть того, что в скрипке:
var fb = new Firebase(URL); var userId; // log in and store user.uid here // run our create routine createRecord(data, function (recordId, timestamp) { console.log('created record ' + recordId + ' at time ' + new Date(timestamp)); }); // updates the last_message/ path and returns the current timestamp function getTimestamp(next) { var ref = fb.child('last_message/' + userId); ref.set(Firebase.ServerValue.TIMESTAMP, function (err) { if (err) { console.error(err); } else { ref.once('value', function (snap) { next(snap.val()); }); } }); } function createRecord(data, next) { getTimestamp(function (timestamp) { // add the new timestamp to the record data var data = { sender: userId, timestamp: timestamp, message: 'hello world' }; var ref = fb.child('messages').push(data, function (err) { if (err) { console.error(err); } else { next(ref.name(), timestamp); } }); }) }
У меня недостаточно репутации, чтобы писать в комментарии, но я согласен с комментарием Виктора. Если вы вставляете fb.child('messages').push(...)
в цикл (т. fb.child('messages').push(...)
for (let i = 0; i < 100; i++) {...}
), то он успешно нажал бы 60- 80 сообщений (в этой оконной рамке 500 мс.
Вдохновленный решением Като, я предлагаю изменить правила следующим образом:
rules: { users: { "$uid": { "timestamp": { // similar to Kato's answer ".write": "auth.uid === $uid && newData.exists()" ,".read": "auth.uid === $uid" ,".validate": "newData.hasChildren(['time', 'key'])" ,"time": { ".validate": "newData.isNumber() && newData.val() === now && (!data.exists() || newData.val() > data.val() + 1000)" } ,"key": { } } ,"messages": { "$key": { /// this key has to be the same is the key in timestamp (checked by .validate) ".write": "auth.uid === $uid && !data.exists()" ///only 'create' allow ,".validate": "newData.hasChildren(['message']) && $key === root.child('/users/' + $uid + '/timestamp/key').val()" ,"message": { ".validate": "newData.isString()" } /// ...and any other datas such as 'time', 'to'.... } } } } }
Код .js очень похож на решение Kato, за исключением того, что getTimestamp вернет {time: number, key: string} к следующему обратному вызову. Тогда нам просто нужно ref.update({[key]: data})
Это решение позволяет избежать временного windows 500 мс, нам не нужно беспокоиться о том, что клиент должен быть достаточно быстрым, чтобы нажимать сообщение в течение 500 мс. Если отправлено несколько запросов на запись (спам), они могут записывать только одну клавишу в messages
. При желании правило создания только в messages
предотвращает это.
В существующих ответах используются два обновления базы данных: (1) отметьте метку времени и (2) присоедините отмеченную метку времени к фактической записи. Ответ Като требует 500 мс тайм-windows, в то время как ChiNhan требует запоминания следующего ключа.
Это простой способ сделать это в одном обновлении базы данных. Идея состоит в том, чтобы сразу написать несколько значений в базу данных с помощью метода update () . Правила безопасности проверяют письменные значения, чтобы запись не превышала квоту. Квота определяется как пара значений: quotaTimestamp и postCount . PostCount – это количество сообщений, написанных в течение 1 минуты от quotaTimestamp. Правила безопасности просто отклоняют следующую запись, если postCount превышает определенное значение. PostCount сбрасывается, когда quotaTimestamp становится более статичным, чем 1 минута.
Вот как отправить новое сообщение:
function postMessage(user, message) { const now = Date.now() + serverTimeOffset; if (!user.quotaTimestamp || user.quotaTimestamp + 60 * 1000 < now) { // Resets the quota when 1 minute has elapsed since the quotaTimestamp. user.quotaTimestamp = database.ServerValue.TIMESTAMP; user.postCount = 0; } user.postCount++; const values = {}; const messageId = // generate unique id values[`users/${user.uid}/quotaTimestamp`] = user.quotaTimestamp; values[`users/${user.uid}/postCount`] = user.postCount; values[`messages/${messageId}`] = { sender: ..., message: ..., ... }; return this.db.database.ref().update(values); }
Правила безопасности для ограничения скорости не более 5 сообщений в минуту:
{ "rules": { "users": { "$uid": { ".read": "$uid === auth.uid", ".write": "$uid === auth.uid && newData.child('postCount').val() <= 5", "quotaTimestamp": { // Only allow updating quotaTimestamp if it's staler than 1 minute. ".validate": " newData.isNumber() && (newData.val() === now ? (data.val() + 60 * 1000 < now) : (data.val() == newData.val()))" }, "postCount": { // Only allow postCount to be incremented by 1 // or reset to 1 when the quotaTimestamp is being refreshed. ".validate": " newData.isNumber() && (data.exists() ? (data.val() + 1 === newData.val() || (newData.val() === 1 && newData.parent().child('quotaTimestamp').val() === now)) : (newData.val() === 1))" }, "$other": { ".validate": false } } }, "messages": { ... } } }
Примечание. Чтобы избежать перекоса часов, необходимо поддерживать серверTimeOffset .