How can the client code of a Meteor app detect that a write operation (insert, remove, update) against a collection was denied, so that it can display an appropriate error message?
Collection.remove(id)
The console will display:
remove failed: Access denied
This is rather obvious, but Google didn't do a good job of surfacing the relevant documentation: you need to pass a callback parameter:
Collection.remove(id, function (error) {
if (error)
sAlert.error(error.toString());
});
Related
I'm trying to follow this guide to put some custom logging into a firebase function. The function itself is running, and I can see the data being passed in (it's an https 'callable' function). But as soon as it hits the line where it tries to actually write that log entry, I get "Error: 7 PERMISSION_DENIED"
Since the console.log() calls write to the cloud logs, I'd assumed the firebase function has access to Cloud Logging. But perhaps it needs additional permission? I can't find any reference to where this should be set on that page though.
// Logging, logName, region, functions are provided by the surrounding app
const logging = new Logging()
const log = logging.log(logName)
const METADATA = {
resource: {
type: 'cloud_function',
labels: {
function_name: 'CustomLog',
region
}
}
};
exports = module.exports = functions.https.onCall(async data => {
const exVersion = 6
const exId = data.exId
console.log('***** exVersion:', exVersion, 'exId:', exId) // exId from caller
const entry = log.entry(METADATA, data.error) // data.error from caller
console.log('METADATA:', METADATA) // Shows in Logs Explorer
console.log('entry:', entry) // Shows in Logs Explorer
log.write(entry) // Results in Error: 7 PERMISSION_DENIED
return {
exVersion,
exId,
}
})
If I run it from the CLI using firebase function:shell, the log entry is created correctly, so I'm pretty confident the code is correct.
OK, I finally tracked it down. According to this answer, the service account used by firebase functions is {project-id}#appspot.gserviceaccount.com, and in my project, that account did not have the 'Logs Writer' role. Adding that role solves the problem.
I find it odd that the firebase functions don't need that role to log messages using console.log(), but perhaps that call is intercepted by the functions environment, and the logs are written as a different service account. It also explains why the functions running locally were able to write the logs, as they run using the 'owner' service account, which has full access.
According to the Firebase documentation page you have linked:
The recommended solution for logging from a function is to use the
logger SDK. You can instead use standard JavaScript logging calls such
as console.log and console.error, but you first need to require a
special module to patch the standard methods to work correctly:
require("firebase-functions/lib/logger/compat");
Once you have required the logger compatibility module, you can use console.log() methods as normal in your code.
Thus you might to require this library, however I am not sure this is producing your "Error: 7 PERMISSION_DENIED error, but you might also try some solutions that have worked for some members of the community.
Perhaps the logging API is not enabled in your project. You'll get a permission denied error when attempting to use it in that case.
It's a couple levels in, but the guide you linked points to
https://github.com/googleapis/nodejs-logging#before-you-begin, which includes a step to "Enable the Cloud Logging API"
I'm using Meteor 1.8.1 and have found what seems like inconsistent and undocumented behaviour in the errors returned by Accounts.changePassword.
The docs say that a Meteor error object will include a 'reason' parameter.
But if the attempt to change password fails because the user is not logged in, the error object does not contain 'reason' or 'error', only 'message', which I cannot find in the documentation.
'message' appears to be always returned, despite being undocumented, but is inconsistent in that it includes the error code 403 in the case of incorrect password but not in the case where the user is not logged in.
Accounts.changePassword(oldPassword, newPassword, (error) => {
console.log('error.message', error.message);
// not logged in provides message
console.log('error.reason', error.reason);
// incorrect password provides reason and message
if (error) {
const text = error.reason || error.message;
console.log('error', text);
}
// success
});
So my questions are:
have I missed something? Or is the behaviour really inconsistent and undocumented?
is there an easy way to get a consistent error message in both cases?
are there any other 'gotchas' I should test for where Meteor returns an error in a different format again?
Many thanks for any enlightenment.
according to documentation, changePassword method runs only on the client, so you could always check if the user is logged in before attempting to change the password
if (!Meteor.user()) {
return reportError(new Error("Must be logged in to change password."), callback);
}
According to the docs the description of Accounts.changePassword is:
Change the current user's password. Must be logged in.
Therefore, when you try to call this when no user is logged in, it is reasonable for the response to be undefined or inconsistent. Yes, the docs for a Meteor.Error object specify a reason property, but Meteor.Errors are only thrown when a method wants to return a descriptive error, not when a method is called illegally.
Your user interface code should ensure that a change password form is never shown unless a user is logged in. If your change password form is only ever shown to logged in users, then you don't need to worry about catching these undefined errors.
Using Meteor, the Meteor.loginWithPassword function calls the server, which then returns a User not found error when the user does not exist, or a Match failed when the password does not match the user's password.
Is there an easy way for the server to return the same error (or no error) during both conditions of a failed login?
I don't want hackers to know when they found a valid username or UserID on my system. I don't want the server to say User not found, telling potential hackers when they have (or have not) found a valid user. It would be great if there's an easy way to change the error message the server returns from the accounts-password Meteor module, to harden the security a little bit. I'd like the server's error result to be something generic, like failed or undefined or null, regardless of the reason of the login error.
I know I can probably fork/re-purpose the accounts-password module, but hoping there's something simpler.
Thanks!
Yes, you can place the function somewhere in the /server-folder and change the error message. Like this:
Accounts.validateLoginAttempt(function(options) {
if (!options.allowed) { //that is, if there's an error
throw new Meteor.Error('login-error', 'Error!');
}
//...other code for other purposes below
});
/server/accounts.js
The browser console shows the records the collection has, but when I try Tasks.remove({}); in the browser console I get some error:
errorClass {error: 403, reason: "Not permitted. Untrusted code may only remove documents by ID.", details: undefined, message: "Not permitted. Untrusted code may only remove documents by ID. [403]", errorType: "Meteor.Error"}
Any idea how to remove all the documents from the collection including the _id(s)? Thanks
Meteor.subscribe('tasks');
Meteor.publish('tasks', function(){
return Tasks.find();
});
You can only remove multiple documents at once on the server as documented on the Meteor site.
So to clear the collection you'll need to do this in server code, and create a method if you need to call this from the client.
meteor.js uses magic (ie: websockets) to sync the local db with the server. I have been searching for, but sofar have not been able to find, a way to see the status of the synchronisation. I would like the be able to check if a update for instance has been synced to the server. How could I do that?
thanks,
Paul
The callback associated with an insert/update/remove will be invoked with an error as its first argument. If error is defined, the server failed to make the modification. If it isn't defined, the modification succeeded.
Comments.update(commentId, {$set: {message: newMessage}}, function(error) {
if (error) {
console.log('it failed!');
} else {
console.log('it worked!');
}
});