Meteor.userId() not available in ConnectHandlers - meteor

I am trying to create a file upload feature in Meteor where a logged in user is able to upload a file to the server under a directory named after their username. I have the basics working but when I take it a step further by checking the logged in user ID, things start breaking. Specifically:
WebApp.connectHandlers.use('/upload/', function(req, res) {
if (this.userId) {
// Do cool stuff.
} else {
res.writeHead(500, {"content-type":"text/html"});
res.end("this.userId = " + this.userId); // End the response.
}
});
Result:
this.userId = undefined
And...
WebApp.connectHandlers.use('/upload/', function(req, res) {
if (Meteor.userId()) {
// Do cool stuff.
} else {
res.writeHead(500, {"content-type":"text/html"});
res.end("Meteor.userId() = " + Meteor.userId()); // End the response.
}
});
Result:
Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
at Object.Meteor.userId (packages/accounts-base/accounts_server.js:19:1)
at Object.Package [as handle] (packages/cool_package/upload.js:34:1)
at next (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:190:15)
at Function.app.handle (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:198:3)
at Object.fn [as handle] (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:74:14)
at next (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:190:15)
at Object.WebAppInternals.staticFilesMiddleware (packages/webapp/webapp_server.js:331:1)
at packages/webapp/webapp_server.js:625:1
The code above is included in a Meteor package I'm developing. The package.js file specifies that the code should run on the server:
api.add_files("upload.js", "server");
So my questions are:
What is the correct way to check the logged in user ID and username?
Can this code be moved to an Iron Router route instead?

It looks like the line
WebApp.connectHandlers.use('/upload/', function(req, res) {
Is Express.js or similar code -- if so, you have broken out of the Meteor frameowrk providing your own REST services etc. If that is the case you also have to provide your own user management and authentication scheme for incoming REST calls, just as you would in any other bare-bones REST applications

Related

Insert new collection after function runs on server

When I return the geocode from googles API I'm trying to save it into my database. I've been trying to use the code below, to just insert a Test document with no luck. I think it has something to do with meteor being asynchronous. If I run the insert function before the googleMapsClient.geocode function it works fine. Can someone show me what I'm doing wrong.
Meteor.methods({
'myTestFunction'() {
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Test.insert({test: 'test name'});
}
});
}
});
I see now where you got the idea to run the NPM library on the client side, but this is not what you really want here. You should be getting some errors on the server side of your meteor instance when you run the initial piece of code you gave us here. The problem is that the google npm library runs in it's own thread, this prevents us from using Meteor's methods. The easiest thing you could do is wrap the function with Meteor.wrapAsync so it would look something like this.
try {
var wrappedGeocode = Meteor.wrapAsync(googleMapsClient.geocode);
var results = wrappedGeocode({ address : "testAddress" });
console.log("results ", results);
Test.insert({ test : results });
} catch (err) {
throw new Meteor.Error('error code', 'error message');
}
You can find more info by looking at this thread, there are others dealing with the same issue as well
You should run the googleMapsClient.geocode() function on the client side, and the Test.insert() function on the server side (via a method). Try this:
Server side
Meteor.methods({
'insertIntoTest'(json) {
Test.insert({results: json.results});
}
});
Client side
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Meteor.call('insertIntoTest', response.json);
}
});
Meteor Methods should be available on the both the server and client sides. Therefore make sure that your method is accessible by server; via proper importing on /server/main.js or proper folder structuring.
(If a method contains a secret logic run on the server, it should be isolated from the method runs on both server & client, though)

Meteor collection updated on server, not reflecting on client

