Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): add Entra ID identity provider integration for Redis client authentication #2877

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,150 changes: 1,104 additions & 46 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions packages/authx/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export { TokenManager, TokenManagerConfig, TokenStreamListener, RetryPolicy, IDPError } from './lib/token-manager';
export {
CredentialsProvider,
StreamingCredentialsProvider,
UnableToObtainNewCredentialsError,
CredentialsError,
StreamingCredentialsListener,
AsyncCredentialsProvider,
ReAuthenticationError,
BasicAuth
} from './lib/credentials-provider';
export { Token } from './lib/token';
export { IdentityProvider, TokenResponse } from './lib/identity-provider';
102 changes: 102 additions & 0 deletions packages/authx/lib/credentials-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

/**
* Provides credentials asynchronously.
*/
export interface AsyncCredentialsProvider {
readonly type: 'async-credentials-provider';
credentials: () => Promise<BasicAuth>
}

/**
* Provides credentials asynchronously with support for continuous updates via a subscription model.
* This is useful for environments where credentials are frequently rotated or updated or can be revoked.
*/
export interface StreamingCredentialsProvider {
readonly type: 'streaming-credentials-provider';

/**
* Provides initial credentials and subscribes to subsequent updates. This is used internally by the node-redis client
* to handle credential rotation and re-authentication.
*
* Note: The node-redis client manages the subscription lifecycle automatically. Users only need to implement
* onReAuthenticationError if they want to be notified about authentication failures.
*
* Error handling:
* - Errors received via onError indicate a fatal issue with the credentials stream
* - The stream is automatically closed(disposed) when onError occurs
* - onError typically mean the provider failed to fetch new credentials after retrying
*
* @example
* ```ts
* const provider = getStreamingProvider();
* const [initialCredentials, disposable] = await provider.subscribe({
* onNext: (newCredentials) => {
* // Handle credential update
* },
* onError: (error) => {
* // Handle fatal stream error
* }
* });
*
* @param listener - Callbacks to handle credential updates and errors
* @returns A Promise resolving to [initial credentials, cleanup function]
*/
subscribe: (listener: StreamingCredentialsListener<BasicAuth>) => Promise<[BasicAuth, Disposable]>

/**
* Called when authentication fails or credentials cannot be renewed in time.
* Implement this to handle authentication errors in your application.
*
* @param error - Either a CredentialsError (invalid/expired credentials) or
* UnableToObtainNewCredentialsError (failed to fetch new credentials on time)
*/
onReAuthenticationError: (error: ReAuthenticationError) => void;

}

/**
* Type representing basic authentication credentials.
*/
export type BasicAuth = { username?: string, password?: string }

/**
* Callback to handle credential updates and errors.
*/
export type StreamingCredentialsListener<T> = {
onNext: (credentials: T) => void;
onError: (e: Error) => void;
}


/**
* Providers that can supply authentication credentials
*/
export type CredentialsProvider = AsyncCredentialsProvider | StreamingCredentialsProvider

/**
* Errors that can occur during re-authentication.
*/
export type ReAuthenticationError = CredentialsError | UnableToObtainNewCredentialsError

/**
* Thrown when re-authentication fails with provided credentials .
* e.g. when the credentials are invalid, expired or revoked.
*
*/
export class CredentialsError extends Error {
constructor(message: string) {
super(`Re-authentication with latest credentials failed: ${message}`);
this.name = 'CredentialsError';
}

}

/**
* Thrown when new credentials cannot be obtained before current ones expire
*/
export class UnableToObtainNewCredentialsError extends Error {
constructor(message: string) {
super(`Unable to obtain new credentials : ${message}`);
this.name = 'UnableToObtainNewCredentialsError';
}
}
22 changes: 22 additions & 0 deletions packages/authx/lib/identity-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* An identity provider is responsible for providing a token that can be used to authenticate with a service.
*/

/**
* The response from an identity provider when requesting a token.
*
* note: "native" refers to the type of the token that the actual identity provider library is using.
*
* @type T The type of the native idp token.
* @property token The token.
* @property ttlMs The time-to-live of the token in epoch milliseconds extracted from the native token in local time.
*/
export type TokenResponse<T> = { token: T, ttlMs: number };

export interface IdentityProvider<T> {
/**
* Request a token from the identity provider.
* @returns A promise that resolves to an object containing the token and the time-to-live in epoch milliseconds.
*/
requestToken(): Promise<TokenResponse<T>>;
}
Loading
Loading