1
0
mirror of https://github.com/redis/node-redis.git synced 2025-12-11 09:22:35 +03:00
Files
node-redis/packages/entraid
Nikolay Karadzhov 208a0d250f Hitless upgrades (#3021)
* feat(errors): Add specialized timeout error types for maintenance scenarios

- Added `SocketTimeoutDuringMaintananceError`, a subclass of `TimeoutError`, to handle socket timeouts during maintenance.
- Added `CommandTimeoutDuringMaintenanceError`, another subclass of `TimeoutError`, to address command write timeouts during maintenance.

* feat(linked-list): Add EmptyAwareSinglyLinkedList and enhance DoublyLinkedList functionality

- Introduced `EmptyAwareSinglyLinkedList`, a subclass of `SinglyLinkedList` that emits an `empty` event when the list becomes empty due to `reset`, `shift`, or `remove` operations.
- Added `nodes()` iterator method to `DoublyLinkedList` for iterating over nodes directly.
- Enhanced unit tests for `DoublyLinkedList` and `SinglyLinkedList` to cover edge cases and new functionality.
- Added comprehensive tests for `EmptyAwareSinglyLinkedList` to validate `empty` event emission under various scenarios.
- Improved code formatting and consistency.

* refactor(commands-queue): Improve push notification handling

- Replaced `setInvalidateCallback` with a more flexible `addPushHandler` method, allowing multiple handlers for push notifications.
- Introduced the `PushHandler` type to standardize push notification processing.
- Refactored `RedisCommandsQueue` to use a `#pushHandlers` array, enabling dynamic and modular handling of push notifications.
- Updated `RedisClient` to leverage the new handler mechanism for `invalidate` push notifications, simplifying and decoupling logic.

* feat(commands-queue): Add method to wait for in-flight commands to complete

- Introduced `waitForInflightCommandsToComplete` method to asynchronously wait for all in-flight commands to finish processing.
- Utilized the `empty` event from `#waitingForReply` to signal when all commands have been completed.

* feat(commands-queue): Introduce maintenance mode support for commands-queue

- Added `#maintenanceCommandTimeout` and `setMaintenanceCommandTimeout` method to dynamically adjust command timeouts during maintenance

* refator(client): Extract socket event listener setup into helper method

* refactor(socket): Add maintenance mode support and dynamic timeout handling

- Added `#maintenanceTimeout` and `setMaintenanceTimeout` method to dynamically adjust socket timeouts during maintenance.

* feat(client): Add Redis Enterprise maintenance configuration options

- Added `maintPushNotifications` option to control how the client handles Redis Enterprise maintenance push notifications (`disabled`, `enabled`, `au
to`).
- Added `maintMovingEndpointType` option to specify the endpoint type for reconnecting during a MOVING notification (`auto`, `internal-ip`, `external-ip`,
etc.).
- Added `maintRelaxedCommandTimeout` option to define a relaxed timeout for commands during maintenance.
- Added `maintRelaxedSocketTimeout` option to define a relaxed timeout for the socket during maintenance.
- Enforced RESP3 requirement for maintenance-related features (`maintPushNotifications`).

* feat(client): Add socket helpers and pause mechanism

- Introduced `#paused` flag with corresponding `_pause` and `_unpause` methods to
temporarily halt writing commands to the socket during maintenance windows.
- Updated `#write` method to respect the `#paused` flag, preventing new commands from being written during maintenance.
- Added `_ejectSocket` method to safely detach from and return the current socket
- Added `_insertSocket` method to receive and start using a new socket

* feat(client): Add Redis Enterprise maintenance handling capabilities

- Introduced `EnterpriseMaintenanceManager` to manage Redis Enterprise maintenance events and push notifications.
- Integrated `EnterpriseMaintenanceManager` into `RedisClient` to handle maintenance push notifications and manage socket transitions.
- Implemented graceful handling of MOVING, MIGRATING, and FAILOVER push notifications, including socket replacement and timeout adjustments.


* test: add E2E test infrastructure for Redis maintenance scenarios

* test: add E2E tests for Redis Enterprise maintenance timeout handling (#3)

* test: add connection handoff test


---------

Co-authored-by: Pavel Pashov <pavel.pashov@redis.com>
Co-authored-by: Pavel Pashov <60297174+PavelPashov@users.noreply.github.com>
2025-09-10 11:05:37 +03:00
..
2025-06-04 10:52:14 +03:00
2025-09-10 11:05:37 +03:00

@redis/entraid

Secure token-based authentication for Redis clients using Microsoft Entra ID (formerly Azure Active Directory).

Features

  • Token-based authentication using Microsoft Entra ID
  • Automatic token refresh before expiration
  • Automatic re-authentication of all connections after token refresh
  • Support for multiple authentication flows:
    • Managed identities (system-assigned and user-assigned)
    • Service principals (with or without certificates)
    • Authorization Code with PKCE flow
    • DefaultAzureCredential from @azure/identity
  • Built-in retry mechanisms for transient failures

Installation

npm install "@redis/client@5.0.0-next.7"
npm install "@redis/entraid@5.0.0-next.7"

Getting Started

The first step to using @redis/entraid is choosing the right credentials provider for your authentication needs. The EntraIdCredentialsProviderFactory class provides several factory methods to create the appropriate provider:

  • createForSystemAssignedManagedIdentity: Use when your application runs in Azure with a system-assigned managed identity
  • createForUserAssignedManagedIdentity: Use when your application runs in Azure with a user-assigned managed identity
  • createForClientCredentials: Use when authenticating with a service principal using client secret
  • createForClientCredentialsWithCertificate: Use when authenticating with a service principal using a certificate
  • createForAuthorizationCodeWithPKCE: Use for interactive authentication flows in user applications
  • createForDefaultAzureCredential: Use when you want to leverage Azure Identity's DefaultAzureCredential

Usage Examples

Service Principal Authentication

import { createClient } from '@redis/client';
import { EntraIdCredentialsProviderFactory } from '@redis/entraid';

const provider = EntraIdCredentialsProviderFactory.createForClientCredentials({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  authorityConfig: {
    type: 'multi-tenant',
    tenantId: 'your-tenant-id'
  },
  tokenManagerConfig: {
    expirationRefreshRatio: 0.8 // Refresh token after 80% of its lifetime
  }
});

const client = createClient({
  url: 'redis://your-host',
  credentialsProvider: provider
});

await client.connect();

System-Assigned Managed Identity

const provider = EntraIdCredentialsProviderFactory.createForSystemAssignedManagedIdentity({
  clientId: 'your-client-id',
  tokenManagerConfig: {
    expirationRefreshRatio: 0.8
  }
});

User-Assigned Managed Identity

const provider = EntraIdCredentialsProviderFactory.createForUserAssignedManagedIdentity({
  clientId: 'your-client-id',
  userAssignedClientId: 'your-user-assigned-client-id',
  tokenManagerConfig: {
    expirationRefreshRatio: 0.8
  }
});

DefaultAzureCredential Authentication

tip: see a real sample here: samples/interactive-browser/index.ts

The DefaultAzureCredential from @azure/identity provides a simplified authentication experience that automatically tries different authentication methods based on the environment. This is especially useful for applications that need to work in different environments (local development, CI/CD, and production).

import { createClient } from '@redis/client';
import { getDefaultAzureCredential } from '@azure/identity';
import { EntraIdCredentialsProviderFactory, REDIS_SCOPE_DEFAULT } from '@redis/entraid';

// Create a DefaultAzureCredential instance
const credential = getDefaultAzureCredential();

// Create a provider using DefaultAzureCredential
const provider = EntraIdCredentialsProviderFactory.createForDefaultAzureCredential({
  // Use the same parameters you would pass to credential.getToken()
  credential,
  scopes: REDIS_SCOPE_DEFAULT, // The Redis scope
  // Optional additional parameters for getToken
  options: {
    // Any options you would normally pass to credential.getToken()
  },
  tokenManagerConfig: {
    expirationRefreshRatio: 0.8
  }
});

const client = createClient({
  url: 'redis://your-host',
  credentialsProvider: provider
});

await client.connect();

Important Notes on Using DefaultAzureCredential

When using the createForDefaultAzureCredential method, you need to:

  1. Create your own instance of DefaultAzureCredential
  2. Pass the same parameters to the factory method that you would use with the getToken() method:
    • scopes: The Redis scope (use the exported REDIS_SCOPE_DEFAULT constant)
    • options: Any additional options for the getToken method

This factory method creates a wrapper around DefaultAzureCredential that adapts it to the Redis client's authentication system, while maintaining all the flexibility of the original Azure Identity authentication.

Important Limitations

RESP2 PUB/SUB Limitations

When using RESP2 (Redis Serialization Protocol 2), there are important limitations with PUB/SUB:

  • No Re-Authentication in PUB/SUB Mode: In RESP2, once a connection enters PUB/SUB mode, the socket is blocked and cannot process out-of-band commands like AUTH. This means that connections in PUB/SUB mode cannot be re-authenticated when tokens are refreshed.
  • Connection Eviction: As a result, PUB/SUB connections will be evicted by the Redis proxy when their tokens expire. The client will need to establish new connections with fresh tokens.

Transaction Safety

When using token-based authentication, special care must be taken with Redis transactions. The token manager runs in the background and may attempt to re-authenticate connections at any time by sending AUTH commands. This can interfere with manually constructed transactions.

Always use the official transaction API provided by the client:

// Correct way to handle transactions
const multi = client.multi();
multi.set('key1', 'value1');
multi.set('key2', 'value2');
await multi.exec();

Avoid: Manual Transaction Construction

Do not manually construct transactions by sending individual MULTI/EXEC commands:

// Incorrect and potentially dangerous
await client.sendCommand(['MULTI']);
await client.sendCommand(['SET', 'key1', 'value1']);
await client.sendCommand(['SET', 'key2', 'value2']);
await client.sendCommand(['EXEC']); // Risk of AUTH command being injected before EXEC

Error Handling

The provider includes built-in retry mechanisms for transient errors:

const provider = EntraIdCredentialsProviderFactory.createForClientCredentials({
  // ... other config ...
  tokenManagerConfig: {
    retry: {
      maxAttempts: 3,
      initialDelayMs: 100,
      maxDelayMs: 1000,
      backoffMultiplier: 2
    }
  }
});