Xolvio/meteor-cucumber: get this.userId when logged in - meteor

when using xxx shouldnt my fixture methods be aware of this.userId if I am logged in?
'userId': function () {
console.log("#################");
console.log("################# USERID: ", this.userId);
console.log("#################");
}
This always prints null when called from a step definition.

if you are calling this from a step definition using this.server.call you are making a server to server connection therefore you won't be logged in.
You need to use the client to make a call to be authenticated:
this.client.executeAsync(function() {
Meteor.call('userId') // this will be sent from the client
});

Related

Meteor: Access Functions from Client, Run on Server

I'm attempting to create a program where I use the Steam API. I want to be able to call the method to retrieve a user's info from the client, while keeping the actual code of the method secret from the client, since it contains an API Key. I tried defining the methods as global in a server folder, like this:
key = 'xxxxxxxxxxxxxxxx';
Meteor.steamFunctions = {
getName: function(user){
var userSteamId = user.profile.id;
Meteor.http.get('http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=' + key + '&steamids=' + userSteamId, function(error, resultJSON){
if (error){
return 'Error in Steam API';
} else {
var json = JSON.parse(resultJSON);
return json.personaname;
}
})
},
getPic: function(user){
var userSteamId = user.profile.id;
Meteor.http.get('http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=' + key + '&steamids=' + userSteamId, function(error, resultJSON){
if (error){
return 'Error in Steam API';
} else {
var json = JSON.parse(resultJSON);
return json.avatarfull;
}
})
}
}
I then try to call it like this in a client-side script:
if (Meteor.isClient){
Template.profile.helpers({
'getName': function(){
return Meteor.steamFunctions.getName(Meteor.user());
}
});
}
That, however, throws
Exception in template helper: TypeError: Cannot read property 'getName' of undefined
at Object.Template.profile.helpers.getName
How can I go about keeping the key secret to the user while still accessing the data?
Well, it is not quite as simple as adding a property to the Meteor global. Also, the remote method/call API to do this will involve asynchronous code.
Put the call to the API, with the secret API key, on the server side in code only visible on the server, e.g. the ./server subdirectory. Define a Meteor.method on the server side that can be called with Meteor.call on the client side.
In the server side Meteor method there are method security checks you can make to check for a logged in user or userid, and use this to decide whether to make the calls or ignore the request. You can throw a new Meteor.Error from the server side if a request is improper or there is an error, but these take resources to communicate.
The thing to understand about Meteor is that it has nothing magical to change how Javascript behaves on the browser or the server. The server is ultimately running nodejs. Objects defined on the server do not magically migrate to the client, or vice versa. If an object is defined on both, it is actually two separate pieces of code.
Therefore, in the client code, the Meteor.call to call the server-side code from the browser... is actually using an existing websocket or ajax API that is asynchronous in nature. This means that you will need to structure client code to provide callback functions on the browser to handle the asynchronously returned results of looking up Name or Pic. A direct return and imperative coding style is not possible.
Typically you'll want to update something on a user's screen as a result of information returned from a lookup. The usual Meteor coding is to have the callback function update a session global variable with Session.set(). Templates can reference these session variables, and through an implied or explicit Tracker.autorun(), the screen can be updated when the API returns the data.
You need to:
Move your steamFunctions into methods which are defined only on the server.
Properly invoke the methods from the client.
Below is some example code based on your original question. Please note this has not been tested and may require some tweaking.
server/methods.js
const KEY = 'xxxxxxxxxxxxxxxx';
const URL = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002';
Meteor.methods({
getName() {
const userSteamId = Meteor.user().profile.id;
const params = {
key: KEY,
steamids: userSteamId,
};
try {
var result = HTTP.get(URL, { params });
// Double check this - I have no idea what this API returns. The value
// you want may be nested under result, like result.data or something.
return JSON.parse(result).personaname;
} catch (e) {
// Something bad happened - maybe throw an error.
return false;
}
},
});
Note this method is defined on the server, so we don't expose our KEY to the client. Also note we are using the synchronous version of the HTTP api, so the value can be returned to the client.
client/lib/user.js
Tracker.autorun(function () {
user = Meteor.user();
if (user && user.profile && user.profile.id) {
Meteor.call('getName', (err, name) => {
Session.set('steamName', name);
});
} else {
Session.set('steamName', '');
}
});
When the user logs is or is updated, get the steam name and set a global session variable.
client/templates/profile.js
Template.profile.helpers({
getName: function () {
return Session.get('steamName');
},
});
Read the steamName session variable for use in your template.

