UNPKG

@usebruno/requests

Version:

199 lines (172 loc) 5.36 kB
import axios, { AxiosError } from 'axios'; import qs from 'qs'; export interface TokenStore { saveToken(serviceId: string, account: string, token: any): Promise<boolean>; getToken(serviceId: string, account: string): Promise<any>; deleteToken(serviceId: string, account: string): Promise<boolean>; } export interface OAuth2Config { grantType: 'client_credentials' | 'password'; accessTokenUrl: string; clientId?: string; clientSecret?: string; username?: string; password?: string; scope?: string; credentialsPlacement?: 'header' | 'body'; } interface RequestConfig { headers: { 'Content-Type': string; 'Authorization'?: string; }; } interface ClientCredentialsData { grant_type: string; scope: string; client_id?: string; client_secret?: string; } interface PasswordGrantData { grant_type: string; username: string; password: string; scope: string; client_id?: string; client_secret?: string; } /** * Fetches an OAuth2 token using client credentials grant */ const fetchTokenClientCredentials = async (oauth2Config: OAuth2Config) => { const { accessTokenUrl, clientId, clientSecret, scope, credentialsPlacement = 'header' } = oauth2Config; if (!accessTokenUrl || !clientId) { throw new Error('Missing required OAuth2 parameters'); } const data: ClientCredentialsData = { grant_type: 'client_credentials', scope: scope || '' }; const config: RequestConfig = { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; // Handle credentials placement if (credentialsPlacement === 'header') { config.headers['Authorization'] = `Basic ${Buffer.from(`${clientId}:${clientSecret || ''}`).toString('base64')}`; } else { // Credentials in body data.client_id = clientId; if (clientSecret) { data.client_secret = clientSecret; } } try { const response = await axios.post(accessTokenUrl, qs.stringify(data), config); return response.data; } catch (error) { if (error instanceof Error) { console.error('CLIENT_CREDENTIALS: Error fetching OAuth2 token:', error.message); } throw error; } }; /** * Fetches an OAuth2 token using password grant */ const fetchTokenPassword = async (oauth2Config: OAuth2Config) => { const { accessTokenUrl, clientId, clientSecret, username, password, scope, credentialsPlacement = 'header' } = oauth2Config; if (!accessTokenUrl || !username || !password) { throw new Error('Missing required OAuth2 parameters for password grant'); } const data: PasswordGrantData = { grant_type: 'password', username, password, scope: scope || '' }; const config: RequestConfig = { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; // Handle credentials placement if (credentialsPlacement === 'header' && clientId) { config.headers['Authorization'] = `Basic ${Buffer.from(`${clientId}:${clientSecret || ''}`).toString('base64')}`; } else if (clientId) { // Credentials in body data.client_id = clientId; if (clientSecret) { data.client_secret = clientSecret; } } try { const response = await axios.post(accessTokenUrl, qs.stringify(data), config); return response.data; } catch (error) { if (error instanceof AxiosError && error.response) { console.error('PASSWORD_GRANT: Error fetching OAuth2 token:', error.message); console.error('Status:', error.response.status, 'Response:', error.response.data); } else if (error instanceof Error) { console.error('PASSWORD_GRANT: Error fetching OAuth2 token:', error.message); } throw error; } }; /** * Manages OAuth2 token retrieval and storage */ export const getOAuth2Token = async (oauth2Config: OAuth2Config, tokenStore: TokenStore): Promise<string | null> => { const { grantType, clientId, accessTokenUrl } = oauth2Config; if (!grantType || !accessTokenUrl) { throw new Error('Missing required OAuth2 parameters: grantType or accessTokenUrl'); } const serviceId = accessTokenUrl; const account = clientId || oauth2Config.username || 'default'; // Check if we already have a token stored const existingToken = await tokenStore.getToken(serviceId, account); if (existingToken) { // Check if token is expired if (existingToken.expires_at && existingToken.expires_at > Date.now()) { return existingToken.access_token; } } // No valid token found, fetch a new one try { let tokenResponse; if (grantType === 'client_credentials') { tokenResponse = await fetchTokenClientCredentials(oauth2Config); } else if (grantType === 'password') { tokenResponse = await fetchTokenPassword(oauth2Config); } else { throw new Error(`Unsupported grant type: ${grantType}`); } // Calculate expiry time if expires_in is provided if (tokenResponse.expires_in) { tokenResponse.expires_at = Date.now() + tokenResponse.expires_in * 1000; } // Store the token await tokenStore.saveToken(serviceId, account, tokenResponse); return tokenResponse.access_token; } catch (error) { if (error instanceof Error) { console.error('Failed to get OAuth2 token:', error.message); } return null; } };