From 8b517f4b534d877ef8e6378b011a997de54e809c Mon Sep 17 00:00:00 2001 From: Dani Akash Date: Tue, 2 Jul 2024 19:23:52 +0530 Subject: [PATCH] feat: custom root certificate support (#19) Release-As: 0.0.3 --- src/client/auth/helper.ts | 33 ++++++++++++++++++++++++++- src/client/base.ts | 4 ++++ src/utils/types.ts | 1 + tests/client/auth/helper.unit.test.ts | 15 ++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/client/auth/helper.ts b/src/client/auth/helper.ts index 68c6cb5..bb82fde 100644 --- a/src/client/auth/helper.ts +++ b/src/client/auth/helper.ts @@ -2,6 +2,7 @@ import resources_pb2 from "clarifai-nodejs-grpc/proto/clarifai/api/resources_pb" import { grpc } from "clarifai-nodejs-grpc"; import { V2Client } from "clarifai-nodejs-grpc/proto/clarifai/api/service_grpc_pb"; import process from "process"; +import fs from "fs"; // TypeScript interface for the cache export interface Cache { @@ -75,6 +76,7 @@ export class ClarifaiAuthHelper { private token: string; private _base: string; private _ui: string; + private _rootCertificatesPath: string; /** * A helper to get the authorization information needed to make API calls with the grpc @@ -97,6 +99,7 @@ export class ClarifaiAuthHelper { * https://clarifai.com (default), https://host:port, http://host:port, * host:port (will be treated as http, not https). It's highly recommended to include * the http:// or https:// otherwise we need to check the endpoint to determine if it has SSL during this __init__. + * @param rootCertificatesPath - path to the root certificates file. This is only used for grpc secure channels. * @param validate - Whether to validate the inputs. This is useful for overriding vars then validating. */ constructor( @@ -106,12 +109,14 @@ export class ClarifaiAuthHelper { token: string = "", base: string = DEFAULT_BASE, ui: string = DEFAULT_UI, + rootCertificatesPath: string = "", validate: boolean = true, ) { this.userId = userId; this.appId = appId; this._pat = pat; this.token = token; + this._rootCertificatesPath = rootCertificatesPath; this._base = base; this._ui = ui; @@ -142,6 +147,13 @@ export class ClarifaiAuthHelper { throw new Error( "Need 'pat' or 'token' in the query params or use one of the CLARIFAI_PAT or CLARIFAI_SESSION_TOKEN env vars", ); + } else if ( + this._rootCertificatesPath && + !fs.existsSync(this._rootCertificatesPath) + ) { + throw new Error( + `Root certificates path ${this._rootCertificatesPath} does not exist`, + ); } } @@ -163,6 +175,8 @@ export class ClarifaiAuthHelper { const pat = process.env.CLARIFAI_PAT || ""; const base = process.env.CLARIFAI_API_BASE || DEFAULT_BASE; const ui = process.env.CLARIFAI_UI || DEFAULT_UI; + const rootCertificatesPath = + process.env.CLARIFAI_ROOT_CERTIFICATES_PATH || ""; return new ClarifaiAuthHelper( userId, @@ -171,6 +185,7 @@ export class ClarifaiAuthHelper { token, base, ui, + rootCertificatesPath, validate, ); } @@ -227,7 +242,16 @@ export class ClarifaiAuthHelper { let client: V2Client; if (https) { - client = new V2Client(this._base, grpc.ChannelCredentials.createSsl()); + if (this._rootCertificatesPath) { + client = new V2Client( + this._base, + grpc.ChannelCredentials.createSsl( + fs.readFileSync(this._rootCertificatesPath), + ), + ); + } else { + client = new V2Client(this._base, grpc.ChannelCredentials.createSsl()); + } } else { let host: string; let port: number = 80; @@ -282,6 +306,13 @@ export class ClarifaiAuthHelper { this._ui = httpsCache(uiHttpsCache, ui); } + /** + * Return the root certificates path. + */ + get rootCertificatesPath(): string { + return this._rootCertificatesPath; + } + /** * Return the base domain for the API. */ diff --git a/src/client/base.ts b/src/client/base.ts index 2461153..a2a5016 100644 --- a/src/client/base.ts +++ b/src/client/base.ts @@ -28,6 +28,7 @@ export class BaseClient { protected pat: string; protected userAppId: UserAppIDSet; protected base: string; + protected rootCertificatesPath: string; /** * Constructs a new BaseClient instance with specified configuration options. @@ -39,6 +40,7 @@ export class BaseClient { * @param {string} [authConfig.token] An optional token for authentication. * @param {string} [authConfig.base='https://api.clarifai.com'] The base URL for the API endpoint. Defaults to 'https://api.clarifai.com'. * @param {string} [authConfig.ui='https://clarifai.com'] The URL for the UI. Defaults to 'https://clarifai.com'. + * @param {string} [authConfig.rootCertificatesPath] Path to the SSL root certificates file, used to establish secure gRPC connections. */ constructor(authConfig: AuthConfig = {}) { const pat = getFromDictOrEnv("pat", "CLARIFAI_PAT", authConfig); @@ -52,6 +54,7 @@ export class BaseClient { authConfig.token, authConfig.base, authConfig.ui, + authConfig.rootCertificatesPath, false, ) : ClarifaiAuthHelper.fromEnv(false); // The validate parameter is set to false explicitly @@ -60,6 +63,7 @@ export class BaseClient { this.pat = this.authHelper.pat; this.userAppId = this.authHelper.getUserAppIdProto(); this.base = this.authHelper.base; + this.rootCertificatesPath = this.authHelper.rootCertificatesPath; } /** diff --git a/src/utils/types.ts b/src/utils/types.ts index 6023b39..39789b4 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -5,6 +5,7 @@ export type AuthConfig = userId: string; appId: string; pat: string; + rootCertificatesPath?: string; token?: string; base?: string; ui?: string; diff --git a/tests/client/auth/helper.unit.test.ts b/tests/client/auth/helper.unit.test.ts index 94a90d6..3f6e021 100644 --- a/tests/client/auth/helper.unit.test.ts +++ b/tests/client/auth/helper.unit.test.ts @@ -118,6 +118,21 @@ describe("ClarifaiAuthHelper", () => { expect(() => ClarifaiAuthHelper.validateSecretsDict(secrets)).toThrow(); }); + it("throws error for invalid root certificates path", () => { + expect( + () => + new ClarifaiAuthHelper( + "userId", + "appId", + "pat", + "", + "https://customdomain.com", + "https://customdomain.com/ui", + "invalid", + ), + ).toThrow(`Root certificates path invalid does not exist`); + }); + it("getUserAppIdProto returns correct UserAppIDSet proto object", () => { const helper = new ClarifaiAuthHelper("userId", "appId", "pat"); const userAppIdProto = helper.getUserAppIdProto();