Rules: get vs exists - firebase

I have a Firebase project that I haven't touched for a year or so, it was previously working fine. I fired it up again and was getting access denied on something that definitely worked before. After some debugging I tracked it down to this security rule:
function isPlayer() {
return exists(/databases/$(database)/documents/games/$(gameId)/players/$(request.auth.token.email));
}
(Please ignore email vs uid unless you think it's relevant, this is a POC)
With this code, permission denied. A change to:
function isPlayer() {
return get(/databases/$(database)/documents/games/$(gameId)/players/$(request.auth.token.email)) != null;
}
works as previously.
Any ideas what is going on here? Is exists broken somehow?

Related

Nuxt Middleware with Firebase and FirebaseUI: Error: Redirected when going from "/anything" to "/login" via a navigation guard

Nuxt SSR app using FirebaseUI to handle auth flows. Logging in and out works perfectly. When I add Middleware to check auth state and redirect if not logged in I get this error:
Error: Redirected when going from "/list-cheatsheets" to "/login" via a navigation guard.
middleware/auth.js
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.user) {
return redirect('/login')
}
}
There is absolutely no other redirecting that I can find in the app....
I have been digging and trying things for hours. Others who get this error that I have found aren't using Nuxt and none of those solutions work.
As there is a bounty one cannot mark it duplicate thus following up is a copy of my answer at Redirecting twice in a single Vue navigation
tldr: vm.$router.push(route) is a promise and needs to .catch(e=>gotCaught(e)) errors.
This will be changed in the next major#4
Currently#3 errors are not distinguished whether they are NavigationFailures or regular Errors.
The naive expected route after vm.$router.push(to) should be to. Thus one can expect some failure message once there was a redirect. Before patching router.push to be a promise the error was ignored silently.
The current solution is to antipattern a .catch(...) onto every push, or to anticipate the change in design and wrap it to expose the failure as result.
Future plans have it to put those informations into the result:
let failure = await this.$router.push(to);
if(failure.type == NavigationFailureType[type]){}
else{}
Imo this error is just by design and should be handled:
hook(route, current, (to: any) => { ... abort(createNavigationRedirectedError(current, route)) ...}
So basically if to contains a redirect it is an error, which kinda is equal to using vm.$router.push into a guard.
To ignore the unhandled error behaviour one can pass an empty onComplete (breaks in future releases):
vm.$router.push(Route, ()=>{})
or wrap it in try .. catch
try {
await this.$router.push("/")
} catch {
}
which prevents the promise to throw uncaught.
to support this without redirecting twice means you put the guard to your exit:
let path = "/"
navguard({path}, undefined, (to)=>this.$router.push(to||path))
which will polute every component redirecting to home
btw the router-link component uses an empty onComplete
Assumption that redirecting twice is not allowed is wrong.

Firebase Cloud Storage Permission denied issue

I started out with an issue simply displaying storage contents. The error message was the same Code 400 "Permission denied. Could not access bucket bucket. Please enable Firebase Storage for your bucket by visiting the Storage tab in the Firebase Console and ensure that you have sufficient permission to properly provision resources."
But the changes I made based on This stackoverflow question made the issue worse, now my upload form is giving the same error now and blocks file uploads, even after rolling back all the changes. I tried creating a brand new project on Firebase, but that has the same error.
I also tried to set my bucket permissions to
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read;
allow write;
}
}
}
since I thought that it could be a rights issue, but I get the same error.
I'm not sure what I was trying to do when I added this bit of code, but I had added
{ provide: StorageBucket, useValue: 'bucket' }
To my providers: on my app.module. I think it was a place holder for something and I forgot to remove it before committing the project.
This led me down and interesting troubleshooting path that finally led me to doing breakpoints and console logs until I found the issue.
const task = this.storage.upload(filePath, file);
I did a console log of task, which gave me a location, that's where I found
bucket: bucket
I then added the same console.log to a working project that showed
bucket: whatever.appspot.com
and I knew I made a mistake somewhere. I just happened across the entry in the app.module.ts file while trying to recreate the issue in stackblitz. The error is resolved.

Cant get Firebase Storage security rules to refuse access to a file

