express-ntlm returning the wrong user - node.js

I'm using express-ntlm to get the current user's windows ID in an intranet setting. It works fine most of the time, but occasionally it will return the ID of a completely different person. I'm guessing this is something to do with sessions maybe?
const ntlm = require('express-ntlm');
module.exports = app => {
app.use(
ntlm({
debug: function() {
var args = Array.prototype.slice.apply(arguments);
console.log.apply(null, args);
},
domain: 'MS',
domaincontroller: 'ldap://something.com'
})
);
app.post('/get-user-details/', (req, res) => {
console.log(req.ntlm.UserName); //Returns correct user most of the time, but sometimes it returns different person who open site at the same time
});

Unfortunately NTLM authenticates connections, not sessions. Which was fine in the past, but doesn't make sense anymore, since browser tend to open multiple connections at once to speed up page loading and reverse proxies are sharing connections to the backend. That's where the problem is: Your reverse proxy will reuse already authenticated connections to the backend, and therefore mix up users. To mitigate this issue, you have to make sure your reverse proxy has NTLM support enabled.
There is still an open pull request for express-ntlm that adds a Keep-Alive property which might solve this issue, unfortunately it's widely untested and first needs to be verified.

Related

what nodejs, react-native IP address has to be used in endpoint when app is in google play store?

So I made a react native app with nodeJS and in order to connect nodejs backend to react native frontend I had to create an endpoint like such:
app.post("/send_mail", cors(), async (req, res) => {
let {text} = req.body
var transport = nodemailer.createTransport({
host: "smtp.mailtrap.io",
port: 2525,
auth: {
user: "usertoken",
pass: "password"
}
})
await transport.sendMail({
from: "email#email.com",
to: "email2#email.com",
subject: "message",
html: `<p>${text}</p>`
})
})
and in react native frontend call function like that :
const handleSend = async() => {
try {
await axios.post("http://192.168.0.104:3333/send_mail", { //localhost
text: placeHolderLocationLatLon
})
} catch (error) {
setHandleSetError(error)
}}
and it works fine in the local environment. But in the google play store, this IP address doesn't work because it's my localhost ipv4. I tried to use localhost:3333, but that doesn't work too. I can't find anywhere described anything about what IP has to be used in the google play store. Does anyone know how I should make this endpoint? Thank you
You can't just host a service by yourself like that (typically your back-end). Well, you can but not with your knowledge level. It would be inefficient (as in you'd have to keep your computer up 24/7) and present security issues.
Does anyone know how I should make this endpoint?
Don't get me wrong, your endpoint is fine in itself. You're just lacking the networking part.
1) For testing purposes ONLY!
If this app is only for testing purposes on your end, and won't be part of a final product that'll be present on the Google Store, there's a way you can achieve this, called ngrok.
It takes your locally-running project and provides it a https URL anyone can use to access it. I'll let you read the docs to find out how it works.
2) A somewhat viable solution (be extremely careful)
Since ngrok will provide a new URL everytime you run it with your local project, it's not very reliable in the long term.
What you could do, if you have a static IP address, is registering a domain name and link it to your IP address, and do all the shenanigans behind it (figuring out how to proxy incoming traffic to your machine and project, and so on). But honestly, it's way too cumbersome and it implies that you have valuable knowledge when it comes to securing your own private network, which I doubt.
3) A long-lasting, viable solution
You could loan a preemptive machine on GCP and run your back-end service on it. Of course, you would still have to figure out some networking things and configs, but it's way more reliable than solution 2 and 1 if your whole app aims to be public.
Google gives you free credit (I think it's like 200 or 250€, can't recall exactly) to begin with their stuff, and running a preemptive machine is really cheap.

Is 'long-term credentials' authentication mechanism *required* for WebRTC to work with TURN servers?

