Meteor Routes & Sessions -> How to coordinate - meteor

Meteor Question: what is the best way to implement real sessions?
I have a normal login page with statistics. No issues there.
Now, I want people to be able to check-in with specific urls; assume they later
hit a url like http://localhost:3000/checkin/area1
How can I coordinate that with their login?
I have a route for the checkin:
Router.route('/checkin/:_id', function () {
var req = this.request;
var res = this.response;
this.userId = 'steve'; //TODO: Need way to pull userId
if (!this.userId) {
res.end('Please login first');
} else {
//Verify correct area
//Verify that haven't check before
var lastCheckin = checkins.find({ user: this.userId, visited: this.params._id });
if (lastCheckin.count() == 0) {
//we haven't checkedin yet
checkins.insert({ user: this.userId, visited: this.params._id, createdAt: new Date() })
res.end('Checkin '+this.userId+' for '+this.params._id);
} else {
console.log('already checked in '+lastCheckin.createdAt);
res.end(this.userId+' already visited '+this.params._id);
}
}
}, {where: 'server'});
Things I've tried:
Persistent session: But that doesn't work because request is not coming from the main page, so no session variable to pull.
Pull cookie in Request (since Persistent session seems to have 1). The original login doesn't appear to have the request object, so I don't know where to get that.
Others (diff situations) have shown a Meteor.user() inside a route, but the software complains that it can only be used inside a method. What can be used inside a route?
What else can I try?
Thanks for you help.

Ok, I am not sure if this will help exactly but.
If you are on /myPage, then you have an event that runs:
Router.go('/yourPage');
Session.set('yourVariable', "yourValue");
You will be able to access the session variable (yourVariable) in /yourPage (and yourPage's code).

Related

Reactive subscription on user collection

I am trying to subscribe to profdle information of a different user than the logged in user, but I am facing issues as mentioned below
I am using angular-material and my code looks like below:
//publish user info upon following user
Meteor.publish("getUserInfo", function (userId) {
return (Meteor.users.find({_id: userId}, {fields: {profile: 1}}));
});
//subscribe
$scope.$meteorSubscribe("getUserInfo", askLikeController.$root.askLike[0].userId).then(function (subscriptionHandle) {
//Second element in the userProfile array will have the profile of required user
askLikeController.$root.usersProfile = $meteor.collection(Meteor.users, false);
});
Issues:
1. In the variable askLikeController.$root.usersProfile, I am getting both the loggedIn user and the desired userinfo having userId, I was expecting userinfo of only desired userId, why is this?
2. The subscription "getUserInfo" is not reactive, and even the subscription is lost after processing few blocks of code and then in the askLikeController.$root.usersProfile I am left with only user profile of logged in user, my guess is that my subscription is being replaced by inbuilt Meteor subscription for user.
How do I solve the issues?
Regards,
Chidan
First, make sure you have removed autopublish:
> meteor remove autopublish
To get reactivity in angular-meteor you need $meteor.autorun and $scope.getReactively. Here's an example:
// we need the requested id in a scope variable
// anytime the scope var changes, $scope.getReactively will
// ... react!
$scope.reqId = askLikeController.$root.askLike[0].userId;
$meteor.autorun($scope, function() {
$scope.$meteorSubscribe('getUserInfo', $scope.getReactively('reqId')));
}).then(function(){
askLikeController.$root.usersProfile = $meteor.collection(Meteor.users, false);
})
Getting only the user you selected: NOTICE- the logged in users is always published. So you need to specify which user you want to look at on the client side, just like you did on the publish method. So, in the subscribe method:
askLikeController.$root.usersProfile = $meteor.collection(function() {
return Meteor.Users.find({_id: $scope.getReactively('reqId')})
}, false);
At this point you might be better off changing it to an object rather than a collection:
askLikeController.$root.usersProfile = $scope.$meteorObject(Meteor.Users, {_id: $scope.getReactively('reqId')});

Refreshing page with meteor iron router

