Create a dynamic URL parameter that includes every other URL parameter - paw-app

I'm trying to create a signature for the JW Platform API. It requires a URL parameter named "api_signature" that a SHA1 digest of every other URL parameter.
I tried creating the parameter with the SHA1 digest dynamic with a JS Script as its input. Here's the basic code from the JS Script:
function evaluate(context)
{
var request = context.getCurrentRequest()
var params = request.getUrlParameters()
var sbs = ''
for (key of params) {
if (key === 'api_signature')
continue;
if (sbs.length > 0)
sbs += '&'
sbs += encodeURIComponent(key) + '=' + encodeURIComponent(params[key])
}
return sbs + '<API SECRET GOES HERE>'
}
But with this I get the warning:
JS Script cannot be used in URL Parameter Value because this creates a self-dependency.
How can I get around this?

The reason this warning appears is because request.getURLParameters() has to evaluate all the url parameters of the request, including api_signature, and therefore has to call the method evaluate of the JS Script, thus creating an infinite recursion issue which is detected and blocked at the first iteration (the content of the dangerous url parameter is replaced by '').
In theory, one could avoid this issue by using request.getURLParameters(true), which returns a non-evaluated DynamicString for each url parameter. However, it seems that in practice, the detection is a bit too strong and it will still raise the warning.
Another solution could be to use request.getURLParametersNames() and request.getURLParameterByName(name) together. However, the detection is again too strong and a warning is raised.
Both of these solutions should in theory work without raising a warning, and we are working on a fix for this issue in Paw 3.0.13. In any case, although a warning is raised, the script still functions normally.
Example with request.getURLParameters(true)
function evaluate(context)
{
var request = context.getCurrentRequest()
var params = request.getUrlParameters(true)
var sbs = []
for (var key in params) {
if (key === 'api_signature')
continue;
sbs.push(encodeURIComponent(key) + '=' +
encodeURIComponent(params[key].getEvaluatedString()))
}
return sbs.join('&') + '<API SECRET GOES HERE>'
}
Example with request.getURLParametersName()
function evaluate(context)
{
var request = context.getCurrentRequest()
var params = request.getUrlParametersNames()
var sbs = []
for (var param of params) {
if (param === 'api_signature')
continue;
sbs.push(encodeURIComponent(param) + '=' +
encodeURIComponent(request.getUrlParameterByName(param)))
}
return sbs.join('&') + '<API SECRET GOES HERE>'
}

Related

MVC minification seems to creating duplicate variable names

This question seems to be more or less a duplicate of this one, but that one received no answers and is over 2 years old so I don't know what the protocol is (I tried to find out).
Anyway, I've written an ASP.NET MVC5 web app and it all works fine in debug. After publishing to the server with the Release configuration I started seeing the error "Uncaught ReferenceError: Cannot access 'n' before initialization".
After many hours of trawling through the code I think I've isolated the issue. I have this small function (it's a Knockout view model, but that's irrelevant):
eventIndexViewModel = function (params) {
let self = this;
// Map values from params object to properties of self
for (const [key, value] of Object.entries(params)) {
self['_' + key] = value;
}
self.eventsView = ko.observable(self._eventsView);
self.calendarToggleButtonClass = ko.pureComputed(function () {
return self.eventsView() === "calendar" ? "active" : "";
});
self.tableToggleButtonClass = ko.pureComputed(function () {
return self.eventsView() === "table" ? "active" : "";
});
};
After being minified and published to the server, if I view the source in the dev tools console it looks like this:
eventIndexViewModel = function(n) {
let t = this;
for (const [n,i] of Object.entries(n))
t["_" + n] = i;
t.eventsView = ko.observable(t._eventsView);
t.calendarToggleButtonClass = ko.pureComputed(function() {
return t.eventsView() === "calendar" ? "active" : ""
});
t.tableToggleButtonClass = ko.pureComputed(function() {
return t.eventsView() === "table" ? "active" : ""
})
}
It is overkill to map the properties for the params object in this way in this particular instance, but I have much larger view models with many more properties in the same project and I want to keep them code consistent, so go with it.
Unless I'm misunderstanding something, the minified version has renamed both the params variable and the key variable in the for statement to n, and I think that is what is causing my error. Certainly, that line with the for statement is where the error is thrown.
Am I understanding the cause of this problem correctly? If so, is it a bug in the minification process? And either way, how can I get around it?

Meteor's FlowRouter.url() returning IP address of host, not FQDN