I'm intending to run my own TURN service for a WebRTC app with coturn - https://code.google.com/p/coturn/. The manual says this about authentication and credentials:
...
-a, --lt-cred-mech
Use long-term credentials mechanism (this one you need for WebRTC usage). This option can be used with
either flat file user database or PostgreSQL DB or MySQL DB or MongoDB or Redis for user keys storage.
...
This client code example also suggests that credentials are required for TURN:
// use google's ice servers
var iceServers = [
{ url: 'stun:stun.l.google.com:19302' }
// { url: 'turn:192.158.29.39:3478?transport=udp',
// credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
// username: '28224511:1379330808'
// },
// { url: 'turn:192.158.29.39:3478?transport=tcp',
// credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
// username: '28224511:1379330808'
// }
];
Are they always required? (Coturn can be run without any auth mechanism, but it isn't clear from the man page whether it's strictly required for WebRTC to work)
If required, can I just create one set of credentials and use that for all clients? (The client code example is obviously just for demonstration, but it seems to suggest that you might hard-code the credentials into the clientside code. If this is not possible/recommendable, what would be the recommended way of passing out appropriate credentials to the clientside code?)
After testing it seems that passing credentials is required for clientside code to work (you get an error in the console otherwise).
Leaving the "no-auth" option enabled in Coturn (or leaving both lt-cred-mech and st-cred-mech commented) but still passing credentials in the application JS also doesn't work, as the TURN messages are somehow signed using the password credential. Maybe Coturn isn't expecting the clients to send authentication details if it's running in no-auth mode, so it doesn't know how to interpret the messages.
Solution
Turning on lt-cred-mech and hard-coding the username and password into both the Coturn config file, and the JS for the application, seems to work. There are commented out "static user" entries in the Coturn configuration file - use the plain password format as opposed to key format.
Coturn config (this is the entire config file I got it working with):
fingerprint
lt-cred-mech
#single static user details for long-term authentication:
user=username1:password1
#your domain here:
realm=mydomain.com
ICE server list from web app JS:
var iceServers = [
{
url: 'turn:123.234.123.23:3478', //your TURN server address here
credential: 'password1', //actual hardcoded value
username: 'username1' //actual hardcoded value
}
];
Obviously this offers no actual security for the TURN server, as the credentials are visible to anyone (so anyone can use up bandwidth and processor time using it as a relay).
In summary:
yes, long-term authentication is required for WebRTC to use TURN.
yes, it seems that you can just hard-code a single set of credentials for everyone to use -- coturn isn't bothered that two clients get allocations simultaneously with the same credentials.
one possible solution for proper security with minimal hassle would be a TURN REST API, which Coturn supports.

NodeJS / express / connect-redis: no error on redis being down; session missing