I'm sure I'm missing something wrt Firebase Storage rules, but I've done the following:
STEP 1
Firstly I set the following Firebase Storage rule:
service firebase.storage {
match /b/{bucket}/o {
match /items/{dev_key}/{perm_id}/{file_name} {
allow write: if request.auth.uid == dev_id;
allow read: if request.auth.token.permId == perm_id;
}
}
}
I expected only signed in users with a custom claim permId matching the relevant location to be able to download the file, allow read: if request.auth.token.permId == perm_id;.
So, I then set a custom claim in Cloud Functions on a user as follows:
STEP 2
admin.auth().setCustomUserClaims(uid, {permId: '1'}).then(() => {
// send off some triggers to let user know the download is coming
admin.database().ref(`collection/${uid}/${itemId}`).update({
downloadReady: true
});
});
Then I signed the user out and signed back in again... which set the custom claims.
I checked that they were set in Cloud Functions as follows:
STEP 3
admin.auth().verifyIdToken(idToken).then((claims) => {
console.log("--------------claims -------------");
console.log(JSON.stringify(claims));
});
And I saw in the claims string... permID: "1"
On the client side I then requested a downloadURL (here is hopefully where I'm going wrong)... I expected this to not be the public download url but rather the download url that the Firebase Storage security rules will check:
STEP 4
var pathReference = storage.ref('items/<some-key>/1/Item-1');
pathReference.getDownloadURL()
.then((url)=>{
console.log("url: ", url);
})
The url I received from this call gave me this link
https://firebasestorage.googleapis.com/v0/b/emiru84-games.appspot.com/o/games%2FcfaoVuEdJqOWDi9oeaLLphXl0E82%2F1%2FGame-1?alt=media&token=45653143-924a-4a7e-b51d-00774d8986a0
(a tiny little image I use for testing)
So far so good, the user with the correct claim was able to view this image
I then repeated step 2, logout/login again, except this time with a permId of "0". I expected the url generated previously to no longer work since my user no longer had the correct custom claim... and the bucket location was still at the same location (bucket/dev_key/1/filename) but it still worked.
If I repeated step 4 I got a new url, which then gave the appropriate 403 error response. However the old url still worked (I guess as long as the token parameter is tacked on). Is this expected, if so, I'm not sure I understand how the Storage security rules make a difference if the download url is public anyway?
Any help clearing my foggy brain would be appreciated.
The download URL in Cloud Storage for Firebase is always publicly readable. It is not affected by security rules.
If you don't want to allow public access to a file, you can revoke its download URL.

Location of Iron Router Code

I want to verify that I am placing my Iron Router code in the correct location. Right now I have it stored in lib/router.js, which means the code is shared on the client and server. Is that correct?
Also, some of my routes require admin status, such as:
Router.route('/manage', function () {
if ($.inArray('admin', Meteor.user().roles) > -1) {
this.render('manage');
} else {
this.render('403_forbidden');
}
});
Is that code safe in its current location? I am also interested in knowing how I can test these kinds of security holes so that I don't have to ask in the future.
Thanks
As to location of router.js ...
Yes, you want it to be available on both the client and server. So putting inside the /lib directory is fine. Really, you can put it anywhere other the the /client or /server directories.
FWIW, in most projects I've looked at, router.js is stored in the top-level project directory. Possibly this is to avoid load order issues (i.e. if the router has some dependencies on files in /lib, /client, or /server, which will generally be loaded before top-level files), or possibly its because everyone I've looked at is working off the same boilerplate code. Check out the Meteor official docs if you want to know more about load order.
As for your admin question, that route should be OK. You can test it by opening up a client side console like firebug and trying something like :
Meteor.users.update(Meteor.userId(), {$set: {roles: ['admin']}});
I believe users can only update fields in the Meteor.users.profile, so this should fail. If it doesn't, you can just add the following deny rule (in client + server);
Meteor.users.deny({
update: function() {
return true;
}
});

Meteor user permissions

I'am trying to check the users permission (roles) in the routing (iron routing). I have a boolean property in the user which I set in the
Accounts.onCreateUser
e.g. isvalid = false. The first problem is that only username and id are exposed so I try too publish
Meteor.publish('userData',function() {
return Meteor.users.find({_id:this.userId},{fields:{'isvalid':1}});
});
In the router I check
onBeforeAction: function() {
if(!Meteor.user() || !Meteor.user().isvalid)
this.render('nopermission');
else
this.next();
}
It works but when I debug I can see that the onBeforeAction fires three times. First time the user is undefined, second time I have a user without the property isvalid and third time I have everything. In debug I can see the screen flash with the template "nopermission" but when I run it live it seems ok. I think I have done it wrong, how can I check the permission in the correct way? I know that this code runs in client and I plan to do the check even on the server and I suppose that the Meteor.user().isvalid works without any problems on the server.
Thanks for help

Resources