How do request to external resource in Meteor? - meteor

I need get data from external service. It has API. This is example:
http://portal.example.com/portal.api?l=username&p=keyphrase&act=brand_by_nr&nr=kl2&alt
Parameters are:
"l" - login, "p" - password, "act" - function to execute, "nr" - part number
I try connect by Meteor http.This is my server code:
var sources = {
mskv: {
url: "http://portal.example.com/portal.api",
auth: { l: "mylogin", p: "cBKoTyalCgbOQb37NG6sbb0qv2I0Q4PmWRJIJMWpOhCPFombqeDv7fBhdkjsdhkjah" },
params: { act: "brand_by_nr", nr: null }
}
};
Meteor.methods({
doRequest: function(partNumber) {
for (var key in sources) {
var url = sources[key].url;
var authData = sources[key].auth;
var paramsData = sources[key].params;
paramsData.nr = partNumber;
HTTP.call("POST", url, { auth: authData, params: paramsData }, function(err, res) {
if (err) {
throw new Meteor.Error("not-response", "Remote server not responding");
}
return res;
});
}
}
});
This is my client code:
Template.search.events({
"click .search": function(event) {
var partNumber = document.getElementsByClassName("input")[0].value;
Meteor.call("doRequest", partNumber, function(err, res) {
if(err === "not-response") return;
console.log(res);
});
}
});
I have error:
> Exception while invoking method 'doRequest' TypeError: Object
> #<Object> has no method 'indexOf' I20150227-00:01:35.455(3)? at Object._call (packages/http/httpcall_server.js:42:1)
> I20150227-00:01:35.455(3)? at Object._.extend.wrapAsync [as call]
> (packages/meteor/helpers.js:118:1) I20150227-00:01:35.455(3)? at
> [object Object].Meteor.methods.doRequest (app/server/server.js:19:18)
Can you help me, where is my error?

Try
var paramsData = [sources[key].params];
I suspect it's looking for an array there.

In my case auth field is not correct. The true way is auth:"login: password", look as simply string. Second error - auth field is not need. For this service login and password send as parameters { params: {l:"login", p: "password", act: "brand_by_nr" ....} }

Related

S3 bucket with credentials error

I'm having trouble using the meteor slingshot component with the S3 with temporary AWS Credentials component. I keep getting the error Exception while invoking method 'slingshot/uploadRequest' InvalidClientTokenId: The security token included in the request is invalid.
Absolutely no idea what I'm doing wrong. If I use slingshot normally without credentials it works fine.
import { Meteor } from 'meteor/meteor';
import moment from 'moment';
const cryptoRandomString = require('crypto-random-string');
var AWS = require('aws-sdk');
var sts = new AWS.STS();
Slingshot.createDirective('UserProfileResumeUpload', Slingshot.S3Storage.TempCredentials, {
bucket: 'mybuckname', // change this to your s3's bucket name
region: 'ap-southeast-2',
acl: 'private',
temporaryCredentials: Meteor.wrapAsync(function (expire, callback) {
//AWS dictates that the minimum duration must be 900 seconds:
var duration = Math.max(Math.round(expire / 1000), 900);
sts.getSessionToken({
DurationSeconds: duration
}, function (error, result) {
callback(error, result && result.Credentials);
});
}),
authorize: function () {
//Deny uploads if user is not logged in.
if (!this.userId) {
const message = 'Please login before posting files';
throw new Meteor.Error('Login Required', message);
}
return true;
},
key: function () {
return 'mydirectory' + '/' + cryptoRandomString(10) + moment().valueOf();
}
});
Path: Settings.json
{
"AWSAccessKeyId": "myAWSKEYID",
"AWSSecretAccessKey": "MyAWSSeceretAccessKey"
}
I've done it in server side like this :
Slingshot.createDirective("UserProfileResumeUpload", Slingshot.S3Storage, {
AWSAccessKeyId: Meteor.settings.AWS.AccessKeyId,
AWSSecretAccessKey: Meteor.settings.AWS.SecretAccessKey,
bucket: 'mybuckname', // change this to your s3's bucket name
region: 'ap-southeast-2',
acl: 'private',
...
}
and in settings.json
{
"AWS": {
"AccessKeyId": "myAWSKEYID",
"SecretAccessKey": "MyAWSSeceretAccessKey"
}
}

Meteor HTTP.POST call on same machine (for testing)

I have created a server side route (using iron-router). Code is as follows :
Router.route( "/apiCall/:username", function(){
var id = this.params.username;
},{ where: "server" } )
.post( function(req, res) {
// If a POST request is made, create the user's profile.
//check for legit request
console.log('post detected')
var userId = Meteor.users.findOne({username : id})._id;
})
.delete( function() {
// If a DELETE request is made, delete the user's profile.
});
This app is running on port 3000 on my local. Now I have created another dummy app running on port 5000. Frrom the dummy app, I am firing a http.post request and then listening it on the app on 3000 port. I fire the http.post request via dummy app using the below code :
apiTest : function(){
console.log('apiTest called')
HTTP.post("http://192.168.1.5:3000/apiCall/testUser", {
data: [
{
"name" : "test"
}
]
}, function (err, res) {
if(!err)
console.log("succesfully posted"); // 4
else
console.log('err',err)
});
return true;
}
But I get the following error on the callback :
err { [Error: socket hang up] code: 'ECONNRESET' }
Not able to figure out whats the problem here.
The server side route is successfully called, but the .post() method is not being entered.
Using meteor version 1.6
192.168.1.5 is my ip addr
Okay so if I use Router.map function, the issue is resolved.
Router.map(function () {
this.route("apiRoute", {path: "/apiCall/:username",
where: "server",
action: function(){
// console.log('------------------------------');
// console.log('apiRoute');
// console.log((this.params));
// console.log(this.request.body);
var id = this.params.username;
this.response.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
});
if (this.request.method == 'POST') {
// console.log('POST');
var user = Meteor.users.findOne({username : id});
// console.log(user)
if(!user){
return 'no user found'
}
else{
var userId = user._id;
}
}
});
});
It looks like the content type is not set the application/json. So you should do that...
Setting the "Content-Type" header in HTTP.call on client side in Meteor