Here is the problem :
I am currently programming a chatapp based on what i found on github (https://github.com/sasikanth513/chatDemo)
I am refactoring it with iron-router.
When I go to the page (clicking on the link) I get an existing chatroom (that's what I want)
When I refresh the page (F5) I get a new created chatroom ! (what i want is getting the existing chatroom ...)
Here is the code in ironrouter :
Router.route('/chatroom', {
name: 'chatroom',
data: function() {
var currentId = Session.get('currentId'); //id of the other person
var res=ChatRooms.findOne({chatIds:{$all:[currentId,Meteor.userId()]}});
console.log(res);
if(res){
Session.set("roomid",res._id);
}
else{
var newRoom= ChatRooms.insert({chatIds:[currentId, Meteor.userId()],messages:[]});
Session.set('roomid',newRoom);
}
}
});
You can find my github repo with the whole project : https://github.com/balibou/textr
Thanx a lot !
Your route data depends on Session variables which will be erased after a refresh. You have a few options but the easiest would be to put the room id directly into the route: '/chatroom/:_id'. Then you can use this.params._id to fetch the appropriate ChatRooms document. Note that you could still keep '/chatroom' for cases where the room doesn't exist, however you'd need to redirect to '/chatroom/:_id' after the insert.
In meteor, the Session object is empty when the client starts, and loading/refreshing the page via HTTP "restarts" the client. To deal with this issue, you could persist the user's correspondent id in a Meteor.user attribute, so that you could easily do:
Router.route('/chatroom', {
name: 'chatroom',
data: function() {
var currentId = Meteor.user().profile.correspondentId;
var res=ChatRooms.findOne({chatIds:{$all:[currentId,Meteor.userId()]}});
console.log(res);
if(res){
Session.set("roomid",res._id);
}
else{
var newRoom= ChatRooms.insert({chatIds:[currentId, Meteor.userId()],messages:[]});
Session.set('roomid',newRoom);
}
}
});
This would work, with the proper permissions, but I would recommend not allowing the direct update of that value on the client (I don't know if you want users to be able to override their correspondentId). So if you want to secure this process, replace all that code with a server method call, where your updates are safer.
Another (and more common case) solution was given by David Weldon, if you don't mind having ids in your URL (and therefore not a single url)

How to prevent a client race condition between Meteor.userId() and subscription updates that depend on userId?

I am seeing a repeatable issue where a user authenticates ("logs in") with a Meteor server, and then a client subscription that depends on userId is updated (and dependent UI templates reactively update) before Meteor.userId() registers the successful login.
For example, in this code snippet, the assert will throw:
var coll = new Meteor.Collection("test");
if (Meteor.isServer) {
Meteor.publish('mineOrPublic', function () {
// Publish public records and those owned by subscribing user
return coll.find({owner: { $in: [ this.userId, null ]}});
});
}
if (Meteor.isClient) {
var sub = Meteor.subscribe('mineOrPublic');
var cursor = coll.find({});
cursor.observe({
added: function (doc) {
if (doc.owner) {
// This should always be true?!
assert(doc.owner === Meteor.userId());
}
}
});
}
Analogous to the added function above, if I write a template helper that checks Meteor.userId(), it will see a value of null, even when it is invoked with a data context of a document with an owner.
There is apparently a race condition between Meteor collection Pub/Sub and the Account userId update mechanisms. It seems to me that Meteor.userId() should always be updated before any subscriptions update based on a change in this.userId in a server publish function, but for some reason the opposite usually seems to be true (that is, the assert in the code above will usually throw).
The reason I care is because I have packages that depend on obtaining a valid Meteor Authentication token (using Accounts._storedLoginToken()) on the client for use in securing HTTP requests for files stored on the Meteor server. And the authentication token isn't correct until Meteor.userId() is. So the flow of events usually goes something like this:
User logs in
Publish function on server reruns based on the change in this.userId.
Client begins receiving new documents corresponding to the change in userId.
UI Template reactively updates to add DOM elements driven by new documents
Some of the DOM elements are <img> tags with src= values that depend on the data context.
HTTP requests are triggered and ultimately fail with 403 (forbidden) errors because the required authentication cookie hasn't been set yet.
Meteor.userId() finally updates on the client, and code reactively runs to set the authentication cookie
Helpers in the template that depend on a session variable set in the cookie update code are rerun, but the DOM doesn't change, because the URLs in the <img> tags don't change.
Because the DOM doesn't change, the tags don't retry their failed attempts to load the images.
Everything settles down, and the user has to manually reload the page to get their images to appear.
I've come up with two possible approaches to work around this issue:
In the template helper that generates the URL for the <img> tag, always append a dummy query string such as: "?time=" + new Date().getTime(). This causes the DOM to change every time the helper is called and fixes the problem, but it screws-up browser caching and if not coordinated will cause some assets to unnecessarily load multiple times, etc.
In every template helper that touches document data add a test of:
if (this.owner && this.owner !== Meteor.userId()) {
// Perhaps Meteor.loggingIn() could be used above?
// Invalid state, output placeholder
} else {
// Valid state, output proper value for template
}
I really hope someone knows of a less kludgy way to work around this. Alternatively, if consensus arises that this is a bug and Meteor's behavior is incorrect in this respect. I will happily file an issue on Github. I mostly really enjoy working with Meteor, but this is the kind of gritty annoyance that grinds in the gears of "it just works".
Thanks for any and all insights.
After trying lots of things, this variation on the example code in the OP seems to consistently solve the race condition, and I find this an acceptable resolution, unlike my initial attempted workarounds.
I still feel that this kind of logic should be unnecessary and welcome other approaches or opinions on whether Meteor's behavior in the OP sample code is correct or erroneous. If consensus emerges in the comments that Meteor's behavior is wrong, I will create an issue on Github for this.
Thanks for any additional feedback or alternative solutions.
var coll = new Meteor.Collection("test");
if (Meteor.isServer) {
Meteor.publish('mineOrPublic', function (clientUserId) {
if (this.userId === clientUserId) {
// Publish public records and those owned by subscribing user
return coll.find({owner: { $in: [ this.userId, null ]}});
} else {
// Don't return user owned docs unless client sub matches
return coll.find({owner: null});
}
});
}
if (Meteor.isClient) {
Deps.autorun(function () {
// Resubscribe anytime userId changes
var sub = Meteor.subscribe('mineOrPublic', Meteor.userId());
});
var cursor = coll.find({});
cursor.observe({
added: function (doc) {
if (doc.owner) {
// This should always be true?!
assert(doc.owner === Meteor.userId());
}
}
});
}
This code works by giving the server publish function the information it needs to recognize when it is running ahead of the client's own login state, thereby breaking the race condition.
I think this is something that Meteor should do automatically: clients should not see documents based on changes to this.userId in a publish function until after the client Meteor.userId() has been updated.
Do others agree?
I tried with this code that works on server too. In association with FileCollection package.
if (Meteor.isServer) {
CurrentUserId = null;
Meteor.publish(null, function() {
CurrentUserId = this.userId;
});
}
....
OrgFiles.allow({
read: function (userId, file) {
if (CurrentUserId !== file.metadata.owner) {
return false;
} else {
return true;
}
}
...

Prevent items in scope from writing to a different user's records

I was having success with using AngularFire in a scenario where there is one user on my application.
Now that I have authentication up and running, I'm noticing that assigning items to $scope.items is catastrophic when switching users, mainly due to the $scope failing to update correctly.
Reading directly from the docs...
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items');
angularFire(ref, $scope, 'items');
I need these to be only the items of the currently authorized user. So currently, I do this (if there's a better way, don't hesitate to tell me!)
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items/userId');
angularFire(ref, $scope, 'items');
I generate userId using auth.provider and auth.id, btw. Now that my items are namespaced in (let's say) user1
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items/[user1id]');
angularFire(ref, $scope, 'items');
I add items to $scope.items
$scope.create = function(item) {
$scope.items.push(item)
/* Pretend the user adds these from the interface.
[
{ name: 'eenie' },
{ name: 'meenie' },
{ name: 'miney' },
{ name: 'moe' }
]
*/
}
The problem
Now if I just log out and login as someone else, magically that user has eenie meenie miney and moe because $scope.items held the array between logout and login.
I tried to set $scope.items = [] on logout event, but that actually empties all the records. I'm pulling my hair out. This is 0.001% of what I need to do in my project and it's taking my whole weekend.
Update New method
$scope.create = function() {
$scope.selectedDevice = {
name: 'New Device',
userId: $scope.user.provider + $scope.user.id
};
return $scope.devices.push($scope.selectedDevice);
};
$scope.$on('angularFireAuth:login', function(evt, user) {
var promise, ref;
ref = new Firebase('https://mysite.firebaseio.com/users/' + (user.provider + user.id) + '/registry/');
promise = angularFire(ref, $scope, 'devices');
});
It now will accurately create items under the user's id. However, still, once you logout and log back in, those items do not get cleared from $scope.devices. Therefore, they just add themselves to data but under the newly logged in user.
Update
I did a lot of trial and error. I probably set $scope.devices to [] and moved around login events in every possible combination. What eventually worked was #hiattp's fiddle in the accepted answer.
This is a result of the implicit data binding remaining intact as you switch users. If the new user shows up and creates a new binding, it will consider the existing data to be local changes that it should assimilate (that's why you see the original user's items being added to the new user), but if you try to clear them first without releasing the binding then you are implicitly telling Firebase to delete that data from the original user's item list (also not what you want). So you need to release the data bindings when you detect the logout (or login) events as needed.
The callback in the angularFire promise provides an "unbind" method (see here and here):
var promise = angularFire(ref, $scope, 'items');
promise.then(function(unbind){
// Calling unbind() will disassociate $scope.items from Firebase
// and generally it's useful to add unbind to the $scope for future use.
});
You have a few idiosyncrasies in your code that are likely causing it not to work, and remember that unbind won't clear the local collection for you. But just so you have an idea of how it should work (and to prove it does work) here is a fiddle.
You need to unbind $scope.items on logout. The best way to do this will be to save the unbind function given to your promise in $scope:
var ref = new Firebase('https://<my-firebase>.firebaseio.com/items/[user1id]');
angularFire(ref, $scope, 'items').then(function(unbind) {
$scope.unbindItems = unbind;
});
$scope.$on('angularFireAuth:logout', function() {
$scope.unbindItems();
});

Meteor.js: Get anonymous visitors unique ID/ip/whatever?

Let's say I'm building an app using meteor.js where I just collect some simple form data from users. Maybe an answer to a simple question or something. They don't need to log in to submit data.
How can I protect my app from someone creating a js-loop in their Chrome Console that just inserts crap into my DB?
I can protect removal and updates by doing this:
Formanswers.allow({
insert: function () {
return true;
},
update: function () {
return false;
},
remove: function () {
return false;
},
});
And if the user was logged in (which as you remember is not the case in my app) I could timestamp each insert and check something like:
insert: function (userId, doc) {
if (userId && (Formanswers.findOnd({userid: userId, time: SOMETHING TIME SPECIFIC}).count() < 1)) return true;
},
So my question is: is there any other way of getting a unique userId-thing or IP-address or something for an anonymous (not logged in) user so I can do the above check on him as well?
Thanks!
You can use a meteorite package.
accounts-anonymous
https://github.com/tmeasday/meteor-accounts-anonymous
So you use
Meteor.loginAnonymously();
if the user visits your page for the first time, and use .allow to check what you need
To get the ip address, the observatory (https://github.com/jhoxray/observatory) project uses this:
in coffee:
Meteor.userIP = (uid)->
ret = {}
if uid?
s = ss for k, ss of Meteor.default_server.sessions when ss.userId is uid
if s
ret.forwardedFor = s.socket?.headers?['x-forwarded-for']
ret.remoteAddress = s.socket?.remoteAddress
ret
Which returns an object like { forwardedFor: '192.168.5.4', remoteAddress: '192.168.5.4' }
Use a session or localStorage key. When the visitor submits the form check if the key has been set, and if it has, reject the insert.
You can do something like this:
if (Meteor.isClient) {
Meteor.startup(function () {
Session.set('currentuser', 'something randomly generated by another function');
}
}
and check if the 'currentuser' already has inserted in your database.

Resources