UNPKG

4.98 kBJavaScriptView Raw
1const open = require('open');
2const moment = require('moment');
3const {
4 promptUser,
5 PERSONAL_ACCESS_KEY_FLOW,
6 PERSONAL_ACCESS_KEY,
7} = require('@hubspot/cms-cli/lib/prompts');
8
9const { HubSpotAuthError } = require('@hubspot/api-auth-lib/Errors');
10const {
11 getEnv,
12 getPortalId,
13 getPortalConfig,
14 updatePortalConfig,
15 updateDefaultPortal,
16} = require('./lib/config');
17const {
18 DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME,
19 PERSONAL_ACCESS_KEY_AUTH_METHOD,
20} = require('./lib/constants');
21const { logger } = require('./logger');
22const { fetchAccessToken } = require('./api/localDevAuth');
23
24const refreshRequests = new Map();
25
26function getRefreshKey(personalAccessKey, expiration) {
27 return `${personalAccessKey}-${expiration || 'fresh'}`;
28}
29
30async function getAccessToken(personalAccessKey, env = 'PROD') {
31 let response;
32 try {
33 response = await fetchAccessToken(personalAccessKey, env);
34 } catch (e) {
35 if (e.response) {
36 const errorOutput = `Error while retrieving new access token: ${e.response.body.message}.`;
37 if (e.response.statusCode === 401) {
38 throw new HubSpotAuthError(
39 `${errorOutput} \nYour personal CMS access key is invalid. Please run "hs auth personalaccesskey" to reauthenticate. See https://designers.hubspot.com/docs/personal-access-keys for more information.`
40 );
41 } else {
42 throw new HubSpotAuthError(errorOutput);
43 }
44 } else {
45 throw e;
46 }
47 }
48 return {
49 portalId: response.hubId,
50 accessToken: response.oauthAccessToken,
51 expiresAt: moment(response.expiresAtMillis),
52 scopeGroups: response.scopeGroups,
53 encodedOauthRefreshToken: response.encodedOauthRefreshToken,
54 };
55}
56
57async function refreshAccessToken(personalAccessKey, env = 'PROD') {
58 const { accessToken, expiresAt, portalId } = await getAccessToken(
59 personalAccessKey,
60 env
61 );
62 const config = getPortalConfig(portalId);
63
64 updatePortalConfig({
65 ...config,
66 portalId,
67 tokenInfo: {
68 accessToken,
69 expiresAt: expiresAt.toISOString(),
70 },
71 });
72
73 return accessToken;
74}
75
76async function getNewAccessToken(personalAccessKey, expiresAt, env) {
77 const key = getRefreshKey(personalAccessKey, expiresAt);
78 if (refreshRequests.has(key)) {
79 return refreshRequests.get(key);
80 }
81 let accessToken;
82 try {
83 const refreshAccessPromise = refreshAccessToken(personalAccessKey, env);
84 if (key) {
85 refreshRequests.set(key, refreshAccessPromise);
86 }
87 accessToken = await refreshAccessPromise;
88 } catch (e) {
89 if (key) {
90 refreshRequests.delete(key);
91 }
92 throw e;
93 }
94 return accessToken;
95}
96
97async function accessTokenForPersonalAccessKey(portalId) {
98 const { auth, personalAccessKey, env } = getPortalConfig(portalId);
99 const authTokenInfo = auth && auth.tokenInfo;
100 const authDataExists = authTokenInfo && auth.tokenInfo.accessToken;
101
102 if (
103 !authDataExists ||
104 moment()
105 .add(30, 'minutes')
106 .isAfter(moment(authTokenInfo.expiresAt))
107 ) {
108 return getNewAccessToken(
109 personalAccessKey,
110 authTokenInfo && authTokenInfo.expiresAt,
111 env
112 );
113 }
114
115 return auth.tokenInfo.accessToken;
116}
117
118/**
119 * Prompts user for portal name, then opens their browser to the shortlink to personal-access-key
120 */
121const personalAccessKeyPrompt = async () => {
122 const { name } = await promptUser(PERSONAL_ACCESS_KEY_FLOW);
123 const portalId = getPortalId(name);
124 const env = getEnv(name);
125 if (portalId) {
126 open(
127 `https://app.hubspot${
128 env === 'QA' ? 'qa' : ''
129 }.com/personal-access-key/${portalId}`
130 );
131 } else {
132 open(
133 `https://app.hubspot${env === 'QA' ? 'qa' : ''}.com/l/personal-access-key`
134 );
135 }
136 const { personalAccessKey } = await promptUser(PERSONAL_ACCESS_KEY);
137
138 return {
139 personalAccessKey,
140 name,
141 };
142};
143
144/**
145 * Adds a portal to the config using authType: personalAccessKey
146 *
147 * @param {object} configData Data containing personalAccessKey and name properties
148 * @param {string} configData.personalAccessKey Personal access key string to place in config
149 * @param {string} configData.name Unique name to identify this config entry
150 * @param {boolean} makeDefault option to make the portal being added to the config the default portal
151 */
152const updateConfigWithPersonalAccessKey = async (configData, makeDefault) => {
153 const { personalAccessKey, name } = configData;
154
155 const { portalId, accessToken, expiresAt } = await getAccessToken(
156 personalAccessKey,
157 getEnv(name)
158 );
159
160 updatePortalConfig({
161 portalId,
162 personalAccessKey,
163 name,
164 authType: PERSONAL_ACCESS_KEY_AUTH_METHOD.value,
165 tokenInfo: { accessToken, expiresAt },
166 });
167
168 if (makeDefault) {
169 updateDefaultPortal(name);
170 }
171
172 logger.log(
173 `Success: ${DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME} created with ${PERSONAL_ACCESS_KEY_AUTH_METHOD.name}.`
174 );
175};
176
177module.exports = {
178 accessTokenForPersonalAccessKey,
179 personalAccessKeyPrompt,
180 updateConfigWithPersonalAccessKey,
181};