From 6497b3a76a7656fe31cc38d3ea6e72a5346fdbd2 Mon Sep 17 00:00:00 2001 From: bartes Date: Mon, 25 Jul 2022 10:27:00 +0200 Subject: [PATCH] payload generation improvements --- src/Castle.ts | 2 +- .../services/headers-extract.service.ts | 2 +- src/payload/models/filter_payload.ts | 2 +- src/payload/models/log_payload.ts | 2 +- src/payload/models/payload.ts | 2 +- src/payload/models/risk_payload.ts | 2 +- test/Castle.test.ts | 315 +++++++++++------- ...core-generate-request-body.service.test.ts | 10 +- 8 files changed, 195 insertions(+), 142 deletions(-) diff --git a/src/Castle.ts b/src/Castle.ts index e15e81c..55ce13c 100644 --- a/src/Castle.ts +++ b/src/Castle.ts @@ -20,7 +20,7 @@ import type { import { Configuration } from './configuraton'; export class Castle { - private configuration: Configuration; + public configuration: Configuration; constructor(configAttributes) { this.configuration = new Configuration(configAttributes); diff --git a/src/headers/services/headers-extract.service.ts b/src/headers/services/headers-extract.service.ts index 0533fef..3bbef59 100644 --- a/src/headers/services/headers-extract.service.ts +++ b/src/headers/services/headers-extract.service.ts @@ -7,7 +7,7 @@ const ALWAYS_DENYLISTED = ['cookie', 'authorization']; export const HeadersExtractService = { call: ( - headers: IncomingHttpHeaders, + headers: IncomingHttpHeaders | { [key: string]: string }, configuration: Configuration ): { [key: string]: boolean | string } => { return reduce( diff --git a/src/payload/models/filter_payload.ts b/src/payload/models/filter_payload.ts index b36a8c2..be31e79 100644 --- a/src/payload/models/filter_payload.ts +++ b/src/payload/models/filter_payload.ts @@ -11,6 +11,6 @@ export interface FilterPayload { properties?: object; context: { ip: string; - headers: IncomingHttpHeaders; + headers: IncomingHttpHeaders | { [key: string]: string | boolean }; }; } diff --git a/src/payload/models/log_payload.ts b/src/payload/models/log_payload.ts index ebf1a32..95889b5 100644 --- a/src/payload/models/log_payload.ts +++ b/src/payload/models/log_payload.ts @@ -15,6 +15,6 @@ export interface LogPayload { properties?: object; context?: { ip?: string; - headers?: IncomingHttpHeaders; + headers: IncomingHttpHeaders | { [key: string]: string | boolean }; }; } diff --git a/src/payload/models/payload.ts b/src/payload/models/payload.ts index 020d654..8df4146 100644 --- a/src/payload/models/payload.ts +++ b/src/payload/models/payload.ts @@ -9,6 +9,6 @@ export interface Payload { device_token?: string; context?: { ip: string; - headers: IncomingHttpHeaders; + headers: IncomingHttpHeaders | { [key: string]: string | boolean }; }; } diff --git a/src/payload/models/risk_payload.ts b/src/payload/models/risk_payload.ts index ef4b568..ac8c295 100644 --- a/src/payload/models/risk_payload.ts +++ b/src/payload/models/risk_payload.ts @@ -14,6 +14,6 @@ export interface RiskPayload { properties?: object; context: { ip: string; - headers: IncomingHttpHeaders; + headers: IncomingHttpHeaders | { [key: string]: string | boolean }; }; } diff --git a/test/Castle.test.ts b/test/Castle.test.ts index 6c09194..923fcf0 100644 --- a/test/Castle.test.ts +++ b/test/Castle.test.ts @@ -3,86 +3,109 @@ import fetchMock from 'fetch-mock'; import { FailoverStrategy } from '../src/failover/models'; import MockDate from 'mockdate'; import { ContextPrepareService } from '../src/context/services'; - -const sampleRequestData = { - event: '$login.succeeded', - created_at: 'now', - user_id: 'userid', - user_traits: { - email: 'myemail', - registered_at: 'today', - }, - context: { - ip: '8.8.8.8', - client_id: 'clientid', - headers: { - Cookie: 'SECRET=pleasedontbehere', - 'x-forwarded-for': '8.8.8.8', +import { ClientIdExtractService } from '../src/client-id/client-id.module'; +import { HeadersExtractService } from '../src/headers/headers.module'; +import { Configuration } from '../src/configuraton'; + +const sampleRequestData = (configuration) => { + return { + event: '$login.succeeded', + created_at: 'now', + user_id: 'userid', + user_traits: { + email: 'myemail', + registered_at: 'today', + }, + context: { + ip: '8.8.8.8', + client_id: 'clientid', + headers: HeadersExtractService.call( + { + Cookie: 'SECRET=pleasedontbehere', + 'x-forwarded-for': '8.8.8.8', + }, + configuration + ), }, - }, + }; }; -const sampleRiskRequestData = { - event: '$login', - request_token: 'token', - status: '$succeeded', - user: { - id: 'userid', - email: 'email', - traits: { - registered_at: 'today', +const sampleRiskRequestData = (configuration) => { + return { + event: '$login', + request_token: 'token', + status: '$succeeded', + user: { + id: 'userid', + email: 'email', + traits: { + registered_at: 'today', + }, }, - }, - context: { - ip: '8.8.8.8', - client_id: 'clientid', - headers: { - Cookie: 'SECRET=pleasedontbehere', - 'x-forwarded-for': '8.8.8.8', + context: { + ip: '8.8.8.8', + client_id: 'clientid', + headers: HeadersExtractService.call( + { + Cookie: 'SECRET=pleasedontbehere', + 'x-forwarded-for': '8.8.8.8', + }, + configuration + ), }, - }, + }; }; -const sampleFilterRequestData = { - event: '$login', - request_token: 'token', - status: '$succeeded', - user: { - id: 'userid', - email: 'email', - traits: { - registered_at: 'today', +const sampleFilterRequestData = (configuration) => { + return { + event: '$login', + request_token: 'token', + status: '$succeeded', + user: { + id: 'userid', + email: 'email', + traits: { + registered_at: 'today', + }, }, - }, - context: { - ip: '8.8.8.8', - client_id: 'clientid', - headers: { - Cookie: 'SECRET=pleasedontbehere', - 'x-forwarded-for': '8.8.8.8', + context: { + ip: '8.8.8.8', + client_id: 'clientid', + headers: HeadersExtractService.call( + { + Cookie: 'SECRET=pleasedontbehere', + 'x-forwarded-for': '8.8.8.8', + }, + configuration + ), }, - }, + }; }; -const sampleLogRequestData = { - event: '$login', - status: '$succeeded', - user: { - id: 'userid', - email: 'email', - traits: { - email: 'myemail', - registered_at: 'today', +const sampleLogRequestData = (configuration) => { + return { + event: '$login', + status: '$succeeded', + user: { + id: 'userid', + email: 'email', + traits: { + email: 'myemail', + registered_at: 'today', + }, }, - }, - context: { - ip: '8.8.8.8', - client_id: 'clientid', - headers: { - Cookie: 'SECRET=pleasedontbehere', - 'x-forwarded-for': '8.8.8.8', + context: { + ip: '8.8.8.8', + client_id: 'clientid', + headers: HeadersExtractService.call( + { + Cookie: 'SECRET=pleasedontbehere', + 'x-forwarded-for': '8.8.8.8', + }, + configuration + ), }, - }, + }; }; describe('Castle', () => { @@ -119,36 +142,38 @@ describe('Castle', () => { overrideFetch: fetch, logger: { info: () => {} }, }); - castle.track(sampleRequestData); + + const sampleRequestDataLocal = sampleRequestData(castle.configuration); + castle.track(sampleRequestDataLocal); const lastOptions: any = fetch.lastOptions(); const payload = JSON.parse(lastOptions.body.toString()); // Ensure the client set the sent_at property. expect(payload).toHaveProperty('sent_at', new Date().toISOString()); // Verify that the passed in properties are passed on. - expect(payload).toHaveProperty('event', sampleRequestData.event); + expect(payload).toHaveProperty('event', sampleRequestDataLocal.event); expect(payload).toHaveProperty( 'created_at', - sampleRequestData.created_at + sampleRequestDataLocal.created_at ); - expect(payload).toHaveProperty('user_id', sampleRequestData.user_id); + expect(payload).toHaveProperty('user_id', sampleRequestDataLocal.user_id); expect(payload).toHaveProperty('user_traits'); expect(payload.user_traits).toHaveProperty( 'email', - sampleRequestData.user_traits.email + sampleRequestDataLocal.user_traits.email ); expect(payload.user_traits).toHaveProperty( 'registered_at', - sampleRequestData.user_traits.registered_at + sampleRequestDataLocal.user_traits.registered_at ); expect(payload).toHaveProperty('context'); expect(payload.context).toHaveProperty( 'ip', - sampleRequestData.context.ip + sampleRequestDataLocal.context.ip ); expect(payload.context).toHaveProperty( 'client_id', - sampleRequestData.context.client_id + sampleRequestDataLocal.context.client_id ); expect(payload.context).toHaveProperty('headers'); // Ensure that cookie header property is scrubbed. @@ -169,14 +194,19 @@ describe('Castle', () => { logger: { info: () => {} }, }); + const sampleRequestDataLocal = sampleRequestData(castle.configuration); + castle.track({ - ...sampleRequestData, + ...sampleRequestDataLocal, context: { - ...sampleRequestData.context, - headers: { - 'X-NOT-A-SECRET': 'not secret!', - 'X-SUPER-SECRET': 'so secret!', - }, + ...sampleRequestDataLocal.context, + headers: HeadersExtractService.call( + { + 'X-NOT-A-SECRET': 'not secret!', + 'X-SUPER-SECRET': 'so secret!', + }, + castle.configuration + ), }, }); @@ -207,14 +237,19 @@ describe('Castle', () => { logger: { info: () => {} }, }); + const sampleRequestDataLocal = sampleRequestData(castle.configuration); + castle.track({ - ...sampleRequestData, + ...sampleRequestDataLocal, context: { - ...sampleRequestData.context, - headers: { - 'X-NOT-A-SECRET': 'not secret!', - 'X-SUPER-SECRET': 'so secret!', - }, + ...sampleRequestDataLocal.context, + headers: HeadersExtractService.call( + { + 'X-NOT-A-SECRET': 'not secret!', + 'X-SUPER-SECRET': 'so secret!', + }, + castle.configuration + ), }, }); @@ -240,7 +275,9 @@ describe('Castle', () => { logger: { info: () => {} }, }); - await castle.track(sampleRequestData); + const sampleRequestDataLocal = sampleRequestData(castle.configuration); + + await castle.track(sampleRequestDataLocal); // Ensure that fetch was never called. When do not track // is on, the SDK should generate no outbound requests. @@ -256,11 +293,13 @@ describe('Castle', () => { logger: { info: () => {} }, }); + const sampleRequestDataLocal = sampleRequestData(castle.configuration); + // Promise based expectations have to be awaited to properly fail // tests, instead of just logging unhandled rejections. - await expect(castle.authenticate(sampleRequestData)).rejects.toThrowError( - 'Castle: Responded with 401 code' - ); + await expect( + castle.authenticate(sampleRequestDataLocal) + ).rejects.toThrowError('Castle: Responded with 401 code'); }); }); @@ -297,7 +336,8 @@ describe('Castle', () => { logger: { info: () => {} }, }); - const response = await castle.authenticate(sampleRequestData); + const sampleRequestDataLocal = sampleRequestData(castle.configuration); + const response = await castle.authenticate(sampleRequestDataLocal); expect(response).toHaveProperty('action', 'allow'); expect(response).toHaveProperty('device_token', 'device_token'); expect(response).toHaveProperty('user_id', 'user_id'); @@ -313,29 +353,29 @@ describe('Castle', () => { // Ensure the client set the sent_at property. expect(payload).toHaveProperty('sent_at', new Date().toISOString()); // Verify that the passed in properties are passed on. - expect(payload).toHaveProperty('event', sampleRequestData.event); + expect(payload).toHaveProperty('event', sampleRequestDataLocal.event); expect(payload).toHaveProperty( 'created_at', - sampleRequestData.created_at + sampleRequestDataLocal.created_at ); - expect(payload).toHaveProperty('user_id', sampleRequestData.user_id); + expect(payload).toHaveProperty('user_id', sampleRequestDataLocal.user_id); expect(payload).toHaveProperty('user_traits'); expect(payload.user_traits).toHaveProperty( 'email', - sampleRequestData.user_traits.email + sampleRequestDataLocal.user_traits.email ); expect(payload.user_traits).toHaveProperty( 'registered_at', - sampleRequestData.user_traits.registered_at + sampleRequestDataLocal.user_traits.registered_at ); expect(payload).toHaveProperty('context'); expect(payload.context).toHaveProperty( 'ip', - sampleRequestData.context.ip + sampleRequestDataLocal.context.ip ); expect(payload.context).toHaveProperty( 'client_id', - sampleRequestData.context.client_id + sampleRequestDataLocal.context.client_id ); expect(payload.context).toHaveProperty('headers'); // Ensure that cookie header property is scrubbed. @@ -355,8 +395,8 @@ describe('Castle', () => { failoverStrategy: FailoverStrategy.deny, logger: { info: () => {} }, }); - - const response = await castle.authenticate(sampleRequestData); + const sampleRequestDataLocal = sampleRequestData(castle.configuration); + const response = await castle.authenticate(sampleRequestDataLocal); expect(response).toHaveProperty('action', 'deny'); expect(response).toHaveProperty('failover', true); expect(response).toHaveProperty('failover_reason', 'timeout'); @@ -371,8 +411,8 @@ describe('Castle', () => { failoverStrategy: FailoverStrategy.deny, logger: { info: () => {} }, }); - - const response = await castle.authenticate(sampleRequestData); + const sampleRequestDataLocal = sampleRequestData(castle.configuration); + const response = await castle.authenticate(sampleRequestDataLocal); expect(response).toHaveProperty('action', 'deny'); expect(response).toHaveProperty('failover', true); expect(response).toHaveProperty('failover_reason', 'server error'); @@ -388,8 +428,8 @@ describe('Castle', () => { failoverStrategy: FailoverStrategy.deny, logger: { info: () => {} }, }); - - const response = await castle.authenticate(sampleRequestData); + const sampleRequestDataLocal = sampleRequestData(castle.configuration); + const response = await castle.authenticate(sampleRequestDataLocal); expect(response).toHaveProperty('action', 'allow'); expect(response).toHaveProperty('failover', true); expect(response).toHaveProperty('failover_reason', 'do not track'); @@ -408,12 +448,12 @@ describe('Castle', () => { overrideFetch: fetch, logger: { info: () => {} }, }); - + const sampleRequestDataLocal = sampleRequestData(castle.configuration); // Promise based expectations have to be awaited to properly fail // tests, instead of just logging unhandled rejections. - await expect(castle.authenticate(sampleRequestData)).rejects.toThrowError( - 'Castle: Responded with 401 code' - ); + await expect( + castle.authenticate(sampleRequestDataLocal) + ).rejects.toThrowError('Castle: Responded with 401 code'); }); }); @@ -449,7 +489,10 @@ describe('Castle', () => { logger: { info: () => {} }, }); - const response = await (castle.risk(sampleRiskRequestData)); + const sampleRiskRequestDataLocal = sampleRiskRequestData( + castle.configuration + ); + const response = await (castle.risk(sampleRiskRequestDataLocal)); expect(response).toHaveProperty('action', 'allow'); expect(response).toHaveProperty('device.token', 'device_token'); expect(response.policy).toHaveProperty('id', 'q-rbeMzBTdW2Fd09sbz55A'); @@ -464,21 +507,24 @@ describe('Castle', () => { // Ensure the client set the sent_at property. expect(payload).toHaveProperty('sent_at', new Date().toISOString()); // Verify that the passed in properties are passed on. - expect(payload).toHaveProperty('event', sampleRiskRequestData.event); - expect(payload).toHaveProperty('user.id', sampleRiskRequestData.user.id); + expect(payload).toHaveProperty('event', sampleRiskRequestDataLocal.event); + expect(payload).toHaveProperty( + 'user.id', + sampleRiskRequestDataLocal.user.id + ); expect(payload).toHaveProperty('user.traits'); expect(payload.user).toHaveProperty( 'email', - sampleRiskRequestData.user.email + sampleRiskRequestDataLocal.user.email ); expect(payload.user.traits).toHaveProperty( 'registered_at', - sampleRiskRequestData.user.traits.registered_at + sampleRiskRequestDataLocal.user.traits.registered_at ); expect(payload).toHaveProperty('context'); expect(payload.context).toHaveProperty( 'ip', - sampleRiskRequestData.context.ip + sampleRiskRequestDataLocal.context.ip ); expect(payload.context).toHaveProperty('headers'); // Ensure that cookie header property is scrubbed. @@ -518,7 +564,10 @@ describe('Castle', () => { logger: { info: () => {} }, }); - const response = await (castle.filter(sampleFilterRequestData)); + const sampleFilterRequestDataLocal = sampleRiskRequestData( + castle.configuration + ); + const response = await (castle.filter(sampleFilterRequestDataLocal)); expect(response).toHaveProperty('action', 'allow'); expect(response).toHaveProperty('device.token', 'device_token'); expect(response.policy).toHaveProperty('id', 'q-rbeMzBTdW2Fd09sbz55A'); @@ -533,24 +582,27 @@ describe('Castle', () => { // Ensure the client set the sent_at property. expect(payload).toHaveProperty('sent_at', new Date().toISOString()); // Verify that the passed in properties are passed on. - expect(payload).toHaveProperty('event', sampleFilterRequestData.event); + expect(payload).toHaveProperty( + 'event', + sampleFilterRequestDataLocal.event + ); expect(payload).toHaveProperty( 'user.id', - sampleFilterRequestData.user.id + sampleFilterRequestDataLocal.user.id ); expect(payload).toHaveProperty('user.traits'); expect(payload.user).toHaveProperty( 'email', - sampleFilterRequestData.user.email + sampleFilterRequestDataLocal.user.email ); expect(payload.user.traits).toHaveProperty( 'registered_at', - sampleFilterRequestData.user.traits.registered_at + sampleFilterRequestDataLocal.user.traits.registered_at ); expect(payload).toHaveProperty('context'); expect(payload.context).toHaveProperty( 'ip', - sampleFilterRequestData.context.ip + sampleFilterRequestDataLocal.context.ip ); expect(payload.context).toHaveProperty('headers'); // Ensure that cookie header property is scrubbed. @@ -579,32 +631,39 @@ describe('Castle', () => { overrideFetch: fetch, logger: { info: () => {} }, }); - castle.log(sampleLogRequestData); + const sampleLogRequestDataLocal = sampleLogRequestData( + castle.configuration + ); + castle.log(sampleLogRequestDataLocal); const lastOptions: any = fetch.lastOptions(); + const payload = JSON.parse(lastOptions.body.toString()); // Ensure the client set the sent_at property. expect(payload).toHaveProperty('sent_at', new Date().toISOString()); // Verify that the passed in properties are passed on. - expect(payload).toHaveProperty('event', sampleLogRequestData.event); - expect(payload).toHaveProperty('user.id', sampleLogRequestData.user.id); + expect(payload).toHaveProperty('event', sampleLogRequestDataLocal.event); + expect(payload).toHaveProperty( + 'user.id', + sampleLogRequestDataLocal.user.id + ); expect(payload).toHaveProperty('user.traits'); expect(payload.user).toHaveProperty( 'email', - sampleLogRequestData.user.email + sampleLogRequestDataLocal.user.email ); expect(payload.user.traits).toHaveProperty( 'registered_at', - sampleLogRequestData.user.traits.registered_at + sampleLogRequestDataLocal.user.traits.registered_at ); expect(payload).toHaveProperty('context'); expect(payload.context).toHaveProperty( 'ip', - sampleLogRequestData.context.ip + sampleLogRequestDataLocal.context.ip ); expect(payload.context).toHaveProperty( 'client_id', - sampleLogRequestData.context.client_id + sampleLogRequestDataLocal.context.client_id ); expect(payload.context).toHaveProperty('headers'); // Ensure that cookie header property is scrubbed. diff --git a/test/core/services/core-generate-request-body.service.test.ts b/test/core/services/core-generate-request-body.service.test.ts index e871e28..fdb75ac 100644 --- a/test/core/services/core-generate-request-body.service.test.ts +++ b/test/core/services/core-generate-request-body.service.test.ts @@ -12,7 +12,7 @@ describe('CoreGenerateRequestBody', () => { MockDate.reset(); }); - fdescribe('call', () => { + describe('call', () => { const result = JSON.stringify({ sent_at: '2021-01-25T00:00:00.000Z', event: '$login.succeeded', @@ -23,12 +23,6 @@ describe('CoreGenerateRequestBody', () => { 'x-forwarded-for': '127.0.0.1', 'x-castle-client-id': 'client_id', }, - client_id: 'client_id', - active: true, - library: { - name: 'castle-node', - version, - }, }, }); @@ -49,7 +43,7 @@ describe('CoreGenerateRequestBody', () => { }; it('generates request body', () => { - expect(CoreGenerateRequestBody.call(payload, config)).toEqual(result); + expect(CoreGenerateRequestBody.call(payload)).toEqual(result); }); }); });