Skip to content

Commit

Permalink
CAS-7032 Add filter, risk and log endpoint clients
Browse files Browse the repository at this point in the history
  • Loading branch information
dawlib committed May 26, 2021
1 parent 946749d commit b89991b
Show file tree
Hide file tree
Showing 18 changed files with 740 additions and 0 deletions.
66 changes: 66 additions & 0 deletions src/api/services/api-filter.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Configuration } from '../../configuraton';
import { InternalServerError } from '../../errors';
import { FilterResult } from '../../models';
import { CommandFilterService } from '../../command/command.module';
import {
FailoverResponsePrepareService,
FailoverStrategy,
} from '../../failover/failover.module';
import { FilterPayload } from '../../payload/payload.module';
import { APIService } from './api.service';
import AbortController from 'abort-controller';

const handleFailover = (
userId: string,
reason: string,
configuration: Configuration,
err?: Error
): FilterResult => {
// Have to check it this way to make sure TS understands
// that this.failoverStrategy is of type Verdict,
// not FailoverStrategyType.
if (configuration.failoverStrategy === FailoverStrategy.throw) {
throw err;
}

return FailoverResponsePrepareService.call(
userId,
reason,
configuration.failoverStrategy
);
};

const isTimeoutError = (e: Error) => e.name === 'AbortError';

export const APIFilterService = {
call: async (
options: FilterPayload,
configuration: Configuration
): Promise<FilterResult> => {
const controller = new AbortController();
const command = CommandFilterService.call(
controller,
options,
configuration
);

let processedResponse;
try {
processedResponse = await APIService.call(
controller,
command,
configuration
);
} catch (e) {
if (isTimeoutError(e)) {
return handleFailover(options.user.id, 'timeout', configuration, e);
} else if (e instanceof InternalServerError) {
return handleFailover(options.user.id, 'server error', configuration);
} else {
throw e;
}
}

return processedResponse;
},
};
21 changes: 21 additions & 0 deletions src/api/services/api-log.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Configuration } from '../../configuraton';
import { CommandLogService } from '../../command/command.module';
import { LogPayload } from '../../payload/payload.module';
import { APIService } from './api.service';
import AbortController from 'abort-controller';

export const APILogService = {
call: async (
options: LogPayload,
configuration: Configuration
): Promise<void> => {
const controller = new AbortController();
const command = CommandLogService.call(
controller,
options,
configuration
);

APIService.call(controller, command, configuration);
},
};
66 changes: 66 additions & 0 deletions src/api/services/api-risk.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Configuration } from '../../configuraton';
import { InternalServerError } from '../../errors';
import { RiskResult } from '../../models';
import { CommandRiskService } from '../../command/command.module';
import {
FailoverResponsePrepareService,
FailoverStrategy,
} from '../../failover/failover.module';
import { RiskPayload } from '../../payload/risk_payload.module';
import { APIService } from './api.service';
import AbortController from 'abort-controller';

const handleFailover = (
userId: string,
reason: string,
configuration: Configuration,
err?: Error
): RiskResult => {
// Have to check it this way to make sure TS understands
// that this.failoverStrategy is of type Verdict,
// not FailoverStrategyType.
if (configuration.failoverStrategy === FailoverStrategy.throw) {
throw err;
}

return FailoverResponsePrepareService.call(
userId,
reason,
configuration.failoverStrategy
);
};

const isTimeoutError = (e: Error) => e.name === 'AbortError';

export const APIRiskService = {
call: async (
options: RiskPayload,
configuration: Configuration
): Promise<RiskResult> => {
const controller = new AbortController();
const command = CommandRiskService.call(
controller,
options,
configuration
);

let processedResponse;
try {
processedResponse = await APIService.call(
controller,
command,
configuration
);
} catch (e) {
if (isTimeoutError(e)) {
return handleFailover(options.user.id, 'timeout', configuration, e);
} else if (e instanceof InternalServerError) {
return handleFailover(options.user.id, 'server error', configuration);
} else {
throw e;
}
}

return processedResponse;
},
};
17 changes: 17 additions & 0 deletions src/command/services/command-filter.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Configuration } from '../../configuraton';
import { ContextSanitizeService } from '../../context/context.module';
import { FilterPayload } from '../../payload/payload.module';
import { CommandGenerateService } from './command-generate.service';

export const CommandFilterService = {
call: (controller, options: FilterPayload, configuration: Configuration) => {
const context = ContextSanitizeService.call(options.context);
return CommandGenerateService.call(
controller,
'filter',
{ ...options, ...{ context } },
'POST',
configuration
);
},
};
17 changes: 17 additions & 0 deletions src/command/services/command-log.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Configuration } from '../../configuraton';
import { ContextSanitizeService } from '../../context/context.module';
import { LogPayload } from '../../payload/payload.module';
import { CommandGenerateService } from './command-generate.service';

export const CommandLogService = {
call: (controller, options: LogPayload, configuration: Configuration) => {
const context = ContextSanitizeService.call(options.context);
return CommandGenerateService.call(
controller,
'log',
{ ...options, ...{ context } },
'POST',
configuration
);
},
};
17 changes: 17 additions & 0 deletions src/command/services/command-risk.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Configuration } from '../../configuraton';
import { ContextSanitizeService } from '../../context/context.module';
import { RiskPayload } from '../../payload/payload.module';
import { CommandGenerateService } from './command-generate.service';