i'm building a web application on NodeJS using express; the session store is a Redis instance which i talk to using connect-redis. the usual bits look like, well, usual:
RedisStore = ( require 'connect-redis' ) express
express_options =
...
'session':
'secret': 'xxxxxxxx'
'store': new RedisStore host: '127.0.0.1', port: 6379, ttl: 2 * weeks
'cookie': maxAge: 2 * weeks
app = express()
# Middleware
...
app.use express.cookieParser 'yyyyyy'
app.use express.session express_options[ 'session' ]
...
this does work well as such. however, i have not demonized Redis yet. after starting the server (but not Redis) and re-issuing an HTTP request from the browser, the application (apparently, naturally) failed to recognize yesterday's session cookie. to be more precise, the point of failure was
request.session.regenerate =>
request.session.user = uid_hint
in a login view, and the message was TypeError: Cannot call method 'regenerate' of undefined. now the question is:
(1) is my impression true that express won't balk at me when i try to use a session middleware that is configured to ask for data on a specific port, and yet that port is not served at all? if so, why is there no error message?
(2) what is a good way to test for that condition? i'd like a helpful message at that point.
(3) given that a DB instance may become unavailable at any one time—especially when it is separated by a network from the app server—what are best practices in such a case? fall back to memory-based sessions? refuse to serve clients?
(4) let us assume we fall back on another session storage mechanism. now all existing sessions have become invalid, right? unless we can decide whether a given signed SID coming in from a client is computationally valid in the absence of an existing record. those sessions will still be devoid of data, so it's not clear how useful that would be. we might as well throw away the old session and start a new one. but how? request.session = new ( require 'express' ).session.Session(), maybe?
Bonus Points (i'm aware some people will scoff at me for asking so many different things, but i think a discussion centered on sessions & cookies should include the below aspect)
thinking it over, i'm somewhat unhappy i'm using Redis at all—not because it's Redis, but because i have yet another DB make in the app. a theoretical alternative to using a session DB could be a reasonably secure way to keep all session data (NOT the user ID data, NO credit card numbers—just general stuff like which page did you come from etc) within the cookie. that way, any one server process can accept a request and has all the session data at hand to respond properly. i'm aware that cookie storage space is limited (like 4kB), but that might prove enough still. any middleware to recommend here? or is the idea dumb / insecure / too 1990?
connect-reddis listens to redis for the error event
./lib/connect-redis.js
self.client.on('error', function () { self.emit('disconnect'); });
So after creating the store, listen to the disconnect event
var store = new RedisStore({
host: 'localhost',
port: 6379,
db: 2,
pass: 'RedisPASS'
});
store.on('disconnect', function(){
console.log('disconnect');
});

Securing Socket.io

I am using a Node.js based https server that authenticates using HTTP Basic (which is fine as the data are sent over the SSL encrypted connection).
Now I want to provide a Socket.io connection which should be
encrypted and
for authenticated users only.
The question is how to do this. I already found out that I need to specify { secure: true } in the client JavaScript code when connecting to the socket, but how do I force on the server-side that socket connections can only be run over SSL, and that it works only for authenticated users?
I guess that the SSL thing is the easy part, as the Socket.io server is bound to the https server only, so it should run using SSL only, and there should be no possibility to run it over an (additionally) running http server, right?
Regarding the other thing I have not the slightest idea of how to ensure that socket connections can only be established once the user successfully authenticated using HTTP Basic.
Any ideas?
Edit: Of course OP is right in their other answer; what's more, with socket.io >1.0 you might use socket.io-express-session.
Original answer:
Socket.io supports authorization via the io.set('authorization', callback) mechanism. See the relevant documentation: Authorizing. Here's a simple example (this authenticates using a cookie-based session, and a connect/express session store -- if you need something else, you just implement another 'authorization' handler):
var utils = require('connect').utils;
// Set up a session store of some kind
var sessionId = 'some id';
var sessionStore = new MemoryStore();
// Make express app use the session store
app.use(express.session({store: sessionStore, key: sessionId});
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
var cookie = utils.parseCookie(handshakeData.headers.cookie);
if(!(sessionId in cookie)) {
return callback(null, false);
}
sessionStore.get(cookie[sessionId], function (err, session) {
if(err) {
return callback(err);
}
if(!('user' in session)) {
return callback(null, false);
}
// This is an authenticated user!
// Store the session on handshakeData, it will be available in connection handler
handshakeData.session = session;
callback(null, true);
});
});
});
Although the answer by Linus is basically right, I now solved it in a more easy way using the session.socket.io - which basically does the same thing, but with way less custom-code to write.
Setting the socket to be secured is done by:
setting the secure flag - as you wrote ({secure: true})
OR by using https protocol when creating the server (instead of http) - which will set the secure flag to be true automatically
Good lib that simplify the authentication process is: https://github.com/auth0/socketio-jwt

Node.js HTTPS 400 Error - 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'

