Skip to content

Commit 3d38039

Browse files
authored
Abstract OAuth implementation in the SDK (#13983)
* Install `simple-oauth2` to handle OAuth client requests * Rename some methods and types to reuse them * Abstract the HTTP requests so that it can be used for Connect and for normal API requests * Implement logic to fetch OAuth client credentials when needed * Bump minor version of the SDK
1 parent a407a12 commit 3d38039

File tree

4 files changed

+154
-12
lines changed

4 files changed

+154
-12
lines changed

packages/sdk/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/sdk/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/sdk",
3-
"version": "0.0.13",
3+
"version": "0.1.0",
44
"description": "Pipedream SDK",
55
"type": "module",
66
"main": "dist/server/index.js",
@@ -40,6 +40,10 @@
4040
],
4141
"devDependencies": {
4242
"@types/node": "^20.14.9",
43+
"@types/simple-oauth2": "^5.0.7",
4344
"typescript": "^5.5.2"
45+
},
46+
"dependencies": {
47+
"simple-oauth2": "^5.1.0"
4448
}
4549
}

packages/sdk/src/server/index.ts

Lines changed: 105 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
// Pipedream project's public and secret keys and access customer credentials.
33
// See the browser/ directory for the browser client.
44

5+
import {
6+
AccessToken,
7+
ClientCredentials,
8+
} from "simple-oauth2";
9+
510
/**
611
* Options for creating a server-side client.
712
* This is used to configure the ServerClient instance.
@@ -22,6 +27,16 @@ export type CreateServerClientOpts = {
2227
*/
2328
secretKey: string;
2429

30+
/**
31+
* The client ID of your workspace's OAuth application.
32+
*/
33+
oauthClientId?: string;
34+
35+
/**
36+
* The client secret of your workspace's OAuth application.
37+
*/
38+
oauthClientSecret?: string;
39+
2540
/**
2641
* The API host URL. Used by Pipedream employees. Defaults to "api.pipedream.com" if not provided.
2742
*/
@@ -231,9 +246,9 @@ export type ErrorResponse = {
231246
export type ConnectAPIResponse<T> = T | ErrorResponse;
232247

233248
/**
234-
* Options for making a request to the Connect API.
249+
* Options for making a request to the Pipedream API.
235250
*/
236-
interface ConnectRequestOptions extends Omit<RequestInit, "headers"> {
251+
interface RequestOptions extends Omit<RequestInit, "headers"> {
237252
/**
238253
* Query parameters to include in the request URL.
239254
*/
@@ -269,6 +284,8 @@ class ServerClient {
269284
environment?: string;
270285
secretKey: string;
271286
publicKey: string;
287+
oauthClient: ClientCredentials;
288+
oauthToken?: AccessToken;
272289
baseURL: string;
273290

274291
/**
@@ -283,30 +300,64 @@ class ServerClient {
283300

284301
const { apiHost = "api.pipedream.com" } = opts;
285302
this.baseURL = `https://${apiHost}/v1`;
303+
304+
this._configureOauthClient(opts, this.baseURL);
305+
}
306+
307+
private _configureOauthClient(
308+
{
309+
oauthClientId: id,
310+
oauthClientSecret: secret,
311+
}: CreateServerClientOpts,
312+
tokenHost: string,
313+
) {
314+
if (!id || !secret) {
315+
return;
316+
}
317+
318+
this.oauthClient = new ClientCredentials({
319+
client: {
320+
id,
321+
secret,
322+
},
323+
auth: {
324+
tokenHost,
325+
tokenPath: "/v1/oauth/token",
326+
},
327+
});
286328
}
287329

288330
/**
289-
* Generates an Authorization header using the public and secret keys.
331+
* Generates an Authorization header for Connect using the public and secret
332+
* keys of the target project.
290333
*
291334
* @returns The authorization header as a string.
292335
*/
293-
private _authorizationHeader(): string {
336+
private _connectAuthorizationHeader(): string {
294337
const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString("base64");
295338
return `Basic ${encoded}`;
296339
}
297340

341+
async _oauthAuthorizationHeader(): Promise<string> {
342+
if (!this.oauthToken || this.oauthToken.expired()) {
343+
this.oauthToken = await this.oauthClient.getToken({});
344+
}
345+
346+
return `Bearer ${this.oauthToken.token.access_token}`;
347+
}
348+
298349
/**
299-
* Makes a request to the Connect API.
350+
* Makes an HTTP request
300351
*
301352
* @template T - The expected response type.
302353
* @param path - The API endpoint path.
303354
* @param opts - The options for the request.
304355
* @returns A promise resolving to the API response.
305356
* @throws Will throw an error if the response status is not OK.
306357
*/
307-
async _makeConnectRequest<T>(
358+
async _makeRequest<T>(
308359
path: string,
309-
opts: ConnectRequestOptions = {},
360+
opts: RequestOptions = {},
310361
): Promise<T> {
311362
const {
312363
params,
@@ -315,7 +366,7 @@ class ServerClient {
315366
method = "GET",
316367
...fetchOpts
317368
} = opts;
318-
const url = new URL(`${this.baseURL}/connect${path}`);
369+
const url = new URL(`${this.baseURL}${path}`);
319370

320371
if (params) {
321372
Object.entries(params).forEach(([
@@ -329,7 +380,6 @@ class ServerClient {
329380
}
330381

331382
const headers = {
332-
"Authorization": this._authorizationHeader(),
333383
"Content-Type": "application/json",
334384
...customHeaders,
335385
};
@@ -358,6 +408,52 @@ class ServerClient {
358408
return result;
359409
}
360410

411+
/**
412+
* Makes a request to the Pipedream API.
413+
*
414+
* @template T - The expected response type.
415+
* @param path - The API endpoint path.
416+
* @param opts - The options for the request.
417+
* @returns A promise resolving to the API response.
418+
* @throws Will throw an error if the response status is not OK.
419+
*/
420+
async _makeApiRequest<T>(
421+
path: string,
422+
opts: RequestOptions = {},
423+
): Promise<T> {
424+
const headers = {
425+
...opts.headers ?? {},
426+
"Authorization": await this._oauthAuthorizationHeader(),
427+
};
428+
return this._makeRequest<T>(path, {
429+
headers,
430+
...opts,
431+
});
432+
}
433+
434+
/**
435+
* Makes a request to the Connect API.
436+
*
437+
* @template T - The expected response type.
438+
* @param path - The API endpoint path.
439+
* @param opts - The options for the request.
440+
* @returns A promise resolving to the API response.
441+
* @throws Will throw an error if the response status is not OK.
442+
*/
443+
async _makeConnectRequest<T>(
444+
path: string,
445+
opts: RequestOptions = {},
446+
): Promise<T> {
447+
const headers = {
448+
...opts.headers ?? {},
449+
"Authorization": this._connectAuthorizationHeader(),
450+
};
451+
return this._makeRequest<T>(`/connect${path}`, {
452+
headers,
453+
...opts,
454+
});
455+
}
456+
361457
/**
362458
* Creates a new connect token.
363459
*

pnpm-lock.yaml

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)