Skip to content

Commit 4271248

Browse files
committed
Merge branch 1.2 into 1.3
2 parents d93bc39 + 5d3b349 commit 4271248

File tree

6 files changed

+110
-11
lines changed

6 files changed

+110
-11
lines changed

src/v1/internal/connector.js

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ class Connection {
254254
* failing, and the connection getting ejected from the session pool.
255255
*
256256
* @param err an error object, forwarded to all current and future subscribers
257-
* @private
257+
* @protected
258258
*/
259259
_handleFatalError( err ) {
260260
this._isBroken = true;
@@ -271,6 +271,12 @@ class Connection {
271271
}
272272

273273
_handleMessage( msg ) {
274+
if (this._isBroken) {
275+
// ignore all incoming messages when this connection is broken. all previously pending observers failed
276+
// with the fatal error. all future observers will fail with same fatal error.
277+
return;
278+
}
279+
274280
const payload = msg.fields[0];
275281

276282
switch( msg.signature ) {
@@ -283,7 +289,7 @@ class Connection {
283289
try {
284290
this._currentObserver.onCompleted( payload );
285291
} finally {
286-
this._currentObserver = this._pendingObservers.shift();
292+
this._updateCurrentObserver();
287293
}
288294
break;
289295
case FAILURE:
@@ -292,7 +298,7 @@ class Connection {
292298
this._currentFailure = newError(payload.message, payload.code);
293299
this._currentObserver.onError( this._currentFailure );
294300
} finally {
295-
this._currentObserver = this._pendingObservers.shift();
301+
this._updateCurrentObserver();
296302
// Things are now broken. Pending observers will get FAILURE messages routed until
297303
// We are done handling this failure.
298304
if( !this._isHandlingFailure ) {
@@ -322,7 +328,7 @@ class Connection {
322328
else if(this._currentObserver.onError)
323329
this._currentObserver.onError(payload);
324330
} finally {
325-
this._currentObserver = this._pendingObservers.shift();
331+
this._updateCurrentObserver();
326332
}
327333
break;
328334
default:
@@ -429,6 +435,14 @@ class Connection {
429435
return this._state.initializationCompleted();
430436
}
431437

438+
/*
439+
* Pop next pending observer form the list of observers and make it current observer.
440+
* @protected
441+
*/
442+
_updateCurrentObserver() {
443+
this._currentObserver = this._pendingObservers.shift();
444+
}
445+
432446
/**
433447
* Synchronize - flush all queued outgoing messages and route their responses
434448
* to their respective handlers.
@@ -480,7 +494,8 @@ class ConnectionState {
480494
}
481495

482496
/**
483-
* Wrap the given observer to track connection's initialization state.
497+
* Wrap the given observer to track connection's initialization state. Connection is closed by the server if
498+
* processing of INIT message fails so returned observer will handle initialization failure as a fatal error.
484499
* @param {StreamObserver} observer the observer used for INIT message.
485500
* @return {StreamObserver} updated observer.
486501
*/
@@ -497,8 +512,14 @@ class ConnectionState {
497512
this._rejectPromise(error);
498513
this._rejectPromise = null;
499514
}
500-
if (observer && observer.onError) {
501-
observer.onError(error);
515+
516+
this._connection._updateCurrentObserver(); // make sure this same observer will not be called again
517+
try {
518+
if (observer && observer.onError) {
519+
observer.onError(error);
520+
}
521+
} finally {
522+
this._connection._handleFatalError(error);
502523
}
503524
},
504525
onCompleted: metaData => {

src/v1/internal/routing-util.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const CALL_GET_SERVERS = 'CALL dbms.cluster.routing.getServers';
2626
const GET_ROUTING_TABLE_PARAM = 'context';
2727
const CALL_GET_ROUTING_TABLE = 'CALL dbms.cluster.routing.getRoutingTable({' + GET_ROUTING_TABLE_PARAM + '})';
2828
const PROCEDURE_NOT_FOUND_CODE = 'Neo.ClientError.Procedure.ProcedureNotFound';
29+
const UNAUTHORIZED_CODE = 'Neo.ClientError.Security.Unauthorized';
2930

3031
export default class RoutingUtil {
3132

@@ -49,10 +50,14 @@ export default class RoutingUtil {
4950
// throw when getServers procedure not found because this is clearly a configuration issue
5051
throw newError('Server ' + routerAddress + ' could not perform routing. ' +
5152
'Make sure you are connecting to a causal cluster', SERVICE_UNAVAILABLE);
53+
} else if (error.code === UNAUTHORIZED_CODE) {
54+
// auth error is a sign of a configuration issue, rediscovery should not proceed
55+
throw error;
56+
} else {
57+
// return nothing when failed to connect because code higher in the callstack is still able to retry with a
58+
// different session towards a different router
59+
return null;
5260
}
53-
// return nothing when failed to connect because code higher in the callstack is still able to retry with a
54-
// different session towards a different router
55-
return null;
5661
});
5762
}
5863

test/internal/connector.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ describe('connector', () => {
145145

146146
connection.initialize('mydriver/0.0.0', basicAuthToken(), {
147147
onCompleted: metaData => {
148+
expect(connection.isOpen()).toBeTruthy();
148149
expect(metaData).toBeDefined();
149150
done();
150151
},
@@ -156,6 +157,7 @@ describe('connector', () => {
156157

157158
connection.initialize('mydriver/0.0.0', basicAuthToken(), {
158159
onError: error => {
160+
expect(connection.isOpen()).toBeFalsy();
159161
expect(error).toBeDefined();
160162
done();
161163
},
@@ -174,6 +176,30 @@ describe('connector', () => {
174176
connection.initialize('mydriver/0.0.0', basicAuthToken());
175177
});
176178

179+
it('should fail all new observers after initialization error', done => {
180+
const connection = connect('bolt://localhost:7474'); // wrong port
181+
182+
connection.initialize('mydriver/0.0.0', basicAuthToken(), {
183+
onError: initialError => {
184+
expect(initialError).toBeDefined();
185+
186+
connection.run('RETURN 1', {}, {
187+
onError: error1 => {
188+
expect(error1).toEqual(initialError);
189+
190+
connection.initialize('mydriver/0.0.0', basicAuthToken(), {
191+
onError: error2 => {
192+
expect(error2).toEqual(initialError);
193+
194+
done();
195+
}
196+
});
197+
}
198+
});
199+
},
200+
});
201+
});
202+
177203
function packedHandshakeMessage() {
178204
const result = alloc(4);
179205
result.putInt32(0, 1);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
!: AUTO RESET
2+
!: AUTO PULL_ALL
3+
4+
C: INIT "neo4j-javascript/0.0.0-dev" {"credentials": "neo4j", "scheme": "basic", "principal": "neo4j"}
5+
S: FAILURE {"code": "Neo.ClientError.Security.Unauthorized", "message": "Some server auth error message"}
6+
S: <EXIT>

test/v1/driver.test.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ describe('driver', () => {
8080

8181
it('should fail early on wrong credentials', done => {
8282
// Given
83-
driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "who would use such a password"));
83+
driver = neo4j.driver("bolt://localhost", wrongCredentials());
8484

8585
// Expect
8686
driver.onError = err => {
@@ -93,6 +93,16 @@ describe('driver', () => {
9393
startNewTransaction(driver);
9494
});
9595

96+
it('should fail queries on wrong credentials', done => {
97+
driver = neo4j.driver('bolt://localhost', wrongCredentials());
98+
99+
const session = driver.session();
100+
session.run('RETURN 1').catch(error => {
101+
expect(error.code).toEqual('Neo.ClientError.Security.Unauthorized');
102+
done();
103+
});
104+
});
105+
96106
it('should indicate success early on correct credentials', done => {
97107
// Given
98108
driver = neo4j.driver("bolt://localhost", sharedNeo4j.authToken);
@@ -211,4 +221,8 @@ describe('driver', () => {
211221
expect(session.beginTransaction()).toBeDefined();
212222
}
213223

224+
function wrongCredentials() {
225+
return neo4j.auth.basic('neo4j', 'who would use such a password');
226+
}
227+
214228
});

test/v1/routing.driver.boltkit.it.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,6 +1831,33 @@ describe('routing driver', () => {
18311831
});
18321832
});
18331833

1834+
it('should fail rediscovery on auth error', done => {
1835+
if (!boltkit.BoltKitSupport) {
1836+
done();
1837+
return;
1838+
}
1839+
1840+
const kit = new boltkit.BoltKit();
1841+
const router = kit.start('./test/resources/boltkit/failed_auth.script', 9010);
1842+
1843+
kit.run(() => {
1844+
const driver = newDriver('bolt+routing://127.0.0.1:9010');
1845+
const session = driver.session();
1846+
session.run('RETURN 1').catch(error => {
1847+
expect(error.code).toEqual('Neo.ClientError.Security.Unauthorized');
1848+
expect(error.message).toEqual('Some server auth error message');
1849+
1850+
session.close(() => {
1851+
driver.close();
1852+
router.exit(code => {
1853+
expect(code).toEqual(0);
1854+
done();
1855+
});
1856+
});
1857+
});
1858+
});
1859+
});
1860+
18341861
function moveNextDateNow30SecondsForward() {
18351862
const currentTime = Date.now();
18361863
hijackNextDateNowCall(currentTime + 30 * 1000 + 1);

0 commit comments

Comments
 (0)