Parse Server / JS SDK, error 206 when saving a user object - javascript

I am having trouble using the Parse Server JS SDK to edit and save a user.
I am signing in, logging in and retrieving the user just fine, I can call without exception user.set and add/edit any field I want, but when I try to save, even when using the masterKey, I get Error 206: Can t modify user <id>.
I also have tried to use save to direcly set the fields, same result.
A interesting thing is that in the DB, the User's Schema get updated with the new fields and types.
Here is my update function:
function login(user, callback) {
let username = user.email,
password = user.password;
Parse.User.logIn(username, password).then(
(user) => {
if(!user) {
callback('No user found');
} else {
callback(null, user);
}
},
(error) => {
callback(error.message, null);
}
);
}
function update(user, callback) {
login(user, (error, user) => {
if(error) {
callback('Can t find user');
} else {
console.log('save');
console.log('Session token: ' + user.getSessionToken());
console.log('Master key: ' + Parse.masterKey);
user.set('user', 'set');
user.save({key: 'test'}, {useMasterKey: true}).then(
(test) => {
console.log('OK - ' + test);
callback();
}, (err) => {
console.log('ERR - ' + require('util').inspect(err));
callback(error.message);
}
);
}
});
}
And a exemple of the error:
update
save
Session token: r:c29b35a48d144f146838638f6cbed091
Master key: <my master key>
ERR- ParseError { code: 206, message: 'cannot modify user NPubttVAYv' }
How can I save correctly my edited user?

I had the exact same problem when using Parse Server with migrated data from an existing app.
The app was created before March 2015 when the new Enhanced Sessions was introduced. The app was still using legacy session tokens and the migration to the new revocable sessions system was never made. Parse Server requires revocable sessions tokens and will fail when encountering legacy session tokens.
In the app settings panel, the Require revocable sessions setting was not enabled before the migration and users sessions were not migrated to the new system when switching to Parse Server. The result when trying to edit a user was a 400 Bad Request with the message cannot modify user xxxxx (Code: 206).
To fix the issue, I followed the Session Migration Tutorial provided by Parse which explain how to upgrade from legacy session tokens to revocable sessions. Multiple methods are described depending on your needs like enableRevocableSession() to enable these sessions on a mobile app, if you're only having a web app, you can enforce that any API requests with a legacy session token to return an invalid session token error, etc.
You should also check if you're handling invalid session token error correctly during the migration to prompt the user to login again and therefore obtain a new session token.