export const CommandRiskService = {
call: (controller, options: RiskPayload, configuration: Configuration) => {
const context = ContextSanitizeService.call(options.context);
return CommandGenerateService.call(
controller,
'risk',
{ ...options, ...{ context } },
'POST',
configuration
);
},
};
6 changes: 6 additions & 0 deletions src/models/filter-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { RiskPolicy } from './risk-policy';
import { Verdict } from './verdict';

export type FilterResult = {

};
6 changes: 6 additions & 0 deletions src/models/log-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { RiskPolicy } from './risk-policy';
import { Verdict } from './verdict';

export type LogResult = {

};
6 changes: 6 additions & 0 deletions src/models/risk-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { RiskPolicy } from './risk-policy';
import { Verdict } from './verdict';

export type RiskResult = {

};
16 changes: 16 additions & 0 deletions src/payload/models/filter_payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { IncomingHttpHeaders } from 'http2';

export type FilterPayload = {
request_token?: string;
event?: string;
user?: {
id: string;
email: string;
};
properties?: object;
created_at?: string;
context?: {
ip: string;
headers: IncomingHttpHeaders;
};
};
17 changes: 17 additions & 0 deletions src/payload/models/log_payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IncomingHttpHeaders } from 'http2';

export type LogPayload = {
request_token?: string;
event?: string;
status?: string;
user?: {
id: string;
email: string;
};
properties?: object;
created_at?: string;
context?: {
ip: string;
headers: IncomingHttpHeaders;
};
};
17 changes: 17 additions & 0 deletions src/payload/models/risk_payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IncomingHttpHeaders } from 'http2';

export type RiskPayload = {
request_token?: string;
event?: string;
status?: string;
user?: {
id: string;
email: string;
};
properties?: object;
created_at?: string;
context?: {
ip: string;
headers: IncomingHttpHeaders;
};
};
113 changes: 113 additions & 0 deletions test/api/services/api-filter.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { APIFilterService } from '../../../src/api/api.module';
import { Configuration } from '../../../src/configuraton';
import { EVENTS } from '../../../src/events';
import MockDate from 'mockdate';
import fetchMock from 'fetch-mock';

describe('APIFilterService', () => {
beforeEach(() => {
MockDate.set(new Date('2021-01-25T00:00:00.000Z'));
});

afterEach(() => {
MockDate.reset();
});

const sampleRequestData = {
event: EVENTS.LOGIN_SUCCEEDED,
created_at: 'now',
user_id: 'userid',
user_traits: {
email: 'myemail',
updated_at: 'today',
},
context: {
ip: '8.8.8.8',
headers: {},
},
};

describe('call', () => {
it('handles allow response', async () => {
const fetch = fetchMock.sandbox().mock('*', {
action: 'allow',
device_token: 'device_token',
user_id: 'user_id',
});

const config = new Configuration({
apiSecret: 'test',
overrideFetch: fetch,
logger: { info: () => {} },
});

const response = await APIFilterService.call(
sampleRequestData,
config
);
expect(response).toHaveProperty('action', 'allow');
expect(response).toHaveProperty('device_token', 'device_token');
expect(response).toHaveProperty('user_id', 'user_id');
});

it('handles deny response without risk policy', async () => {
const fetch = fetchMock.sandbox().mock('*', {
action: 'deny',
device_token: 'device_token',
user_id: 'user_id',
});

const config = new Configuration({
apiSecret: 'test',
overrideFetch: fetch,
logger: { info: () => {} },
});

const response = await APIFilterService.call(
sampleRequestData,
config
);
expect(response).toHaveProperty('action', 'deny');
expect(response).toHaveProperty('device_token', 'device_token');
expect(response).toHaveProperty('user_id', 'user_id');
});

it('handles deny response with risk policy', async () => {
const fetch = fetchMock.sandbox().mock('*', {
action: 'deny',
device_token: 'device_token',
user_id: 'user_id',
risk_policy: {
id: 'q-rbeMzBTdW2Fd09sbz55A',
revision_id: 'pke4zqO2TnqVr-NHJOAHEg',
name: 'Block Users from X',
type: 'bot',
},
});

const config = new Configuration({
apiSecret: 'test',
overrideFetch: fetch,
logger: { info: () => {} },
});

const response = await APIFilterService.call(
sampleRequestData,
config
);
expect(response).toHaveProperty('action', 'deny');
expect(response).toHaveProperty('device_token', 'device_token');
expect(response).toHaveProperty('user_id', 'user_id');
expect(response.risk_policy).toHaveProperty(
'id',
'q-rbeMzBTdW2Fd09sbz55A'
);
expect(response.risk_policy).toHaveProperty(
'revision_id',
'pke4zqO2TnqVr-NHJOAHEg'
);
expect(response.risk_policy).toHaveProperty('type', 'bot');
expect(response.risk_policy).toHaveProperty('name', 'Block Users from X');
});
});
});
Loading

0 comments on commit b89991b

Please sign in to comment.