UNPKG

@basetime/bldr-sfmc

Version:

CLI application for SFMC Development workflow and package deployment

360 lines (306 loc) 13.1 kB
// import BLDR from '@basetime/bldr-sfmc-sdk'; const BLDR = require('@basetime/bldr-sfmc-sdk'); const axios = require('axios').default; const redirectURL = 'https://bldr.io/cli/sfmc/authenticate/'; const redirect = encodeURIComponent(redirectURL); const open = require('open'); import { CLI_Client } from '@basetime/bldr-sfmc-sdk/lib/cli/types/cli_client'; import { SFMC_Client } from '@basetime/bldr-sfmc-sdk/lib/cli/types/sfmc_client'; import { getPassword, setPassword } from 'keytar-sync'; import { Config } from '../_bldr/_processes/config'; import { State } from '../_bldr/_processes/state'; import { isExpired } from '../_utils'; import { displayLine } from '../_utils/display'; const { getState, debug } = new State(); const { getInstanceConfiguration } = new Config(); /** * * @param accessToken */ const getAuthenticatedUserPermissions = async (authObject: any) => { try { const userRequest = await axios.get(`${authObject.auth_url}v2/userinfo`, { headers: { Authorization: `Bearer ${authObject.access_token}`, }, }); debug('Retrieve Authenticated User Permissions', 'info', userRequest); return userRequest?.data?.permissions; } catch (err) { debug('Retrieve Authenticated User Permissions Err', 'error', err); return err; } }; /** * * * @param authObject * @param code * @returns */ const verifyChallengeCode = async (authObject: any, code: string) => { try { if (!code) { displayLine('Challenge Code Not Received', 'error'); return; } displayLine('Verify Challenge Code Request', 'info'); const challengePayload = { grant_type: 'authorization_code', client_id: authObject.client_id, client_secret: authObject.client_secret, redirect_uri: redirectURL, account_id: authObject.account_id, code: code, }; debug('Challenge Code Request', 'info', { authObject, challengePayload }); const tokenRequest = await axios.post(`${authObject.auth_url}v2/token`, challengePayload); if (tokenRequest && new RegExp(/^2/).test(tokenRequest.status)) { displayLine('Challenge Code verified', 'success'); let authObjectResponse = tokenRequest.data; authObjectResponse.scope = authObjectResponse.scope.split(' '); authObjectResponse.expiration = process.hrtime()[0] + authObjectResponse.expires_in; authObjectResponse.account_id = authObject.account_id; authObjectResponse.auth_url = authObject.auth_url; return authObjectResponse; } else { debug('No Token Request', 'error', tokenRequest); return tokenRequest; } return false; } catch (err) { debug('Verify Challenge Code Err', 'error', err); return err; } }; /** * * @param authObject * @returns */ const oAuthInitiator = async (authObject: any) => { return new Promise(async (resolve, reject) => { const express = require('express'); const cors = require('cors'); const app = express(); const port = 3000; displayLine('Initiating Authentication', 'info'); displayLine('Opening Browser for Authentication, action may be required', 'info'); await open( `${authObject.auth_url}v2/authorize?client_id=${authObject.client_id}&redirect_uri=${redirect}&response_type=code` ); const bodyParser = require('body-parser'); let httpServer = require('http').createServer(app); app.use(bodyParser.json()); app.use(cors({ origin: '*' })); app.post('/oauth', async function (req: any, res: any) { //const code = req.query.code const code = req.body.code; code && displayLine('BLDR Received Challenge Code', 'info'); debug('Challenge Code', 'info', code); const verified = code && (await verifyChallengeCode(authObject, code)); debug('Verify Challenge Code Response', 'info', verified); const userPermissions = verified && (await getAuthenticatedUserPermissions(verified)); verified.user = { permissions: verified && userPermissions, }; verified && displayLine('Finishing oAuthentication', 'info'); verified && displayLine('Open browser window can be closed', 'info'); setTimeout(() => { !verified && displayLine('Authentication Failed', 'info'); res.end(); httpServer.close(); }, 6000); res.send('BLDR -> SFMC authentication complete. You can close this window now!'); res.end(); httpServer.close(); resolve(verified); }); httpServer.listen(port, () => {}); }); }; /** * * @param authObject.client_id * @param authObject.client_secret * @param authObject.account_id * @param authObject.auth_url */ const initiateBldrSDK = async ( authObject?: { client_id: string; client_secret: string; account_id: number; auth_url: string; }, instance?: string, configurationType?: string, account_id?: number ): Promise<{ sfmc: SFMC_Client; cli: CLI_Client; }> => { try { debug('Initiating bldr sdk: initial request', 'info', ''); // If authObject is passed use those credentials to initiate SDK if (authObject && configurationType && configurationType === 'Server-to-Server') { debug('Initiate sdk Server-To-Server', 'info', { authObject, instance, configurationType, account_id, }); return new BLDR(authObject); } else if (authObject && configurationType && configurationType === 'Web App') { debug('Initiate sdk Web-App', 'info', ''); const verified = Object.assign({}, await oAuthInitiator(authObject)); debug('Initiate sdk Web-App: Received Verification', 'info', verified); if (verified) { const oAuthJSON = { ...verified, ...authObject, }; await setPassword( 'bldr', 'currentSession', JSON.stringify({ instance, authObject: oAuthJSON, }) ); debug('Check Session Saved', 'info', await getPassword('bldr', 'currentSession')); return oAuthJSON && new BLDR(oAuthJSON); } } debug('Initiating bldr sdk from current state', 'info', ''); // If authObject is not passed use the current set credentials to initiate SDK const currentState = await getState(); const stateInstance = currentState.instance; const activeMID = currentState.activeMID; debug('Current bldr state', 'info', { instance: stateInstance, mid: activeMID, }); let stateConfiguration = await getInstanceConfiguration(stateInstance); debug('Current Configuration', 'info', { ...stateConfiguration, apiClientId: stateConfiguration.apiClientId.substring(0, 5), apiClientSecret: stateConfiguration.apiClientSecret.substring(0, 5), }); stateConfiguration.configurationType = stateConfiguration.configurationType || 'Server-to-Server'; const currentSession = await getPassword('bldr', 'currentSession'); const currentSessionJSON = currentSession && JSON.parse(currentSession); const currentAuthObject = currentSessionJSON && currentSessionJSON.authObject; debug('Current session', 'info', currentSession); //Check if session is expired let sessionExpired = currentAuthObject && (await isExpired(currentSessionJSON.authObject)); debug('Session expired', 'info', sessionExpired); //Check if target MID has been updated let midUpdated = false; if (currentAuthObject && activeMID !== currentAuthObject.account_id) { currentAuthObject.account_id = activeMID; midUpdated = true; } let sdkConfiguration = { client_id: stateConfiguration.apiClientId, client_secret: stateConfiguration.apiClientSecret, account_id: account_id || currentState.activeMID || stateConfiguration.parentMID, auth_url: stateConfiguration.authURI, }; if ( Object.prototype.hasOwnProperty.call(stateConfiguration, 'configurationType') && stateConfiguration.configurationType === 'Server-to-Server' ) { if (currentSession && !sessionExpired && !midUpdated && stateInstance === currentSessionJSON.instance) { debug('Initiating bldr sdk: request', 'info', { ...sdkConfiguration, ...currentAuthObject, }); return new BLDR({ ...sdkConfiguration, ...currentAuthObject, }); } else { debug('Requesting Authentication Token Refresh: request', 'info', sdkConfiguration); const newSession = new BLDR(sdkConfiguration); let accessToken = await newSession.sfmc.account.getAccessTokenResponse(); debug('Requesting Authentication Token Refresh: response', 'info', accessToken); accessToken.scope = accessToken.scope.split(' '); delete accessToken.client_id; delete accessToken.client_secret; await setPassword( 'bldr', 'currentSession', JSON.stringify({ instance: stateInstance, authObject: accessToken, }) ); debug('Check Session Saved', 'info', await getPassword('bldr', 'currentSession')); return newSession; } } else if ( Object.prototype.hasOwnProperty.call(stateConfiguration, 'configurationType') && stateConfiguration.configurationType === 'Web App' ) { if (currentSession && stateInstance === currentSessionJSON.instance && !sessionExpired && !midUpdated) { sdkConfiguration = { ...sdkConfiguration, ...currentAuthObject, }; } else if ( currentSession && stateInstance === currentSessionJSON.instance && (sessionExpired || midUpdated) ) { debug('Requesting Authentication Token Refresh: request', 'info', sdkConfiguration); const verified = Object.assign( {}, await oAuthInitiator({ ...sdkConfiguration, ...currentAuthObject, }) ); debug('Initiate sdk Web-App: Received Verification', 'info', verified); if (verified) { sdkConfiguration = { ...sdkConfiguration, ...verified, }; await setPassword( 'bldr', 'currentSession', JSON.stringify({ instance: stateInstance, authObject: verified, }) ); debug('Check Session Saved', 'info', await getPassword('bldr', 'currentSession')); } } else if ((currentSession && stateInstance !== currentSessionJSON.instance) || !currentSession) { const verified = Object.assign({}, await oAuthInitiator(sdkConfiguration)); debug('Initiate sdk Web-App: Received Verification', 'info', verified); if (verified) { sdkConfiguration = { ...sdkConfiguration, ...verified, }; await setPassword( 'bldr', 'currentSession', JSON.stringify({ instance: stateInstance, authObject: verified, }) ); debug('Check Session Saved', 'info', await getPassword('bldr', 'currentSession')); } } } return new BLDR(sdkConfiguration); } catch (err: any) { debug('Initiate sdk Err', 'error', err); return err; } }; export { initiateBldrSDK };