Hello there everybody!
I am writing a simple multi-platform app for media tagging. It is written with the help of node-webkit and it is "almost" ready, the last and most important part is missing - beatport integration.
I have already acquired my self an API Key to work with (played with the docs), however I am heavily struggling to wrap my head around the OAuth protocol.
As far as I know I have to go thru the auth process, exchange consumer key and secret, login and receive the real access token - all of this can be done via the docs page and you end up with the access token and secret.
The question is how can I directly use the token and secret with something like this.
My awful attempt
var OAuth = require('mashape-oauth').OAuth;
var oa = new OAuth(method(url, oauth_token, oauth_token_secret, body, type, parameters, callback));
var url = "https://oauth-api.beatport.com/catalog/3/search/",
oauth_token = "MyToken", //obtained directly from the doc page
oauth_token_secret = "MyTokenSecret", //obtained directly from the doc page
parameters = "?query=Symphonica&facets=artistName%3ANicky+Romero",
body = "",
type = "",
callback = "";
console.log(oa);
Thank you very much any help will be appreciated. If anyone of you who help me, happen to be in prague I'll be very happy to buy you a beer.
(please take in consideration, that this is my first attempt to node/js I come from a php background I like to throw my self in the water even though I can't swim)
It is always about tinkering...
var sys = require('sys');
var OAuth = require('oauth').OAuth;
var oa = new OAuth("https://oauth-api.beatport.com/catalog/3/search/",
"https://oauth-api.beatport.com/catalog/3/search/",
"API KEY","API KEY SECRET",
"1.0A", undefined, "HMAC-SHA1");
var url = 'https://oauth-api.beatport.com/catalog/3/search/?query=Symphonica&facets=artistName:Nicky Romero',
access_token = "Access Token Obtained on doc pages",
access_token_secret = "Access Token Secret obtained on doc pages";
var request = oa.get(url, access_token, access_token_secret, function(error, data) {
if (error) {
console.log(error);
} else {
console.log(JSON.parse(data));
}
});
Changed the node module to this and tinkered with this example
I am going to buy myself a beer :-)
Related
I was excited to hear that I can now use the Spotify web API without having a backend application via PKCE. Unfortunately, I seem to have some sort of misunderstanding and have been unable to get it to work.
I am likely making some minor mistake along the way, but I did it once to no avail and I wiped the slate clean and tried again but still without luck. From this I gather that I must be misunderstanding the documentation.
I will explain what I am doing and hopefully someone here can point out what I'm missing or doing wrong. I'm assuming I have a fundamental conceptual misunderstanding.
I first generate a cryptographically random string using an npm package called crypto-random-string. I store that in the browser's local storage before using js-sha256 to hash it and then using another npm package called base64url to encode it.
let verifier = cryptoRandomString({length: 50})
window.localStorage.setItem('verifier', verifier)
let params = {
client_id: '[MY CLIENT ID]',
response_type: 'code',
redirect_uri: 'http://localhost:3000/callback',
code_challenge_method: 'S256',
code_challenge: base64url(sha256(verifier))
}
let endpoint = new URL('https://accounts.spotify.com/authorize');
endpoint.search = new URLSearchParams(params);
window.location = endpoint.toString();
From here, I redirect to the /authorize endpoint with the proper url parameters. I have gotten this far successfully and then been redirected accordingly to my provided redirect_uri, where I grab the given code from the url parameters.
At this point, I try the fetch to the /api/token endpoint with the client_id, grant_type, the code I got from the url params, my redirect_uri, and the locally stored code_verifier.
let params = new URLSearchParams(window.location.search);
console.log(params.get('code'));
let newParams = {
client_id: '[MY CLIENT ID]',
grant_type: 'authorization_code',
code: params.get('code'),
redirect_uri: 'http://localhost:3000/callback',
code_verifier: window.localStorage.getItem('verifier')
}
let endpoint = new URL('https://accounts.spotify.com/api/token');
endpoint.search = new URLSearchParams(newParams);
fetch(endpoint.toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(data => data.json()).then(console.log)
At this point, after both of my attempts I received the error:
{ error: "invalid_grant", error_description: "code_verifier was incorrect" }
Is there anything that I am obviously doing wrong? The error leads me to believe I'm doing something wrong as far as the actual generation of the code_verifier, but I am at a loss to what that issue may be.
Someone on the Spotify forum pointed me to this answer. Not sure why exactly, but doing the encoding the following way does work:
async function sha256(plain) {
const encoder = new TextEncoder()
const data = encoder.encode(plain)
return window.crypto.subtle.digest('SHA-256', data)
}
function base64urlencode(a){
return btoa(String.fromCharCode.apply(null, new Uint8Array(a))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
const hashed = await sha256(verifyCode)
const codeChallenge = base64urlencode(hashed)
Previous answers and comments, in addition to OP, has documented most information needed so I will only add what helped me:
The verifier itself most be encoded as a Base64-URL.
Pseduo-code (as I myself code in C#):
verifier = Base64UrlEncode(GetRandomString(length: 50))
challenge = Base64UrlEncode(HashWithSha256(verifier))
Im trying to implement Sing In with Twitter working, however i keep getting the following error
Whoa there!
The request token for this page is invalid. It may have already been used, or expired because it is too old. Please go back to the site or application that sent you here and try again; it was probably just a mistake.
Here's my code, the what am I doing wrong?
// Server
var OAuth = require('oauth').OAuth;
var callbackUrl = null;
var oa = new OAuth(
'https://api.twitter.com/oauth/request_token',
'https://api.twitter.com/oauth/access_token',
CONSUMER_KEY,
CONSUMER_SECRET,
'1.0',
callbackUrl,
'HMAC-SHA1'
);
const promise = new Promise((resolve, reject) => {
oa.getOAuthRequestToken(function(error, token, secret, results) {
resolve({
token,
secret,
});
});
});
var oauth = await promise;
return { oauth };
// Client
window.open(`https://api.twitter.com/oauth/authenticate?oauth_token=${twitter.oauth.token})`);
I've also set the callback url to the same as what I have in the app details page, but no dice!
var callbackUrl = 'http://0.0.0.0:3001/settings/social/twitter/oauth';
Small note, not sure if it helps, but I've noticed the returned tokens are shorter than the examples I've seen online. Example:
Mine: EBhy3AAAAAAA94TPAAABajDwCww%
Twitter Site: NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0&
My client code had an extra ")"
window.open(`https://api.twitter.com/oauth/authenticate?oauth_token=${twitter.oauth.token})`);
should read
window.open(`https://api.twitter.com/oauth/authenticate?oauth_token=${twitter.oauth.token}`);
Autodesk Forge's DerivativeApi is not working with my client id and secret. Apis returns unauthorized error, { statusCode: 403, statusMessage: 'Unauthorized’ }.
But they works with the sample's client id/secret (from https://github.com/Autodesk-Forge/forge-api-nodejs-client/blob/master/samples/dmSample.js).
Is there any limitation for calling DerivativeApi (for translating) with free account? or should I do something?
Here is the sample code...
var ForgeSDK = require('forge-apis');
// TODO - insert your CLIENT_ID and CLIENT_SECRET
// Below id/secret from the sample are working, but mine is not.
var CLIENT_ID = 'wmizntnpzCJxPGF9lxsIiTZGbGO2cJqw',
CLIENT_SECRET = 'g5IPJwvhOHcrdbFy';
var derivativesApi = new ForgeSDK.DerivativesApi();
// Initialize the 2-legged oauth2 client
var oAuth2TwoLegged = new ForgeSDK.AuthClientTwoLegged(CLIENT_ID, CLIENT_SECRET,
['data:write', 'data:read', 'bucket:read', 'bucket:update', 'bucket:create'], true);
function defaultHandleError(err) {
console.error('\x1b[31m Error:', err, '\x1b[0m');
}
oAuth2TwoLegged.authenticate().then(function (credentials) {
console.log("**** Got Credentials", credentials);
derivativesApi.getFormats({}, oAuth2TwoLegged, oAuth2TwoLegged.getCredentials()).then(res => {
console.log(res);
}, defaultHandleError);
}, defaultHandleError);
Thanks
First, and extremely important, NEVER share your ID & Secret. If the above is your correct one, please create a new secret (to invalidate this).
Now the ID & Secret should work for any sample, just make sure you activated the APIs for it. On your app, select Model Derivative API on the screen, if not available, please review this article.
Finally, the code sample above is not actually calling Model Derivative. If so, note that files uploaded into one account are not accessible from other accounts, and URNs are unique.
I have a Meteor Mobile app that accesses a lot of photos stored in my S3 bucket. These photos are user uploaded and change frequently. I don't want these photos to be accessible to anyone that isn't using my app. (ie: these photos are only viewable from my application and going to the url directly in a browser won't load them).
What is the best way to accomplish this? AWS Cognito seems to be the logical choice, but it doesn't seem easy to implement and I'm not exactly sure how to authenticate to AWS from the client once it gets a Cognito identity.
My other thought was putting a read only AWS Key on every url and authenticating that way, but that's almost pointless. It would be really easy to find out the key and secret.
EDIT:
To be specific, the URLs for the images are in a Mongo collection and I pass them into a template. So, the S3 resources are just loaded up with an image tag (<img src="). Something like AWS STS sounds like a great option, but I don't know of a way to pass the tokens in the headers when I'm loading them like this. Doing them as a pre-signed query string seems inefficient.
Another option is to restrict access with the referrer header, like this issue. But like Martijn said, it isn't really a secure way of doing it.
After some research and testing I solved this myself. My ultimate solution was to use the referer header to limit access to my S3 bucket. I created a more secure and detailed solution (see below), but it came with a performance hit that wouldn't work for my app. My app is based around viewing photos and videos, and not being able to have them load near instantly wasn't in the cards. Although, I feel like it could be a sufficient solution for most use-cases. Because my app isn't highly sensitive, the referer header is sufficient for me. Here is how to use the http header referer to limit access to a bucket.
Solution using Amazon's STS:
First, you need to have the AWS SDK on both the server and the client. There was no up to date packages for Meteor available, so I created my own. (I'll publish it shortly and put a link here once I do.)
On the server, you must use credentials that have the ability to assume a role. The role to be assumed must have a Trust Relationship with the user that is assuming the role. Article on using IAM. - Article on using credentials with SDK
In the server.js file I created a Meteor Method that I can call from the client. It first checks if a user is logged in. If that's true, it checks to see if it's current temp-credentials are expiring in the next 5 minutes. If they are, I issue new credentials and either write them to the user document or return them as a callback. If they aren't expiring in the next 5 minutes, I return their current temp-credentials.
You must use Meteor.bindEnvironmentfor the callback. See docs
Meteor.methods({
'awsKey': function(){
if (Meteor.userId()){
var user = Meteor.userId();
var now = moment(new Date());
var userDoc = Meteor.users.findOne({_id: user});
var expire = moment(userDoc.aws.expiration);
var fiveMinutes = 5 * 60 * 1000;
var fut = new Future();
if(moment.duration(expire.diff(now))._milliseconds < fiveMinutes ){
var params = {
RoleArn: 'arn:aws:iam::556754141176:role/RoleToAssume',
RoleSessionName: 'SessionName',
DurationSeconds: 3600 //1 Hour
};
var sts = new AWS.STS();
sts.assumeRole(params, Meteor.bindEnvironment((err, data) => {
if (err){
fut.throw(new Error(err));
}else{
Meteor.users.update({_id: user}, {$set: {aws: {accessKey: data.Credentials.AccessKeyId, secretKey: data.Credentials.SecretAccessKey, sessionToken: data.Credentials.SessionToken, expiration: data.Credentials.Expiration}}});
fut.return(data.Credentials);
}
}));
return fut.wait();
}else{
return userDoc.aws;
}
}
}
}
});
Then you can invoke this method manually or in an setInterval on Meteor.startup.
Meteor.setInterval(function(){
if(Meteor.userId()){
Meteor.call('awsKey', function(err, data){
if (err){
console.log(err);
}else{
if(data.accessKey){
Session.set('accessKey', data.accessKey);
Session.set('secretKey', data.secretKey);
Session.set('sessionToken', data.sessionToken);
}else{
Session.set('accessKey', data.AccessKeyId);
Session.set('secretKey', data.SecretAccessKey);
Session.set('sessionToken', data.SessionToken);
}
}
});
}
}, 300000); //5 Minute interval
This way just sets the keys in a Session variable from the callback. You could do this by querying the user's document to get them as well.
Then, you can use these temporary credentials to get a signed URL for the object you are trying to access in your bucket.
I put this in a template helper by passing the object name to it in the template:
{{getAwsUrl imageName}}
Template.templateName.helpers({
'getAwsUrl': function(filename){
var accessKey = Session.get('accessKey');
var secretKey = Session.get('secretKey');
var sessionToken = Session.get('sessionToken');
var filename = filename;
var params = {Bucket: 'bucketName', Key: filename, Expires: 6000};
new AWS.S3({accessKeyId: accessKey, secretAccessKey: secretKey, sessionToken: sessionToken, region: 'us-west-2'}).getSignedUrl('getObject', params, function (err, url) {
if (err) {
console.log("Error:" +err);
}else{
result = url;
}
});
return result;
}
});
That's all there is to it! I'm sure this can be refined to be better, but this is just what I came up with in testing it really fast. Like I said, it should work in most use cases. My particular one didn't. For some reason, when you tried to toggle the visibility: visible|hidden; on an img src of these signedURLs they would take a lot longer to load than just setting the URL directly. It must be because Amazon has to decrypt the signed URL on their side before return the object.
Thanks to Mikkel for the direction.
I'm attempting to authenticate requests to the Withings API using node-oauth, a widely used OAuth module for Node. All of the initials steps of the OAuth process are working, and I'm able to acquire the user ID, access token, and access token secret. However, when attempting to actually use these tokens to make an authenticated request, I get one of the following errors:
2554 Wrong action or wrong webservice
2555 An unknown error occurred
2556 Service is not defined
I've verified that the credentials I'm using are valid by testing them here. Am I doing something wrong, or is the Withings API implemented in some non-standard way, which makes it incompatible with node-oauth?
var consumerKey = "";
var consumerSecret = "";
var oauth_access_token = "";
var oauth_access_token_secret = "";
var userid = "";
var oauth = require("oauth");
var withings = new oauth.OAuth(
"https://oauth.withings.com/account/request_token",
"https://oauth.withings.com/account/access_token",
consumerKey,
consumerSecret,
"1.0",
null,
"HMAC-SHA1"
);
var url = "http://wbsapi.withings.net/measure?action=getmeas&userid=" + userid;
withings.get(url, oauth_access_token, oauth_access_token_secret, function(error, response) {
console.log(response);
});
Output:
{"status":2554}
I figured this one out. The node-oauth library assumes that most APIs expect OAuth parameters to be defined in headers. However, OAuth parameters may also be defined in the query string, which is how Withings decided to implement it. The node-oauth library defines a signUrl function for this purpose, but you must use it explicitly. Once you wrap the URL in that function, the problem is solved. Note that there is no need to pass the access tokens into the get function because the request is already signed.
var url = withings.signUrl("http://wbsapi.withings.net/measure?action=getmeas&userid=" + userid, oauth_access_token, oauth_access_token_secret);
withings.get(url, null, null, function(error, response) {
console.log(response);
});
The withings API is maybe a little special and very capricious. For example, if you don't send the options in the query string in the right order you got sometimes an error. I haven't tried with node-oauth because I work with it in angular and my friend in Rails but it's difficult to make it work.
I don't see your callback url send in the options of node-oauth have you changed it in the options of your Withings app ?
You can try to modify node-oauth to log the response of each call to Withings and look if it's the first, the second or the third who's failing.
Good luck ;)
check your url may be you have missed out any required params suc as acces_token.
sample URL:
https://wbsapi.withings.net/measure?action=getmeas&category=1&access_token=XXXXXxxxxxxxxXXXXX&meastype=1&startdate=1543581749&enddate=1543581750