I am trying to create a Meteor app that stores content in a Meteor collection to be passed between the server and the client to display a success message after an asynchronous api call through the twit package.
However, I am running into an issue where when I update the collection on the server and the updates are not reflected on the client. My code is as follows:
/lib
Alerts = new Meteor.Collection("alerts");
/client
Template.suggestionForm.events({
"submit form": function (e) {
return Meteor.call('submitMessage', message);
}
});
Meteor.subscribe('alerts');
Meteor.startup(function() {
Tracker.autorun(function() {
console.log(Alerts.find());
})
});
/server
Fiber = Npm.require('fibers')
Twit = new TwitMaker({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...'
});
Meteor.publish("alerts", function(){
Alerts.find();
});
Meteor.methods({
submitMessage: function(message) {
this.unblock();
Twit.post('statuses/update', { 'status': message }, function(err, data, response) {
Fiber(
Alerts.remove({});
Alerts.insert({response: err});
).run();
}));
}
});
When I submit the form the function calls just fine and updates the collection, however the Tracker.autorun() does not run. Any ideas why this is happening or how I can make the client listen for changes in collections would be super helpful. Thank you!
Remember to return the resulting cursor in the publish():
Meteor.publish("alerts", function(){
return Alerts.find();
});
Reference: http://docs.meteor.com/#/full/meteor_publish
Publish functions can return a Collection.Cursor, in which case Meteor will publish that cursor's documents to each subscribed client. You can also return an array of Collection.Cursors, in which case Meteor will publish all of the cursors.
and
Alternatively, a publish function can directly control its published record set by calling the functions added (to add a new document to the published record set), changed (to change or clear some fields on a document already in the published record set), and removed (to remove documents from the published record set). These methods are provided by this in your publish function.
If a publish function does not return a cursor or array of cursors, it is assumed to be using the low-level added/changed/removed interface, and it must also call ready once the initial record set is complete.

DDP.connect and Meteor.users