I'm writing a Node.js app that has to request some data from one of our internal APIs. The tricky part is that the server I'm requesting data from has certain limitations:
The request must be made on HTTPS protocol (not HTTP)
The request must be made using a LAN IP address, because the domain name will not work internally
The request must appear to be requesting from the external domain name, because that is what the Virtual Host is setup for.
In order to do this, I'm running a bit of code that looks like this:
var headers = {
Host: externalHostname,
Hostname: externalHostname,
};
var options = {
host: InternalIP,
path: path,
method: 'GET',
headers: headers
};
var req = https.request(options, function(res) {
res.setEncoding('utf8');
var data = "";
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
//Do something with that data
});
res.on('error', function(err) {
console.log("Error during HTTP request");
console.log(err);
});
});
req.end();
Unfortunately, I'm getting a 400 (Your browser sent a request that this server could not understand) error as a response. I've double and triple checked that the hostname, ip address, and path name are all correct (I can test them from within my browser, and all is good).
I did an output of my response variable (res), and am receiving an authorizationError value of UNABLE_TO_VERIFY_LEAF_SIGNATURE. I'm not sure what that is, or if it's my problem, but it's the only useful bit of information I could find.
I put a full output of my response variable here.
Any ideas on what might be causing this?
Update: I figured it out! I was trying to authenticate with the server by passing a ?PHPSESSID=asdad GET variable, but they have that disabled. I was able to make it work by setting PHPSESSID in the Cookie header.
set this process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
I hit here while debugging UNABLE_TO_VERIFY_LEAF_SIGNATURE error in an external api call from my nodejs server.
This error is hit when there is error during verification of the server certificate. While it is not recommended to disable the security by the following code (which is also available as another answer), it helps to verify if you are chasing the right bug. In other words, if putting this also does not fix it, there is something else wrong with the code.
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
In my case, there was silly bug & request was going to localhost itself. Even after putting the above, request failed and that helped me uncover the bug.
Having said that, it is not recommended to use this as a solution. Rather figure out how you can provide additional certificates by setting agent:false & ca:[fs.readFileSync('root-cert.pem')] options. https.request documentation provides details. While chasing my bug, I also found few more useful resources:
ssl-tools.net site provides root & intermediate certificates. For example: Baltimore CyberTrust Root used by lives.api.net
ssl-root-cas module claims to provide additional CA certificates as used by popular browsers. I have not verified the claim.
openssl s_client -connect apis.live.net:443 -- prints the certificate chain. you need to replace the last parameter (url & port) with what you are connecting to.
check this out from the tls.js source in the latest node.js (there is much more this is what I think you need)
// AUTHENTICATION MODES
//
// There are several levels of authentication that TLS/SSL supports.
// Read more about this in "man SSL_set_verify".
//
// 1. The server sends a certificate to the client but does not request a
// cert from the client. This is common for most HTTPS servers. The browser
// can verify the identity of the server, but the server does not know who
// the client is. Authenticating the client is usually done over HTTP using
// login boxes and cookies and stuff.
//
// 2. The server sends a cert to the client and requests that the client
// also send it a cert. The client knows who the server is and the server is
// requesting the client also identify themselves. There are several
// outcomes:
//
// A) verifyError returns null meaning the client's certificate is signed
// by one of the server's CAs. The server know's the client idenity now
// and the client is authorized.
//
// B) For some reason the client's certificate is not acceptable -
// verifyError returns a string indicating the problem. The server can
// either (i) reject the client or (ii) allow the client to connect as an
// unauthorized connection.
//
// The mode is controlled by two boolean variables.
//
// requestCert
// If true the server requests a certificate from client connections. For
// the common HTTPS case, users will want this to be false, which is what
// it defaults to.
//
// rejectUnauthorized
// If true clients whose certificates are invalid for any reason will not
// be allowed to make connections. If false, they will simply be marked as
// unauthorized but secure communication will continue. By default this is
// false.
//
set rejectUnauthorized to false in your options and cross your fingers...let me know if the output changes.
Set this process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
Fixed the UNABLE_TO_VERIFY_LEAF_SIGNATURE problem for superagent.
Try this in command line:
npm config set strict-ssl false
It worked for me on mac.

Resources