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
Related
I can get puppeteer-sharp (with headless both true and false) to obtain server-side-rendered Blazor pages, but it looks like I'm only getting the server pre-render and NOT any client-side initiated events (e.g., for instance the second OnInitialized, or any OnAfterRender, OnAfterRenderAsync).
My best guess is that Signal R is not working properly using puppeteer-sharp.
I do this:
using var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions {
Headless = true
});
await using var page = await browser.NewPageAsync();
await page.SetJavaScriptEnabledAsync(true);
await page.GoToAsync("<<mypage>>,
new NavigationOptions {
WaitUntil = new WaitUntilNavigation[] {
WaitUntilNavigation.Load
}
}
);
string sContent = await page.GetContentAsync();
Is there any way to get ALL of the Blazor events to fire properly, or any good discussion of this on the web somewhere?
Thanks in advance!
The answer here was that puppeteer-sharp is actually working just fine with SignalR in all cases.
I really had a separate, more general problem with my authorization settings where the fallback was improperly set. In that case, pages that supposedly did not require authorization were really only providing a single server-prerender (and no later SignalR-enabled onParameterSet, etc.). This incorrect behavior was the same in both a manual browser windows and puppeteer.
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.
I am using this code to generate password reset links:
private async Task<string> GetNewEmailConfirmationLink(ApplicationUser user)
{
var code = await this.UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action(
"ConfirmEmail",
"Account",
new { userId = user.Id, code = code },
protocol: Request.Url.Scheme);
return callbackUrl;
}
In principle, this code works just fine - but the link generated is extremely long. While secure, this links needs sometimes to be copy pasted etc. and then it's length tends to cause errors (forgotten elements etc.).
Is there any way to shorten this?
An generated link as example:
http://example.com:9999/de-DE/Account.aspx/ConfirmEmail?userId=1f4f605a-1be5-4b79-9fb0-139687fe8edc&code=0AjkI5qK917WkZw%2Bz0nXf1uK%2BK9bvYAK6BNEVM2l%2Fc%2BlBYOIiitrQ0gMUrB96CCNYc11hnpNpp2Wg2buC548mAb8l9JwzcwfuOiMAXiwJ%2F3iDH1LlXWgiLW%2FXxcVqLEs2hIhvmye2%2FJxQZD3RcODyNyGD%2FnWjrJRoFK%2B16FtyrtimfNRL%2F1L9vgrk5HiWENn
For ASP.NET Core, yup we can make short, less headache.
services.AddIdentity<ApplicationUser, ApplicationRole>(options => {
options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
}).AddDefaultTokenProviders()
.AddEntityFrameworkStores<YourDbContext>();
There are two ways to achieve this which come to my mind.
One is to shorten the generated code itself. This means that you give a shorter version (generated from the original code) and use it for all the user interaction. Before confirming the email address of the user you convert the link to its original representation.
The other option is to implement the IUserTokeProvider which creates your own token.
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
})
.AddDefaultTokenProviders();
Found out, after some digging, that the default EmailTokenProvider generates exactly the six digits you're looking for but that by default it is wrapped into a DataProtectorTokenProvider that scrambles the output.
By 'resetting' the EmailConfirmationTokenProvider to TokenOptions.DefaultEmailProvider you unwrap it and get the normal unscrambled token when invoking the GenerateEmailConfirmationTokenAsync method.
I see my answer matches https://stackoverflow.com/a/54523545/129269 but I didn't figure that out without knowing the background.
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
I am building an IM platform based on Firebase and I would like that every user got an address that directed them to the chat room.
http://chatt3r.sitecloud.cytanium.com/
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="https://cdn.firebase.com/v0/firebase.js"></script>
<script>
var hash; // global incase needed elsewhere
$(function() {
hash = window.location.hash.replace("#", "");;
myRootRef = new Firebase("http://xxxx.firebaseio.com");
var postingRef;
if (hash) {
// user has been given a hashed URL from their friend, so sets firebase root to that place
console.log(hash);
postingRef = new Firebase("http://xxxx.firebaseio.com/chatrooms/" + hash);
} else {
// there is no hash, so the user is looking to share a URL for a new room
console.log("no hash");
postingRef = new Firebase("http://xxxx.firebaseio.com/chatrooms/");
// push for a unique ID for the chatroom
postingRef = postingRef.push();
// exploit this unique ID to provide a unique ID host for you app
window.location.hash = postingRef.toString().slice(34);
}
// listener
// will pull all old messages up once bound
postingRef.on("child_added", function(data) {
console.log(data.val().user + ": " + data.val().message);
});
// later:
postingRef.push({"message": "etc", "user": "Jimmybobjimbobjimbobjim"});
});
</script>
</head>
That's working for me locally. You need to change xxxx to whatever URL yours is at, and add on however many characters that first part is at the .slice() bit.
Hashes.
If I understand your question correctly, you want to be able to share a URL that will allow anyone who clicks on the URL to log onto the same chatroom.
I did this for a Firebase application I made once. The first thing you need to be doing is using the .push() method. Push the room to Firebase, then use the toString() method to get the URL of the push. Some quick JS string manipulation - window.location.hash = pushRef.toString().slice(x) - where 'x' is whatever place you want to snip the URL at. window.location.hash will set the hash for you. Add the hash to the sharing URL, and then for the next step:
You will want a hash listener to check if there is already a hash when you open the page, so $(window).bind('hashchange', function() {UpdateHash()}) goes into a doc.ready function, and then...
function UpdateHash() {
global_windowHash = window.hash.replace("#", "");
// to assign the hash to a global hash variable
if (global_windowHash) {
// if there was already a hash
chatRef = new Firebase("[Your Firebase URL here]" + "/chatrooms/" + global_windowHash);
// chatRef is the global that you append the chat data to, and listen from.
} else {
// there wasn't a hash, so you can auto-create a new one here, in which case:
chatRef = new Firebase("[Your Firebase URL here]" + "/chatrooms/");
chatRef = chatRef.push();
window.location.hash = chatRef.toString().slice(x);
}
}
I hope that helps (and works :P ). If there are any questions or problems, then just ask!