Access denied [403] when updating user accounts client-side in Meteor - javascript

I'm reading through the docs for Meteor here and the useraccounts package here but can't find an answer. I've added the useraccounts package successfully and have created a few users, but now I want to add some data to the record in the collection for a given user.
For example, after account creation and login. I want the user to be able to add/edit some fields on their record (short biography, etc..), but I keep getting a 403 error whenever performing a Meteor.users.update(..).
My login config file can be found here.
The code that's causing an error:
Template.editProfile.events({
'submit form': function(e) {
e.preventDefault();
var profileInfo = {
displayName: $(e.target).find('[name=displayName]').val(),
tagLine: $(e.target).find('[name=tagLine]').val(),
aboutMe: $(e.target).find('[name=aboutMe]').val()
};
Meteor.users.update(
{ _id: Meteor.userId()},
{ $set: profileInfo},
function (err) {
if(err) {
console.log('there was an error submitting editProfile data');
console.log(err);
} else {
Router.go('profile');
}
}
);
}
});
Doing console logs show the Meteor.userId() coming back correctly so I'm not sure what the problem is. I'm assuming it's an issue with allow/deny but I don't even know where to begin to troubleshoot.
The exact error is:
error: 403
errorType: "Meteor.Error"
message: "Access denied [403]"
reason: "Access denied"

By removing the insecure package, client-side write access will be denied by default.
If you want to allow clients to write directly to a collection, you need to define rules.
For example:
Meteor.users.allow({
update: ownsDocument
});
ownsDocument = function (userId, doc) {
return doc && doc.userId === userId;
};
The ownsDocument() function checks if the userId specified owns the document. In addition to the update callback, you can set rules for insert and remove.
Read more about Meteor's collection.allow(options), access a demo app or clone the repository.

Related

Can't login with Meteor function loginWithPassword, Unrecognized options for login request [400] error

I'm working on React + Meteor application and can't login using accounts-password package via loginWithPassword function.
The official API says that Unrecognized options for login request [400] error pops up when your user or password is undefined (or, i guess, just do not match the API), but i've checked the arguments and everything seems correct. username and password are strings. Meteor has ability to operate with user object, but this is not working too.
Here's the sample of my code.
const submit = useCallback(
(values) => {
const { email, password } = values;
const callback = (err) => {
if (err) {
console.log(err);
notifyError();
return;
}
login();
history.replace(from);
};
Meteor.loginWithPassword(email, password, callback);
},
[from, history, login, notifyError]
);
Any help appreciated.
It looks like that particular error can occur when there are no login handlers registered.
Have you added the accounts-password package ?
meteor add accounts-password

When using auth0, I cannot gain access to the scope read:current_user

