// @flow import {parse as urlParse} from 'url'; import * as c from './constants'; import {buildQueryStringFromParams, joinUrlAndQueryString} from 'insomnia-url'; import {authorizeUserInWindow, responseToObject} from './misc'; import {escapeRegex} from '../../common/misc'; import * as models from '../../models/index'; import {sendWithSettings} from '../network'; import {getBasicAuthHeader} from '../basic-auth/get-header'; export default async function ( requestId: string, authorizeUrl: string, accessTokenUrl: string, credentialsInBody: boolean, clientId: string, clientSecret: string, redirectUri: string = '', scope: string = '', state: string = '' ): Promise { if (!authorizeUrl) { throw new Error('Invalid authorization URL'); } if (!accessTokenUrl) { throw new Error('Invalid access token URL'); } const authorizeResults = await _authorize( authorizeUrl, clientId, redirectUri, scope, state ); // Handle the error if (authorizeResults[c.P_ERROR]) { const code = authorizeResults[c.P_ERROR]; const msg = authorizeResults[c.P_ERROR_DESCRIPTION]; const uri = authorizeResults[c.P_ERROR_URI]; throw new Error(`OAuth 2.0 Error ${code}\n\n${msg}\n\n${uri}`); } return _getToken( requestId, accessTokenUrl, credentialsInBody, clientId, clientSecret, authorizeResults[c.P_CODE], redirectUri, state ); } async function _authorize (url, clientId, redirectUri = '', scope = '', state = '') { const params = [ {name: c.P_RESPONSE_TYPE, value: c.RESPONSE_TYPE_CODE}, {name: c.P_CLIENT_ID, value: clientId} ]; // Add optional params redirectUri && params.push({name: c.P_REDIRECT_URI, value: redirectUri}); scope && params.push({name: c.P_SCOPE, value: scope}); state && params.push({name: c.P_STATE, value: state}); // Add query params to URL const qs = buildQueryStringFromParams(params); const finalUrl = joinUrlAndQueryString(url, qs); const successRegex = new RegExp(`${escapeRegex(redirectUri)}.*(code=)`, 'i'); const failureRegex = new RegExp(`${escapeRegex(redirectUri)}.*(error=)`, 'i'); const redirectedTo = await authorizeUserInWindow(finalUrl, successRegex, failureRegex); console.log('[oauth2] Detected redirect ' + redirectedTo); const {query} = urlParse(redirectedTo); return responseToObject(query, [ c.P_CODE, c.P_STATE, c.P_ERROR, c.P_ERROR_DESCRIPTION, c.P_ERROR_URI ]); } async function _getToken ( requestId: string, url: string, credentialsInBody: boolean, clientId: string, clientSecret: string, code: string, redirectUri: string = '', state: string = '' ): Promise { const params = [ {name: c.P_GRANT_TYPE, value: c.GRANT_TYPE_AUTHORIZATION_CODE}, {name: c.P_CODE, value: code} ]; // Add optional params redirectUri && params.push({name: c.P_REDIRECT_URI, value: redirectUri}); state && params.push({name: c.P_STATE, value: state}); const headers = [ {name: 'Content-Type', value: 'application/x-www-form-urlencoded'}, {name: 'Accept', value: 'application/x-www-form-urlencoded, application/json'} ]; if (credentialsInBody) { params.push({name: c.P_CLIENT_ID, value: clientId}); params.push({name: c.P_CLIENT_SECRET, value: clientSecret}); } else { headers.push(getBasicAuthHeader(clientId, clientSecret)); } const response = await sendWithSettings(requestId, { headers, url, method: 'POST', body: models.request.newBodyFormUrlEncoded(params) }); const bodyBuffer = models.response.getBodyBuffer(response); if (!bodyBuffer) { throw new Error(`[oauth2] No body returned from ${url}`); } const statusCode = response.statusCode || 0; if (statusCode < 200 || statusCode >= 300) { throw new Error( `[oauth2] Failed to fetch token url=${url} status=${statusCode}\n` + bodyBuffer.toString('utf8') ); } return responseToObject(bodyBuffer.toString('utf8'), [ c.P_ACCESS_TOKEN, c.P_REFRESH_TOKEN, c.P_EXPIRES_IN, c.P_TOKEN_TYPE, c.P_SCOPE, c.P_ERROR, c.P_ERROR_URI, c.P_ERROR_DESCRIPTION ]); }