testing local https server with mocha/superagent - node.js

So I see there was a pull request a few months ago for superagent to allow you to specify the CA in a request. It does not appear that the docs were updated to reflect this change, so I can't seem to figure out how to do it.
I am trying to test on my local machine a REST service which exposes both http and https endpoints. All the http ones work fine, the SSL ones....well.....not so much.
After spending all day yesterday running down certificate errors, I am 90% certain I have the server working correctly. Curl seems to think so, as does a vanilla node request object.
I assume superagent is probably creating a request under the hood - I just need to know how to pass in the CA for it.
Thanks in advance.

There is a usage example in their tests.
Basically:
var https = require('https'),
fs = require('fs'),
key = fs.readFileSync(__dirname + 'key.pem'),
cert = fs.readFileSync(__dirname + 'cert.pem'),
assert = require('better-assert'),
express = require('express'),
app = express();
app.get('/', function(req, res) {
res.send('Safe and secure!');
});
var server = https.createServer({
key: key,
cert: cert
}, app);
server.listen(8443);
describe('request', function() {
it('should give a good response', function(done) {
request
.get('https://localhost:8443/')
.ca(cert)
.end(function(res) {
assert(res.ok);
assert('Safe and secure!' === res.text);
done();
});
});
});

This worked for me:
...
var user = request.agent({ca: cert});
...
Full example:
var expect = require('chai').expect;
var should = require('should');
var request= require('superagent');
var fs = require('fs');
var cert = fs.readFileSync('sslcert/server.crt', 'utf8');
var validUser = { username: 'test#test.com', password: 'secret111' };
describe('User', function() {
// provide certificate as agent parameter
var user = request.agent({ca: cert});
it("/login", function(done) {
user
.get('https://localhost:3000/login')
.end(function(err, res) {
if(err) throw err;
// HTTP status should be 200
res.status.should.equal(200);
user
.post('https://localhost:3000/login')
.send(validUser)
.end(function(err, res) {
if(err) throw err;
// HTTP status should be 200
res.status.should.equal(200);
done();
// user will manage its own cookies
// res.redirects contains an Array of redirects
});
});
});
it("/", function(done) {
user
.get('https://localhost:3000/')
.end(function(err, res) {
if(err) throw err;
// HTTP status should be 200
res.status.should.equal(200);
done();
});
});
it("/logout", function(done) {
user
.get('https://localhost:3000/logout')
.end(function(err, res) {
if(err) throw err;
// HTTP status should be 200
res.status.should.equal(200);
done();
});
});
});

Related

URL as query param in proxy, how to navigate?

I'm using http-proxy to create a simple proxy: localhost:3000?q=${encodeURIComponent(targetURL)} will access targetURL using the proxy.
Here's my working code:
var http = require("http");
var httpProxy = require("http-proxy");
//create a server object:
http
.createServer(function (req, res) {
try {
// Get the `?q=` query param.
const url = req.query.q;
const parsed = new URL(decodeURIComponents(url));
const proxy = httpProxy.createProxyServer();
// Fix URLs and query params forwarding.
// https://stackoverflow.com/questions/55285448/node-http-proxy-how-to-pass-new-query-parameters-to-initial-request
proxy.on("proxyReq", function (proxyReq) {
proxyReq.path = parsed.toString();
});
proxy.on("end", () => res.end());
// Avoid the following error:
// "Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames"
req.headers["host"] = parsed.host || undefined;
proxy.web(req, res, { target: url }, (err) => {
throw err;
});
} catch (err) {
res.write(JSON.stringify({ error: err.message }));
res.end();
}
})
.listen(8080); //the server object listens on port 8080
When I visit localhost:3000?q=https://google.com, everything works. However, if I click on a link in the website, then the route is changed on my hostname directly, not in the query param.
So:
I go to localhost:3000?q=https://google.com
I click on "Images', which should bring me to localhost:3000?q=https://google.com/images
instead, it brings me to localhost:3000/images?q=https://google.com, which 404s
How do I solve navigation in the target website?

Want to write capture and re-transmit http/https request to Browser?

I want to write a simple Node Js application which will capture and re-transmit http/https request to Browser?
I have written the below code, but it works only for http request.
var server = http.createServer(function (req,res) {
console.log("start request:", req.url);
var option = url.parse(req.url);
option.headers = req.headers;
var proxyrequest = http.request(option, function (proxyresponce) {
proxyresponce.on('data', function (chunk) {
console.log("proxy responce length" ,chunk.length);
res.write(chunk,'binary');
});
proxyresponce.on('end',function () {
console.log("proxy responce ended");
res.end();
});
res.writeHead(proxyresponce.statusCode, proxyresponce.headers);
});
});

