Skip to content

Commit

Permalink
Merge pull request #309 from deepstreamIO/feature/#226-terminate-unau…
Browse files Browse the repository at this point in the history
…thenticated-connections

Feature/#226 terminate unauthenticated connections
  • Loading branch information
yasserf committed Jul 28, 2016
2 parents 6048246 + 498eefa commit c3cb842
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 6 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@
### Bug Fixes

- CLI: installer for connectors sometimes fail to download (and extract) the archive [#305](https://github.com/deepstreamIO/deepstream.io/issues/305)
- CLI: detach mode fails on package binary [#246](https://github.com/deepstreamIO/deepstream.io/issues/246)
- Auth: File authentication doesn't contain `serverData` and `clientData` [#304](https://github.com/deepstreamIO/deepstream.io/issues/304)

###### Read data using `FileAuthentication` using clientData and serverData rather than data

```yaml
userA:
password: tsA+yfWGoEk9uEU/GX1JokkzteayLj6YFTwmraQrO7k=75KQ2Mzm
serverData:
role: admin
clientData:
nickname: Dave
```

### Features

###### Make connection timeout
Expand Down
1 change: 1 addition & 0 deletions conf/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ plugins:
storageExclusion: null # a RegExp that matches recordNames. If it matches, the record's data won't be stored in the db

# Security
unauthenticatedClientTimeout: 180000 # amount of time a connection can remain open while not being logged in
maxAuthAttempts: 3 # invalid login attempts before the connection is cut
logInvalidAuthData: true # if true, the logs will contain the cleartext username / password of invalid login attempts
maxMessageSize: 1048576 # maximum allowed size of an individual message in bytes
Expand Down
1 change: 1 addition & 0 deletions src/constants/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ exports.EVENT.MESSAGE_PARSE_ERROR = 'MESSAGE_PARSE_ERROR';
exports.EVENT.MAXIMUM_MESSAGE_SIZE_EXCEEDED = 'MAXIMUM_MESSAGE_SIZE_EXCEEDED';
exports.EVENT.MESSAGE_DENIED = 'MESSAGE_DENIED';
exports.EVENT.INVALID_MESSAGE_DATA = 'INVALID_MESSAGE_DATA';
exports.EVENT.CONNECTION_AUTHENTICATION_TIMEOUT = 'CONNECTION_AUTHENTICATION_TIMEOUT';
exports.EVENT.UNKNOWN_TOPIC = 'UNKNOWN_TOPIC';
exports.EVENT.UNKNOWN_ACTION = 'UNKNOWN_ACTION';
exports.EVENT.MULTIPLE_SUBSCRIPTIONS = 'MULTIPLE_SUBSCRIPTIONS';
Expand Down
1 change: 1 addition & 0 deletions src/default-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ exports.get = function() {
/*
* Security
*/
unauthenticatedClientTimeout: 180000,
maxAuthAttempts: 3,
logInvalidAuthData: true,
maxMessageSize: 1048576,
Expand Down
39 changes: 34 additions & 5 deletions src/message/connection-endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ ConnectionEndpoint.prototype._checkClosed = function() {
ConnectionEndpoint.prototype._onConnection = function( endpoint, socket ) {
var socketWrapper = new SocketWrapper( socket, this._options ),
handshakeData = socketWrapper.getHandshakeData(),
logMsg;
logMsg,
disconnectTimer;

if( endpoint === ENGINE_IO ) {
logMsg = 'from ' + handshakeData.referer + ' (' + handshakeData.remoteAddress + ')' + ' via engine.io';
Expand All @@ -237,8 +238,16 @@ ConnectionEndpoint.prototype._onConnection = function( endpoint, socket ) {
}

this._options.logger.log( C.LOG_LEVEL.INFO, C.EVENT.INCOMING_CONNECTION, logMsg );
socketWrapper.authCallBack = this._authenticateConnection.bind( this, socketWrapper );

if( this._options.unauthenticatedClientTimeout !== null ) {
disconnectTimer = setTimeout( this._processConnectionTimeout.bind( this, socketWrapper ), this._options.unauthenticatedClientTimeout );
socketWrapper.socket.once( 'close', clearTimeout.bind( null, disconnectTimer ) );
}

socketWrapper.authCallBack = this._authenticateConnection.bind( this, socketWrapper, disconnectTimer );

socketWrapper.sendMessage( C.TOPIC.CONNECTION, C.ACTIONS.ACK );

socket.on( 'message', socketWrapper.authCallBack );
};

Expand All @@ -248,13 +257,14 @@ ConnectionEndpoint.prototype._onConnection = function( endpoint, socket ) {
* the case and - if so - forwards it to the permission handler for authentication
*
* @param {SocketWrapper} socketWrapper
* @param {Timeout} disconnectTimeout
* @param {String} authMsg
*
* @private
*
* @returns {void}
*/
ConnectionEndpoint.prototype._authenticateConnection = function( socketWrapper, authMsg ) {
ConnectionEndpoint.prototype._authenticateConnection = function( socketWrapper, disconnectTimeout, authMsg ) {
var msg = messageParser.parse( authMsg )[ 0 ],
logMsg,
authData,
Expand Down Expand Up @@ -297,7 +307,7 @@ ConnectionEndpoint.prototype._authenticateConnection = function( socketWrapper,
this._options.authenticationHandler.isValidUser(
socketWrapper.getHandshakeData(),
authData,
this._processAuthResult.bind( this, authData, socketWrapper )
this._processAuthResult.bind( this, authData, socketWrapper, disconnectTimeout )
);
};

Expand Down Expand Up @@ -377,6 +387,23 @@ ConnectionEndpoint.prototype._processInvalidAuth = function( clientData, authDat
}
};

/**
* Callback for connections that have not authenticated succesfully within
* the expected timeframe
*
* @param {SocketWrapper} socketWrapper
*
* @private
*
* @returns {void}
*/
ConnectionEndpoint.prototype._processConnectionTimeout = function( socketWrapper ) {
var log = 'connection has not authenticated successfully in the expected time';
this._options.logger.log( C.LOG_LEVEL.INFO, C.EVENT.CONNECTION_AUTHENTICATION_TIMEOUT, log );
socketWrapper.sendError( C.TOPIC.CONNECTION, C.EVENT.CONNECTION_AUTHENTICATION_TIMEOUT, messageBuilder.typed( log ) );
socketWrapper.destroy();
};

/**
* Callback for the results returned by the permissionHandler
*
Expand All @@ -389,9 +416,11 @@ ConnectionEndpoint.prototype._processInvalidAuth = function( clientData, authDat
*
* @returns {void}
*/
ConnectionEndpoint.prototype._processAuthResult = function( authData, socketWrapper, isAllowed, userData ) {
ConnectionEndpoint.prototype._processAuthResult = function( authData, socketWrapper, disconnectTimeout, isAllowed, userData ) {
userData = userData || {};

clearTimeout( disconnectTimeout );

if( isAllowed === true ) {
this._registerAuthenticatedSocket( socketWrapper, userData );
} else {
Expand Down
21 changes: 21 additions & 0 deletions test/message/connection-endpointSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var proxyquire = require( 'proxyquire' ).noCallThru(),
connectionEndpoint;

options = {
unauthenticatedClientTimeout: null,
permissionHandler: permissionHandlerMock,
authenticationHandler: authenticationHandlerMock,
logger: { log: function( logLevel, event, msg ){ lastLoggedMessage = msg; } },
Expand Down Expand Up @@ -149,6 +150,26 @@ describe( 'connection endpoint', function() {
});
});

describe( 'disconnects client if authentication timeout is exceeded', function(){

beforeAll( function() {
options.unauthenticatedClientTimeout = 100;
socketMock = engineIoMock.simulateConnection();
} );

afterAll( function() {
options.unauthenticatedClientTimeout = null;
} );

it( 'disconnects client after timeout and sends force close', function( done ){
setTimeout( function() {
expect( socketMock.lastSendMessage ).toBe( _msg( 'C|E|CONNECTION_AUTHENTICATION_TIMEOUT|Sconnection has not authenticated successfully in the expected time+') );
expect( socketMock.isDisconnected ).toBe( true );
done();
}, 150 );
});
});

describe( 'doesn\'t log credentials if logInvalidAuthData is set to false', function(){
it( 'creates the connection endpoint', function(){
options.logInvalidAuthData = false;
Expand Down

0 comments on commit c3cb842

Please sign in to comment.