I had the same error and neither useMasterKey nor sessionToken worked for me either. :(
Here's my code:
console.log("### attempt 1 sessionToken: " + request.user.getSessionToken());
var p1 = plan.save();
var p2 = request.user.save(null, {sessionToken: request.user.getSessionToken()});
return Parse.Promise.when([p1, p2]).then(function(savedPlan) {
...
}
I see the matching session token in log output:
2016-08-21T00:19:03.318662+00:00 app[web.1]: ### attempt 1 sessionToken: r:506deaeecf8a0299c9a4678ccac47126
my user object has the correct ACL values:
"ACL":{"*":{"read":true},"PC7AuAVDLY":{"read":true,"write":true}}
I also see a bunch of beforeSave and afterSave logs with user being "undefined". not sure whether that's related.
beforeSave triggered for _User for user undefined:
I'm running latest parser-server version 2.2.18 on Heroku (tried it on AWS and results are the same)

function login(logInfo, callback) {
let username = logInfo.email,
password = logInfo.password;
Parse.User.logIn(username, password).then(
(user) => {
if(!user) {
callback('No user found');
} else {
callback(null, user);
}
},
(error) => {
callback(error.message, null);
}
);
}
function update(userInfo, data, callback) {
login(userInfo, (error, user) => {
if(error) {
callback('Can t find user');
} else {
getUpdatedData(user.get('data'), data, (error, updateData) => {
if(error) {
callback(error);
} else {
user.save({data: updateData}, /*{useMasterKey: true}*/ {sessionToken: user.get("sessionToken")}).then(
(test) => {
callback();
}, (err) => {
callback(error.message);
}
);
}
});
}
});
}
For some reason, retrying to use sessionToken worked.

This is not how asynchronous functions work in JavaScript. When createUser returns, the user has not yet been created. Calling user.save kicks off the save process, but it isn't finished until the success or error callback has been executed. You should have createUser take another callback as an argument, and call it from the user.save success callback.
Also, you can't create a user with save. You need to use Parse.User.signUp.
The function returns long before success or error is called.

Related

Make http cloud function executable only by the project owner

I am using http cloud function ( https://firebase.google.com/docs/functions/http-events ) to write documents to a firestore collection:
exports.hello = functions.https.onRequest(
(req: { query: { name: string } }, res: { send: (arg0: string) => void }) => {
console.log(req.query.name);
var name = req.query.name || 'unknown';
res.send('hello' + name);
admin
.firestore()
.collection('ulala')
.doc()
.set({ token: 'asd' }, { merge: true });
}
);
this is a test. The problem is that, once you deploy and get the link to the function, it is executable by everyone. I would like instead that only I (project owner) can use it . Is it possible to do this?
One possible solution is to restrict your HTTPS Cloud Function to only a specific "Admin" user of your app.
There is an official Cloud Function sample which shows how to restrict an HTTPS Function to only the Firebase users of the app/Firebase project: Authorized HTTPS Endpoint sample.
You need to adapt it to check if the user is the Admin user. For example by checking the userID in the try/catch block at line 60 of the index.js file (untested).
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
if (decodedToken.uid !== "<the admin user uid>") {
throw new Error("Wrong user");
} else {
req.user = decodedIdToken;
next();
}
return;
} catch (error) {
functions.logger.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
return;
}
The two drawbacks of this approach are:
Your Admin user needs to be declared as a Firebase project user in the Authentication service
You hard code the Admin userID in your Cloud Function (you could use the Google Cloud Secret Manager service to securely store it as a configuration value, see the doc).
IMPORTANT SIDE NOTE:
In your Cloud Function you call the send() method before the asynchronous work is complete:
res.send('hello' + name);
admin
.firestore()
.collection('ulala')
.doc()
.set({ token: 'asd' }, { merge: true });
By calling the send() method, you actually terminate the Cloud Function, indicating to the Cloud Functions instance running your function that it can shut down. Therefore in the majority of the cases the asynchronous set() operation will not be executed.
You need to do as follows:
admin
.firestore()
.collection('ulala')
.doc()
.set({ token: 'asd' }, { merge: true })
.then(() => {
res.send('hello' + name);
})
I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series as well as read this page of the documentation which explain this key point.

MS graph api get teams using javascript returns no results

i am using graph api javascript example from here https://learn.microsoft.com/en-us/graph/api/user-list-joinedteams?view=graph-rest-beta&tabs=javascript
and my code is like:
async function(req, res) {
if (!req.isAuthenticated()) {
// Redirect unauthenticated requests to home page
res.redirect('/')
} else {
let params = {
active: { calendar: true }
};
// Get the access token
var accessToken;
try {
accessToken = await tokens.getAccessToken(req);
console.log("access token is:", accessToken)
} catch (err) {
req.flash('error_msg', {
message: 'Could not get access token. Try signing out and signing in again.',
debug: JSON.stringify(err)
});
}
if (accessToken && accessToken.length > 0) {
try {
console.log("vik testing stuff12 for teams")
const user = await graph.getTeams(accessToken)
console.log("graph me:::", user)
} catch (err) {
req.flash('error_msg', {
message: 'Could not fetch events',
debug: JSON.stringify(err)
});
}
} else {
req.flash('error_msg', 'Could not get an access token');
}
res.render('calendar', params);
}
}
getTeams is
getTeams: async function(accessToken) {
const client = getAuthenticatedClient(accessToken);
const events = await client
.api('/me/joinedTeams')
.version('beta')
.get();
return events;
}
this prints no results and no error. if I replace 'me/joinedTeams' to just 'me' then it returns logged in user details.
You can got a response successfully, so it seems no error with your code as you said if you call https://graph.microsoft.com/v1.0/me you can get user information.
And I tried to call this API using my account(my account hasn't joined any Teams), and got response like below, so if you got the same response as mine, perhaps you need to check if you have joined any Teams:
On the other hand, following the document, this API needs several permissions. So please obtain your access token when debug and use JWT tool to decrypt it to check if the access token have enough scope.
And I used the same request and got Teams information after adding my account to a team.

After signing out, my firebase idToken is still valid?

I have the following code in my firebase front-end:
document.querySelector('#sign-out').addEventListener('click', () => {
firebase.auth().signOut().then(() => {
document.location = '/sign-in';
}, (error) => {
console.error('Sign Out Error', error);
});
})
This seems to be working. I see the network request succeed, and the redirect to /sign-in occurs.
However, when I then navigate to /chatroom, I am considered logged in. My python/flask backend has the following code on the endpoint:
#app.route("/chatroom")
def chatroom():
if 'idToken' in request.cookies:
id_token = request.cookies.get("idToken")
try:
decoded_token = auth.verify_id_token(id_token)
except:
print("INVALID TOKEN")
return redirect(url_for("sign_in"))
return render_template("chatroom.html")
else:
return redirect(url_for("sign_in"))
Rather than redirecting the user to /sign-in when I hit this path, Firebase is verifying that the token is valid and so I am allowed to proceed, even though I had logged out.
What am I missing?
Edit:
Changed code to this for debugging purposes:
firebase.auth().signOut().then(() => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log('still signed in');
} else {
console.log('signed out');
}
});
}, (error) => {
console.error('Sign Out Error', error);
});
It outputs signed out.....so I'm quite confused.
Signing out does not invalidate the token. It just causes the SDK to forget about it, so it doesn't get refreshed or passed along to other Firebase products automatically. The old token will be valid for up to 1 hour, until it needs to be refreshed. If not refreshed, then it will fail verification.
If you are saving the ID token in a cookie, you should also remove it from the cookie in order to effectively sign out, so it doesn't get passed along to your backend.

