In the client-side of a METEORJS application, i have a controller that display some users.
I have a problem with Meteor.user() function, the error is : Meteor.user(...) is undefined.
Here is my code :
this.AdminUsersController = RouteController.extend({
template: "Admin",
yieldTemplates: {
'AdminUsers': { to: 'AdminSubcontent'}
},
onBeforeAction: function() {
var permissions = Meteor.user().profile.permissions;
if (permissions && permissions.indexOf('Users') != -1)
this.next();
else this.redirect('/admin/dashboard');
},
action: function() {
if(this.isReady()) { this.render(); } else { this.render("Admin"); this.render("loading", { to: "AdminSubcontent" });}
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
Meteor.subscribe("users")
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
var data = {
params: this.params || {},
users: Users.find({labo_id: Meteor.user().profile.labo_id}, {sort: {createdAt:-1}})
};
return data;
},
onAfterAction: function() {
}});
It's in the data function.
I try to retrieve all users that are connected to the logged in user and got the same labo_id field...
I don't know why it give me that, because in the onBeforeAction function, i can access to Meteor.user(), and specially his profile...
Someone know what can i do to make it run ?
Thanks for your future answers :)
This is a timing issue. Meteor.user() does not necessarily return data, if it hasn't been loaded yet. Meteor.userId() however will return the _id of the user record (if they are logged in). If you can change your query to rely on that _id, then it can work.
Depending on which router you are using, you can add a resolve: entry to ensure that the route waits for the user record to be loaded before activating it, and then your query will work.
Related
I am really struggling with waiting on a subscription to load for a specific route before returning the data to the template. I can see on from the publish on the server that a document is found, but on the client there is no document.
If I do a find().count() on the publish, it shows 1 document found, which is correct, but when I do the count on the subscription, it shows 0 documents.
I have tried a number of different methods, like using subscriptions:function() instead of waitOn:function(), but nothing works.
Collections.js lib:
SinglePackage = new Mongo.Collection("SinglePackage");
SinglePackage.allow({
insert: function(){
return true;
},
update: function(){
return true;
},
remove: function(){
return true;
}
});
Publications.js server:
Meteor.publish("SinglePackage", function(pack_id) {
return Packages.find({shortId: pack_id});
});
Iron Router:
Router.route('/package/:id', {
name: 'package.show',
template: 'Package_page',
layoutTemplate: 'Landing_layout',
waitOn: function() {
return Meteor.subscribe('SinglePackage', this.params.id);
},
data: function() {
return SinglePackage.find();
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Am I doing something very wrong, or is this just a complicated thing to achieve? One would think that waitOn would make the rest of the function wait until the subscription is ready.
Any help would be highly appreciated.
It appears that the data function is running before the subscription is ready. Even if the data function did run after the subscription was ready, it wouldn't be a reactive data source rendering the pub/sub here pointless. Here's a great article on reactive data sources.
Referring to the example from the Iron Router Docs for subscriptions, you would do something like this:
Router.route('/package/:id', {
subscriptions: function() {
// returning a subscription handle or an array of subscription handles
// adds them to the wait list.
return Meteor.subscribe('SinglePackage', this.params.id);
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
Then in your template.js:
Template.Package_page.helpers({
singlePackage() {
// This is now a reactive data source and will automatically update whenever SinglePackage changes in Mongo.
return Package.find().fetch();
}
});
In your template.html you can now use singlePackage:
<template name="Package_page">
{#with singlePackage} <!-- Use #each if you're singlePackage is an array -->
ID: {_id}
{/with}
</template>
I'm enjoying working with Meteor and trying out new things, but I often try to keep security in mind. So while I'm building out a prototype app, I'm trying to find the best practices for keeping the app secure. One thing I keep coming across is restricting a user based on either a roll, or whether or not they're logged in. Here are two examples of issues I'm having.
// First example, trying to only fire an event if the user is an admin
// This is using the alaning:roles package
Template.homeIndex.events({
"click .someclass": function(event) {
if (Roles.userIsInRole(Meteor.user(), 'admin', 'admin-group') {
// Do something only if an admin in admin-group
}
});
My problem with the above is I can override this by typing:
Roles.userIsInRole = function() { return true; } in this console. Ouch.
The second example is using Iron Router. Here I want to allow a user to the "/chat" route only if they're logged in.
Router.route("/chat", {
name: 'chatHome',
onBeforeAction: function() {
// Not secure! Meteor.user = function() { return true; } in the console.
if (!Meteor.user()) {
return this.redirect('homeIndex');
} else {
this.next();
}
},
waitOn: function () {
if (!!Meteor.user()) {
return Meteor.subscribe("messages");
}
},
data: function () {
return {
chatActive: true
}
}
});
Again I run into the same problem. Meteor.user = function() { return true; } in this console blows this pattern up. The only way around this I have found thus far is using a Meteor.method call, which seems improper, as they are stubs that require callbacks.
What is the proper way to address this issue?
Edit:
Using a Meteor.call callback doesn't work for me since it's calling for a response asynchronously. It's moving out of the hook before it can handle the response.
onBeforeAction: function() {
var self = this;
Meteor.call('someBooleanFunc', function(err, res) {
if (!res) {
return self.redirect('homeIndex');
} else {
self.next();
}
})
},
I guess you should try adding a check in the publish method in server.
Something like this:
Meteor.publish('messages') {
if (Roles.userIsInRole(this.userId, 'admin', 'admin-group')) {
return Meteor.messages.find();
}
else {
// user not authorized. do not publish messages
this.stop();
return;
}
});
You may do a similar check in your call methods in server.
I'm trying to create a page for users so that when they are logged in they can see the content they've uploaded onto the site. I can't seem to get the page to render. I've searched all over, but can't seem to find anything. Any ideas of where I'm going wrong?
from my publication.js file
Meteor.publish('myTickets', function() {
var currentUserId = this.userId;
return Tickets.find({
createdBy: currentUserId
})
});
from the router.js
Router.route('/users/:_id', {
name: 'userPage',
waitOn: function() {
return Meteor.subscribe('myTickets');
},
data: function() {
return Tickets.find({
userId: this.userId
})
}
});
userpage.js
Template.userPage.helpers({
tickets: function() {
var currentUserId = Meteor.userId();
return Tickets.find({
createdBy: currentUserId
}, {
sort: {
createdAt: -1
}
});
}
});
I asked you in a comment what you exactly wanted to do (you have a route with a parameter, but you are never using it), but anyway, I think I got your problem;
You are using this.userId to get your current user ID in your router, but your should use Meteor.userId() instead; this.userId can only be used in publish methods.
I'm getting an object not found error when I try and lookup the owner of the objects
i'm trying to render. I'm looping through a collection of video clips, that can be updated or administered by users. The code works fine when I'm logged in, but when I try to use this and I'm logged out, I get "Exception in queued task: TypeError: Cannot read property '_id' of undefined at Object.Template.video_info.creatorName "
I've tried to debug this by doing this:
console.log(this.owner);
var owner = Meteor.users.findOne(this.owner);
console.log(owner);
When I check the console log, I can see that the correct userid is being found, and when i manually run Meteor.users.findOne with this id I get a user object returned. Is there something strange about the timings in Meteor that is preventing this?
UPDATE: If I add a try...catch to the template creatorname function then 2 errors get logged but the template still renders... ??? Seems like this template is being called twice, one when it's not ready, and again once it is. Why would that be.
Example of the try...catch block:
Template.video_info.creatorName = function () {
try{
var owner = Meteor.users.findOne(this.owner);
if (owner._id === Meteor.userId())
return "me";
return displayName(owner);
} catch (e){
console.log(e);
}
};
ORIGINAL BROKEN CODE BELOW THIS POINT
This is in my HTML:
<body>
<div>
{{> video_list}}
</div>
</body>
<template name="video_list">
<h1>Video List</h1>
{{#each videos}}
<ul>
{{> video_info}}
</ul>
{{else}}
No videos yet.
{{/each}}
<div class="footer">
<button>Like!</button>
</div>
</template>
<template name="video_info">
<li class="video-list {{maybe_selected}}">
<img src="{{image}}" />
<div>
<h3>{{title}}</h3>
<p>{{description}}</p>
<h4>{{creatorName}}</h4>
</div>
</li>
</template>
This is in my client.js
Meteor.subscribe("videos");
if (Meteor.isClient) {
Template.video_list.videos = function() {
return Videos.find({}, {sort: {title: 1}});
};
Template.video_list.events = {
'click button': function(){
Videos.update(Session.get('session_video'),{$inc: {likes: 1}});
}
}
Template.video_info.maybe_selected = function() {
return Session.equals('session_video', this._id) ? "selected" : "";
}
Template.video_info.events = {
'click': function(){
Session.set('session_video', this._id);
}
}
Template.video_info.creatorName = function () {
var owner = Meteor.users.findOne(this.owner);
if (owner._id === Meteor.userId())
return "me";
return displayName(owner);
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
This is in my model.js
Videos = new Meteor.Collection("videos");
Videos.allow({
insert: function (userId, video) {
return false; // no cowboy inserts -- use createParty method
},
update: function (userId, video, fields, modifier) {
if (userId !== video.owner)
return false; // not the owner
var allowed = ["title", "description", "videoid", "image", "start"];
if (_.difference(fields, allowed).length)
return false; // tried to write to forbidden field
// A good improvement would be to validate the type of the new
// value of the field (and if a string, the length.) In the
// future Meteor will have a schema system to makes that easier.
return true;
},
remove: function (userId, video) {
// You can only remove parties that you created and nobody is going to.
return video.owner === userId; //&& attending(video) === 0;
}
});
var NonEmptyString = Match.Where(function (x) {
check(x, String);
return x.length !== 0;
});
var NonEmptyNumber = Match.Where(function (x) {
check(x, Number);
return x.length !== 0;
});
createVideo = function (options) {
var id = Random.id();
Meteor.call('createVideo', _.extend({ _id: id }, options));
return id;
};
Meteor.methods({
// options should include: title, description, x, y, public
createVideo: function (options) {
check(options, {
title: NonEmptyString,
description: NonEmptyString,
videoid: NonEmptyString,
image:NonEmptyString,
start: NonEmptyNumber,
_id: Match.Optional(NonEmptyString)
});
if (options.title.length > 100)
throw new Meteor.Error(413, "Title too long");
if (options.description.length > 1000)
throw new Meteor.Error(413, "Description too long");
if (! this.userId)
throw new Meteor.Error(403, "You must be logged in");
var id = options._id || Random.id();
Videos.insert({
_id: id,
owner: this.userId,
videoid: options.videoid,
image: options.image,
start: options.start,
title: options.title,
description: options.description,
public: !! options.public,
invited: [],
rsvps: []
});
return id;
},
});
///////////////////////////////////////////////////////////////////////////////
// Users
displayName = function (user) {
if (user.profile && user.profile.name)
return user.profile.name;
return user.emails[0].address;
};
var contactEmail = function (user) {
if (user.emails && user.emails.length)
return user.emails[0].address;
if (user.services && user.services.facebook && user.services.facebook.email)
return user.services.facebook.email;
return null;
};
I think I've found the solution to this one. After reading about the caching works in Meteor, I've discovered the subscription model and how this relates to meteors minimongo http://docs.meteor.com/#dataandsecurity. The reason this was failing then succeeding was that on the first load the data is still being cached in minimongo. I'm currently checking against Accounts login Services Configured to check if the user data has been loaded. I'm currently using this because I can't find a way to subscribe to the Metor users service, but my guess is that the Accounts login service would rely on the Metor users collection. My current solution looks like this:
if(Accounts.loginServicesConfigured()){
var owner = Meteor.users.findOne(this.owner);
if (owner._id === Meteor.userId())
return "me";
return displayName(owner);
}
Currently this appears to be working correctly. I'm still delving into how to subscribe to this users service.Couple of really userful resferences I found while searching for a solution for this
https://github.com/oortcloud/unofficial-meteor-faq
http://psychopyko.com/cool-stuff/meteor-6-simple-tips/
https://groups.google.com/forum/#!topic/meteor-talk/QKXe7qfBfqg
The app might not be publishing the user id to the client when you are logged out. You can try calling the find method on the server and return the user. Or use a different key for querying/
I am very new to ember and trying to implement authentication via facebook
I am using ember-facebook.js library to connect with facebook. Once the authentication is successful, I want to transition to some other route e.g. '/index'. This library creates a App.FBUser object in mixin which is populated from the facebook response. The blog say following:
Whenever the user changes (login, logout, app authorization, etc) the method updateFBUser is called, updating the App.FBUser object on your application. You can do whatever you want with this binding, observe it, put it in the DOM, whatever.
Ember.Facebook = Ember.Mixin.create({
FBUser: void 0,
appId: void 0,
fetchPicture: true,
init: function() {
this._super();
return window.FBApp = this;
},
appIdChanged: (function() {
var _this = this;
this.removeObserver('appId');
window.fbAsyncInit = function() {
return _this.fbAsyncInit();
};
return $(function() {
var js;
js = document.createElement('script');
$(js).attr({
id: 'facebook-jssdk',
async: true,
src: "//connect.facebook.net/en_US/all.js"
});
return $('head').append(js);
});
}).observes('appId'),
fbAsyncInit: function() {
var _this = this;
FB.init({
appId: this.get('appId'),
status: true,
cookie: true,
xfbml: true
});
this.set('FBloading', true);
FB.Event.subscribe('auth.authResponseChange', function(response) {
return _this.updateFBUser(response);
});
return FB.getLoginStatus(function(response) {
return _this.updateFBUser(response);
});
},
updateFBUser: function(response) {
console.log("Facebook.updateFBUser: Start");
var _this = this;
if (response.status === 'connected') {
//console.log(_this);
return FB.api('/me', function(user) {
var FBUser;
FBUser = user;
FBUser.accessToken = response.authResponse.accessToken;
if (_this.get('fetchPicture')) {
return FB.api('/me/picture', function(path) {
FBUser.picture = path;
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
});
} else {
_this.set('FBUser', FBUser);
return _this.set('FBloading', false);
}
});
} else {
this.set('FBUser', false);
return this.set('FBloading', false);
}
}//updateFBUser
});
Update :
Adding following observer in my LoginController, I am able to capture the App.FBUser update event(it is update after getting response from FB; as indicated by the blog).
From this observer method, when I try to 'transitionTo' my index route I get following error
Uncaught TypeError: Object data-size has no method 'transitionTo'. Following is the code
App.LoginController = Ember.Controller.extend({
onSuccess: (function(){
var self = this;
/*
//tried all these method to redirect but error is the same
var attemptedTransition = this.get('attemptedTransition');
attemptedTransition.retry();
*/
/*
//tried all these method to redirect but error is the same
var router = this.get('target.router');
router.transitionTo('index');
*/
//tried all these method to redirect but error is the same
this.transitionToRoute('index');
}).observes('App.FBUser')
});
Index Route
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition){
var self = this;
if(!App.FBUser){
self.redirectToLogin(transition);
}
},
redirectToLogin: function(transition){
var loginController = this.controllerFor('login');
loginController.set('attemptedTransition', transition);
this.transitionTo('login');
}
});
I am not able to get my head around it.
Any help is greatly appreciated. Thanks
How can I access this object in my Route.beforeModel() hook.
Depending on what route's beforModel hook you are talking about, this is how you could do it:
App.SomeRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!Ember.isNone(App.FBUser)) {
// calling 'transitionTo' aborts the transition, redirects to 'index'
this.transitionTo('index');
}
}
});
Update in response to your last comment
The addon you are using is slightly outdated and the proposed implementation method for the mixin in your application will not work with the current version of ember:
App = Ember.Application.create(Ember.Facebook)
App.set('appId', 'yourfacebookappid');
starting from version 1.0.0-rc3 of ember you should rather do it like this:
App = Ember.Application.creatWithMixins(Ember.Facebook);
App.set('appId', 'yourfacebookappid');
After that you should be able to have access to the App.FBUser object as mentioned above.
Update 2
If you want to be able to be notified when some events happend, like login, logout etc. you should (as the Author of the addon states on it's blog post) override the updateFBUser method and do in there your transitions.
Since the addon is trough the mixin available in our App namespace you should be able to do the following:
App = Ember.Application.creatWithMixins(Ember.Facebook, {
updateFBUser: function() {
this._super();
// we are calling super to let the addon
// do it's work but at the same time we get
// notified that something happened, so do at this
// point your transition
}
});
Hope it helps.
As per Issue 1 adding
attributeBindings: [],
to:
return Ember.FacebookView = Ember.View.extend({
solved the issue.