Set accepted CA list and ignore SSL errors with chai-http

I'm trying to write unit tests for my node code with chai/chai-http. Everything was working fine until I switched my server to an HTTPS server, but because my certificate is signed by an internal company root and the common name of the certificate I'm using doesn't match localhost, chai is throwing an error on my request.
I'd like to do the following:
Ignore SSL errors related to domain name verification.
Set the list of CAs to check against. If this cannot be done, I'd be fine with just skipping all client-side certificate checks instead.
My code is as follows:
var chai = require('chai');
var chaiHttp = require('chai-http');
var https = require('https');
var fs = require('fs');
var server = require('../app.js');
chai.should();
chai.use(chaiHttp);
https.globalAgent.options.ca = [
fs.readFileSync('./ssl/Root.cer'),
];
describe('Attachments', function () {
it('should succeed when passed valid arguments', function (done) {
chai.request(server)
.get('/10881057300D0A4E8E8586542AA3626E41')
.set('userId', 'user')
.set('region', 'US')
.end(function (err, res) {
chai.assert(res);
res.should.have.status(200);
chai.assert(res.body);
done();
});
});
it('should return error without userId header', function (done) {
chai.request(server)
.get('/10881057300D0A4E8E8586542AA3626E41')
.end(function (err, res) {
chai.assert(res);
res.should.have.status(500);
chai.assert(res.type == 'application/json');
done();
});
});
});
And I get the following stack trace:
Uncaught AssertionError: Unspecified AssertionError
at test\test.js:21:18
at Test.Request.callback (node_modules\superagent\lib\node\index.js:615:12
)
at ClientRequest.<anonymous> (node_modules\superagent\lib\node\index.js:56
7:10)
at TLSSocket.socketErrorListener (_http_client.js:267:9)
at emitErrorNT (net.js:1253:8)
I solved it by the suggestion here.
I think it is rejecting as invalid TLS. Even though mine was not using an invalid cert, I assume somewhere in the guts it is changing the valid cert's url to localhost or resolving to an IP address which isn't associated to the FQDN of the cert I am using. Adding the following code before the first "describe()" fixed it for me.
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
Here is the full test code:
var chai = require('chai');
var chaiHttp = require('chai-http');
var server = require('../server');
var should = chai.should();
chai.use(chaiHttp);
// This line allows use with https
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
describe('Auth', function() {
it('should return 401 with invalid credentials', function(done){
chai.request(server)
.post('/api/v1/user/authenticate')
.send({"email":"badaccount#somedomain.com", "password": "password"})
.end(function(err, res) {
res.should.have.status(401);
done();
});
});
});

_http_server.js:192 throw new RangeError(`Invalid status code: ${statusCode}`);

This is my code:
var express = require('express');
var http = require('http');
var redis = require('redis');
var url = require('url');
var client = redis.createClient().setMaxListeners(0);
var app = express();
app.set('port', 3000);
app.get('/*', function(req, res) {
var key = url.parse(req.url).pathname;
client.on('connect', function() {
console.log('connected to redis!');
});
client.get(key, function(err, reply) {
if( reply == null) {
client.set(key, 1);
client.expire(key, 300);
res.send('1');
}
else {
client.incr(key, function(err, reply) {
console.log('increment value: ' + reply);
res.sendStatus(reply);
});
}
});
});
http.createServer(app).listen(app.get('port'), function() {
console.log('listening');
});
This is my output when I run the file ($ node test.js):
I tried this on my ubuntu machine and it perfectly works. This is what I get on my mac. Could someone explain me why this is happening. Any help would be appreciated.
listening
increment value: 2
_http_server.js:192
throw new RangeError(`Invalid status code: ${statusCode}`);
^
RangeError: Invalid status code: 2
at ServerResponse.writeHead (_http_server.js:192:11)
at ServerResponse._implicitHeader (_http_server.js:157:8)
at ServerResponse.OutgoingMessage.end (_http_outgoing.js:559:10)
at ServerResponse.send (/Users/sharath/webapps/docker/node_modules/express/lib/response.js:209:10)
at ServerResponse.sendStatus (/Users/sharath/webapps/docker/node_modules/express/lib/response.js:346:15)
at Command.callback (/Users/sharath/webapps/docker/test.js:24:13)
at normal_reply (/Users/sharath/webapps/docker/node_modules/redis/index.js:714:21)
at RedisClient.return_reply (/Users/sharath/webapps/docker/node_modules/redis/index.js:816:9)
at JavascriptRedisParser.returnReply (/Users/sharath/webapps/docker/node_modules/redis/index.js:188:18)
at JavascriptRedisParser.execute (/Users/sharath/webapps/docker/node_modules/redis-parser/lib/parser.js:415:12)
Http response statuses should be integers. It cannot be strings, objects, array or like that and should begin from 100.
From your code i see that you try to do
res.sendStatus(reply);
Check reply variable. From redis incr response im thinking it's string "OK".
Which is bad.. So to fix it just use
res.sendStatus(reply ? 200 : 500);
Also check this.
http://expressjs.com/en/4x/api.html#res.sendStatus
And this
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
EDIT
If you need to send some JSON or data into front-end just do like this
res.json({thisIsMyNumber: reply});
or
res.send({thisIsMyNumber: reply});
Hope this helps.

