@slates/cli 1.0.0-rc.4 → 1.0.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/cli.ts +206 -176
- package/src/commands/auth.test.ts +60 -0
- package/src/commands/auth.ts +45 -4
- package/src/lib/context.ts +9 -2
- package/src/lib/oauth.ts +45 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slates/cli",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.6",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@inquirer/prompts": "^7.4.0",
|
|
28
|
-
"@slates/client": "1.0.0-rc.
|
|
29
|
-
"@slates/oauth-microsoft": "1.0.0-rc.
|
|
30
|
-
"@slates/profiles": "1.0.0-rc.
|
|
28
|
+
"@slates/client": "1.0.0-rc.8",
|
|
29
|
+
"@slates/oauth-microsoft": "1.0.0-rc.3",
|
|
30
|
+
"@slates/profiles": "1.0.0-rc.6",
|
|
31
31
|
"sade": "^1.8.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
package/src/cli.ts
CHANGED
|
@@ -59,203 +59,233 @@ if (isGlobalTestCommand) {
|
|
|
59
59
|
|
|
60
60
|
cli.parse([process.argv[0] ?? 'bun', process.argv[1] ?? 'slates', ...argv]);
|
|
61
61
|
} else {
|
|
62
|
+
cli
|
|
63
|
+
.command('profiles add')
|
|
64
|
+
.option('--name', 'Profile name')
|
|
65
|
+
.option('--entry', 'Local slate entry file')
|
|
66
|
+
.option('--export-name', 'Named export for the local slate provider')
|
|
67
|
+
.option('--default', 'Use this profile as the default')
|
|
68
|
+
.action(opts =>
|
|
69
|
+
printResult(() =>
|
|
70
|
+
addProfile({
|
|
71
|
+
integration: integration!,
|
|
72
|
+
name: opts.name,
|
|
73
|
+
entry: opts.entry,
|
|
74
|
+
exportName: opts.exportName,
|
|
75
|
+
useAsDefault: Boolean(opts.default)
|
|
76
|
+
})
|
|
77
|
+
)
|
|
78
|
+
);
|
|
62
79
|
|
|
63
|
-
cli
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.option('--entry', 'Local slate entry file')
|
|
67
|
-
.option('--export-name', 'Named export for the local slate provider')
|
|
68
|
-
.option('--default', 'Use this profile as the default')
|
|
69
|
-
.action(opts =>
|
|
70
|
-
printResult(() =>
|
|
71
|
-
addProfile({
|
|
72
|
-
integration: integration!,
|
|
73
|
-
name: opts.name,
|
|
74
|
-
entry: opts.entry,
|
|
75
|
-
exportName: opts.exportName,
|
|
76
|
-
useAsDefault: Boolean(opts.default)
|
|
77
|
-
})
|
|
78
|
-
)
|
|
79
|
-
);
|
|
80
|
+
cli
|
|
81
|
+
.command('profiles list')
|
|
82
|
+
.action(() => printResult(() => listProfiles({ integration: integration! })));
|
|
80
83
|
|
|
81
|
-
cli
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
cli
|
|
85
|
+
.command('profiles get [profile]')
|
|
86
|
+
.action((profile: string | undefined) =>
|
|
87
|
+
printResult(() => getProfile({ integration: integration!, profile }))
|
|
88
|
+
);
|
|
84
89
|
|
|
85
|
-
cli
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
cli
|
|
91
|
+
.command('profiles use [profile]')
|
|
92
|
+
.action((profile: string | undefined) =>
|
|
93
|
+
printResult(() => useProfile({ integration: integration!, profile }))
|
|
94
|
+
);
|
|
90
95
|
|
|
91
|
-
cli
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
cli
|
|
97
|
+
.command('profiles remove [profile]')
|
|
98
|
+
.action((profile: string | undefined) =>
|
|
99
|
+
printResult(() => removeProfile({ integration: integration!, profile }))
|
|
100
|
+
);
|
|
96
101
|
|
|
97
|
-
cli
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
cli
|
|
103
|
+
.command('setup')
|
|
104
|
+
.option('--name', 'Profile name')
|
|
105
|
+
.option('--export-name', 'Named export for the local slate provider')
|
|
106
|
+
.action(opts =>
|
|
107
|
+
printResult(() =>
|
|
108
|
+
setupIntegration({
|
|
109
|
+
integration: integration!,
|
|
110
|
+
name: opts.name,
|
|
111
|
+
exportName: opts.exportName
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
);
|
|
102
115
|
|
|
103
|
-
cli
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
);
|
|
116
|
+
cli
|
|
117
|
+
.command('tools list')
|
|
118
|
+
.option('--profile', 'Profile ID or name')
|
|
119
|
+
.action(opts =>
|
|
120
|
+
printResult(() =>
|
|
121
|
+
listTools({
|
|
122
|
+
integration: integration!,
|
|
123
|
+
profile: opts.profile
|
|
124
|
+
})
|
|
125
|
+
)
|
|
126
|
+
);
|
|
116
127
|
|
|
117
|
-
cli
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
cli
|
|
129
|
+
.command('tools get [toolId]')
|
|
130
|
+
.option('--profile', 'Profile ID or name')
|
|
131
|
+
.action((toolId: string | undefined, opts) =>
|
|
132
|
+
printResult(() =>
|
|
133
|
+
getTool({
|
|
134
|
+
integration: integration!,
|
|
135
|
+
profile: opts.profile,
|
|
136
|
+
toolId
|
|
137
|
+
})
|
|
138
|
+
)
|
|
139
|
+
);
|
|
121
140
|
|
|
122
|
-
cli
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
printResult(async () => {
|
|
134
|
-
let tool = await getTool({ integration: integration!, profile: opts.profile, toolId });
|
|
135
|
-
return tool.inputSchema;
|
|
136
|
-
})
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
cli
|
|
140
|
-
.command('tools call [toolId]')
|
|
141
|
-
.option('--profile', 'Profile ID or name')
|
|
142
|
-
.option('--input', 'JSON input object')
|
|
143
|
-
.option('--auth-method-id', 'Preferred auth method ID')
|
|
144
|
-
.action((toolId: string | undefined, opts) =>
|
|
145
|
-
printResult(() =>
|
|
146
|
-
callTool({
|
|
147
|
-
integration: integration!,
|
|
148
|
-
profile: opts.profile,
|
|
149
|
-
toolId,
|
|
150
|
-
input: opts.input,
|
|
151
|
-
authMethodId: opts.authMethodId
|
|
141
|
+
cli
|
|
142
|
+
.command('tools schema [toolId]')
|
|
143
|
+
.option('--profile', 'Profile ID or name')
|
|
144
|
+
.action((toolId: string | undefined, opts) =>
|
|
145
|
+
printResult(async () => {
|
|
146
|
+
let tool = await getTool({
|
|
147
|
+
integration: integration!,
|
|
148
|
+
profile: opts.profile,
|
|
149
|
+
toolId
|
|
150
|
+
});
|
|
151
|
+
return tool.inputSchema;
|
|
152
152
|
})
|
|
153
|
-
)
|
|
154
|
-
);
|
|
153
|
+
);
|
|
155
154
|
|
|
156
|
-
cli
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
cli
|
|
156
|
+
.command('tools call [toolId]')
|
|
157
|
+
.option('--profile', 'Profile ID or name')
|
|
158
|
+
.option('--input', 'JSON input object')
|
|
159
|
+
.option('--auth-method-id', 'Preferred auth method ID')
|
|
160
|
+
.action((toolId: string | undefined, opts) =>
|
|
161
|
+
printResult(() =>
|
|
162
|
+
callTool({
|
|
163
|
+
integration: integration!,
|
|
164
|
+
profile: opts.profile,
|
|
165
|
+
toolId,
|
|
166
|
+
input: opts.input,
|
|
167
|
+
authMethodId: opts.authMethodId
|
|
168
|
+
})
|
|
169
|
+
)
|
|
170
|
+
);
|
|
160
171
|
|
|
161
|
-
cli
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
172
|
+
cli
|
|
173
|
+
.command('auth list')
|
|
174
|
+
.option('--profile', 'Profile ID or name')
|
|
175
|
+
.action(opts =>
|
|
176
|
+
printResult(() => listAuth({ integration: integration!, profile: opts.profile }))
|
|
177
|
+
);
|
|
167
178
|
|
|
168
|
-
cli
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
.action((authMethodId: string | undefined, opts) =>
|
|
177
|
-
printResult(() =>
|
|
178
|
-
setupAuth({
|
|
179
|
-
integration: integration!,
|
|
180
|
-
profile: opts.profile,
|
|
181
|
-
authMethodId,
|
|
182
|
-
input: opts.input,
|
|
183
|
-
oauthCredential: opts.oauthCredential,
|
|
184
|
-
clientId: opts.clientId,
|
|
185
|
-
clientSecret: opts.clientSecret,
|
|
186
|
-
scopes: opts.scopes
|
|
187
|
-
})
|
|
188
|
-
)
|
|
189
|
-
);
|
|
179
|
+
cli
|
|
180
|
+
.command('auth get [authMethodId]')
|
|
181
|
+
.option('--profile', 'Profile ID or name')
|
|
182
|
+
.action((authMethodId: string | undefined, opts) =>
|
|
183
|
+
printResult(() =>
|
|
184
|
+
getAuth({ integration: integration!, profile: opts.profile, authMethodId })
|
|
185
|
+
)
|
|
186
|
+
);
|
|
190
187
|
|
|
191
|
-
cli
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
cli
|
|
189
|
+
.command('auth setup [authMethodId]')
|
|
190
|
+
.option('--profile', 'Profile ID or name')
|
|
191
|
+
.option('--input', 'JSON auth input object')
|
|
192
|
+
.option('--oauth-credential', 'OAuth credential ID or name')
|
|
193
|
+
.option('--client-id', 'OAuth client ID')
|
|
194
|
+
.option('--client-secret', 'OAuth client secret')
|
|
195
|
+
.option('--scopes', 'Comma-separated OAuth scopes')
|
|
196
|
+
.action((authMethodId: string | undefined, opts) =>
|
|
197
|
+
printResult(() =>
|
|
198
|
+
setupAuth({
|
|
199
|
+
integration: integration!,
|
|
200
|
+
profile: opts.profile,
|
|
201
|
+
authMethodId,
|
|
202
|
+
input: opts.input,
|
|
203
|
+
oauthCredential: opts.oauthCredential,
|
|
204
|
+
clientId: opts.clientId,
|
|
205
|
+
clientSecret: opts.clientSecret,
|
|
206
|
+
scopes: opts.scopes
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
);
|
|
196
210
|
|
|
197
|
-
cli
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.action((authMethodId: string | undefined, opts) =>
|
|
203
|
-
printResult(() =>
|
|
204
|
-
addOAuthCredentials({
|
|
205
|
-
integration: integration!,
|
|
206
|
-
authMethodId,
|
|
207
|
-
name: opts.name,
|
|
208
|
-
clientId: opts.clientId,
|
|
209
|
-
clientSecret: opts.clientSecret
|
|
210
|
-
})
|
|
211
|
-
)
|
|
212
|
-
);
|
|
211
|
+
cli
|
|
212
|
+
.command('auth credentials list [authMethodId]')
|
|
213
|
+
.action((authMethodId: string | undefined) =>
|
|
214
|
+
printResult(() => listOAuthCredentials({ integration: integration!, authMethodId }))
|
|
215
|
+
);
|
|
213
216
|
|
|
214
|
-
cli
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
cli
|
|
218
|
+
.command('auth credentials add [authMethodId]')
|
|
219
|
+
.option('--name', 'Credential name')
|
|
220
|
+
.option('--client-id', 'OAuth client ID')
|
|
221
|
+
.option('--client-secret', 'OAuth client secret')
|
|
222
|
+
.action((authMethodId: string | undefined, opts) =>
|
|
223
|
+
printResult(() =>
|
|
224
|
+
addOAuthCredentials({
|
|
225
|
+
integration: integration!,
|
|
226
|
+
authMethodId,
|
|
227
|
+
name: opts.name,
|
|
228
|
+
clientId: opts.clientId,
|
|
229
|
+
clientSecret: opts.clientSecret
|
|
230
|
+
})
|
|
231
|
+
)
|
|
232
|
+
);
|
|
220
233
|
|
|
221
|
-
cli
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
234
|
+
cli
|
|
235
|
+
.command('auth refresh [authMethodId]')
|
|
236
|
+
.option('--profile', 'Profile ID or name')
|
|
237
|
+
.action((authMethodId: string | undefined, opts) =>
|
|
238
|
+
printResult(() =>
|
|
239
|
+
refreshAuth({ integration: integration!, profile: opts.profile, authMethodId })
|
|
240
|
+
)
|
|
241
|
+
);
|
|
225
242
|
|
|
226
|
-
cli
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
);
|
|
243
|
+
cli
|
|
244
|
+
.command('config get')
|
|
245
|
+
.option('--profile', 'Profile ID or name')
|
|
246
|
+
.action(opts =>
|
|
247
|
+
printResult(() => getConfig({ integration: integration!, profile: opts.profile }))
|
|
248
|
+
);
|
|
233
249
|
|
|
234
|
-
cli
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
250
|
+
cli
|
|
251
|
+
.command('config set')
|
|
252
|
+
.option('--profile', 'Profile ID or name')
|
|
253
|
+
.option('--input', 'JSON config object')
|
|
254
|
+
.action(opts =>
|
|
255
|
+
printResult(() =>
|
|
256
|
+
setConfig({ integration: integration!, profile: opts.profile, input: opts.input })
|
|
257
|
+
)
|
|
258
|
+
);
|
|
238
259
|
|
|
239
|
-
cli
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
await runVitestWithProfile({
|
|
246
|
-
integration: integration!,
|
|
247
|
-
profile: opts.profile,
|
|
248
|
-
vitestArgs: separatorIndex === -1 ? [] : process.argv.slice(separatorIndex + 1)
|
|
249
|
-
});
|
|
260
|
+
cli
|
|
261
|
+
.command('config schema')
|
|
262
|
+
.option('--profile', 'Profile ID or name')
|
|
263
|
+
.action(opts =>
|
|
264
|
+
printResult(() => getConfigSchema({ integration: integration!, profile: opts.profile }))
|
|
265
|
+
);
|
|
250
266
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
267
|
+
cli
|
|
268
|
+
.command('test')
|
|
269
|
+
.option('--profile', 'Profile ID or name')
|
|
270
|
+
.action(opts =>
|
|
271
|
+
printResult(async () => {
|
|
272
|
+
let separatorIndex = process.argv.indexOf('--');
|
|
273
|
+
await runVitestWithProfile({
|
|
274
|
+
integration: integration!,
|
|
275
|
+
profile: opts.profile,
|
|
276
|
+
vitestArgs: separatorIndex === -1 ? [] : process.argv.slice(separatorIndex + 1)
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return { success: true };
|
|
280
|
+
})
|
|
281
|
+
);
|
|
254
282
|
|
|
255
|
-
cli
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
283
|
+
cli
|
|
284
|
+
.command('repl')
|
|
285
|
+
.option('--profile', 'Profile ID or name')
|
|
286
|
+
.action(opts =>
|
|
287
|
+
printResult(() => startRepl({ integration: integration!, profile: opts.profile }))
|
|
288
|
+
);
|
|
259
289
|
|
|
260
290
|
cli.parse([process.argv[0] ?? 'bun', process.argv[1] ?? 'slates', ...argv.slice(1)]);
|
|
261
291
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { normalizeCallbackRedirectUriForIntegration } from './auth';
|
|
3
|
+
|
|
4
|
+
describe('normalizeCallbackRedirectUriForIntegration', () => {
|
|
5
|
+
it('normalizes Notion loopback redirects to localhost', () => {
|
|
6
|
+
expect(
|
|
7
|
+
normalizeCallbackRedirectUriForIntegration('notion', 'http://127.0.0.1:45873/callback')
|
|
8
|
+
).toBe('http://localhost:45873/callback');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('normalizes Intercom loopback redirects to localhost', () => {
|
|
12
|
+
expect(
|
|
13
|
+
normalizeCallbackRedirectUriForIntegration('intercom', 'http://127.0.0.1:45873/callback')
|
|
14
|
+
).toBe('http://localhost:45873/callback');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('normalizes Typeform loopback redirects to localhost', () => {
|
|
18
|
+
expect(
|
|
19
|
+
normalizeCallbackRedirectUriForIntegration('typeform', 'http://127.0.0.1:45873/callback')
|
|
20
|
+
).toBe('http://localhost:45873/callback');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('normalizes Xero loopback redirects to localhost', () => {
|
|
24
|
+
expect(
|
|
25
|
+
normalizeCallbackRedirectUriForIntegration('xero', 'http://127.0.0.1:45873/callback')
|
|
26
|
+
).toBe('http://localhost:45873/callback');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('normalizes Zendesk loopback redirects to localhost', () => {
|
|
30
|
+
expect(
|
|
31
|
+
normalizeCallbackRedirectUriForIntegration('zendesk', 'http://127.0.0.1:45873/callback')
|
|
32
|
+
).toBe('http://localhost:45873/callback');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('leaves unrelated integration redirects unchanged', () => {
|
|
36
|
+
expect(
|
|
37
|
+
normalizeCallbackRedirectUriForIntegration('attio', 'http://127.0.0.1:45873/callback')
|
|
38
|
+
).toBe('http://127.0.0.1:45873/callback');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('normalizes HubSpot developer platform OAuth redirects to localhost', () => {
|
|
42
|
+
expect(
|
|
43
|
+
normalizeCallbackRedirectUriForIntegration(
|
|
44
|
+
'hubspot',
|
|
45
|
+
'http://127.0.0.1:45873/callback',
|
|
46
|
+
'developer_platform_oauth'
|
|
47
|
+
)
|
|
48
|
+
).toBe('http://localhost:45873/callback');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('leaves HubSpot legacy OAuth redirects unchanged', () => {
|
|
52
|
+
expect(
|
|
53
|
+
normalizeCallbackRedirectUriForIntegration(
|
|
54
|
+
'hubspot',
|
|
55
|
+
'http://127.0.0.1:45873/callback',
|
|
56
|
+
'oauth'
|
|
57
|
+
)
|
|
58
|
+
).toBe('http://127.0.0.1:45873/callback');
|
|
59
|
+
});
|
|
60
|
+
});
|
package/src/commands/auth.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { confirm, select } from '@inquirer/prompts';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
normalizeMicrosoftRedirectUri,
|
|
4
|
+
normalizeMicrosoftRedirectUriForIntegration
|
|
5
|
+
} from '@slates/oauth-microsoft';
|
|
3
6
|
import { SlatesOAuthCredentialRecord, SlatesStoredAuth } from '@slates/profiles';
|
|
4
7
|
import {
|
|
5
8
|
chooseAuthMethod,
|
|
@@ -17,6 +20,22 @@ import {
|
|
|
17
20
|
import { JsonInput, WithProfile } from '../lib/types';
|
|
18
21
|
|
|
19
22
|
type JsonObject = Record<string, any>;
|
|
23
|
+
let NOTION_INTEGRATION_KEY = 'notion';
|
|
24
|
+
let SALESFORCE_INTEGRATION_KEY = 'salesforce';
|
|
25
|
+
let INTERCOM_INTEGRATION_KEY = 'intercom';
|
|
26
|
+
let TYPEFORM_INTEGRATION_KEY = 'typeform';
|
|
27
|
+
let XERO_INTEGRATION_KEY = 'xero';
|
|
28
|
+
let ZENDESK_INTEGRATION_KEY = 'zendesk';
|
|
29
|
+
let HUBSPOT_INTEGRATION_KEY = 'hubspot';
|
|
30
|
+
let HUBSPOT_DEVELOPER_PLATFORM_OAUTH_METHOD_ID = 'developer_platform_oauth';
|
|
31
|
+
let LOOPBACK_REDIRECT_NORMALIZED_INTEGRATIONS = new Set([
|
|
32
|
+
INTERCOM_INTEGRATION_KEY,
|
|
33
|
+
NOTION_INTEGRATION_KEY,
|
|
34
|
+
SALESFORCE_INTEGRATION_KEY,
|
|
35
|
+
TYPEFORM_INTEGRATION_KEY,
|
|
36
|
+
XERO_INTEGRATION_KEY,
|
|
37
|
+
ZENDESK_INTEGRATION_KEY
|
|
38
|
+
]);
|
|
20
39
|
|
|
21
40
|
type AuthSetupOptions = WithProfile &
|
|
22
41
|
JsonInput & {
|
|
@@ -27,6 +46,22 @@ type AuthSetupOptions = WithProfile &
|
|
|
27
46
|
scopes?: string;
|
|
28
47
|
};
|
|
29
48
|
|
|
49
|
+
export let normalizeCallbackRedirectUriForIntegration = (
|
|
50
|
+
integration: string,
|
|
51
|
+
redirectUri: string,
|
|
52
|
+
authMethodId?: string
|
|
53
|
+
) => {
|
|
54
|
+
if (
|
|
55
|
+
LOOPBACK_REDIRECT_NORMALIZED_INTEGRATIONS.has(integration) ||
|
|
56
|
+
(integration === HUBSPOT_INTEGRATION_KEY &&
|
|
57
|
+
authMethodId === HUBSPOT_DEVELOPER_PLATFORM_OAUTH_METHOD_ID)
|
|
58
|
+
) {
|
|
59
|
+
return normalizeMicrosoftRedirectUri(redirectUri);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return normalizeMicrosoftRedirectUriForIntegration(integration, redirectUri);
|
|
63
|
+
};
|
|
64
|
+
|
|
30
65
|
export let listAuth = async (opts: WithProfile) => {
|
|
31
66
|
let { store, profile } = await createClientContext(opts);
|
|
32
67
|
return store.listAuth(profile.id);
|
|
@@ -232,7 +267,11 @@ let chooseOAuthCredentialsForSetup = async (opts: {
|
|
|
232
267
|
};
|
|
233
268
|
|
|
234
269
|
let runAuthSetup = async (opts: AuthSetupOptions): Promise<SlatesStoredAuth> => {
|
|
235
|
-
let { store, profile, client } = await createClientContext(
|
|
270
|
+
let { store, profile, client } = await createClientContext({
|
|
271
|
+
...opts,
|
|
272
|
+
autoRefresh: false
|
|
273
|
+
});
|
|
274
|
+
client.clearAuth();
|
|
236
275
|
let authMethod = await chooseAuthMethod({
|
|
237
276
|
client,
|
|
238
277
|
authMethodId: opts.authMethodId,
|
|
@@ -264,9 +303,10 @@ let runAuthSetup = async (opts: AuthSetupOptions): Promise<SlatesStoredAuth> =>
|
|
|
264
303
|
|
|
265
304
|
if (authMethod.type === 'auth.oauth') {
|
|
266
305
|
let callback = await createOAuthCallbackListener();
|
|
267
|
-
let redirectUri =
|
|
306
|
+
let redirectUri = normalizeCallbackRedirectUriForIntegration(
|
|
268
307
|
opts.integration,
|
|
269
|
-
callback.redirectUri
|
|
308
|
+
callback.redirectUri,
|
|
309
|
+
authMethod.id
|
|
270
310
|
);
|
|
271
311
|
console.log(`OAuth redirect URL: ${redirectUri}`);
|
|
272
312
|
|
|
@@ -313,6 +353,7 @@ let runAuthSetup = async (opts: AuthSetupOptions): Promise<SlatesStoredAuth> =>
|
|
|
313
353
|
clientId,
|
|
314
354
|
clientSecret,
|
|
315
355
|
scopes,
|
|
356
|
+
callbackParams: callbackResult.callbackParams,
|
|
316
357
|
callbackState: callbackState ?? undefined
|
|
317
358
|
});
|
|
318
359
|
|
package/src/lib/context.ts
CHANGED
|
@@ -71,9 +71,16 @@ export let chooseProfile = async (d: {
|
|
|
71
71
|
};
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
export let createClientContext = async (opts: {
|
|
74
|
+
export let createClientContext = async (opts: {
|
|
75
|
+
integration: string;
|
|
76
|
+
profile?: string;
|
|
77
|
+
autoRefresh?: boolean;
|
|
78
|
+
}) => {
|
|
75
79
|
let { integration, store, profile } = await chooseProfile(opts);
|
|
76
|
-
let client = await createSlatesClientFromProfile(profile, {
|
|
80
|
+
let client = await createSlatesClientFromProfile(profile, {
|
|
81
|
+
store,
|
|
82
|
+
autoRefresh: opts.autoRefresh
|
|
83
|
+
});
|
|
77
84
|
return { integration, store, profile, client };
|
|
78
85
|
};
|
|
79
86
|
|
package/src/lib/oauth.ts
CHANGED
|
@@ -21,7 +21,7 @@ export let chooseScopes = async (
|
|
|
21
21
|
checked:
|
|
22
22
|
initialScopes.length > 0
|
|
23
23
|
? initialScopes.includes(scope.id)
|
|
24
|
-
: scope.defaultChecked ?? true
|
|
24
|
+
: (scope.defaultChecked ?? true)
|
|
25
25
|
}))
|
|
26
26
|
})) as string[];
|
|
27
27
|
};
|
|
@@ -34,7 +34,11 @@ export let createOAuthCallbackListener = async () => {
|
|
|
34
34
|
return new Promise<{
|
|
35
35
|
redirectUri: string;
|
|
36
36
|
state: string;
|
|
37
|
-
wait: () => Promise<{
|
|
37
|
+
wait: () => Promise<{
|
|
38
|
+
code: string;
|
|
39
|
+
state: string;
|
|
40
|
+
callbackParams: Record<string, string>;
|
|
41
|
+
}>;
|
|
38
42
|
}>((resolve, reject) => {
|
|
39
43
|
let expectedState = randomUUID();
|
|
40
44
|
let settled = false;
|
|
@@ -45,17 +49,45 @@ export let createOAuthCallbackListener = async () => {
|
|
|
45
49
|
let url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
46
50
|
let code = url.searchParams.get('code');
|
|
47
51
|
let state = url.searchParams.get('state');
|
|
52
|
+
let oauthError = url.searchParams.get('error');
|
|
53
|
+
let oauthErrorDescription = url.searchParams.get('error_description');
|
|
54
|
+
let oauthErrorUri = url.searchParams.get('error_uri');
|
|
55
|
+
|
|
56
|
+
if (oauthError) {
|
|
57
|
+
let description = oauthErrorDescription ?? 'No error description was provided.';
|
|
58
|
+
let errorMessage = `OAuth callback returned "${oauthError}": ${description}${
|
|
59
|
+
oauthErrorUri ? ` (${oauthErrorUri})` : ''
|
|
60
|
+
}`;
|
|
61
|
+
|
|
62
|
+
res.statusCode = 400;
|
|
63
|
+
res.end(errorMessage);
|
|
64
|
+
server.close();
|
|
65
|
+
settled = true;
|
|
66
|
+
waiter.reject(new Error(errorMessage));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
48
69
|
|
|
49
70
|
if (!code || !state) {
|
|
50
71
|
res.statusCode = 400;
|
|
51
72
|
res.end('Missing code or state.');
|
|
73
|
+
server.close();
|
|
74
|
+
settled = true;
|
|
75
|
+
waiter.reject(
|
|
76
|
+
new Error(
|
|
77
|
+
`OAuth callback did not include the required query parameters. Received path: ${url.pathname}${url.search}`
|
|
78
|
+
)
|
|
79
|
+
);
|
|
52
80
|
return;
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
res.end('Authentication complete. You can close this window.');
|
|
56
84
|
server.close();
|
|
57
85
|
settled = true;
|
|
58
|
-
waiter.resolve({
|
|
86
|
+
waiter.resolve({
|
|
87
|
+
code,
|
|
88
|
+
state,
|
|
89
|
+
callbackParams: Object.fromEntries(url.searchParams.entries())
|
|
90
|
+
});
|
|
59
91
|
} catch (error) {
|
|
60
92
|
server.close();
|
|
61
93
|
settled = true;
|
|
@@ -64,9 +96,17 @@ export let createOAuthCallbackListener = async () => {
|
|
|
64
96
|
});
|
|
65
97
|
|
|
66
98
|
let waiter = (() => {
|
|
67
|
-
let resolvePromise!: (value: {
|
|
99
|
+
let resolvePromise!: (value: {
|
|
100
|
+
code: string;
|
|
101
|
+
state: string;
|
|
102
|
+
callbackParams: Record<string, string>;
|
|
103
|
+
}) => void;
|
|
68
104
|
let rejectPromise!: (error: unknown) => void;
|
|
69
|
-
let promise = new Promise<{
|
|
105
|
+
let promise = new Promise<{
|
|
106
|
+
code: string;
|
|
107
|
+
state: string;
|
|
108
|
+
callbackParams: Record<string, string>;
|
|
109
|
+
}>((resolveFn, rejectFn) => {
|
|
70
110
|
resolvePromise = resolveFn;
|
|
71
111
|
rejectPromise = rejectFn;
|
|
72
112
|
});
|