When calling:
FlowRouter.url("myRouteName");
I'm getting the IP address of the server, i.e
"http://XX.XXX.XX.XXX/loggedin/my-route"
Instead of the FQDN, i.e
"http://example.com/loggedin/my-route"
Any idea how this can be configured properly?
Thanks.
Answering my own question. Source.
Router.prototype.url = function() {
// We need to remove the leading base path, or "/", as it will be inserted
// automatically by `Meteor.absoluteUrl` as documented in:
// http://docs.meteor.com/#/full/meteor_absoluteurl
var completePath = this.path.apply(this, arguments);
var basePath = this._basePath || '/';
var pathWithoutBase = completePath.replace(new RegExp('^' + basePath), '');
return Meteor.absoluteUrl(pathWithoutBase);
};
So it seems FlowRouter is using Meteor.absoluteUrl.
Now the absolute path can be set with Meteor.absoluteUrl.defaultOptions.rootUrl = "http://example.com" However this isn't the correct approach for me, as my app can actually serve multiple domain names.
So I'll have to write my own function, which extracts the FQDN from the client, ad hoc.
flowRouterUrl = function(id, params, queryParams) {
return window.location.protocol + "//" + window.location.host + FlowRouter.path(id, params, queryParams);
}
flowRouterUrl("myRoute");
returns
http://example.com/loggedin/my-route
Which is what I'm after.

meteor restivus how to read multiple queryParams

I am building an API with Restivus in Meteor.
In a custom route I would like to have multiple values as queryParams like this (e.g. value1 and value2):
...domain/api/update?key=1234&value1=10
How do I get them in endpoint function?
When I try this I get undefined:
var query = this.queryParams.key // result: 1234
var value1 = this.queryParams.value1 // result: undefined
Update
This is my new fresh code with the same result.
Use a standard Meteor project. Meteor version 1.0.3.2
// Create collection
Posts = new Mongo.Collection("posts");
if (Meteor.isServer) {
Meteor.startup(function () {
// RESTIVUS
// Global configuration
Restivus.configure({
useAuth: false,
prettyJson: true
});
// Given the url: "/posts?key=1234&value1=10"
Restivus.addRoute('posts', {
get: function () {
var key = this.queryParams.key;
var value1 = this.queryParams.value1;
console.log("key: " + key); // result: 1234
console.log("value1: " + value1); // result: undefined
}
});
});
}
This is the solution to the problem. Taken from here:
https://github.com/kahmali/meteor-restivus/issues/16
You're using curl to test, right? Well apparently (and don't feel bad for not knowing this, because neither did I), the & symbol means that the previous command will be run in the background, so the query params were just being truncated once the curl command reached the & for the second query param. All you have to do is wrap the URL in quotes, and voila! Try this command instead:
curl "http://testivus.meteor.com/api/posts?key=1234&value1=10"
That should work. So if you had just punched that URL into a browser or used a mored advanced REST client, you would have seen the extra query param defined. I got the answer from this StackOverflow question.

Dynamic param value calculation depending on params in Paw

I have some API that must be signed with request params hash.
For example I have 2 params - login and password in request params. So I need to add the param checksum that is calculated with login and password fields hash.
How can I implement it? Now when I try to calculate it, I have the self-dependency error.
login = test
password = test
somefield = lalala
checksum = md5([login][password][somefield]) <- here is dynamic evaluation
The self-dependency error is shown because it actually tries to evaluate the full URL to get one of the other parameters. That's probably something that needs to be fixed in Paw.
However, you can simply ignore the warning, as it still works. Here is an example:
In your example, the checksum is 8bc22595f820ff1612fd16294c02359a which is the expected result.
Update: if you want to do that with JavaScript code, here's an example.
function evaluate(context) {
var url = context.getCurrentRequest().url;
var query = url.split('?')[1];
var fragments = query.split('&');
var login, password, somefield;
for (var i in fragments) {
var keyvalue = fragments[i].split('=');
if (keyvalue[0] == "login") {
login = keyvalue[1];
} else if (keyvalue[0] == "password") {
password = keyvalue[1];
} else if (keyvalue[0] == "somefield") {
somefield = keyvalue[1];
}
}
// you can now compute whatever hash you want with these values
// the self-dependency error will be shown but it should work
return "" + login + "-" + password + "-" + somefield;
};
To calculate MD5 hashes with JS, you'll need to include a 3rd party library. That can be more easily (and more cleanly) done via npm. See how we are managing dependencies in other Extensions: https://github.com/LuckyMarmot/Paw-PythonRequestsCodeGenerator

Cant get digest auth to work with node.js

