Skip to content

Commit

Permalink
Abstract OAuth implementation in the SDK (#13983)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jverce committed Sep 19, 2024
1 parent a407a12 commit 3d38039
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 12 deletions.
4 changes: 2 additions & 2 deletions packages/sdk/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/sdk",
"version": "0.0.13",
"version": "0.1.0",
"description": "Pipedream SDK",
"type": "module",
"main": "dist/server/index.js",
Expand Down Expand Up @@ -40,6 +40,10 @@
],
"devDependencies": {
"@types/node": "^20.14.9",
"@types/simple-oauth2": "^5.0.7",
"typescript": "^5.5.2"
},
"dependencies": {
"simple-oauth2": "^5.1.0"
}
}
114 changes: 105 additions & 9 deletions packages/sdk/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
// Pipedream project's public and secret keys and access customer credentials.
// See the browser/ directory for the browser client.

import {
AccessToken,
ClientCredentials,
} from "simple-oauth2";

/**
* Options for creating a server-side client.
* This is used to configure the ServerClient instance.
Expand All @@ -22,6 +27,16 @@ export type CreateServerClientOpts = {
*/
secretKey: string;

/**
* The client ID of your workspace's OAuth application.
*/
oauthClientId?: string;

/**
* The client secret of your workspace's OAuth application.
*/
oauthClientSecret?: string;

/**
* The API host URL. Used by Pipedream employees. Defaults to "api.pipedream.com" if not provided.
*/
Expand Down Expand Up @@ -231,9 +246,9 @@ export type ErrorResponse = {
export type ConnectAPIResponse<T> = T | ErrorResponse;

/**
* Options for making a request to the Connect API.
* Options for making a request to the Pipedream API.
*/
interface ConnectRequestOptions extends Omit<RequestInit, "headers"> {
interface RequestOptions extends Omit<RequestInit, "headers"> {
/**
* Query parameters to include in the request URL.
*/
Expand Down Expand Up @@ -269,6 +284,8 @@ class ServerClient {
environment?: string;
secretKey: string;
publicKey: string;
oauthClient: ClientCredentials;
oauthToken?: AccessToken;
baseURL: string;

/**
Expand All @@ -283,30 +300,64 @@ class ServerClient {

const { apiHost = "api.pipedream.com" } = opts;
this.baseURL = `https://${apiHost}/v1`;

this._configureOauthClient(opts, this.baseURL);
}

private _configureOauthClient(
{
oauthClientId: id,
oauthClientSecret: secret,
}: CreateServerClientOpts,
tokenHost: string,
) {
if (!id || !secret) {
return;
}

this.oauthClient = new ClientCredentials({
client: {
id,
secret,
},
auth: {
tokenHost,
tokenPath: "/v1/oauth/token",
},
});
}

/**
* Generates an Authorization header using the public and secret keys.
* Generates an Authorization header for Connect using the public and secret
* keys of the target project.
*
* @returns The authorization header as a string.
*/
private _authorizationHeader(): string {
private _connectAuthorizationHeader(): string {
const encoded = Buffer.from(`${this.publicKey}:${this.secretKey}`).toString("base64");
return `Basic ${encoded}`;
}

async _oauthAuthorizationHeader(): Promise<string> {
if (!this.oauthToken || this.oauthToken.expired()) {
this.oauthToken = await this.oauthClient.getToken({});
}

return `Bearer ${this.oauthToken.token.access_token}`;
}

/**
* Makes a request to the Connect API.
* Makes an HTTP request
*
* @template T - The expected response type.
* @param path - The API endpoint path.
* @param opts - The options for the request.
* @returns A promise resolving to the API response.
* @throws Will throw an error if the response status is not OK.
*/
async _makeConnectRequest<T>(
async _makeRequest<T>(
path: string,
opts: ConnectRequestOptions = {},
opts: RequestOptions = {},
): Promise<T> {
const {
params,
Expand All @@ -315,7 +366,7 @@ class ServerClient {
method = "GET",
...fetchOpts
} = opts;
const url = new URL(`${this.baseURL}/connect${path}`);
const url = new URL(`${this.baseURL}${path}`);

if (params) {
Object.entries(params).forEach(([
Expand All @@ -329,7 +380,6 @@ class ServerClient {
}

const headers = {
"Authorization": this._authorizationHeader(),
"Content-Type": "application/json",
...customHeaders,
};
Expand Down Expand Up @@ -358,6 +408,52 @@ class ServerClient {
return result;
}

/**
* Makes a request to the Pipedream API.
*
* @template T - The expected response type.
* @param path - The API endpoint path.
* @param opts - The options for the request.
* @returns A promise resolving to the API response.
* @throws Will throw an error if the response status is not OK.
*/
async _makeApiRequest<T>(
path: string,
opts: RequestOptions = {},
): Promise<T> {
const headers = {
...opts.headers ?? {},
"Authorization": await this._oauthAuthorizationHeader(),
};
return this._makeRequest<T>(path, {
headers,
...opts,
});
}

/**
* Makes a request to the Connect API.
*
* @template T - The expected response type.
* @param path - The API endpoint path.
* @param opts - The options for the request.
* @returns A promise resolving to the API response.
* @throws Will throw an error if the response status is not OK.
*/
async _makeConnectRequest<T>(
path: string,
opts: RequestOptions = {},
): Promise<T> {
const headers = {
...opts.headers ?? {},
"Authorization": this._connectAuthorizationHeader(),
};
return this._makeRequest<T>(`/connect${path}`, {
headers,
...opts,
});
}

/**
* Creates a new connect token.
*
Expand Down
42 changes: 42 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3d38039

Please sign in to comment.