How to refresh data after refresh token refreshes jwt

I've been trying to get my refresh token to work for a while now, and I hope I'm close. My token refreshes and triggers a subsequent 200 call to whatever call caused the 401, but my the data on my page doesn't refresh.
When an access token expires, the following happens:
After the 401, the GetListofCompanyNames returns 200 with a list of names using the correct updated access token. However, my dropdown does not refresh.
My interceptor:
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
//var promise = $q.reject(rejection);
var authService = $injector.get('authService');
if (rejection.status === 401) {
// refresh the token
authService.refreshToken().then(function() {
// retry the request
var $http = $injector.get('$http');
return $http(rejection.config);
});
}
if (rejection.status === 400) {
authService.logOut();
$location.path('/login');
}
return $q.reject(rejection);
}
};
}
]);
My return statement on the 401 rejection looks suspect here, but I'm not sure what to replace it with. Thereby my question is: How can I get my page to refresh it's data when I make the new call?
Update:
This gets me past when the 200 returns and I can get a dropdown to refresh, but I lose any state on the page (ex. selected dropdown) with the below.
authService.refreshToken().then(function() {
var $state = $injector.get('$state');
$state.reload();
});
Back to the drawing board!
Try putting up your retry call in $timeout, it should work.
Here's the updated code:
app.factory('authInterceptorService',['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
//var promise = $q.reject(rejection);
var authService = $injector.get('authService');
if (rejection.status === 401) {
// refresh the token
authService.refreshToken().then(function() {
// retry the request
return $timeout(function() {
var $http = $injector.get('$http');
return $http(rejection.config);
}});
}
if (rejection.status === 400) {
authService.logOut();
$location.path('/login');
}
return $q.reject(rejection);
}
};
}
]);
$timeout returns a promise that is completed with what is returned
from the function parameter, so we can conveniently just return the
$http call wrapped in $timeout.
Thanks.
I think you may want to change up how you go about this. One way to go about this would be to inject the $rootScope into your authInterceptorService and then once you successfully refresh the token, call something like $rootScope.broadcast('tokenRefreshed').
I don't quite know how you have set up the view and controller that handles your dropdown, but I would set up a listener for that 'tokenRefreshed' event. From here, you can do another call to GetListofCompanyNames. If you do it this way you can easily control and ensure that the model gets updated.
My final solution:
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', '$injector', function($q, $location, localStorageService, $injector) {
var $http;
var retryHttpRequest = function(config, deferred) {
$http = $http || $injector.get('$http');
$http(config).then(function(response) {
deferred.resolve(response);
},
function(response) {
deferred.reject(response);
});
}
return {
request: function(config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
},
responseError: function(rejection) {
var deferred = $q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
authService.refreshToken().then(function() {
retryHttpRequest(rejection.config, deferred);
},
function () {
authService.logOut();
$location.path('/login');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
}
};
}
]);
Copied almost 1 for 1 from https://github.com/tjoudeh/AngularJSAuthentication/blob/master/AngularJSAuthentication.Web/app/services/authInterceptorService.js .
This one transparently handles all requests and refreshes them when necessary. It logs out users when the refresh token is expired and passes errors along to the controllers by properly rejecting them. However, it doesn't seem to work with multiple in flight requests, I'll look into that when I get a use case for it in my system.