Accounts.createUser() is returning undefined on client

I have a SignUp/SignIn Process on my meteor site. I have a form to register new users and a form to login users.
I init users on serverside via
Accounts.createUser({username: "myusername", password: "mypassword"});
and I can login into my dummyusers via my loginform on the client with
Meteor.loginWithPassword(username, password)
I dont know why but for some reason I canĀ“t create new users on clientside. If I call
Accounts.createUser({username: username, password: password});
it returns undefined (safari)
I checked the input strings (username, password) and its not generating the error even pure strings like
Accounts.createUser({username: "test", password: "pass"});
returns undefined on client console.
I published and subscribed my users via:
Server
Meteor.publish('users', function () {
console.log("Server Log: publishing all users");
return Meteor.users.find();
});
Client
Meteor.subscribe("users");
I also can count() users on the client console but I cant use Accounts.createUser() so the createUser() function is not even working in my console.
Why I cant use Accounts.createUser() on client/console?
Accounts.createUser is an asynchronous function on the client because it needs to query the server to know if it actually succeeded (the username or email could already be in use). The return value of an asynchronous function will always be undefined, so the result is expected.
If you start with an empty project and an empty database, you could do:
Accounts.createUser({username: 'dave', password: 'password'});
which will return undefined, however a subsequent call to Meteor.userId() should return a string.
In order to determine if the call succeeded, you will need to supply a callback:
Accounts.createUser({username: 'dave', password: 'password'}, function(err) {
if (err)
console.log(err);
else
console.log('success!');
});

Meteor / Jasmine / Velocity : how to test a server method requiring logged in user?