I'm trying to get a simple (!) digest authentication working with node js using an an API from gathercontent.com.
Everything seems to be working except I still get a "Wrong credentials" response that looks like this:
{ success: false, error: 'Wrong Credentials!' }
The code looks like this:
var https = require('https'),
qs = require('querystring');
apikey = "[my api key goes in here]",
pwd = "[my password goes in here]",
crypto = require('crypto');
module.exports.apiCall = function () {
var options = {
host:'abcdefg.gathercontent.com',
port:443,
path:'/api/0.1/get_pages_by_project/get_me',
method:'POST',
headers:{
"Accept":"application/json",
"Content-Type":"application/x-www-form-urlencoded"
}
};
var req = https.request(options, function (res) {
res.on('data', function (d) {
var creds = JSON.parse(d);
var parsedDigest = parseDigest(res.headers['www-authenticate']);
console.log(parsedDigest);
var authopts = {
host:'furthercreative.gathercontent.com',
port:443,
path:'/api/0.1/get_pages_by_project/get_me',
method:'POST',
headers:{
"Accept":"application/json",
"Content-Type":"application/x-www-form-urlencoded",
"Authorization" : getAuthHeader(parsedDigest, apikey, parsedDigest['Digest realm'], pwd)
}
};
console.log(authopts);
console.log('\n\n\n');
var req2 = https.request(authopts, function (res2) {
console.log("statusCode: ", res2.statusCode);
console.log("headers: ", res2.headers);
res2.on('data', function (d2) {
var result = JSON.parse(d2);
});
});
req2.end();
});
});
req.write('id=1234');
req.end();
req.on('error', function (e) {
console.error(e);
});
};
function parseDigest(s){
var parts = s.split(',');
var obj = {};
var nvp = '';
for(var i = 0; i < parts.length; i++){
nvp = parts[i].split('=');
obj[nvp[0]] = nvp[1].replace(/"/gi, '');
}
return obj;
}
function getAuthHeader(digest, apikey, realm, pwd){
var md5 = crypto.createHash('md5');
var s = '';
var nc = '00000001';
var cn = '0a4f113b';
var HA1in = apikey+':'+realm+':'+pwd;
md5 = crypto.createHash('md5');
md5.update(HA1in);
var HA1out = md5.digest('hex');
var HA2in = 'POST:/api/0.1/get_pages_by_project/get_me';
md5 = crypto.createHash('md5');
md5.update(HA2in);
var HA2out = md5.digest('hex');
md5 = crypto.createHash('md5');
var respIn = HA1out + ':' + digest.nonce + ':'+nc+':'+cn+':'+digest.qop+':'+ HA2out;
md5.update(respIn);
var resp = md5.digest('hex');
s = [ 'Digest username="',apikey,'", ',
'realm="',digest['Digest realm'],'", ',
'nonce="',digest.nonce,'", ',
'uri="/api/0.1/get_pages_by_project/get_me", ',
'cnonce="',cn,'", ',
'nc="',nc,'", ',
'qop="',digest.qop,'", ',
'response="',resp,'", ',
'opaque="',digest.opaque,'"'].join('')
return s;
}
I'd try and Curl to it but I'm not sure how!
Any help appreciated!
I see a couple of issues potentially related to your problem. It's hard to tell which ones are the actual culprits, not knowing anything about gathercontent's implementation. If you pasted an example of their 'WWW-Authenticate' header, it would be much easier to provide specific help.
So I'm speculating what the actual cause is, but here are some actual problems that you should address anyway, to conform to the spec (i.e. protect it from breaking in the future because the server starts doing things slightly differently):
in the Authorization headers you are creating, remove the double quotes around nc, and maybe also qop
I don't know what qop value gathercontent is using. If it's auth-int, then you'd also have to append the hashed HTTP body to HA2, see #3.2.2.3 of the spec - furthermore, they might be specifying a comma-separated list of qop values for you to choose from - or the server might not send a value for qop at all, i.e. they use the most basic from of HTTP digest auth, in which your implementation would be violating the spec, as then you aren't allowed to e.g. send a cnonce, nc etc.
you try to get the realm via parsedDigest['Digest realm'], i.e. you are assuming that the realm is the first attribute after the initial Digest keyword. That might or might not be the case, but you should not rely upon it (modify your parseDigest function to strip of the string "Digest " before splitting the rest)
the way you use parsedDigest, you make the assumption that Digest is always capitalized that way, and that realm, nonce, etc. are always in lowercase. According to the spec, these are all case-insensitive
A couple of unrelated issues:
Does the server really force you to use Digest authentication? This is HTTPS, so you might as well do Basic authentication, it's way easier, and with HTTPS, just as safe. (Answering myself here, after checking out gathercontent: Basic auth is apparently not possible)
As mentioned in my comment to your question, cnonce should be random for every request, especially, you shouldn't copy and paste it from Wikipedia, which makes you more vulnerable (but not an issue here, as all data goes over SSL anyway in your case)
Regarding how to curl it - try this:
curl --data 'id=1234' --digest --user "apikey:pwd" https://abcdefg.gathercontent.com:443/api/0.1/get_pages_by_project/get_me
It's Peter from GatherContent.
The first, pretty obvious thing would be to use just get_me instead of get_pages_by_project/get_me. You are mixing two different things in the latter. get_me doesn't require any parameters sent via POST, so you can drop them.
Also, please make sure that your password is always lowercase x.
Does it change anything?
Edit:
For anyone interested, here's our API docs:
http://gathercontent.helpjuice.com/questions/26611-How-do-I-use-the-API
The express-auth module supports multiple authentication schemes, including HTTP Digest. See: https://github.com/ciaranj/express-auth
Another excellent option is passport at: https://github.com/jaredhanson/passport
The http-digest examples in the two modules tend to focus on establishing an authentication for you node.js application vs. forwarding the authentication request to a third-party. However, you should be able to make it work with a little noodling.
If pressed, i would use passport. The examples offered are a lot more clear and documented.
Hope that helps...
I would recomand you to use mikeal's request module it make it a lot easier and cleaner.
Request does not have support for HTTP Auth yet, saddly but you would just have to set the Authorization header.
Try urllib it will work with simple and disgest auth.
See Exemple :
https://stackoverflow.com/a/57221051/8490598

Resources