I am in the process to try to separate out the mobile from the desktop part of my application and thought I try DDP.connect as a means for the mobile application to share data with the desktop application.
My first hurdle is concerning Meteor internal collections and publications.
How am I supposed to authenticate users? I know I can call the login method to authenticate a user, but that still doesn't give me all the other nice reactive features I am used to with Meteor.users
Is this supposed to work, and if so what is the pattern.
Thanks
This is what integrated completely with a remote server (except code refresh, which forgets user session)
if (Meteor.isClient) {
Meteor.connection = DDP.connect('http://remote.site.com');
Accounts.connection = Meteor.connection;
Meteor.users = new Meteor.Collection('users');
SomeCollection = new Meteor.Collection('remote_collection');
Meteor.connection.subscribe('users');
Meteor.connection.subscribe('remote_collection');
// rest if the code just as always
}
This way you can use login directly (via accounts-base, accounts-passed, etc) and don't need to call a login method. Just add accounts-ui and include {{>loginButtons}} and it works
I had a similar problem. I wanted to have two different front-ends (although both are for desktop) to the same back-end, so they could use same database, publications, and methods. After looking through Meteor's source code (version 1.1.0.3) I've managed to do this as follows.
1) Start back-end server project.
$ meteor --port 3100
2) In front-end project(s), put following in server/server.config.js.
var backendUrl = process.env.BACKEND_URL;
if (backendUrl) {
__meteor_runtime_config__.BACKEND_URL = backendUrl;
__meteor_runtime_config__.ACCOUNTS_CONNECTION_URL = backendUrl;
console.log('config', __meteor_runtime_config__);
}
3) In front-end project(s), put following in client/lib/client.connection.js. APS is just a namespace for my application. Be sure to have this loaded before you use subscriptions or methods (that's why it's in lib folder).
if (typeof APS == 'undefined') APS = {};
var backendUrl = __meteor_runtime_config__.BACKEND_URL;
if (backendUrl) {
APS.backendConnection = DDP.connect(backendUrl);
Meteor.connection = APS.backendConnection;
_.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect', 'disconnect'], function(name) {
Meteor[name] = _.bind(Meteor.connection[name], Meteor.connection);
});
console.log('connected to backend', APS.backendConnection);
}
4) Start front-end server with BACKEND_URL environment variable pointing to your back-end server.
$ BACKEND_URL=http://192.168.33.10:3100 meteor
That's all. Refresh on client works OK. And we don't have to fiddle with Accounts.*.
UPDATE: Just found a problem with my solution. When calling server methods, this.userId is always null. This is because Meteor.connection and Accounts.connection were two separate connections, despite to the same BACKEND_URL. Upon authentication, user ID gets associated only with the latter. Fixed client.connection.js is as follows.
if (typeof APS == 'undefined') APS = {};
var backendUrl = __meteor_runtime_config__.BACKEND_URL;
if (backendUrl) {
APS.originalConnection = Meteor.connection;
// Accounts is already connected to our BACKEND_URL
APS.backendConnection = Accounts.connection;
// Reusing same (authenticated) connection for method calls and subscriptions
Meteor.connection = APS.backendConnection;
_.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect', 'disconnect'], function(name) {
Meteor[name] = _.bind(Meteor.connection[name], Meteor.connection);
});
console.log('Connected to backend', APS.backendConnection);
}
You can authenticate using code like this:
var connection = DDP.connect("<url>")
To authenticate
connection.call("login", {"password":"qwerty","user":{"username":"user_1"}});
to get the user, add this on the other server)
Meteor.methods({
whoami: function() { return Meteor.user() }
});
Then you can run further commands as if you were authenticated, like this to get who's logged in
console.log(connection.call("whoami");
User account creation/Authentication:
In client.js, create a DDP connection and set it to Accounts.connection
Accounts.connection = Meteor.remoteConnection;
Create an Accounts.users collection in the client and subscribe its contents from the external server as below.
Accounts.users = new Meteor.Collection('users', {connection: Meteor.remoteConnection});
Meteor.remoteConnection.subscribe('users');
Now call the login method required as below and set the token returned in the localStorage. This works for all the internal clicks and routing.
Meteor.loginWithPassword(login_email, login_password, function(err) {
submit_button.button("reset");
if (err)
{
console.log(err);
pageSession.set("errorMessage", err.message);
return false;
}else{
console.log("logged in as "+Meteor.userId());
var token = Accounts._storedLoginToken();
localStorage.setItem('_storedLoginToken', token);
}
});
The problem with the above code is that, the token is reset after every manual client refresh. The result object contains the below signed in information. We have to take the token and login with token for every external client refresh.
id:"5RigABaSzbARHv9ZD"
token:"MItg8P59gsl_T5OXtaWRSjUnETqzns0hGEV26xWYxj7"
tokenExpires:Thu Jul 20 2017 12:46:31 GMT+0530 (India Standard Time)
In client.js, start-up call the loginwithtoken function with the returned token as below, whenever there is no user available.
var user = Meteor.user();
var token = localStorage.getItem('_storedLoginToken');
if(user==null){
console.log("Token"+token +user);
if(token)
Meteor.loginWithToken(token, function(err){
// this is going to throw error if we logged out
if(!err) {
console.log('logged in !!!! ',token);
}
});
}
Meteor throws an error while logging in with the token,
Error logging in with token: Error: You've been logged out by the server. Please log in again. [403]
To overcome this issue, we have to write a tracker function to track the logged in session and login again if required. This is basically a hack suggested in meteor forums.
Tracker.autorun(function () { var user = Meteor.user(); var token
= localStorage.getItem('_storedLoginToken'); if(user==null){ console.log("Token"+token +user); if(token)
Meteor.loginWithToken(token, function(err){
// this is going to throw error if we logged out
if(!err) {
console.log('logged in !!!! ',token); }
}); } });
Reset the localStorage if user navigates to the login path. In Layout.js,
if(path=='/login')
localStorage.setItem('_storedLoginToken',null);

Meteor: Using iron router and custom authentication issue

I might have this pretty close but I'm lacking the knowledge to fix this last issue.
I wanted to use a custom authentication system instead of using accounts-ui so I could track some additional details about each user.
Everything worked great until I get to the resetPassword part. If a user submits their email address in the forgotPassword form, the email is received. But when you click the reset password link in the email it does not display the resetPassword template.
This is on SO here:
Meteor account email verify fails two ways
And the iron-router github issue tracker here (which has the most fixes though is more focused on the enrollmentemail than resetPassword which I'm assuming should be very similar):
Iron-router swallows Accounts.sendEnrollmentEmail
If I understand correctly from the iron-router issue tracker above, iron-router doesn't (or didn't and maybe still doesn't) support hashbang urls like that being sent in the reset password email. A URL like:
http://localhost:3000/#/reset-password/T4rPxcVNWKwBONHSRajSk7dNZvM_YRxTLyzxZVv5SuU
Meteor was then updated so that meteor accounts-base strips out everything after the # and stores them in variables in the Accounts namespace.
While I think I understand all of that, now the question is why I can't get the suggestions in the issue tracker to work for my reset password code. I'm using everything that is in the custom auth system by Julien Le Coupanec and then I've done the following from the issue tracker:
router.js
Router.map(function() {
this.route('invList', {path: '/'});
this.route('resetPassword', {
controller: 'AccountController',
path: '/reset-password/:token',
action: 'resetPassword'
});
});
AccountController = RouteController.extend({
resetPassword: function () {
Accounts.resetPassword(this.params.token, function () {
Router.go('/reset-password');
});
}
});
overrideaccounts.js in /server
(function () {
"use strict";
Accounts.urls.resetPassword = function (token) {
return Meteor.absoluteUrl('reset-password/' + token);
};
Accounts.urls.verifyEmail = function (token) {
return Meteor.absoluteUrl('verify-email/' + token);
};
Accounts.urls.enrollAccount = function (token) {
return Meteor.absoluteUrl('enroll-account/' + token);
};
})();
I'm wondering if the issues isn't related to either bad routing on my part (likely since I don't have my head wrapped around it well yet), if I put "server code" as is listed in the issue track in the right place, or if the session related code below is what is causing the resetPassword template to not display. Or something else that I'm missing of course.
main.js
//forgotPassword helper and event handler
Template.main.helpers({
showForgotPassword: function() {
return Session.get('showForgotPassword');
},
resetPassword: function(){
return Session.get('resetPassword');
}
});
After spending many hours on what I thought would be a really simple authentication system, I'm still at a loss. Appreciate any advice!
Don't struggle with hacking the hash and iron router, just back to Meteor original design flow.
When user click the verify link in email, it lead back to "/" (home), so just did this:
Template.home.created = function() {
if (Accounts._verifyEmailToken) {
Accounts.verifyEmail(Accounts._verifyEmailToken, function(err){
if (err != null) {
// handle the error
} else {
// do what you want, maybe redirec to some route show verify successful message
}
});
}
};
I did this and verify email right, same way worked for enroll, reset password...

How to protect a file directory and only allow authenticated users to access the files?

how do I restrict a folder, so only those who logged in into my Meteor app can download files?
I looked into multiple ways of doing this, but the main problem is that I can't access ( I get null.) with:
Meteor.user() or this.userId()
I tried:
__meteor_bootstrap__.app
.use(connect.query())
.use(function(req, res, next) {
Fiber(function () {
// USER HERE?
}).run();
});
or
__meteor_bootstrap__.app.stack.unshift({
route: "/protected/secret_document.doc", // only users can download this
handle: function(req, res) { Fiber(function() {
// CHECK USER HERE ?
// IF NOT LOGGED IN:
res.writeHead(403, {'Content-Type': 'text/html'});
var content = '<html><body>403 Forbidden</body></html>';
res.end(content, 'utf-8');
}).run() }
});
You could try storing the files in mongodb, which would mean that they would then be hooked into your collection system and be queryable on the client and server. Then, just publish the relevant data to the client for specific users, or use Meteor.methods to expose information that way.
Example:
Assuming files are stored in MongoDB, let's first publish them to the client:
Meteor.publish("files", function(folder) {
if (!this.userId) return;
// the userHasAccessToFolder method checks whether
// this user is allowed to see files in this folder
if (userHasAccessToFolder(this.userId, folder))
// if so, return the files for that folder
// (filter the results however you need to)
return Files.find({folder: folder});
});
Then on the client, we autosubscribe to the published channel so that whenever it changes, it gets refreshed:
Meteor.startup(function() {
Meteor.autosubscribe(function() {
// send the current folder to the server,
// which will return the files in the folder
// only if the current user is allowed to see it
Meteor.subscribe("files", Session.get("currentFolder"));
});
});
NB. I haven't tested above code so consider it pseudocode, but it should point you in the general direction for solving this problem. The hard part is storing the files in mongodb!
i'd be more concerned as to why Meteor.user() isn't working.
a few questions:
are you on meteor 0.5.0?
have you added accounts-base to your meteor project?
have you used one of meteor's login systems (accounts-password, accounts-facebook, etc)? (optional - accounts-ui for ease of use?)
have you still got autopublish on? or have you set up publishing / subscription properly?
Meteor.user() should be the current user, and Meteor.users should be a Meteor Collection of all previous logged in users.

Resources