import axios from 'axios';
import { AuthResult, OAuth2ClientCredentialsConfig } from '../types.js';
import { BaseAuthAdapter } from './base.js';
/**
* Adaptador para OAuth2 Client Credentials Grant
*/
export class OAuth2ClientCredentialsAdapter extends BaseAuthAdapter<OAuth2ClientCredentialsConfig> {
readonly type = 'oauth2-client-credentials' as const;
private accessToken: string | null = null;
private tokenExpiresAt: number | null = null;
async initialize(config: OAuth2ClientCredentialsConfig, baseUrl: string): Promise<void> {
await super.initialize(config, baseUrl);
await this.fetchToken();
}
private async fetchToken(): Promise<void> {
try {
const url = this.resolveUrl(this.config.tokenUrl);
console.error(`[Auth] Fetching OAuth2 token from ${url}...`);
const params = new URLSearchParams();
params.append('grant_type', 'client_credentials');
if (this.config.scopes && this.config.scopes.length > 0) {
params.append('scope', this.config.scopes.join(' '));
}
const headers: Record<string, string> = {
'Content-Type': 'application/x-www-form-urlencoded'
};
// Las credenciales pueden ir en el body o como Basic Auth
if (this.config.credentialsInBody) {
params.append('client_id', this.config.clientId);
params.append('client_secret', this.config.clientSecret);
} else {
const credentials = Buffer.from(
`${this.config.clientId}:${this.config.clientSecret}`
).toString('base64');
headers['Authorization'] = `Basic ${credentials}`;
}
const response = await axios({
method: 'POST',
url,
data: params.toString(),
headers
});
this.accessToken = response.data.access_token;
if (!this.accessToken) {
throw new Error('No access_token in response');
}
// Calcular expiración
if (response.data.expires_in) {
this.tokenExpiresAt = Date.now() + (response.data.expires_in * 1000);
}
console.error('[Auth] OAuth2 token obtained successfully.');
} catch (error: any) {
console.error('[Auth] Error fetching OAuth2 token:', error.message);
if (error.response) {
console.error('[Auth] Response data:', error.response.data);
}
throw error;
}
}
async applyAuth(): Promise<AuthResult> {
if (!this.accessToken) {
throw new Error('No OAuth2 access token available');
}
return {
headers: {
'Authorization': `Bearer ${this.accessToken}`
}
};
}
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 OAuth2 token...');
await this.fetchToken();
}
cleanup(): void {
this.accessToken = null;
this.tokenExpiresAt = null;
}
}