@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.
@@ -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.logger.info('ClusterService', 'Starting failover process...');
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 === 0) {
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', 'Connection to ' +
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', 'Invalid Credentials');
55
- throw new Error('Invalid Credentials, could not authenticate connection to ' +
56
- _this.connection.getAddress().toString());
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', 'Serialization version mismatch');
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', 'Unknown authentication status: '
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
+ }