Extracting data out of http call [duplicate]

I'm using Meteor for first time and i'm trying to have a simple http call within a method so i can call this method from the client.
The problem is that this async call it's keep running even if i put it within a wrapper.
Client side:
Meteor.call('getToken', function(error, results) {
console.log('entered');
if(error) {
console.log(error);
} else {
console.log(results);
}
});
Server Side
Meteor.methods({
getToken: function(){
// App url
var appUrl = 'myAppUrl';
// Key credentials
var apiKey = 'mykey';
var apiSecret = 'mySecret';
function asyncCall(){
Meteor.http.call(
'POST',
appUrl,
{
data: {
key: apiKey,
secret: apiSecret
}
}, function (err, res) {
if(err){
return err;
} else {
return res;
}
}
);
}
var syncCall = Meteor.wrapAsync(asyncCall);
// now you can return the result to client.
return syncCall;
}
});
I'm always getting an undefined return.
If i log the response within the http.post call i'm geting the correct response.
If i try to log the syncCall i get nothing.
I would very appreciate any help on this.
You should use the synchronous version of HTTP.post in this case. Give something like this a try:
Meteor.methods({
getToken: function() {
var appUrl = 'myAppUrl';
var data = {apiKey: 'mykey', apiSecret: 'mySecret'};
try {
var result = HTTP.post(appUrl, {data: data});
return result;
} catch (err) {
return err;
}
}
});
Instead of returning the err I'd recommend determining what kind of error was thrown and then just throw new Meteor.Error(...) so the client can see the error as its first callback argument.

invalid_grant Google OAuth

I am trying to authentiate through Google's OAuth, but I'm having problems establishing a connection to their API
My client code:
'click #addChannel': function (event) {
event.preventDefault();
var userId = Meteor.userId();
var options = {
requestPermissions: [
'https://www.googleapis.com/auth/youtube',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/youtube.force-ssl',
'https://www.googleapis.com/auth/youtube.readonly',
'https://www.googleapis.com/auth/youtube.upload',
'https://www.googleapis.com/auth/youtubepartner',
'https://www.googleapis.com/auth/youtubepartner-channel-audit',
],
requestOfflineToken: true
};
Google.requestCredential(options, function(token) {
Meteor.call('userAddOauthCredentials', userId, token, function(error, result) {
if (error) {
throw error;
}
console.log(result);
});
});
My server code:
userAddOauthCredentials: function(userId, token) {
check(userId, String);
check(token, String);
var config = ServiceConfiguration.configurations.findOne({service: 'google'});
if (!config) {
throw new ServiceConfiguration.ConfigError();
}
console.log(token, config);
var endpoint = 'https://accounts.google.com/o/oauth2/token';
var params = {
code: token,
client_id: config.clientId,
client_secret: OAuth.openSecret(config.secret),
redirect_uri: OAuth._redirectUri('google', config),
grant_type: 'authorization_code',
};
try { <------------------------------------------------------ this fails
response = HTTP.post(endpoint, { params: params });
} catch (err) {
throw _.extend(new Error("(first) Failed to complete OAuth handshake with Google. " + err.message),
{response: err.response});
}
if (response.data.error) { // if the http response was a json object with an error attribute
throw new Error("(second) Failed to complete OAuth handshake with Google. " + response.data);
} else {
return {
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token,
expiresIn: response.data.expires_in,
idToken: response.data.id_token
};
}
The above throws a [400] { "error" : "invalid_grant" } error.
Most of the above code I got from how the meteor accounts-google packages logs in a user (which works fine in my application). Link to that:
https://github.com/meteor/meteor/blob/87e3c6499d5eacce62f10faefe9ce49c77bb03ee/packages/google/google_server.js
Any advice on how to proceed from here?
Much appreciated
UPDATE1:
I get these warnings in my log
W20150318-09:11:42.532(1) (oauth_server.js:71) Unable to base64 decode state from OAuth query: undefined
W20150318-09:11:42.532(1) (oauth_server.js:71) Unable to base64 decode state from OAuth query: undefined
W20150318-09:11:42.533(1) (oauth_server.js:71) Unable to base64 decode state from OAuth query: undefined
W20150318-09:11:42.534(1) (oauth_server.js:398) Error in OAuth Server: Match error: Expected string, got undefined
You have to parse your var params to application/x-www-form-urlencoded. Please find the below code to parse as i done in php
$fields_string="";
foreach($params as $key=>$value)
{
$fields_string .= $key.'='.$value.'&';
}
rtrim($fields_string, '&');
Now the $filed_string will contained the parse of params array.

Resources