This command meteor add patrickml:braintree was run in a Meteor app directory.
In the client.main.js:
A squiggly line Under the variable braintree and the IDE says "unresolved variable or type".
Template.payment.onRendered(function () {
Meteor.call('getClientToken', function (error, clientToken) {
if (error) {
console.log(error); //<---- always prints out
} else {
//vvvvvvvvv
braintree.setup(clientToken, "dropin", {
container: "payment-form", // Injecting into <div id="payment-form"></div>
onPaymentMethodReceived: function (response) {
var nonce = response.nonce;
console.log(nonce);
}
});
}
});
});
In the server code below, clientId is always undefined.
//server/main.js
'getClientToken': function (clientId) {
console.log(clientId); //<--------- undefined
let generateToken = Meteor.wrapAsync(gateway.clientToken.generate, gateway.clientToken);
let options = {};
if (clientId) {
options.clientId = clientId;
}
let response = generateToken(options);
return response.clientToken;
}
And the server console prints out:
Exception while invoking method 'getClientToken' authenticationError: Authentication Error
Any idea what is wrong and how to fix it? thx
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.
When you configure your gateway object, make sure that you use the API credentials documented in your Sandbox Control Panel. Here's how you can find them:
Log into the sandbox Control Panel
Navigate to Account > My user
Under API Keys, Tokenization Keys, Encryption Keys, click View Authorizations
If no API keys appear, click Generate New API Key
Click View under the Private Key column to see your public and private keys, merchant ID, and environment
When you have them, use them to configure your gateway object. For example:
var braintree = require("braintree");
var gateway = braintree.connect({
environment: braintree.Environment.Sandbox,
merchantId: "replaceWithYourMerchantId",
publicKey: "replaceWithYourPublicKey",
privateKey: "replaceWithYourPrivateKey"
});
Related
I am using SolidJS and building a SPA (no server rendering). For authentication, I use the #aws-amplify/core and #aws-amplify/auth packages. At the application root I call the Hub.listen function:
Hub.listen('auth', ({ payload }) => console.log(payload));
In the SignUp component I call Auth.federatedSignIn:
const SignUp = () => {
return (
<button onClick={() => {
Auth.federatedSignIn({ provider: CognitoHostedUIIdentityProvider.Google });
}}>
Sign up
</button>
);
}
I have configured the Amplify as such:
Amplify.configure({
Auth: {
region: import.meta.env.VITE_AWS_REGION,
userPoolId: import.meta.env.VITE_AWS_POOL_ID,
userPoolWebClientId: import.meta.env.VITE_AWS_POOL_CLIENT_ID,
oauth: {
domain: import.meta.env.VITE_AUTH_URL,
responseType: 'code',
redirectSignIn: location.origin + '/account/external',
redirectSignOut: location.origin + '/my',
},
},
});
When I click on the button I am redirected to the import.meta.env.VITE_AUTH_URL (simply outside of my app), choose an account, and then return back to the /account/external page. At that time I expect a consoled payload object in Web tools, but there is nothing. I get it when I call Auth.signOut(), so I assume that I configured Amplify correctly and Hub is subscribed to the auth channel.
My thoughts were that Hub cannot catch any events because after returning the application basically renders again in a new context and Hub simply isn't able to catch anything (events aren't sent from AWS?). I tried to declare the urlOpener function under the oauth property in the config and Google's sign page opened in a new tab, but even then I couldn't get any events in the preserved old page (from which I called Auth.federatedSignIn).
Questions:
How should I organize the code to get the signIn and signUp events?
Can I pass some data into the Auth.federatedSignIn to get it back in the Hub.listen, so I will be able to join the CognitoUser with the data that existed at the time of starting Sign in/Sign up (I want to add a new login type to existed user)?
Here is an example regarding the first question. Just check that your listener is set before you call the Auth.federatedSignIn() method.
export default class SignInService {
constructor(private landingFacade: LandingFacade) {
this.setupAuthListeners(); // Should be called at the top level.
}
private setupAuthListeners() {
Hub.listen('auth', ({ payload: { event, data } }) => {
switch (event) {
case 'signIn':
this.landingFacade.signInSuccess();
break;
case 'signIn_failure':
console.log('Sign in failure', data);
break;
case 'configured':
console.log('the Auth module is configured', data);
}
});
}
public async signIn(): Promise<void> {
await Auth.federatedSignIn();
}
}
For the second one: I'll use a local state and set/query the object you need.
I'm using accounts-ui and accounts-google in Meteor v1.4.1. I can't get the user.services object to appear scoped in the client code. In particular, I need google's profile picture.
I've configured the server-side code to authenticate with Google like so:
import { Meteor } from 'meteor/meteor';
import { ServiceConfiguration } from 'meteor/service-configuration';
const services = Meteor.settings.private.oauth;
for (let service of Object.keys(services)) {
ServiceConfiguration.configurations.upsert({
service
}, {
$set: {
clientId: services[service].app_id,
secret: services[service].secret,
loginStyle: "popup"
}
});
}
...and the client side code to configure permissions like so:
Accounts.ui.config({
requestPermissions: {
google: ['email', 'profile']
},
forceApprovalPrompt: {
google: true
},
passwordSignupFields: 'EMAIL_ONLY'
});
When users click the 'Sign-In with Google' button, a pop-up appears and they can authenticate. No prompt appears, however, despite forceApprovalPrompt being set to true for google.
The big issue is that when I execute this,
const user = Meteor.user();
console.log(user.services);
anywhere in client code, I do not see the expected user services information. I check my database and it is definitely there for the taking:
$ mongo localhost:27017
> db.users.find({})
> ... "services" : { "google" : { "accessToken" : ... } } ...
I'm curious what I'm missing? Should I explicitly define a publish function in order for user services data to exist in the client?
The services property is intentionally hidden on the client side for security reasons. There are a couple of approaches here :
Suggestions
My preferred one would be to expose a meteor method to bring you the
public keys and avatars you might need in the few places you'd need
them.
On a successful login, you could record the data you need somewhere in the user object, but outside of the services property.
As you said, you could make a new publication which explicitly specifies which fields to retrieve and which ones to hide. You have to be careful what you publish, though.
Code Examples
Meteor methods:
// server
Meteor.methods({
getProfilePicture() {
const services = Meteor.user().services;
// replace with actual profile picture property
return services.google && services.google.profilePicture;
}
});
// client
Meteor.call('getProfilePicture', (err, profilePicture) => {
console.log('profile picture url', profilePicture);
});
Update on successful user creation (you might want to have a login hook as well to reflect any avatar/picture changes in google):
// Configure what happens with profile data on user creation
Accounts.onCreateUser((options, user) => {
if (!('profile' in options)) { options.profile = {}; }
if (!('providers' in options.profile)) { options.profile.providers = {}; }
// Define additional specific profile options here
if (user.services.google) {
options.profile.providers.google = {
picture: user.services.google.picture
}
}
user.profile = options.profile;
return user;
});
Publish only select data...
// Server
Meteor.publish('userData', function () {
if (this.userId) {
return Meteor.users.find({ _id: this.userId }, {
fields: { other: 1, things: 1 }
});
} else {
this.ready();
}
});
// Client
Meteor.subscribe('userData');
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.
I am building a realtime game with Meteor streams. I need to update only one client - send a room ID from server. Users are not logged in so Meteor.userId() is null and therefore I can't use this: http://arunoda.github.io/meteor-streams/communication-patterns.html#streaming_private_page
There is only one URL (homepage) where all things happen. So I don't use any URL parameters for room. Everything is on the server.
I have tried to use Meteor.uuid() instead of Meteor.userId() but uuid is changed after each emit (which is strange).
In socket.io I would do this:
//clients is an array of connected socket ids
var clientIndex = clients.indexOf(socket.id);
io.sockets.socket(clients[clientIndex]).emit('message', 'hi client');
Is there any way to do this in Meteor streams or Meteor itself?
Well, this can be easily done if you decided to use database, but I guess it is not the best option if you have a large number of clients.
So another way to achieve this - without database - is to make a good use of the Meteor's publish/subscribe mechanism. Basically the way it could work is the following:
1. client asks server for a communication token (use Meteor.methods)
2. client subscribes to some (abstract) data set using that token
3. server publishes the required data based on the received token
So you will need to define a method - say getToken - on the server that generates tokens for new users (since you don't want to use accounts). This could be something more or less like this:
var clients = {}
Meteor.methods({
getToken: function () {
var token;
do {
token = Random.id();
} while (clients[token]);
clients[token] = {
dependency: new Deps.Dependency(),
messages: [],
};
return token;
},
});
A new client will need to ask for token and subscribe to the data stream:
Meteor.startup(function () {
Meteor.call('getToken', function (error, myToken) {
// possibly use local storage to save the token for further use
if (!error) {
Meteor.subscribe('messages', myToken);
}
});
});
On the server you will need to define a custom publish method:
Meteor.publish('messages', function (token) {
var self = this;
if (!clients[token]) {
throw new Meteor.Error(403, 'Access deniend.');
}
send(token, 'hello my new client');
var handle = Deps.autorun(function () {
clients[token].dependency.depend();
while (clients[token].messages.length) {
self.added('messages', Random.id(), {
message: clients[token].messages.shift()
});
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
and the send function could defined as follows:
var send = function (token, message) {
if (clients[token]) {
clients[token].messages.push(message);
clients[token].dependency.changed();
}
}
That's a method I would use. Please check if it works for you.
I think using Meteor.onConnection() like a login would enable you to do what you want pretty easily in a publish function.
Something like this:
Messages = new Meteor.Collection( 'messages' );
if ( Meteor.isServer ){
var Connections = new Meteor.Collection( 'connections' );
Meteor.onConnection( function( connection ){
var connectionMongoId = Connections.insert( connection );
//example Message
Message.insert( {connectionId: connection.id, msg: "Welcome"});
//remove users when they disconnect
connection.onClose = function(){
Connections.remove( connectionMongoId );
};
});
Meteor.publish( 'messages', function(){
var self = this;
var connectionId = self.connection.id;
return Messages.find( {connectionId: connectionId});
});
}
if ( Meteor.isClient ){
Meteor.subscribe('messages');
Template.myTemplate.messages = function(){
//show all user messages in template
return Messages.find();
};
}
I have used database backed collections here since they are the default but the database is not necessary. Making Messages a collection makes the reactive publishing easy whenever a new message is inserted.
One way that this is different from streams is that all the messages sent to all clients will end up being kept in server memory as it tries to keeps track of all data sent. If that is really undesirable then you could use a Meteor.method so send data instead and just use publish to notify a user a new message is available so call the method and get it.
Anyway this is how I would start.
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.