1 | const open = require('open');
|
2 | const moment = require('moment');
|
3 | const {
|
4 | promptUser,
|
5 | PERSONAL_ACCESS_KEY_FLOW,
|
6 | PERSONAL_ACCESS_KEY,
|
7 | } = require('@hubspot/cms-cli/lib/prompts');
|
8 |
|
9 | const { HubSpotAuthError } = require('@hubspot/api-auth-lib/Errors');
|
10 | const {
|
11 | getEnv,
|
12 | getPortalId,
|
13 | getPortalConfig,
|
14 | updatePortalConfig,
|
15 | updateDefaultPortal,
|
16 | } = require('./lib/config');
|
17 | const {
|
18 | DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME,
|
19 | PERSONAL_ACCESS_KEY_AUTH_METHOD,
|
20 | } = require('./lib/constants');
|
21 | const { logger } = require('./logger');
|
22 | const { fetchAccessToken } = require('./api/localDevAuth');
|
23 |
|
24 | const refreshRequests = new Map();
|
25 |
|
26 | function getRefreshKey(personalAccessKey, expiration) {
|
27 | return `${personalAccessKey}-${expiration || 'fresh'}`;
|
28 | }
|
29 |
|
30 | async 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 |
|
57 | async 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 |
|
76 | async 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 |
|
97 | async 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 |
|
120 |
|
121 | const 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 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 | const 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 |
|
177 | module.exports = {
|
178 | accessTokenForPersonalAccessKey,
|
179 | personalAccessKeyPrompt,
|
180 | updateConfigWithPersonalAccessKey,
|
181 | };
|