@celerispay/hazelcast-client 3.12.5-8 โ 3.12.7-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES_UNCOMMITTED.md +53 -0
- package/FAULT_TOLERANCE_IMPROVEMENTS.md +208 -0
- package/HAZELCAST_CLIENT_EVOLUTION.md +402 -0
- package/lib/HeartbeatService.js +11 -2
- package/lib/PartitionService.d.ts +0 -3
- package/lib/PartitionService.js +3 -32
- package/lib/invocation/ClientConnection.js +41 -11
- package/lib/invocation/ClientConnectionManager.d.ts +54 -0
- package/lib/invocation/ClientConnectionManager.js +210 -4
- package/lib/invocation/ClusterService.d.ts +47 -0
- package/lib/invocation/ClusterService.js +164 -4
- package/lib/invocation/ConnectionAuthenticator.d.ts +11 -0
- package/lib/invocation/ConnectionAuthenticator.js +85 -12
- package/lib/invocation/CredentialPreservationService.d.ts +141 -0
- package/lib/invocation/CredentialPreservationService.js +377 -0
- package/lib/invocation/HazelcastFailoverManager.d.ts +102 -0
- package/lib/invocation/HazelcastFailoverManager.js +285 -0
- package/package.json +7 -6
|
@@ -25,6 +25,7 @@ var MemberAttributeEvent_1 = require("../core/MemberAttributeEvent");
|
|
|
25
25
|
var MembershipEvent_1 = require("../core/MembershipEvent");
|
|
26
26
|
var UuidUtil_1 = require("../util/UuidUtil");
|
|
27
27
|
var Address = require("../Address");
|
|
28
|
+
var HazelcastFailoverManager_1 = require("./HazelcastFailoverManager");
|
|
28
29
|
var MemberEvent;
|
|
29
30
|
(function (MemberEvent) {
|
|
30
31
|
MemberEvent[MemberEvent["ADDED"] = 1] = "ADDED";
|
|
@@ -58,6 +59,7 @@ var ClusterService = /** @class */ (function () {
|
|
|
58
59
|
this.members = [];
|
|
59
60
|
this.startReconnectionTask();
|
|
60
61
|
this.startStateLoggingTask();
|
|
62
|
+
this.failoverManager = new HazelcastFailoverManager_1.HazelcastFailoverManager(client, this.logger);
|
|
61
63
|
}
|
|
62
64
|
/**
|
|
63
65
|
* Starts cluster service.
|
|
@@ -109,6 +111,18 @@ var ClusterService = /** @class */ (function () {
|
|
|
109
111
|
ClusterService.prototype.getOwnerConnection = function () {
|
|
110
112
|
return this.ownerConnection;
|
|
111
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Returns whether failover is currently in progress
|
|
116
|
+
*/
|
|
117
|
+
ClusterService.prototype.isFailoverInProgress = function () {
|
|
118
|
+
return this.failoverInProgress;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Returns the list of known addresses in the cluster
|
|
122
|
+
*/
|
|
123
|
+
ClusterService.prototype.getKnownAddresses = function () {
|
|
124
|
+
return this.knownAddresses.slice(); // Return a copy to prevent external modification
|
|
125
|
+
};
|
|
112
126
|
/**
|
|
113
127
|
* Returns the list of members in the cluster.
|
|
114
128
|
* @returns
|
|
@@ -228,7 +242,6 @@ var ClusterService = /** @class */ (function () {
|
|
|
228
242
|
}
|
|
229
243
|
};
|
|
230
244
|
ClusterService.prototype.triggerFailover = function () {
|
|
231
|
-
var _this = this;
|
|
232
245
|
var now = Date.now();
|
|
233
246
|
if (this.failoverInProgress || (now - this.lastFailoverAttempt) < this.failoverCooldown) {
|
|
234
247
|
this.logger.debug('ClusterService', 'Failover already in progress or too soon since last attempt');
|
|
@@ -236,7 +249,57 @@ var ClusterService = /** @class */ (function () {
|
|
|
236
249
|
}
|
|
237
250
|
this.failoverInProgress = true;
|
|
238
251
|
this.lastFailoverAttempt = now;
|
|
239
|
-
this
|
|
252
|
+
// Check if this is a single-node scenario
|
|
253
|
+
var isSingleNode = this.knownAddresses.length === 1;
|
|
254
|
+
if (isSingleNode) {
|
|
255
|
+
this.logger.info('ClusterService', '๐ SINGLE-NODE CLUSTER RESET: Node restart detected, starting fresh...');
|
|
256
|
+
this.handleSingleNodeClusterReset();
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
this.logger.info('ClusterService', '๐ Starting failover process - SERVER-FIRST APPROACH...');
|
|
260
|
+
// SERVER-FIRST: No credential preservation needed
|
|
261
|
+
// We trust the server will provide correct member information
|
|
262
|
+
this.logger.info('ClusterService', '๐ฏ SERVER-FIRST: No credential management - trusting server data');
|
|
263
|
+
this.handleMultiNodeFailover();
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* Handles single-node cluster reset - treats node restart as fresh cluster
|
|
268
|
+
*/
|
|
269
|
+
ClusterService.prototype.handleSingleNodeClusterReset = function () {
|
|
270
|
+
var _this = this;
|
|
271
|
+
this.logger.info('ClusterService', '๐งน SINGLE-NODE RESET: Clearing all credentials and state...');
|
|
272
|
+
// Clear all stored credentials - node restart means fresh cluster
|
|
273
|
+
this.client.getConnectionManager().clearAllCredentials();
|
|
274
|
+
// Reset client UUIDs - will be assigned fresh by server
|
|
275
|
+
this.uuid = null;
|
|
276
|
+
this.ownerUuid = null;
|
|
277
|
+
// Log state before reset
|
|
278
|
+
this.logCurrentState();
|
|
279
|
+
// Force cleanup of all dead connections
|
|
280
|
+
this.client.getConnectionManager().forceCleanupDeadConnections();
|
|
281
|
+
// Clear partition information
|
|
282
|
+
this.client.getPartitionService().clearPartitionTable();
|
|
283
|
+
// Direct reconnection without waiting for member events
|
|
284
|
+
this.logger.info('ClusterService', '๐ SINGLE-NODE RESET: Attempting direct reconnection...');
|
|
285
|
+
this.connectToCluster()
|
|
286
|
+
.then(function () {
|
|
287
|
+
_this.logger.info('ClusterService', 'โ
Single-node cluster reset completed successfully');
|
|
288
|
+
_this.logCurrentState();
|
|
289
|
+
})
|
|
290
|
+
.catch(function (error) {
|
|
291
|
+
_this.logger.error('ClusterService', 'Single-node cluster reset failed', error);
|
|
292
|
+
_this.logCurrentState();
|
|
293
|
+
})
|
|
294
|
+
.finally(function () {
|
|
295
|
+
_this.failoverInProgress = false;
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* Handles multi-node failover - preserves existing logic
|
|
300
|
+
*/
|
|
301
|
+
ClusterService.prototype.handleMultiNodeFailover = function () {
|
|
302
|
+
var _this = this;
|
|
240
303
|
// Log state before failover
|
|
241
304
|
this.logCurrentState();
|
|
242
305
|
// Force cleanup of all dead connections to prevent leakage
|
|
@@ -246,7 +309,8 @@ var ClusterService = /** @class */ (function () {
|
|
|
246
309
|
// Attempt to reconnect to cluster
|
|
247
310
|
this.connectToCluster()
|
|
248
311
|
.then(function () {
|
|
249
|
-
_this.logger.info('ClusterService', 'Failover completed successfully');
|
|
312
|
+
_this.logger.info('ClusterService', 'โ
Failover completed successfully - SERVER-FIRST approach');
|
|
313
|
+
// No credential management needed - server handles everything
|
|
250
314
|
_this.logCurrentState(); // Log state after successful failover
|
|
251
315
|
})
|
|
252
316
|
.catch(function (error) {
|
|
@@ -345,7 +409,7 @@ var ClusterService = /** @class */ (function () {
|
|
|
345
409
|
+ ', attempt period: ' + attemptPeriod + ', down addresses: ' + this.getDownAddressesInfo());
|
|
346
410
|
if (this.knownAddresses.length <= index) {
|
|
347
411
|
remainingAttemptLimit = remainingAttemptLimit - 1;
|
|
348
|
-
if (remainingAttemptLimit
|
|
412
|
+
if (remainingAttemptLimit <= 0) {
|
|
349
413
|
var errorMessage = 'Unable to connect to any of the following addresses: ' +
|
|
350
414
|
this.knownAddresses.map(function (element) {
|
|
351
415
|
return element.toString();
|
|
@@ -461,6 +525,8 @@ var ClusterService = /** @class */ (function () {
|
|
|
461
525
|
};
|
|
462
526
|
ClusterService.prototype.memberAdded = function (member) {
|
|
463
527
|
this.members.push(member);
|
|
528
|
+
// Handle member added and update preserved credentials
|
|
529
|
+
this.handleMemberAdded(member);
|
|
464
530
|
var membershipEvent = new MembershipEvent_1.MembershipEvent(member, MemberEvent.ADDED, this.members);
|
|
465
531
|
this.fireMembershipEvent(membershipEvent);
|
|
466
532
|
};
|
|
@@ -494,6 +560,76 @@ var ClusterService = /** @class */ (function () {
|
|
|
494
560
|
var membershipEvent = new MembershipEvent_1.MembershipEvent(member, MemberEvent.REMOVED, this.members);
|
|
495
561
|
this.fireMembershipEvent(membershipEvent);
|
|
496
562
|
};
|
|
563
|
+
/**
|
|
564
|
+
* Performs comprehensive credential cleanup when cluster membership changes
|
|
565
|
+
* This ensures ALL credentials are consistent with the current cluster owner UUID
|
|
566
|
+
* @param connectionManager The connection manager instance
|
|
567
|
+
* @param currentClusterOwnerUuid The current cluster owner UUID
|
|
568
|
+
*/
|
|
569
|
+
/**
|
|
570
|
+
* Handles member added event - SERVER-FIRST APPROACH
|
|
571
|
+
* We trust what the server tells us and store it as credentials
|
|
572
|
+
* @param member The member that was added
|
|
573
|
+
*/
|
|
574
|
+
ClusterService.prototype.handleMemberAdded = function (member) {
|
|
575
|
+
this.logger.info('ClusterService', "\u2705 SERVER CONFIRMED: Member[ uuid: " + member.uuid + ", address: " + member.address.toString() + "] added to cluster");
|
|
576
|
+
// SERVER-FIRST: Store server data as credentials
|
|
577
|
+
// The server is the authority - we store what it tells us
|
|
578
|
+
this.logger.info('ClusterService', "\uD83C\uDFAF SERVER-FIRST: Storing server member data as credentials - server is authority");
|
|
579
|
+
var connectionManager = this.client.getConnectionManager();
|
|
580
|
+
// Store the server-provided UUID as the authoritative credential
|
|
581
|
+
if (connectionManager && typeof connectionManager.updatePreservedCredentials === 'function') {
|
|
582
|
+
connectionManager.updatePreservedCredentials(member.address, member.uuid);
|
|
583
|
+
}
|
|
584
|
+
// Record that we received a member added event for this address
|
|
585
|
+
if (connectionManager && typeof connectionManager.recordMemberAddedEvent === 'function') {
|
|
586
|
+
connectionManager.recordMemberAddedEvent(member.address);
|
|
587
|
+
}
|
|
588
|
+
// Find the current owner from the cluster state
|
|
589
|
+
var currentOwner = this.findCurrentOwner();
|
|
590
|
+
if (currentOwner) {
|
|
591
|
+
this.logger.info('ClusterService', "\uD83D\uDD04 SERVER-FIRST: Updating ALL credentials with current owner UUID: " + currentOwner.uuid);
|
|
592
|
+
// Update all credentials with the current owner UUID from server
|
|
593
|
+
if (connectionManager && typeof connectionManager.updateAllCredentialsWithNewOwnerUuid === 'function') {
|
|
594
|
+
connectionManager.updateAllCredentialsWithNewOwnerUuid(currentOwner.uuid);
|
|
595
|
+
}
|
|
596
|
+
// CRITICAL FIX: Update client's own UUIDs to match server expectations
|
|
597
|
+
this.logger.info('ClusterService', "\uD83D\uDD04 SERVER-FIRST: Updating client UUIDs to match server state");
|
|
598
|
+
this.logger.info('ClusterService', " - Old Client UUID: " + (this.uuid || 'NOT SET'));
|
|
599
|
+
this.logger.info('ClusterService', " - Old Owner UUID: " + (this.ownerUuid || 'NOT SET'));
|
|
600
|
+
// Update client's own UUIDs with server-provided data
|
|
601
|
+
// The client UUID should match the owner's UUID for authentication
|
|
602
|
+
this.uuid = currentOwner.uuid;
|
|
603
|
+
this.ownerUuid = currentOwner.uuid;
|
|
604
|
+
this.logger.info('ClusterService', " - New Client UUID: " + this.uuid);
|
|
605
|
+
this.logger.info('ClusterService', " - New Owner UUID: " + this.ownerUuid);
|
|
606
|
+
}
|
|
607
|
+
// Refresh partition table (KEEPING REFRESH METHOD UNTOUCHED as requested)
|
|
608
|
+
this.client.getPartitionService().refresh();
|
|
609
|
+
this.logger.info('ClusterService', "\u2705 SERVER-FIRST: Member " + member.uuid + " at " + member.address.toString() + " credentials stored from server data");
|
|
610
|
+
};
|
|
611
|
+
/**
|
|
612
|
+
* Finds the current owner from the cluster state
|
|
613
|
+
* @returns The current owner member or null if not found
|
|
614
|
+
*/
|
|
615
|
+
ClusterService.prototype.findCurrentOwner = function () {
|
|
616
|
+
// Check if we have an active owner connection
|
|
617
|
+
var ownerConnection = this.ownerConnection;
|
|
618
|
+
if (ownerConnection && ownerConnection.isAlive()) {
|
|
619
|
+
var ownerAddress = ownerConnection.getAddress();
|
|
620
|
+
// Find the member with this address
|
|
621
|
+
for (var _i = 0, _a = this.members; _i < _a.length; _i++) {
|
|
622
|
+
var member = _a[_i];
|
|
623
|
+
if (member.address.toString() === ownerAddress.toString()) {
|
|
624
|
+
this.logger.debug('ClusterService', "Found current owner: " + member.uuid + " at " + member.address.toString());
|
|
625
|
+
return member;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// Fallback: look for any member that might be the owner
|
|
630
|
+
this.logger.debug('ClusterService', "No active owner connection found, checking member list for potential owner");
|
|
631
|
+
return null;
|
|
632
|
+
};
|
|
497
633
|
/**
|
|
498
634
|
* Logs the current state for debugging purposes
|
|
499
635
|
*/
|
|
@@ -718,6 +854,30 @@ var ClusterService = /** @class */ (function () {
|
|
|
718
854
|
}
|
|
719
855
|
}, blockDuration);
|
|
720
856
|
};
|
|
857
|
+
/**
|
|
858
|
+
* Handles ownership change when failover occurs
|
|
859
|
+
* @param newOwnerAddress The address of the new owner
|
|
860
|
+
* @param newOwnerConnection The connection to the new owner
|
|
861
|
+
*/
|
|
862
|
+
ClusterService.prototype.handleOwnershipChange = function (newOwnerAddress, newOwnerConnection) {
|
|
863
|
+
this.logger.info('ClusterService', "Handling ownership change to " + newOwnerAddress.toString());
|
|
864
|
+
// Update owner connection
|
|
865
|
+
this.ownerConnection = newOwnerConnection;
|
|
866
|
+
// Note: Owner UUID will be updated when authentication completes
|
|
867
|
+
// For now, we'll keep the existing owner UUID
|
|
868
|
+
// Clear any stale partition information
|
|
869
|
+
this.client.getPartitionService().clearPartitionTable();
|
|
870
|
+
// Refresh partition information with the new owner
|
|
871
|
+
this.client.getPartitionService().refresh();
|
|
872
|
+
this.logger.info('ClusterService', "Ownership change completed, new owner: " + newOwnerAddress.toString());
|
|
873
|
+
};
|
|
874
|
+
/**
|
|
875
|
+
* Gets the failover manager for external access
|
|
876
|
+
* @returns The failover manager instance
|
|
877
|
+
*/
|
|
878
|
+
ClusterService.prototype.getFailoverManager = function () {
|
|
879
|
+
return this.failoverManager;
|
|
880
|
+
};
|
|
721
881
|
ClusterService.prototype.shutdown = function () {
|
|
722
882
|
if (this.reconnectionTask) {
|
|
723
883
|
clearInterval(this.reconnectionTask);
|
|
@@ -10,5 +10,16 @@ export declare class ConnectionAuthenticator {
|
|
|
10
10
|
private logger;
|
|
11
11
|
constructor(connection: ClientConnection, client: HazelcastClient);
|
|
12
12
|
authenticate(asOwner: boolean): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Gets a human-readable description of authentication status
|
|
15
|
+
*/
|
|
16
|
+
private getStatusDescription(status);
|
|
17
|
+
/**
|
|
18
|
+
* Creates credentials with optional restoration from preservation service
|
|
19
|
+
* @param asOwner Whether this is an owner connection
|
|
20
|
+
* @param preservedCredentials Optional preserved credentials to use
|
|
21
|
+
* @returns The credentials message
|
|
22
|
+
*/
|
|
23
|
+
createCredentialsWithRestoration(asOwner: boolean, preservedCredentials?: any): ClientMessage;
|
|
13
24
|
createCredentials(asOwner: boolean): ClientMessage;
|
|
14
25
|
}
|
|
@@ -34,53 +34,126 @@ var ConnectionAuthenticator = /** @class */ (function () {
|
|
|
34
34
|
}
|
|
35
35
|
ConnectionAuthenticator.prototype.authenticate = function (asOwner) {
|
|
36
36
|
var _this = this;
|
|
37
|
+
var addressStr = this.connection.getAddress().toString();
|
|
38
|
+
this.logger.info('ConnectionAuthenticator', "\uD83D\uDD10 Starting authentication for " + addressStr + " (asOwner=" + asOwner + ")");
|
|
37
39
|
var credentials = this.createCredentials(asOwner);
|
|
40
|
+
this.logger.info('ConnectionAuthenticator', "\uD83D\uDCE4 Sending authentication request to " + addressStr + "...");
|
|
38
41
|
return this.client.getInvocationService()
|
|
39
42
|
.invokeOnConnection(this.connection, credentials)
|
|
40
43
|
.then(function (msg) {
|
|
44
|
+
_this.logger.info('ConnectionAuthenticator', "\uD83D\uDCE5 Received authentication response from " + addressStr);
|
|
41
45
|
var authResponse = ClientAuthenticationCodec_1.ClientAuthenticationCodec.decodeResponse(msg);
|
|
46
|
+
_this.logger.info('ConnectionAuthenticator', "\uD83D\uDD0D Authentication response for " + addressStr + ":");
|
|
47
|
+
_this.logger.info('ConnectionAuthenticator', " - Status: " + authResponse.status + " (" + _this.getStatusDescription(authResponse.status) + ")");
|
|
48
|
+
_this.logger.info('ConnectionAuthenticator', " - Server UUID: " + (authResponse.uuid || 'NOT PROVIDED'));
|
|
49
|
+
_this.logger.info('ConnectionAuthenticator', " - Server Owner UUID: " + (authResponse.ownerUuid || 'NOT PROVIDED'));
|
|
50
|
+
_this.logger.info('ConnectionAuthenticator', " - Server Address: " + (authResponse.address ? authResponse.address.toString() : 'NOT PROVIDED'));
|
|
51
|
+
_this.logger.info('ConnectionAuthenticator', " - Server Version: " + (authResponse.serverHazelcastVersion || 'NOT PROVIDED'));
|
|
42
52
|
switch (authResponse.status) {
|
|
43
53
|
case 0 /* AUTHENTICATED */:
|
|
54
|
+
_this.logger.info('ConnectionAuthenticator', "\u2705 Authentication SUCCESSFUL for " + addressStr);
|
|
44
55
|
_this.connection.setAddress(authResponse.address);
|
|
45
56
|
_this.connection.setConnectedServerVersion(authResponse.serverHazelcastVersion);
|
|
46
57
|
if (asOwner) {
|
|
58
|
+
var oldUuid = _this.clusterService.uuid;
|
|
59
|
+
var oldOwnerUuid = _this.clusterService.ownerUuid;
|
|
47
60
|
_this.clusterService.uuid = authResponse.uuid;
|
|
48
61
|
_this.clusterService.ownerUuid = authResponse.ownerUuid;
|
|
62
|
+
_this.logger.info('ConnectionAuthenticator', "\uD83D\uDD04 Updated cluster service for " + addressStr + ":");
|
|
63
|
+
_this.logger.info('ConnectionAuthenticator', " - UUID: " + (oldUuid || 'NOT SET') + " \u2192 " + authResponse.uuid);
|
|
64
|
+
_this.logger.info('ConnectionAuthenticator', " - Owner UUID: " + (oldOwnerUuid || 'NOT SET') + " \u2192 " + authResponse.ownerUuid);
|
|
49
65
|
}
|
|
50
|
-
_this.logger.info('ConnectionAuthenticator',
|
|
51
|
-
_this.connection.getAddress().toString() + ' authenticated');
|
|
66
|
+
_this.logger.info('ConnectionAuthenticator', "\u2705 Connection to " + addressStr + " authenticated successfully");
|
|
52
67
|
break;
|
|
53
68
|
case 1 /* CREDENTIALS_FAILED */:
|
|
54
|
-
_this.logger.error('ConnectionAuthenticator',
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
_this.logger.error('ConnectionAuthenticator', "\u274C Authentication FAILED for " + addressStr + ": Invalid Credentials");
|
|
70
|
+
_this.logger.error('ConnectionAuthenticator', " - Server rejected our credentials");
|
|
71
|
+
_this.logger.error('ConnectionAuthenticator', " - Check if UUIDs and group credentials are correct");
|
|
72
|
+
throw new Error('Invalid Credentials, could not authenticate connection to ' + addressStr);
|
|
57
73
|
case 2 /* SERIALIZATION_VERSION_MISMATCH */:
|
|
58
|
-
_this.logger.error('ConnectionAuthenticator',
|
|
59
|
-
throw new Error('Serialization version mismatch, could not authenticate connection to ' +
|
|
60
|
-
_this.connection.getAddress().toString());
|
|
74
|
+
_this.logger.error('ConnectionAuthenticator', "\u274C Authentication FAILED for " + addressStr + ": Serialization version mismatch");
|
|
75
|
+
throw new Error('Serialization version mismatch, could not authenticate connection to ' + addressStr);
|
|
61
76
|
default:
|
|
62
|
-
_this.logger.error('ConnectionAuthenticator',
|
|
63
|
-
+ authResponse.status);
|
|
77
|
+
_this.logger.error('ConnectionAuthenticator', "\u274C Authentication FAILED for " + addressStr + ": Unknown status " + authResponse.status);
|
|
64
78
|
throw new HazelcastError_1.AuthenticationError('Unknown authentication status: ' + authResponse.status +
|
|
65
|
-
' , could not authenticate connection to ' +
|
|
66
|
-
_this.connection.getAddress().toString());
|
|
79
|
+
' , could not authenticate connection to ' + addressStr);
|
|
67
80
|
}
|
|
81
|
+
})
|
|
82
|
+
.catch(function (error) {
|
|
83
|
+
_this.logger.error('ConnectionAuthenticator', "\uD83D\uDCA5 Authentication ERROR for " + addressStr + ": " + error.message);
|
|
84
|
+
throw error;
|
|
68
85
|
});
|
|
69
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Gets a human-readable description of authentication status
|
|
89
|
+
*/
|
|
90
|
+
ConnectionAuthenticator.prototype.getStatusDescription = function (status) {
|
|
91
|
+
switch (status) {
|
|
92
|
+
case 0 /* AUTHENTICATED */: return 'AUTHENTICATED';
|
|
93
|
+
case 1 /* CREDENTIALS_FAILED */: return 'CREDENTIALS_FAILED';
|
|
94
|
+
case 2 /* SERIALIZATION_VERSION_MISMATCH */: return 'SERIALIZATION_VERSION_MISMATCH';
|
|
95
|
+
default: return "UNKNOWN_STATUS_" + status;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Creates credentials with optional restoration from preservation service
|
|
100
|
+
* @param asOwner Whether this is an owner connection
|
|
101
|
+
* @param preservedCredentials Optional preserved credentials to use
|
|
102
|
+
* @returns The credentials message
|
|
103
|
+
*/
|
|
104
|
+
ConnectionAuthenticator.prototype.createCredentialsWithRestoration = function (asOwner, preservedCredentials) {
|
|
105
|
+
if (preservedCredentials && preservedCredentials.uuid && preservedCredentials.ownerUuid) {
|
|
106
|
+
this.logger.debug('ConnectionAuthenticator', "Using preserved credentials: uuid=" + preservedCredentials.uuid + ", ownerUuid=" + preservedCredentials.ownerUuid);
|
|
107
|
+
// Use preserved credentials instead of current cluster state
|
|
108
|
+
var groupConfig = this.client.getConfig().groupConfig;
|
|
109
|
+
var customCredentials = this.client.getConfig().customCredentials;
|
|
110
|
+
var clientMessage = void 0;
|
|
111
|
+
var clientVersion = BuildInfo_1.BuildInfo.getClientVersion();
|
|
112
|
+
if (customCredentials != null) {
|
|
113
|
+
var credentialsPayload = this.client.getSerializationService().toData(customCredentials);
|
|
114
|
+
clientMessage = ClientAuthenticationCustomCodec_1.ClientAuthenticationCustomCodec.encodeRequest(credentialsPayload, preservedCredentials.uuid, preservedCredentials.ownerUuid, asOwner, 'NJS', 1, clientVersion);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
clientMessage = ClientAuthenticationCodec_1.ClientAuthenticationCodec.encodeRequest(preservedCredentials.groupName, preservedCredentials.groupPassword, preservedCredentials.uuid, preservedCredentials.ownerUuid, asOwner, 'NJS', 1, clientVersion);
|
|
118
|
+
}
|
|
119
|
+
return clientMessage;
|
|
120
|
+
}
|
|
121
|
+
// Fall back to normal credential creation
|
|
122
|
+
return this.createCredentials(asOwner);
|
|
123
|
+
};
|
|
70
124
|
ConnectionAuthenticator.prototype.createCredentials = function (asOwner) {
|
|
71
125
|
var groupConfig = this.client.getConfig().groupConfig;
|
|
72
126
|
var uuid = this.clusterService.uuid;
|
|
73
127
|
var ownerUuid = this.clusterService.ownerUuid;
|
|
74
128
|
var customCredentials = this.client.getConfig().customCredentials;
|
|
129
|
+
// LOG EXACTLY WHAT CREDENTIALS WE'RE SENDING
|
|
130
|
+
this.logger.info('ConnectionAuthenticator', "\uD83D\uDD10 Creating authentication credentials for " + this.connection.getAddress().toString() + ":");
|
|
131
|
+
this.logger.info('ConnectionAuthenticator', " - As Owner: " + asOwner);
|
|
132
|
+
this.logger.info('ConnectionAuthenticator', " - UUID: " + (uuid || 'NOT SET'));
|
|
133
|
+
this.logger.info('ConnectionAuthenticator', " - Owner UUID: " + (ownerUuid || 'NOT SET'));
|
|
134
|
+
this.logger.info('ConnectionAuthenticator', " - Group Name: " + (groupConfig.name || 'NOT SET'));
|
|
135
|
+
this.logger.info('ConnectionAuthenticator', " - Group Password: " + (groupConfig.password ? '***SET***' : 'NOT SET'));
|
|
136
|
+
this.logger.info('ConnectionAuthenticator', " - Custom Credentials: " + (customCredentials ? 'YES' : 'NO'));
|
|
137
|
+
this.logger.info('ConnectionAuthenticator', " - Client Version: " + BuildInfo_1.BuildInfo.getClientVersion());
|
|
75
138
|
var clientMessage;
|
|
76
139
|
var clientVersion = BuildInfo_1.BuildInfo.getClientVersion();
|
|
77
140
|
if (customCredentials != null) {
|
|
78
141
|
var credentialsPayload = this.client.getSerializationService().toData(customCredentials);
|
|
142
|
+
this.logger.info('ConnectionAuthenticator', "\uD83D\uDCE4 Sending CUSTOM authentication request with:");
|
|
143
|
+
this.logger.info('ConnectionAuthenticator', " - Custom Credentials: " + typeof credentialsPayload);
|
|
144
|
+
this.logger.info('ConnectionAuthenticator', " - UUID: " + (uuid || 'NOT SET'));
|
|
145
|
+
this.logger.info('ConnectionAuthenticator', " - Owner UUID: " + (ownerUuid || 'NOT SET'));
|
|
79
146
|
clientMessage = ClientAuthenticationCustomCodec_1.ClientAuthenticationCustomCodec.encodeRequest(credentialsPayload, uuid, ownerUuid, asOwner, 'NJS', 1, clientVersion);
|
|
80
147
|
}
|
|
81
148
|
else {
|
|
149
|
+
this.logger.info('ConnectionAuthenticator', "\uD83D\uDCE4 Sending STANDARD authentication request with:");
|
|
150
|
+
this.logger.info('ConnectionAuthenticator', " - Group Name: " + (groupConfig.name || 'NOT SET'));
|
|
151
|
+
this.logger.info('ConnectionAuthenticator', " - Group Password: " + (groupConfig.password ? '***SET***' : 'NOT SET'));
|
|
152
|
+
this.logger.info('ConnectionAuthenticator', " - UUID: " + (uuid || 'NOT SET'));
|
|
153
|
+
this.logger.info('ConnectionAuthenticator', " - Owner UUID: " + (ownerUuid || 'NOT SET'));
|
|
82
154
|
clientMessage = ClientAuthenticationCodec_1.ClientAuthenticationCodec.encodeRequest(groupConfig.name, groupConfig.password, uuid, ownerUuid, asOwner, 'NJS', 1, clientVersion);
|
|
83
155
|
}
|
|
156
|
+
this.logger.info('ConnectionAuthenticator', "\uD83D\uDCCB Final authentication message created and ready to send");
|
|
84
157
|
return clientMessage;
|
|
85
158
|
};
|
|
86
159
|
return ConnectionAuthenticator;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { ILogger } from '../logging/ILogger';
|
|
2
|
+
import Address = require('../Address');
|
|
3
|
+
/**
|
|
4
|
+
* Interface for node credentials
|
|
5
|
+
*/
|
|
6
|
+
export interface NodeCredentials {
|
|
7
|
+
uuid: string;
|
|
8
|
+
ownerUuid: string;
|
|
9
|
+
groupName: string;
|
|
10
|
+
groupPassword: string;
|
|
11
|
+
lastAuthenticated: number;
|
|
12
|
+
isOwner: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Service to preserve and restore authentication credentials across failover cycles
|
|
16
|
+
* This fixes the "Invalid Credentials" issue that occurs when nodes rejoin after failover
|
|
17
|
+
*/
|
|
18
|
+
export declare class CredentialPreservationService {
|
|
19
|
+
private readonly logger;
|
|
20
|
+
/**
|
|
21
|
+
* Stores authentication context for each node
|
|
22
|
+
*/
|
|
23
|
+
private nodeCredentials;
|
|
24
|
+
constructor(logger: ILogger);
|
|
25
|
+
/**
|
|
26
|
+
* Preserves authentication credentials for a node before it gets disconnected
|
|
27
|
+
* @param address The node address
|
|
28
|
+
* @param uuid The node's UUID
|
|
29
|
+
* @param ownerUuid The owner UUID
|
|
30
|
+
* @param groupName The group name
|
|
31
|
+
* @param groupPassword The group password
|
|
32
|
+
* @param isOwner Whether this is an owner connection
|
|
33
|
+
*/
|
|
34
|
+
preserveCredentials(address: Address, uuid: string, ownerUuid: string, groupName: string, groupPassword: string, isOwner: boolean): void;
|
|
35
|
+
/**
|
|
36
|
+
* Restores authentication credentials for a specific node
|
|
37
|
+
* @param address The node address
|
|
38
|
+
* @returns The preserved credentials or null if not found
|
|
39
|
+
*/
|
|
40
|
+
restoreCredentials(address: Address): NodeCredentials | null;
|
|
41
|
+
/**
|
|
42
|
+
* Updates credentials after successful authentication
|
|
43
|
+
* @param address The node address
|
|
44
|
+
* @param uuid The new UUID
|
|
45
|
+
* @param ownerUuid The new owner UUID
|
|
46
|
+
*/
|
|
47
|
+
updateCredentials(address: Address, uuid: string, ownerUuid: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* Clears credentials for a specific node
|
|
50
|
+
* @param address The node address
|
|
51
|
+
*/
|
|
52
|
+
clearCredentials(address: Address): void;
|
|
53
|
+
/**
|
|
54
|
+
* Clears all stored credentials - used for cluster reset scenarios
|
|
55
|
+
*/
|
|
56
|
+
clearAllCredentials(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Cleans up stale credentials older than the specified age
|
|
59
|
+
* @param maxAgeMs Maximum age in milliseconds (default: 5 minutes)
|
|
60
|
+
*/
|
|
61
|
+
cleanupStaleCredentials(maxAgeMs?: number): void;
|
|
62
|
+
/**
|
|
63
|
+
* Gets a summary of preserved credentials for debugging
|
|
64
|
+
* @returns A summary string
|
|
65
|
+
*/
|
|
66
|
+
getCredentialsSummary(): string;
|
|
67
|
+
/**
|
|
68
|
+
* Smart credential restoration that handles both scenarios:
|
|
69
|
+
* 1. If member added event occurred -> use new UUID
|
|
70
|
+
* 2. If no member added event -> use old UUID (node probably never changed)
|
|
71
|
+
* @param address The address to restore credentials for
|
|
72
|
+
* @param hasMemberAddedEvent Whether we received a member added event for this address
|
|
73
|
+
* @returns The appropriate credentials to use
|
|
74
|
+
*/
|
|
75
|
+
smartRestoreCredentials(address: Address, hasMemberAddedEvent?: boolean): NodeCredentials | null;
|
|
76
|
+
/**
|
|
77
|
+
* Checks if we have valid credentials for an address
|
|
78
|
+
* @param address The address to check
|
|
79
|
+
* @returns True if we have valid credentials
|
|
80
|
+
*/
|
|
81
|
+
hasValidCredentials(address: Address): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Gets the last known UUID for an address
|
|
84
|
+
* @param address The address to get UUID for
|
|
85
|
+
* @returns The UUID or null if not found
|
|
86
|
+
*/
|
|
87
|
+
getLastKnownUuid(address: Address): string | null;
|
|
88
|
+
/**
|
|
89
|
+
* Updates the owner UUID for ALL preserved credentials
|
|
90
|
+
* This is called when cluster membership changes and we need to sync all credentials
|
|
91
|
+
* @param newOwnerUuid The new owner UUID from the current cluster state
|
|
92
|
+
*/
|
|
93
|
+
updateAllOwnerUuids(newOwnerUuid: string): void;
|
|
94
|
+
/**
|
|
95
|
+
* Gets the current owner UUID from preserved credentials
|
|
96
|
+
* @returns The current owner UUID or null if not found
|
|
97
|
+
*/
|
|
98
|
+
getCurrentOwnerUuid(): string | null;
|
|
99
|
+
/**
|
|
100
|
+
* Validates that all credentials have consistent owner UUIDs
|
|
101
|
+
* @returns True if all credentials are consistent, false otherwise
|
|
102
|
+
*/
|
|
103
|
+
validateOwnerUuidConsistency(): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Invalidates ALL credentials that have a specific owner UUID
|
|
106
|
+
* This is called when cluster membership changes and old owner UUIDs become invalid
|
|
107
|
+
* @param oldOwnerUuid The old owner UUID that is no longer valid
|
|
108
|
+
*/
|
|
109
|
+
invalidateCredentialsWithOwnerUuid(oldOwnerUuid: string): void;
|
|
110
|
+
/**
|
|
111
|
+
* Invalidates ALL credentials that don't match the current cluster owner UUID
|
|
112
|
+
* This ensures complete credential consistency across the entire cluster
|
|
113
|
+
* @param currentClusterOwnerUuid The current cluster owner UUID that should be valid
|
|
114
|
+
*/
|
|
115
|
+
invalidateAllCredentialsExceptCurrentOwner(currentClusterOwnerUuid: string): void;
|
|
116
|
+
/**
|
|
117
|
+
* Invalidates only problematic credentials while preserving working ones
|
|
118
|
+
* This prevents breaking working connections (like 192.168.1.108)
|
|
119
|
+
* @param currentClusterOwnerUuid The current cluster owner UUID that should be valid
|
|
120
|
+
*/
|
|
121
|
+
invalidateOnlyProblematicCredentials(currentClusterOwnerUuid: string): void;
|
|
122
|
+
/**
|
|
123
|
+
* Checks if an address has had recent authentication issues
|
|
124
|
+
* This helps determine which credentials to invalidate
|
|
125
|
+
* @param addressStr The address to check
|
|
126
|
+
* @returns True if the address has recent auth issues
|
|
127
|
+
*/
|
|
128
|
+
private hasRecentAuthenticationIssues(addressStr);
|
|
129
|
+
/**
|
|
130
|
+
* Clears all credentials for a specific address
|
|
131
|
+
* This is called when we need to force fresh authentication for a node
|
|
132
|
+
* @param address The address to clear credentials for
|
|
133
|
+
*/
|
|
134
|
+
clearCredentialsForAddress(address: Address): void;
|
|
135
|
+
/**
|
|
136
|
+
* Gets all addresses that have credentials with a specific owner UUID
|
|
137
|
+
* @param ownerUuid The owner UUID to search for
|
|
138
|
+
* @returns Array of addresses that have credentials with this owner UUID
|
|
139
|
+
*/
|
|
140
|
+
getAddressesWithOwnerUuid(ownerUuid: string): string[];
|
|
141
|
+
}
|