Socket IO only session not persisting

I'm using Socket.io in a chat application I'm building. I'm trying to setup the session so that I can persist the login of the users in between sessions.
Here's the relevant code :
index.js (server)
var cookieParser = require('cookie-parser')();
var session = require('cookie-session')({ secret: 'secret' });
app.use(cookieParser);
app.use(session);
io.use(function(socket, next) {
var req = socket.handshake;
var res = {};
cookieParser(req, res, function(err) {
if (err) return next(err);
session(req, res, next);
});
});
io.on('connection', function(socket){
socket.on('test 1', function(){
socket.handshake.test = 'banana';
});
socket.on('test 2', function(){
console.log(socket.handshake.test);
});
});
chat.js (client)
var socket = io();
socket.emit('test 1');
socket.emit('test 2');
The code above works, it will print banana to the console as expected. However, if I comment out the 'test 1' like so :
var socket = io();
// socket.emit('test 1');
socket.emit('test 2');
It will print undefined to the console.
Shouldn't it still be able to print banana, since it's using a session and it's persisting between requests? I also run into the same issue if I inverse the order in which I call 'test 1' and 'test 2'.
What am I doing wrong? What am I missing for the session to persist as expected?
The problem is that you are using a cookie-session, and socket.io does not allow you to set cookies due specification of XMLHttpRequest, also you don't have a request/response in the middle of a web socket transaction, so you cannot set the response cookie.
With your middleware, you can see the cookies, but you cannot set it in socket.io.
This can be ensured with this example:
io.use(function(socket, next) {
var req = socket.request;
var res = req.res;
cookieParser(req, res, function(err) {
if (err) return next(err);
session(req, res, next);
});
});
io.on('connection', function(socket){
var req = socket.request;
console.log(req.session.test)
socket.on('test 1', function(){
req.session.test = 'banana';
});
socket.on('test 2', function(){
console.log(req.session.test);
});
});
app.get('/', function(req, res) {
console.log(req.session.test)
if (!req.session.test) {
req.session.test = 'banana request'
}
})
You can use something like redis to persist the sessions, or some 'hack' to set it in the handshake.
Another option is not use sessions at all, and just set the property in socket object or in another javascript object, but in this case you cannot share the object between servers/services.
How to share sessions with Socket.IO 1.x and Express 4.x?
https://www.npmjs.com/package/express-socket.io-session
https://github.com/rakeshok/socket.io-client-cookie
http://www.w3.org/TR/XMLHttpRequest/
https://github.com/socketio/socket.io/blob/master/examples/chat/index.js#L35-L36
How to associate properties to socket.io object in Redis Store?
There is still access to headers.
You can save data on server side for each socket: socket.my_value = "any data type";
You can save data in io.sockets object, io.my_value = "any data type"
So, example:
io.on('connection', function(socket){
var id = socket.handshake.query.id; //you can set this in client connection string http://localhost?id=user_id
//Validate user, registered etc. and if ok/
if(validateUser(id)) {
//if this is reconnect
if(io.sessions[id]) {
socket.session_id = io.sessions[id].session_id;
} else {
var session_id = random_value;
socket.session_id = some_random_value;
io.sessions[id] = {
user_id: id,
session_id: socket.session_id
};
}
registerEvent(socket);
} else {
socket.emit('auth_error', 'wrong user');
}
});
function registerEvent(socket) {
socket.on('some_event', function(data, callback) {
if(!socket.session_id) {
callback('unathorized');//or emit
socket.emit('auth_error', 'unauthorized');
}
//do what you want, user is clear
});
}

Resources