import axios from 'axios';
import { AuthResult, BearerEndpointConfig } from '../types.js';
import { BaseAuthAdapter } from './base.js';
/**
* Adaptador para Bearer Token obtenido de un endpoint
* Este es el sistema que tenías implementado originalmente
*/
export class BearerEndpointAdapter extends BaseAuthAdapter<BearerEndpointConfig> {
readonly type = 'bearer-endpoint' as const;
private token: string | null = null;
private tokenExpiresAt: number | null = null;
async initialize(config: BearerEndpointConfig, baseUrl: string): Promise<void> {
await super.initialize(config, baseUrl);
await this.fetchToken();
}
private async fetchToken(): Promise<void> {
try {
const url = this.resolveUrl(this.config.endpoint);
const method = this.config.method || 'POST';
// Procesar el body reemplazando variables de entorno
const body = this.processBody(this.config.body);
console.error(`[Auth] Fetching token from ${url}...`);
const response = await axios({
method,
url,
data: body,
headers: {
'Content-Type': 'application/json',
...this.config.headers
}
});
// Extraer el token del path especificado
this.token = this.getValueFromPath(response.data, this.config.tokenPath);
if (!this.token) {
throw new Error(`Token not found at path: ${this.config.tokenPath}`);
}
// Calcular expiración si está configurado
if (this.config.expiresIn) {
this.tokenExpiresAt = Date.now() + (this.config.expiresIn * 1000);
} else if (this.config.expiresInPath) {
const expiresIn = this.getValueFromPath(response.data, this.config.expiresInPath);
if (expiresIn) {
this.tokenExpiresAt = Date.now() + (Number(expiresIn) * 1000);
}
}
console.error('[Auth] Token obtained successfully.');
} catch (error: any) {
console.error('[Auth] Error fetching token:', error.message);
if (error.response) {
console.error('[Auth] Response data:', error.response.data);
}
throw error;
}
}
/**
* Procesa el body reemplazando placeholders con variables de entorno
*/
private processBody(body?: Record<string, any>): Record<string, any> | undefined {
if (!body) return undefined;
const processed: Record<string, any> = {};
for (const [key, value] of Object.entries(body)) {
if (typeof value === 'string') {
// Reemplazar ${VAR_NAME} con el valor de la variable de entorno
processed[key] = value.replace(/\$\{(\w+)\}/g, (_, varName) => {
return process.env[varName] || '';
});
} else if (typeof value === 'object' && value !== null) {
processed[key] = this.processBody(value);
} else {
processed[key] = value;
}
}
return processed;
}
/**
* Obtiene un valor de un objeto usando un path con puntos (ej: "data.token")
*/
private getValueFromPath(obj: any, path: string): any {
return path.split('.').reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : null;
}, obj);
}
async applyAuth(): Promise<AuthResult> {
if (!this.token) {
throw new Error('No authentication token available');
}
return {
headers: {
'Authorization': `Bearer ${this.token}`
}
};
}
needsRefresh(): boolean {
if (!this.tokenExpiresAt) return false;
// Refrescar 60 segundos antes de que expire
return Date.now() >= (this.tokenExpiresAt - 60000);
}
async refresh(): Promise<void> {
console.error('[Auth] Refreshing token...');
await this.fetchToken();
}
cleanup(): void {
this.token = null;
this.tokenExpiresAt = null;
}
}