Using velocity/jasmine, I'm a bit stuck on how I should test a server-side method requiring that there be a currently logged-in user. Is there a way to make Meteor think a user is logged in via stub/fake ?
myServerSideModel.doThisServerSideThing = function(){
var user = Meteor.user();
if(!user) throw new Meteor.Error('403', 'not-autorized');
}
Jasmine.onTest(function () {
describe("doThisServerSideThing", function(){
it('should only work if user is logged in', function(){
// this only works on the client :(
Meteor.loginWithPassword('user','pwd', function(err){
expect(err).toBeUndefined();
});
});
});
});
What you could do is add users just to your test suite. You could do this by populating these users in a the server-side test script:
Something like:
Jasmine.onTest(function () {
Meteor.startup(function() {
if (!Meteor.users.findOne({username:'test-user'})) {
Accounts.createUser
username: 'test-user'
... etc
Then, a good strategy could be to use the beforeAll in your test to login (this is client side):
Jasmine.onTest(function() {
beforeAll(function(done) {
Meteor.loginWithPassword('test-user','pwd', done);
}
}
This is assuming your test isn't logged in yet. You can make this more fancy by checking for Meteor.user() and properly logging out in an afterAll, etc. Note how you can handily pass the done callback to many of the Accounts functions.
Essentially, you don't have to mock a user. Just make sure you have the right users, with the correct roles, available in the Velocity/Jasmine DB.
Lets say you have a server side method like this:
Meteor.methods({
serverMethod: function(){
// check if user logged in
if(!this.userId) throw new Meteor.Error('not-authenticated', 'You must be logged in to do this!')
// more stuff if user is logged in...
// ....
return 'some result';
}
});
You do not need to make a Meteor.loginWithPassword before executing the method. All you got to do is stub the this.userId by changing the this context of the method function call.
All defined meteor methods are available on the Meteor.methodMap object. So just call the function with a different this context
describe('Method: serverMethod', function(){
it('should error if not authenticated', function(){
var thisContext = {userId: null};
expect(Meteor.methodMap.serverMethod.call(thisContext).toThrow();
});
it('should return a result if authenticated', function(){
var thisContext = {userId: 1};
var result = Meteor.methodMap.serverMethod.call(thisContext);
expect(result).toEqual('some result');
});
});
EDIT: This solution was only tested on Meteor <= 1.0.x
What are you testing and why does it require a user to be logged in? Most of the methods I have that need a user object I pass the user object into. This allows me to call from a test without actually being logged in. So in the actual running of the code I would pass...
var r = myMethod(Meteor.user());
but when running from the test I would call like...
it('should be truthy', function () {
var r = myMethod({_id: '1', username: 'testUser', ...});
expect(r).toBeTruthy();
});
I think that Meteor.server.method_handlers["nameOfMyMethod"] allows you to call/apply a Meteor method and supply this as the first parameter at least in the current version (1.3.3)
this.userId = userId;
Meteor.server.method_handlers["cart/addToCart"].apply(this, arguments);

How to validate data in onCreateUser without losing the form data?

I'm writing an Appliction using Meteor. In this App I want to implement a server-side validation of the user data using Accounts.onCreateUser. There is some data passed which can only be verified on the server side.
At client side I call:
Template.register.events({
'submit form': function (e) {
e.preventDefault();
var attributes = {
username: $("#inputUsername").val(),
password: $("#inputPassword").val(),
confirmation: $("inputConfirmation").val(),
email: $("#inputEmail").val(),
...
};
Accounts.createUser(attributes, function(err){
if (err) {
throwError(err);
} else {
}
});
}
});
And on the server side:
Accounts.onCreateUser(function(options, user) {
if(!verifyData(options))
throw new Meteor.Error(403, "Wrong input");
return user;
});
After the server side verification fails, all form data is lost. What is the best way to keep the data?
I went ahead and reproduced your code on a Meteorpad and from what I can tell, the form data does still persist. You just need to access it via the attributes variable in the client-side.
There may be something I am missing, but i took what you posted above and put it in there.

How to get Meteor.user() to return on the server side?

in a file called /server/main.js (in order to ensure it is loaded last).
console.dir(Meteor.user());
Throws:
Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
So I try to use, in the same file:
console.dir(this.userId);
returns:
undefined
so, not giving up, I'm thinking "that's fine I'll just read from the cookies in the header":
var connect = Npm.require('connect');
__meteor_bootstrap__.app.use(connect.query()).use(function(req, res, next) {
console.dir(req.headers);
next();
});
.... returns nothing in terms of cookies except for 'cookie: 'uvf=1''
I'm not sure what to conclude - this is senseless as I can otherwise use the Meteor.Account framework just fine, read/set user properties, etc. The server is clearly aware of the user, and the current user clearly logged in.
I'm at a complete loss, any explanation / hint / pointer would be greatly appreciated.
You have to use Meteor.user() in a place where a request is made from the client (such as a Meteor.methods or a Meteor.publish).
It can't be placed anywhere else because meteor wouldn't know at that point in the code the user is supposed to bound to. If there is a place a request of some form is made from the client it can do this:
In a Meteor.publish:
Meteor.publish("collection", function() {
//returns undefined if not logged in so check if logged in first
if(this.userId) {
var user = Meteor.users.findOne(this.userId);
//var user is the same info as would be given in Meteor.user();
}
});
In a Meteor.methods:
Meteor.methods({
"test":function() {
//should print the user details if logged in, undefined otherwise.
console.log(Meteor.user());
}
}
To use Meteor.user() on a server side route:
You need Meteor router installed as a package via meteorite to allow you to have a server rendered page. (installed via mrt install router)
A server side route could then handle the web request:
Meteor.Router.add('/awebpage', function(id) {
var userId = this.params.userid;
var logintoken = this.params.logintoken;
var isdirect = this.param.direct;
var user = Meteor.users.findOne({_id:userId,"services.resume.loginTokens.token":logintoken});
if(user) {
//the user is successfully logged in
return "You, "+user.profile.name+", are logged in!";
}
else
{
if(isdirect) {
return "<h3>Loading</h3><script>window.location.href="/awebpage?direct=true&userid="+localStorage.getItem("Meteor.userId") +"&logintoken="+localStorage.getItem("Meteor.loginToken")</script>";
}
else
{
return "Not logged in"
}
}
});
So now when you visit /awebpage it would check whether the user is logged in and do the thing you want when they are logged in. Initially there is a redirect to relay the data from localstorage back to the URI.
You can expose the userId with Meteor.publish() to global scope. Then you can use it with Meteor.Router's server side routes.
--
/server/publications.js
CurrentUserId = null;
Meteor.publish(null, function() {
CurrentUserId = this.userId;
});
-
/server/routes.js
Meteor.Router.add('/upload', 'POST', function() {
if (!CurrentUserId)
return [403, 'Forbidden'];
// proceed with upload...
});
You can use the logged in callback
Accounts.onLogin((obj)->
user = ob.user
)
Accounts.onLogin(function(obj){
var user = ob.user
})
I recently wrote a blog post describing solution to this: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94.
You basically need to set up a server route using a https://atmospherejs.com/mhagmajer/server-router package and you can get current user with this.userId just like with Meteor methods.

Resources