Reputation: 68
Given the following data structure:
Is there anyway to authenticate read requests based on the existence of auth.uid in the compound key for the entry? For example '5f79b99dc6a7c11b9c0eab78' in '5f6d9d83258b65b5f272aa9f|5f79b99dc6a7c11b9c0eab78'??
I've tried this - but i assume contains relates to the fields within the entry rather than the key itself.
{
"rules": {
"messages": {
"$message": {
".read": "$message.contains(auth.uid)"
}
}
}
}
EDIT:
This is the code I have authenticating the user, prefetching existing messages (i'll be added some further logic here to only pull X number of messages at a time), sending and retrieving new messages.
import * as firebase from "firebase/app";
import "firebase/auth";
import "firebase/database";
export const initClient = async (token) => {
if (!firebase.apps.length) {
var firebaseConfig = {
apiKey: "[API]",
authDomain: "[DOMAIN]",
databaseURL: "[URL]",
projectId: "[ID]",
storageBucket: "[STORAGE]",
messagingSenderId: "[SENDERID]",
appId: "[APP_ID]",
measurementId: "[MEAS_ID]"
};
firebase.initializeApp(firebaseConfig);
await firebase.auth().signInWithCustomToken(token).catch(function (error) {
var errorCode = error.code;
var errorMessage = error.message;
//todo
});
return { authenticated: true }
}
}
export const writeMessage = (fromId, chatId, message) => {
var newMessageKey = firebase.database().ref().child('messages/' + chatId).push().key;
firebase.database().ref('messages/' + chatId + '/' + newMessageKey).set({
senderId: fromId,
message: encodeURIComponent(message)
});
}
//get all messages once when a particular chat window is open - works fine without auth rules in place
export const getMessages = async (chatId) => {
//chatId = '5f6d9d83258b65b5f272aa9f|5f79b99dc6a7c11b9c0eab78
var ref = firebase.database().ref('messages/' + chatId);
const snapshot = await ref.once('value')
const messages = snapshot.val();
const messagesArray = Object.keys(messages).map(key => ({ ...messages[key], message: decodeURIComponent(messages[key].message), messageId: key }))
return { chatId, messages: messagesArray };
}
let bound = false;
//set listeners to get new messages and pass the messages back into the application
export const setMessageListeners = async (chatId, onNewMessage) => {
if(bound) return;
bound = true;
var ref = firebase.database().ref('messages/' + chatId);
ref.endAt().limitToLast(1).on('child_added', function (data) {
onNewMessage(chatId, data.key, decodeURIComponent(data.val().message), data.val().senderId);
});
}
Perhaps I need to do something with the custom token for each request?
I should probably note that I cant get the rules to play ball within the rule editor playground either. No doubt it has something to do with the payload im using to test against.
Outside of that I have some server side logic keep track of each conversation, this data is actually stored outside of RTDB - the above is purely a message store.
EDIT: Sorted!
{
"rules": {
"messages": {
"$conversation": {
".read": "$conversation.contains(auth.uid)",
"$message": {
".write": "newData.child('senderId').val() === auth.uid && !data.exists() || data.child('senderId').val() === auth.uid && data.exists()"
}
}
}
}
}
It seems my issue wasn't actually an issue at all. It was just me using the playground incorrectly and adding curly braces in the path segments that matched the dynamic keys in the rule set (no idea why, im sure I saw it somewhere though). The above structure is what I've settled on for now. Anyone involved in a conversation can read messages (until I add a deleted property, rather than removing the message i'll just hide it). You can write new items if they don't exist, or update existing messages that belong to you.
Thanks for your help!
Upvotes: 0
Views: 79
Reputation: 599081
I just tried reproducing this problem, but it seems to work fine for me. I'll share my code, data and rules below in hopes they help you find the problem/difference.
My JSON:
{
"anotheruser-m4yXaC9wmCdJXOiL2LDPEJDPoHw1" : "value2",
"m4yXaC9wmCdJXOiL2LDPEJDPoHw1-anotheruser" : "value1"
}
My code:
firebase.auth().signInAnonymously();
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
console.log("uid: "+user.uid);
var ref = firebase.database().ref("64258893");
ref.child("m4yXaC9wmCdJXOiL2LDPEJDPoHw1-anotheruser").once("value")
.then(function(snapshot) {
console.log(snapshot.key+": "+snapshot.val());
}).catch(function(error) {
console.error(error);
});
}
});
My security rules for node 64258893
:
{
"rules": {
"64258893": {
"$message": {
".read": "$message.contains(auth.uid)"
}
},
...
This code logs:
"uid: m4yXaC9wmCdJXOiL2LDPEJDPoHw1"
"m4yXaC9wmCdJXOiL2LDPEJDPoHw1-anotheruser: value1"
This is exactly what I'd expect, as you can see that the UID is embedded in the key. If I change the read to some other path that doesn't include the UID (like ref.child("yes-anotheruser")
then the read gets rejected.
Upvotes: 1