How to setup Twilio.Device with new token?

By default all tokens generated with the Twilio helper libraries expire after one hour. But you should configure this expiration to be as short as possible for your application.
I am trying to generate a new token each time a user attempts a new connection and try to setup Twilio device. But it creates new device each time. So all Twilio device get incoming call and i can see multiple notification for that. Multiple connections created an dmultiple dtmf sent. I want only one twilio device with fresh token.
Twilio.Device.destroy() method is there but it is not working. What are other option do I have?
How to release/destroy/stop/delete Twilio.Device?
After saving credentials globalTwilioSagaSetup() called and after 58min of that again token is generated and Twilio.Device setup is done.
function globalTwilioSagaSetup()
{
// Get Twilio credentials
// Get Twilio Token
// Setup Twilio Device
// For token re-generation before expire. 58min
setInterval(function(){globalTwilioSagaSetup();},3480000);
}
I've had similar issue although in current version of twilio lib (i.e. 1.2). The thing is that once setup is called ready event is fired but only after first call to the setup method. It means that even if one will initialize device with new token there will be problems with establishing new connection. Therefore calling Twilio.Device.destroy() then setup and then connect (via ready event) solved that issue for me. Here is an example:
srv.connectToTwilio = () => $q((resolve, reject) => {
var connection;
try {
connection = Twilio.Device.connect();
} catch (err) {
$log.debug('Device.connect(): throw', err);
}
if (connection) {
try {
connection.accept(() => {
$log.debug(`Twilio connection.accept.`);
resolve();
});
} catch (err) {
$log.debug('connection.accept(): throw', err);
}
} else {
reject(`Device.connect() did not return connection`);
}
});
srv.connect = (token) => {
return srv.setToken(token).then(() => srv.connectToTwilio());
};
srv.disconnect = () => {
shouldBeConnected = false;
try {
Twilio.Device.activeConnection().disconnect();
} catch (error) {
$log.debug(error);
} finally {
Twilio.Device.destroy();
}
$log.debug(`Twilio disconnect.`);
};

How to validate if its a login page using webdriverio

I am using Javascript, webdriverio (v2.1.2) to perform some data extraction from an internal site. The internal site is SSO enabled, so if I have been authenticated on another application, I need not login for this application (common in enterprise intranet applications).
I plan to achieve the below,
Create a client with required capabilities
Pass the required URL
For fun : Print the title of the page
Check if an element exist on the page. If yes, then it's a login page. If not, then it's not login page
login = function (username, password) {
if (!browserClientUtil) {
throw "Unable to load browserClientUtil.js";
}
browserClientUtil
.createClient()
.url(_Url)
.title(function (err, res) {
console.log('Title is: ' + res.value);
}) .isExisting('input#login_button.login_button', function (err, isExisting) {
browserClientUtil.getCurrentClient()
.setValue('input#USER.input', username)
.setValue('input#PASSWORD.input', password)
//.saveScreenshot('ultimatixLoginDetails.png')
.click('input#login_button.login_button')
.pause(100);
handlePostLogin();
});
};
Is this the best way to do? I tried to separate the code for verifying login page in a separate function, it didn't work as everything in webdriver happens as part of callback and I am not sure if I am doing it in a right way.
How do I return from a callback, that will in-turn be the final value returned by that function?
login = function (username, password) {
if (!browserClientUtil) {
throw "Unable to load browserClientUtil.js";
}
browserClientUtil
.createClient()
.url(_Url)
.title(function (err, res) {
console.log('Title is: ' + res.value);
});
if(isThisLoginPage()){
browserClientUtil.getCurrentClient()
.setValue('input#USER.input', username)
.setValue('input#PASSWORD.input', password)
//.saveScreenshot('ultimatixLoginDetails.png')
.click('input#login_button.login_button')
.pause(100);
handlePostLogin();
}
};
isThisLoginPage = function() {
var client = browserClientUtil.getCurrentClient();
if(!client) {
throw "Unable to get reference for current client, hence cannot validate if this is login page.";
}
client.isExisting('input#login_button.login_button', function (err, isExisting) {
if(isExisting) {
return true;
}
});
return false;
};
You can create your own workflow by creating own commands that wrap other ones. For example you can make an own command to login:
browserClientUtil.addCommand("login", function(url, user, pw, cb) {
this.url(url)
.setValue('#username', user)
.setValue('#password', pw)
.submitForm('#loginForm')
.call(cb);
});
This allows you to hide "complex" asynchronous webdriver actions behind a simple function. It is easy to create an powerful toolchain. At the end your test script looks like:
browserClientUtil
.login("http://example.com/login", "john.doe", "testpass")
.getTitle(function(err, title) {
console.log(title);
})
// ...
Cheers

Categories