Update 2019-11-03: Added a live minimal reproduction of the error. After loading the link in Chrome, hit ctrl+shift+i and select the console to see the output. I have tried hard to make sure this is doing exactly what my original project's code is doing; we'll see if that's the case, eh? The rules file for the shard is the same as the original post below. The source is available on GitHub.
<!DOCTYPE html>
<html>
<body>
<script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-database.js"></script>
<script>
const config={
apiKey: "AIzaSyDLMc0GUf5n2nQa3aqpELQu7lziprQOGs8",
authDomain: "shardautherror.firebaseapp.com",
databaseURL: "https://shardautherror.firebaseio.com",
projectId: "shardautherror",
storageBucket: "shardautherror.appspot.com",
messagingSenderId: "841096336504",
appId: "1:841096336504:web:9899961c8250caa552498d"
};
const shard="https://shardautherror-1e9ed.firebaseio.com/";
async function init(){
try{
firebase.database.enableLogging(true);
const defaultApp=firebase.initializeApp(config);
const auth=defaultApp.auth();
const s="alice@example.com";
await auth.signInWithEmailAndPassword(s,s);
const uid= auth.currentUser.uid;
const shardApp=firebase.initializeApp({databaseURL:shard},'dbAppShard');
const db=firebase.database(shardApp);
const ref= db.ref("/chat/"+uid+"/fail/"+uid);
const time= firebase.database.ServerValue.TIMESTAMP;
ref.set({time});
} catch(e) {
console.error("init failed",e);
}
}
init();
</script>
</body>
</html>
Original Post:
These rules work in the simulator, but not in my real web app. The simulator path and payload are the same as shown in the database logging output below.
database.rules.json
(main
targets both shards to use this rules file; I verified on deploy)
{
"rules":{
"chat":{
"$ownerId":{
"fail":{
"$pId":{
".write": "$pId== auth.uid&& $ownerId== auth.uid",
"time":{".validate": "newData.val()== now"},
"$other":{".validate": "newData.isString()&& newData.val().length>= 28"}
}
}
}
}
}
}
Firebase logging output of set command that is failing. It just writes a single value called time. This is my first time trying to use rtdb. I have it set up with sharding. It acquires the shard name from firestore right before it tryies to access the realtime database, but it does not seem like a race condition (despite the logging output) for reasons I'll outline below.
index.esm.js:81 [2019-10-19T03:02:53.281Z] @firebase/database: 0: set
{"path":"/chat/rpNIK41hNpWkYY2KqndkwCzPJuF3/fail/rpNIK41hNpWkYY2KqndkwCzPJuF3",
"value":{"time":{".sv":"timestamp"}},"priority":null}
22:02:53.285 index.esm.js:81 [2019-10-19T03:02:53.285Z] @firebase/database:
p:0: Buffering put: /chat/rpNIK41hNpWkYY2KqndkwCzPJuF3/fail/rpNIK41hNpWkYY2KqndkwCzPJuF3
22:02:53.293 index.esm.js:81 [2019-10-19T03:02:53.293Z] @firebase/database:
p:0: Making a connection attempt
22:02:53.294 index.esm.js:81 [2019-10-19T03:02:53.294Z] @firebase/database:
getToken() completed. Creating connection.
22:02:53.295 index.esm.js:81 [2019-10-19T03:02:53.295Z] @firebase/database:
c:0:0: Connection created
22:02:53.296 index.esm.js:81 [2019-10-19T03:02:53.296Z] @firebase/database:
p:0: Auth token refreshed
22:02:53.298 index.esm.js:81 [2019-10-19T03:02:53.298Z] @firebase/database:
c:0:0:0 Websocket connecting to wss://quickstart-1551998385825-7f7a6.firebaseio.com/.ws?v=5
22:02:53.534 index.esm.js:81 [2019-10-19T03:02:53.534Z] @firebase/database:
c:0:0:0 Websocket connected.
22:02:53.539 index.esm.js:81 [2019-10-19T03:02:53.539Z] @firebase/database:
c:0:0: Realtime connection established.
22:02:53.539 index.esm.js:81 [2019-10-19T03:02:53.539Z] @firebase/database:
p:0: connection ready
22:02:53.542 index.esm.js:81 [2019-10-19T03:02:53.541Z] @firebase/database:
p:0: reportStats {"c":{"sdk.js.7-0-0":1}}
22:02:53.542 index.esm.js:81 [2019-10-19T03:02:53.542Z] @firebase/database:
p:0: {"r":1,"a":"s","b":{"c":{"sdk.js.7-0-0":1}}}
22:02:53.546 index.esm.js:81 [2019-10-19T03:02:53.546Z] @firebase/database:
p:0: {"r":2,"a":"p","b":{"p":"/chat/rpNIK41hNpWkYY2KqndkwCzPJuF3/fail/rpNIK41hNpWkYY2KqndkwCzPJuF3",
"d":{"time":{".sv":"timestamp"}}}}
22:02:53.591 index.esm.js:81 [2019-10-19T03:02:53.591Z] @firebase/database:
p:0: from server: {"r":1,"b":{"s":"ok","d":""}}
22:02:53.595 index.esm.js:81 [2019-10-19T03:02:53.595Z] @firebase/database:
c:0:0: Primary connection is healthy.
22:02:53.596 index.esm.js:81 [2019-10-19T03:02:53.596Z] @firebase/database:
p:0: from server: {"r":2,"b":{"s":"permission_denied","d":"Permission denied"}}
22:02:53.597 index.esm.js:81 [2019-10-19T03:02:53.597Z] @firebase/database:
p:0: p response {"s":"permission_denied","d":"Permission denied"}
So, after this, if I update the rule to ".write": true,
the write of the timestamp succeeds. In the log it shows "r":3 ...
, so I know it didn't throw away the connection and restart. If I then change it to ".write": "auth.uid != null",
or ".write": "auth != null",
(thus, not checking ownership, just whether the client logs in, unlike above) it denies permission again with "r":4 ..."
indicating the 4th request. So, it seems like I have a total failure of the client to authenticate to the shard.
Recommendations? I'm sure I'm doing something wrong.
By the way, the user documentation is all over the place... Are all of these actually valid?
"baskets": {
".read": "auth.uid != null &&// auth.uid!= null from https://firebase.google.com/docs/database/security/securing-data
".read": "auth != null && auth.uid == $uid" // auth != null from https://firebase.google.com/docs/database/security/user-security
".write": "$user_id === auth.uid" // triple equal from https://firebase.google.com/docs/database/security/user-security
".write": "request.auth.uid == uid" // request.auth from realtime database tab of content owner access from https://firebase.google.com/docs/rules/basics