@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
474 lines • 14.2 kB
JavaScript
/**
* Authentication Utilities for NeuroLink Client SDK
*
* Provides authentication configuration, token management, and OAuth2 support
* for securing API requests to NeuroLink servers.
*
* @module @neurolink/client/auth
*/
// =============================================================================
// OAuth2 Token Manager
// =============================================================================
/**
* OAuth2 Token Manager for client credentials flow
*
* Handles token acquisition, caching, and automatic refresh for OAuth2
* client credentials authentication.
*
* @example Basic usage
* ```typescript
* const tokenManager = new OAuth2TokenManager({
* tokenUrl: 'https://auth.example.com/oauth/token',
* clientId: 'your-client-id',
* clientSecret: 'your-client-secret',
* scope: 'api:read api:write',
* });
*
* // Get token (automatically refreshes if needed)
* const token = await tokenManager.getToken();
*
* // Use with client
* const client = createClient({
* baseUrl: 'https://api.example.com',
* });
* client.use(createDynamicAuthInterceptor(() => tokenManager.getToken()));
* ```
*/
export class OAuth2TokenManager {
config;
token = null;
tokenExpiry = null;
refreshPromise = null;
refreshBufferMs;
constructor(config, options) {
this.config = config;
this.refreshBufferMs = options?.refreshBufferMs ?? 60000;
}
/**
* Get a valid access token
*
* Returns cached token if still valid, otherwise fetches a new one.
* Handles concurrent requests by deduplicating token refresh calls.
*/
async getToken() {
// Check if token is still valid
if (this.token &&
this.tokenExpiry &&
Date.now() < this.tokenExpiry - this.refreshBufferMs) {
return this.token;
}
// Deduplicate concurrent refresh requests
if (this.refreshPromise) {
return this.refreshPromise;
}
// Fetch new token
this.refreshPromise = this.fetchToken();
try {
return await this.refreshPromise;
}
finally {
this.refreshPromise = null;
}
}
/**
* Invalidate the cached token
*
* Call this when the token is rejected by the server to force a refresh.
*/
invalidate() {
this.token = null;
this.tokenExpiry = null;
}
/**
* Check if the cached token is valid
*/
isValid() {
return !!(this.token &&
this.tokenExpiry &&
Date.now() < this.tokenExpiry - this.refreshBufferMs);
}
/**
* Get the token expiry time in milliseconds
*/
getExpiryTime() {
return this.tokenExpiry;
}
/**
* Fetch a new token from the OAuth2 server
*/
async fetchToken() {
const params = new URLSearchParams({
grant_type: "client_credentials",
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
});
if (this.config.scope) {
params.set("scope", this.config.scope);
}
if (this.config.audience) {
params.set("audience", this.config.audience);
}
const response = await fetch(this.config.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: params.toString(),
});
if (!response.ok) {
const errorText = await response.text().catch(() => "Unknown error");
throw new OAuth2Error(`OAuth2 token request failed: ${response.status}`, response.status, errorText);
}
const data = (await response.json());
this.token =
data.accessToken ||
data.access_token ||
"";
const expiresIn = data.expiresIn ||
data.expires_in ||
3600;
this.tokenExpiry = Date.now() + expiresIn * 1000;
if (!this.token) {
throw new OAuth2Error("OAuth2 response missing access token", 200, JSON.stringify(data));
}
return this.token;
}
}
// =============================================================================
// JWT Token Manager
// =============================================================================
/**
* JWT Token Manager with automatic refresh
*
* Manages JWT tokens with automatic refresh using a provided refresh function.
*
* @example
* ```typescript
* const tokenManager = new JWTTokenManager({
* token: 'initial-jwt-token',
* expiresAt: Date.now() + 3600000,
* refreshFn: async () => {
* const response = await fetch('/api/auth/refresh', {
* method: 'POST',
* credentials: 'include',
* });
* const data = await response.json();
* return { accessToken: data.token, expiresIn: data.expiresIn };
* },
* });
* ```
*/
export class JWTTokenManager {
config;
token;
tokenExpiry;
refreshPromise = null;
refreshBufferMs;
constructor(config) {
this.config = config;
this.token = config.token;
this.tokenExpiry = config.expiresAt;
this.refreshBufferMs = config.refreshBufferMs ?? 60000;
}
/**
* Get a valid access token
*/
async getToken() {
if (Date.now() < this.tokenExpiry - this.refreshBufferMs) {
return this.token;
}
if (this.refreshPromise) {
return this.refreshPromise;
}
this.refreshPromise = this.refreshToken();
try {
return await this.refreshPromise;
}
finally {
this.refreshPromise = null;
}
}
/**
* Force token refresh
*/
async forceRefresh() {
this.refreshPromise = this.refreshToken();
try {
return await this.refreshPromise;
}
finally {
this.refreshPromise = null;
}
}
/**
* Update token manually
*/
setToken(token, expiresAt) {
this.token = token;
this.tokenExpiry = expiresAt;
}
/**
* Check if token is valid
*/
isValid() {
return Date.now() < this.tokenExpiry - this.refreshBufferMs;
}
async refreshToken() {
const result = await this.config.refreshFn();
this.token = result.accessToken;
this.tokenExpiry = Date.now() + result.expiresIn * 1000;
return this.token;
}
}
// =============================================================================
// Authentication ClientMiddleware
// =============================================================================
/**
* Create an API key authentication middleware
*
* @example
* ```typescript
* const client = createClient({ baseUrl: 'https://api.example.com' });
* client.use(createApiKeyMiddleware('your-api-key'));
* ```
*/
export function createApiKeyMiddleware(apiKey, headerName = "X-API-Key") {
return async (request, next) => {
request.headers[headerName] = apiKey;
return next();
};
}
/**
* Create a Bearer token authentication middleware
*
* @example
* ```typescript
* const client = createClient({ baseUrl: 'https://api.example.com' });
* client.use(createBearerTokenMiddleware('your-jwt-token'));
* ```
*/
export function createBearerTokenMiddleware(token) {
return async (request, next) => {
request.headers["Authorization"] = `Bearer ${token}`;
return next();
};
}
/**
* Create a dynamic authentication middleware with token manager
*
* @example With OAuth2TokenManager
* ```typescript
* const tokenManager = new OAuth2TokenManager({
* tokenUrl: 'https://auth.example.com/oauth/token',
* clientId: 'client-id',
* clientSecret: 'client-secret',
* });
*
* const client = createClient({ baseUrl: 'https://api.example.com' });
* client.use(createTokenManagerMiddleware(tokenManager));
* ```
*/
export function createTokenManagerMiddleware(tokenManager) {
return async (request, next) => {
const token = await tokenManager.getToken();
request.headers["Authorization"] = `Bearer ${token}`;
return next();
};
}
/**
* Create an authentication middleware with retry on 401
*
* Automatically refreshes token and retries request when receiving 401.
*
* @example
* ```typescript
* const tokenManager = new OAuth2TokenManager({...});
*
* const client = createClient({ baseUrl: 'https://api.example.com' });
* client.use(createAuthWithRetryMiddleware(tokenManager));
* ```
*/
export function createAuthWithRetryMiddleware(tokenManager, maxRetries = 1) {
return async (request, next) => {
let retries = 0;
while (true) {
const token = await tokenManager.getToken();
request.headers["Authorization"] = `Bearer ${token}`;
const response = await next();
if (response.status === 401 && retries < maxRetries) {
tokenManager.invalidate();
retries++;
// Fetch a fresh token and update the request headers for the retry
const freshToken = await tokenManager.getToken();
request.headers["Authorization"] = `Bearer ${freshToken}`;
continue;
}
return response;
}
};
}
/**
* Create a multi-auth middleware that supports multiple authentication methods
*
* @example
* ```typescript
* const client = createClient({ baseUrl: 'https://api.example.com' });
* client.use(createMultiAuthMiddleware({
* apiKey: process.env.API_KEY,
* token: sessionToken,
* }));
* ```
*/
export function createMultiAuthMiddleware(config) {
return async (request, next) => {
// Add API key if provided
if (config.apiKey) {
const headerName = config.apiKeyHeaderName ?? "X-API-Key";
request.headers[headerName] = config.apiKey;
}
// Add bearer token if provided
if (config.token) {
const headerName = config.headerName ?? "Authorization";
request.headers[headerName] = `Bearer ${config.token}`;
}
// Handle dynamic token refresh
if (config.refreshToken && config.tokenExpiresAt) {
const bufferMs = config.refreshBufferMs ?? 60000;
if (Date.now() > config.tokenExpiresAt - bufferMs) {
const newToken = await config.refreshToken();
// Persist the refreshed token back to config so subsequent requests use it
config.token = newToken;
config.tokenExpiresAt = Date.now() + bufferMs * 2;
const headerName = config.headerName ?? "Authorization";
request.headers[headerName] = `Bearer ${newToken}`;
}
}
return next();
};
}
// =============================================================================
// Error Classes
// =============================================================================
/**
* Error thrown during OAuth2 operations
*/
export class OAuth2Error extends Error {
status;
responseBody;
constructor(message, status, responseBody) {
super(message);
this.name = "OAuth2Error";
this.status = status;
this.responseBody = responseBody;
}
}
/**
* Error thrown when authentication fails
*/
export class OAuth2AuthenticationError extends Error {
code;
status;
constructor(message, code = "AUTH_ERROR", status = 401) {
super(message);
this.name = "OAuth2AuthenticationError";
this.code = code;
this.status = status;
}
}
/**
* Error thrown when token refresh fails
*/
export class TokenRefreshError extends Error {
cause;
constructor(message, cause) {
super(message);
this.name = "TokenRefreshError";
this.cause = cause;
}
}
// =============================================================================
// Utility Functions
// =============================================================================
/**
* Decode a JWT token payload without verification
*
* @example
* ```typescript
* const payload = decodeJWTPayload(token);
* console.log('Token expires at:', new Date(payload.exp * 1000));
* ```
*/
export function decodeJWTPayload(token) {
try {
const parts = token.split(".");
if (parts.length !== 3) {
throw new Error("Invalid JWT format");
}
const payload = parts[1];
const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
return JSON.parse(decoded);
}
catch {
throw new Error("Failed to decode JWT token");
}
}
/**
* Check if a JWT token is expired
*
* @example
* ```typescript
* if (isJWTExpired(token)) {
* // Refresh the token
* }
* ```
*/
export function isJWTExpired(token, bufferMs = 0) {
try {
const payload = decodeJWTPayload(token);
const exp = payload.exp;
if (!exp) {
return true;
}
return Date.now() > exp * 1000 - bufferMs;
}
catch {
return true;
}
}
/**
* Extract expiry time from a JWT token
*
* @returns Expiry time in milliseconds, or null if not available
*/
export function getJWTExpiry(token) {
try {
const payload = decodeJWTPayload(token);
const exp = payload.exp;
return exp ? exp * 1000 : null;
}
catch {
return null;
}
}
/**
* Create an API key from environment variable with validation
*
* @example
* ```typescript
* const apiKey = getApiKeyFromEnv('NEUROLINK_API_KEY');
* const client = createClient({
* baseUrl: 'https://api.example.com',
* apiKey,
* });
* ```
*/
export function getApiKeyFromEnv(envVar, options) {
const value = typeof process !== "undefined" ? process.env[envVar] : undefined;
if (!value && options?.required) {
throw new Error(`Required environment variable ${envVar} is not set`);
}
return value;
}
// Types are already exported at their definition sites above
//# sourceMappingURL=auth.js.map