I would like to be able to view and update a user's metadata from a single page application but whenever I attempt to gain access to any of the scopes that would appear from the documentation (https://auth0.com/docs/libraries/auth0js/v9)
However, when I run the following code:
webAuth.checkSession(
{
audience: `https://mysite.eu.auth0.com/api/v2/`,
scope: "read:current_user"
},
(err, result) => {
err && console.log("err", err");
result && console.log("result", result);
}
);
I get the following error:
{error: "consent_required", error_description: "Consent required"}
I've tried putting the scope read:current_user in multiple places but it always appears to fail.
The error means that the server is requesting an additional consent from the user, so checkSession cannot complete the action because it happens silently and cannot prompt the user.
To fix this, go to Auth0 dashboard -> APIs -> Auth0 Management API -> Settings tab -> uncheck 'Allow Skipping User Consent'.

Parse Login in node.js - Login successful but 'There is no current user'

I'm having trouble interacting with my Parse data in node.js. I'm able to login successfully, but Parse.User.current() returns null. After running the below code, I'd like to query data that has ACL read/write only for that user. Currently, that query returns empty, but if I change that data to public read/write, I can see the results of the query output in the terminal.
Here is my node.js code:
Prompt.get([{
name: 'username',
required: true}, {
name: 'password',
hidden: true}], function (err, result) {
if (err) {
console.log('Error: ' + err);
} else {
Parse.User.logIn(result.username, result.password, {
success: function(user) {
console.log('LOGGED IN');
console.log(user);
console.log(Parse.Session.current());
console.log(Parse.User.current());
... (query happens below this)
And my console output:
prompt: username: pablo
prompt: password:
LOGGED IN
ParseUser { _objCount: 0, className: '_User', id: 'EXyg99egkv' }
ParsePromise {
_resolved: false,
_rejected: true,
_resolvedCallbacks: [],
_rejectedCallbacks: [],
_error: 'There is no current user.' }
null
Thanks in advance.
Is this not a usecase for Parse.User.become()? From the parse docs:
If you’ve created your own authentication routines, or otherwise
logged in a user on the server side, you can now pass the session
token to the client and use the become method. This method will ensure
the session token is valid before setting the current user.
Parse.User.become("session-token-here").then(function (user) {
// The current user is now set to user.
}, function (error) {
// The token could not be validated.
});
I had similar problems and found this Parse blog that explains the issue:
Also in Cloud Code, the concept of a method that returns the current user makes sense, as it does in JavaScript on a web page, because there’s only one active request and only one user. However in a context like node.js, there can’t be a global current user, which requires explicit passing of the session token. Version 1.6 and higher of the Parse JavaScript SDK already requires this, so if you’re at that version, you’re safe in your library usage.
You can execute queries with user credentials in a node.js environment like this:
query.find({ sessionToken: request.user.getSessionToken() }).then(function(data) {
// do stuff with data
}, function(error) {
// do stuff with error
});
If you wish to validate that token before using it, here's an explanation of how you could go about doing that:
one way would be to query for an object known to be only readable by the user. You could have a class that stores such objects, and have each one of them use an ACL that restricts read permissions to the user itself. If running a find query over this class returns 0 objects with a given sessionToken, you know it's not valid. You can take it a step further and also compare the user object id to make sure it belongs to the right user.
Session tokens cannot be queried even with the master key.

Reset Loopback Password with Access Token

I'm working on a project that uses Loopback as a framework, and includes users and authentication. I added a password reset route generated and sent in an email, and everything seemed to be working correctly. Recently, I discovered that the password reset does not appear to be working. The process for resetting the password here is:
Call password reset method for user
Send email from reset event, including user ID and access token
From reset link, set $http.defaults.headers.common.authorization to the passed token
Call user.prototype$updateAttributes (generated by lb-ng) to update password attribute based on a form
The expected behavior is that the password would be updated on the password reset form. Instead, I get an authorization error as either a 401 or a 500 (seems to go back and forth). I notice that in the actual headers sent to the API, the authorization token does not match what I'm passing from the route. Trying to set it using LoopBackAUth.setUser doesn't work, and neither doesn't updating the authorization property before actually sending the request.
I definitely spent time testing this when it was first added, and I can't figure out what would have changed to break this. I've been following the example from loopback-faq-user-management, but we have an Angular front-end instead of the server side views in that example.
Edit:
I tried opening up the ACLs completely to see if I could update the password (or any properties) of my user object (which inherits from User, but is its own type). I'm still getting a 401 when trying to do this.
Edit #2:
Here are my ACLs and sample code for how I'm calling this.
ACLs from model definition
...
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "updateAttributes"
}
...
auth.js
...
resetPassword: function(user) {
return MyUser.prototype$updateAttributes(user, user).$promise;
}
...
Figured out what the issue was. In our app's server, we were not using Loopback's token middleware. Adding app.use(loopback.token()); before starting the server causes the access token provided in the reset link to work as expected!
While all of the above answers will prove to be helpful, be aware that Loopback destroys a token during validation when it proved it to be invalid . The token will be gone. So when you're working your way through a solution for the 401's, make sure you're creating a new password reset token each time you try a new iteration of your code.
Otherwise you might find yourself looking at perfectly healthy code to change a password, but with a token that's already deleted in a previous iteration of your code, leading you to the false conclusion that you need to work on your code when you see another 401.
In my particular case the access tokens are stored in a SQL Server database and the token would always be immediately expired due to a timezone problem that was introduced, because I had options.useUTC set to false. That cause all newly tokens to be 7200 seconds in the past which is more than the 900 seconds than the password reset tokens are valid. I failed to notice that those tokens were immediately destroyed and concluded I had still problems with my code as I saw 401's in return. Where in fact the 401 was caused by using a token that was already gone on the server.
#OverlappingElvis put me on the right track. Here's a more complete answer for others running into this. The loopback docs are quite limited in this area.
Make sure that you get both the user id and the token in your email and these get populated in the form.
From the form the following code does the job:
function resetPassword(id, token, password) {
$http.defaults.headers.common.authorization = token;
return User
.prototype$updateAttributes({id:id}, {
password: password
})
.$promise;
}
This was way more complicated than it ought to be. Here is my full solution:
1) I expose new method on server side which does the password updating from token.
Member.updatePasswordFromToken = (accessToken, __, newPassword, cb) => {
const buildError = (code, error) => {
const err = new Error(error);
err.statusCode = 400;
err.code = code;
return err;
};
if (!accessToken) {
cb(buildError('INVALID_TOKEN', 'token is null'));
return;
}
Member.findById(accessToken.userId, function (err, user) {
if (err) {
cb(buildError('INVALID_USER', err));
return;
};
user.updateAttribute('password', newPassword, function (err, user) {
if (err) {
cb(buildError('INVALID_OPERATION', err));
return;
}
// successful,
// notify that everything is OK!
cb(null, null);
});
});
}
and I also define the accessibility:
Member.remoteMethod('updatePasswordFromToken', {
isStatic: true,
accepts: [
{
arg: 'accessToken',
type: 'object',
http: function(ctx) {
return ctx.req.accessToken;
}
},
{arg: 'access_token', type: 'string', required: true, 'http': { source: 'query' }},
{arg: 'newPassword', type: 'string', required: true},
],
http: {path: '/update-password-from-token', verb: 'post'},
returns: {type: 'boolean', arg: 'passwordChanged'}
});
From the client-side, I just call it like this:
this.memberApi.updatePasswordFromToken(token, newPassword);

When attempting to remove a document in Meteor: "Remove failed: Internal server error"

I am attempting to remove a document by the ObjectId.
Template.conferenceItem.events({
'click #delete' : function () {
Conferences.remove(this._id);
}
});
In my console I get "remove failed: Internal server error". I get the same error if I run this in the console: Conferences.remove('tfD9KQsFp8LoftHgS'), where tfD9KQsFp8LoftHgS is an existing ObjectId.
Edit:
I have the following code in my collections folder in conferences.js:
Conferences = new Meteor.Collection('conferences');
Conferences.allow({
remove: function(userID, doc){
// only allow remove if you are logged in
return !! userId;
}
});
This is probably a Meteor allow error.
Collection.allow: Allow users to write directly to this collection from client code, subject to limitations you define.
You must authorize your untrusted client code to allow removes on the server somewhere in your code?
You probably want to do something like this, in your server code:
Conferences.allow({
remove: function (userId, doc) {
// check for proper permissions using passed arguments if any here
return true;
}
});
I believe you have a typo in the function associated with remove. The meteor doc example
remove: function (userId, doc) {
// can only remove your own documents
return doc.owner === userId;
}
so change userID to userId

Categories

Resources