Skip to content

Commit

Permalink
fix(framework): Polish secretKey and apiUrl resolution (#6819)
Browse files Browse the repository at this point in the history
Co-authored-by: Richard Fontein <[email protected]>
  • Loading branch information
SokratisVidros and rifont authored Nov 1, 2024
1 parent 2348bce commit b0b97f3
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 22 deletions.
6 changes: 6 additions & 0 deletions packages/framework/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ describe('Novu Client', () => {
expect(newClient.secretKey).toBe(testSecretKey);
});

it('should set apiUrl to provided apiUrl', () => {
const testApiUrl = 'https://test.host';
const newClient = new Client({ apiUrl: testApiUrl });
expect(newClient.apiUrl).toBe(testApiUrl);
});

it('should set strictAuthentication to false when NODE_ENV is development', () => {
const originalEnv = process.env.NODE_ENV;
process.env = { ...process.env, NODE_ENV: 'development' };
Expand Down
22 changes: 15 additions & 7 deletions packages/framework/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ import type {
Workflow,
} from './types';
import { WithPassthrough } from './types/provider.types';
import { EMOJI, log, sanitizeHtmlInObject, stringifyDataStructureWithSingleQuotes } from './utils';
import {
EMOJI,
log,
resolveApiUrl,
resolveSecretKey,
sanitizeHtmlInObject,
stringifyDataStructureWithSingleQuotes,
} from './utils';
import { validateData } from './validators';

import { mockSchema } from './jsonSchemaFaker';
Expand All @@ -53,14 +60,17 @@ export class Client {
},
});

public secretKey?: string;
public secretKey: string;

public apiUrl: string;

public version: string = SDK_VERSION;

public strictAuthentication: boolean;

constructor(options?: ClientOptions) {
const builtOpts = this.buildOptions(options);
this.apiUrl = builtOpts.apiUrl;
this.secretKey = builtOpts.secretKey;
this.strictAuthentication = builtOpts.strictAuthentication;
this.templateEngine.registerFilter('json', (value, spaces) =>
Expand All @@ -69,14 +79,12 @@ export class Client {
}

private buildOptions(providedOptions?: ClientOptions) {
const builtConfiguration: { secretKey?: string; strictAuthentication: boolean } = {
secretKey: undefined,
const builtConfiguration: Required<ClientOptions> = {
apiUrl: resolveApiUrl(providedOptions?.apiUrl),
secretKey: resolveSecretKey(providedOptions?.secretKey),
strictAuthentication: !isRuntimeInDevelopment(),
};

builtConfiguration.secretKey =
providedOptions?.secretKey || process.env.NOVU_SECRET_KEY || process.env.NOVU_API_KEY;

if (providedOptions?.strictAuthentication !== undefined) {
builtConfiguration.strictAuthentication = providedOptions.strictAuthentication;
} else if (process.env.NOVU_STRICT_AUTHENTICATION_ENABLED !== undefined) {
Expand Down
2 changes: 1 addition & 1 deletion packages/framework/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export class NovuRequestHandler<Input extends any[] = any[], Output = any> {
this.handler = options.handler;
this.client = options.client ? options.client : new Client();
this.client.addWorkflows(options.workflows);
this.http = initApiClient(this.client.secretKey as string);
this.http = initApiClient(this.client.secretKey, this.client.apiUrl);
this.frameworkName = options.frameworkName;
this.hmacEnabled = this.client.strictAuthentication;
}
Expand Down
10 changes: 3 additions & 7 deletions packages/framework/src/resources/workflow/workflow.resource.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ActionStepEnum, ChannelStepEnum } from '../../constants';
import { MissingSecretKeyError, WorkflowPayloadInvalidError } from '../../errors';
import { WorkflowPayloadInvalidError } from '../../errors';
import { channelStepSchemas, delayActionSchemas, digestActionSchemas, emptySchema } from '../../schemas';
import type {
CancelEventTriggerResponse,
Expand All @@ -12,7 +12,7 @@ import type {
WorkflowOptions,
FromSchemaUnvalidated,
} from '../../types';
import { getBridgeUrl, initApiClient } from '../../utils';
import { getBridgeUrl, initApiClient, resolveApiUrl, resolveSecretKey } from '../../utils';
import { transformSchema, validateData } from '../../validators';
import { discoverActionStepFactory } from './discover-action-step-factory';
import { discoverChannelStepFactory } from './discover-channel-step-factory';
Expand All @@ -36,12 +36,8 @@ export function workflow<
): Workflow<T_PayloadUnvalidated> {
const options = workflowOptions || {};

const apiClient = initApiClient(process.env.NOVU_SECRET_KEY as string);

const trigger: Workflow<T_PayloadUnvalidated>['trigger'] = async (event) => {
if (!process.env.NOVU_SECRET_KEY) {
throw new MissingSecretKeyError();
}
const apiClient = initApiClient(resolveSecretKey(event.secretKey), resolveApiUrl(event.apiUrl));

const unvalidatedData = (event.payload || {}) as T_PayloadUnvalidated;
let validatedData: T_PayloadValidated;
Expand Down
5 changes: 5 additions & 0 deletions packages/framework/src/types/config.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export type ClientOptions = {
/**
* Use Novu Cloud US (https://api.novu.co) or EU deployment (https://eu.api.novu.co). Defaults to US.
*/
apiUrl?: string;

/**
* Specify your Novu secret key, to secure the Bridge Endpoint, and Novu API communication.
* Novu communicates securely with your endpoint using a signed HMAC header,
Expand Down
8 changes: 8 additions & 0 deletions packages/framework/src/types/event.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ export type EventTriggerParams<T_Payload = EventPayload> = {
[stepId: string]: Record<string, unknown>;
};
};
/**
* Use Novu Cloud US (https://api.novu.co) or EU deployment (https://eu.api.novu.co). Defaults to US.
*/
apiUrl?: string;
/**
* Override secret key for the trigger
*/
secretKey?: string;
} & ConditionalPartial<
{
/**
Expand Down
12 changes: 7 additions & 5 deletions packages/framework/src/utils/http.utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { checkIsResponseError } from '@novu/shared';
import { BridgeError, PlatformError } from '../errors';
import { BridgeError, MissingSecretKeyError, PlatformError } from '../errors';

export const initApiClient = (apiKey: string, baseURL = 'https://api.novu.co') => {
const apiUrl = process.env.NOVU_API_URL || baseURL;
export const initApiClient = (secretKey: string, apiUrl: string) => {
if (!secretKey) {
throw new MissingSecretKeyError();
}

return {
post: async <T = unknown>(route: string, data: Record<string, unknown>): Promise<T> => {
const response = await fetch(`${apiUrl}/v1${route}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `ApiKey ${apiKey}`,
Authorization: `ApiKey ${secretKey}`,
},
body: JSON.stringify(data),
});
Expand All @@ -31,7 +33,7 @@ export const initApiClient = (apiKey: string, baseURL = 'https://api.novu.co') =
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `ApiKey ${apiKey}`,
Authorization: `ApiKey ${secretKey}`,
},
})
).json() as T;
Expand Down
5 changes: 3 additions & 2 deletions packages/framework/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './env.utils';
export * from './log.utils';
export * from './string.utils';
export * from './http.utils';
export * from './log.utils';
export * from './options.utils';
export * from './sanitize.utils';
export * from './string.utils';
7 changes: 7 additions & 0 deletions packages/framework/src/utils/options.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function resolveApiUrl(providedApiUrl?: string): string {
return providedApiUrl || process.env.NOVU_API_URL || 'https://api.novu.co';
}

export function resolveSecretKey(providedSecretKey?: string): string {
return providedSecretKey || process.env.NOVU_SECRET_KEY || process.env.NOVU_API_KEY || '';
}

0 comments on commit b0b97f3

Please sign in to comment.