From 08b4aea19294312905be86ccb3ea26abc362a34c Mon Sep 17 00:00:00 2001 From: Sebastian Wallin Date: Tue, 9 Nov 2021 00:15:28 +0100 Subject: [PATCH 1/4] Update README.md (#124) * Update README.md * Correct URL to docs --- README.md | 124 +----------------------------------------------------- 1 file changed, 2 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 2ae52e7..1bfecd8 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ **[Castle](https://castle.io) analyzes device, location, and interaction patterns in your web and mobile apps and lets you stop account takeover attacks in real-time..** -## Documentation +## Usage -[Official Castle docs](https://castle.io/docs) +See the [documentation](https://docs.castle.io) for how to use this SDK with the Castle APIs ## Installation @@ -51,123 +51,3 @@ const castle = new Castle({ apiSecret: 'YOUR SECRET HERE' }); | trustProxyChain | `boolean` - False by default, defines if trusting all of the proxy IPs in X-Forwarded-For is enabled. | | trustedProxyDepth | `number` - Number of trusted proxies used in the chain. | -## Actions - -The `castle` instance exposes two methods, `track` and `authenticate`. In order to provide the information required in both these methods, you'll need access to the logged in user information (if that is available at that stage in the user flow), as well as request information. In node connect/express, the user is often found in the `session` object, or directly on the `request` object in the `user` property, if you are using passport. - -### track - -This is the asynchronous version of the castle integration. This is for events where you don't require a response. - -```js - -castle.track({ - event: '$profile_update.failed', - user_id: user.id, - user_traits: { - email: user.email, - registered_at: user.registered_at, - }, - context: { - ip: request.ip, - client_id: request.cookies['__cid'], - headers: request.headers, - }, -}); -``` - -### authenticate - -This is the synchronous version of the castle integration. This is for events where you require a response. It is used in the same way as `track`, except that you have the option of waiting for a response. - -```js -let response; -try { - response = await castle.authenticate({ - event: '$login.succeeded', - user_id: user.id, - user_traits: { - email: user.email, - registered_at: user.registered_at, - }, - context: { - ip: request.ip, - client_id: request.cookies['__cid'], - headers: request.headers, - }, - }); -} catch (e) { - console.error(e); -} - -console.log(response); // { "action": "allow", "user_id": 123, "device_token": "eyj...." } -``` - -#### - -Response format - -| Response key | value | -| --------------- | --------------------------------------------------------------------------------------------------- | -| action | `string` - The recommended action for the given event. Options: `allow`, `challenge`, `deny`. | -| user_id | `string` - The `user_id` of the end user. | -| policy | `object` - object containing risk policy information, such as `id`,`revision_id`, `name` | -| signals | `object` - object containing hash with signals names | -| device_token | `string` - Our token for the device that generated the event. | -| failover | `boolean` - An optional property indicating the request failed and the response is a failover. | -| failover_reason | `string` - A message indicating why the request failed. | - -### All config options for `track` and `authenticate` - -| Config option | Explanation | -| ------------- | ----------- | -| event | `string` - The event generated by the user | -| user_id | `string` - The `user_id` of the end user. | -| user_traits | `object` - An optional, recommended, object containing user information, such as `email` and `registered_at`. | -| properties | `object` - An optional object containing custom information. | -| created_at | `string` - An optional ISO date string indicating when the event occurred, in cases where this might be different from the time when the request is made. | -| device_token | `string` - The optional device token, used for mitigating or escalating. | -| context | `object` - The request context information. | - -## Device management - -This SDK allows issuing requests to [Castle's Device Management Endpoints](https://docs.castle.io/device_management_tool/). Use these endpoints for admin-level management of end-user devices (i.e., for an internal dashboard). - -Fetching device data, approving a device, reporting a device requires a valid `device_token`. - -```js -// Get device data -castle.getDevice({ device_token }) -// Approve a device -castle.approveDevice({ device_token }) -// Report a device -castle.reportDevice({ device_token }) -``` - -Fetching available devices that belong to a given user requires a valid `user_id`. - -```js -// Get user's devices data -castle.getDevicesForUser({ user_id }) -``` - -## Payload - -To generate the payload, use the following command: -```js -const payload = PayloadPrepareService.call( - { - event: '$login.succeeded', - user_id: user.id, - properties: { - key: 'value' - }, - user_traits: { - key: 'value' - } - }, - request, - castle.configuration -) -castle.track(payload) -``` From e52a8bec33fc91559b32bfb4bc1db79f62919e7c Mon Sep 17 00:00:00 2001 From: Sebastian Wallin Date: Wed, 10 Nov 2021 17:50:08 +0100 Subject: [PATCH 2/4] Update README.md (#126) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bfecd8..3e9a678 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Castle SDK for Node -**[Castle](https://castle.io) analyzes device, location, and interaction patterns in your web and mobile apps and lets you stop account takeover attacks in real-time..** +**[Castle](https://castle.io) analyzes user behavior in web and mobile apps to stop fraud before it happens.** ## Usage From 7a555ef236ecbbd523bce1608691e582a9e9f173 Mon Sep 17 00:00:00 2001 From: Bartosz Date: Wed, 10 Nov 2021 21:41:45 +0100 Subject: [PATCH 3/4] corrected readme timeout (#125) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e9a678..e6e1eb1 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ const castle = new Castle({ apiSecret: 'YOUR SECRET HERE' }); | ----------------- | ----------- | | apiSecret | `string` - This can be found in the Castle dashboard. | | baseUrl | `string` - Base Castle API url. | -| timeout | `number` - Time before returning the failover strategy. Default value is 500. | +| timeout | `number` - Time before returning the failover strategy. Default value is 1000. | | allowlisted | `string[]` - An array of strings matching the headers you want to pass fully to the service. We highly recommend using the DEFAULT_ALLOWLIST constant. | | denylisted | `string[]` - An array of of strings matching the headers you do not want to pass fully to the service. | | failoverStrategy | `FailoverStrategy` - If the request to our service would for some reason time out, this is where you select the automatic response from `authenticate`. Options are `FailoverStrategy.allow`, `FailoverStrategy.deny`, `FailoverStrategy.challenge`. | From 52a7cea44d80175ea455a97da7374cb169de23c5 Mon Sep 17 00:00:00 2001 From: Sebastian Wallin Date: Tue, 1 Mar 2022 21:50:00 +0100 Subject: [PATCH 4/4] Add exception for handling invalid request token (#127) --- .../services/core-process-response.service.ts | 10 +++++++++- src/errors.ts | 8 ++++++++ .../core-process-response.service.test.ts | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/core/services/core-process-response.service.ts b/src/core/services/core-process-response.service.ts index e5bd5b5..62316b4 100644 --- a/src/core/services/core-process-response.service.ts +++ b/src/core/services/core-process-response.service.ts @@ -5,6 +5,7 @@ import { NotFoundError, UserUnauthorizedError, InvalidParametersError, + InvalidRequestTokenError, InternalServerError, APIError, } from '../../errors'; @@ -19,6 +20,10 @@ const RESPONSE_ERRORS = { '422': InvalidParametersError, }; +const RESPONSE_SUB_ERRORS = { + 'invalid_request_token': InvalidRequestTokenError +}; + // The body on the request is a stream and can only be // read once, by default. This is a workaround so that the // logging functions can read the body independently @@ -58,7 +63,10 @@ export const CoreProcessResponseService = { ); } - const err = RESPONSE_ERRORS[response.status.toString()]; + // Throw a special exception for subtype errors if defined. Eg. for + // invalid request token, which is a subtype of InvalidParametersError. + // Otherwise, throw exception as defined per status code + const err = RESPONSE_SUB_ERRORS[body.type] || RESPONSE_ERRORS[response.status.toString()]; throw new err(`Castle: Responded with ${response.status} code`); }, diff --git a/src/errors.ts b/src/errors.ts index c5e5326..08d23b2 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -60,6 +60,14 @@ export class InvalidParametersError extends APIError { } } +// api error invalid param 422, subtype +export class InvalidRequestTokenError extends InvalidParametersError { + constructor(message: string) { + super(message); + this.name = 'InvalidRequestTokenError'; + } +} + // all internal server errors export class InternalServerError extends APIError { constructor(message: string) { diff --git a/test/core/services/core-process-response.service.test.ts b/test/core/services/core-process-response.service.test.ts index 3c454d7..43ebbcb 100644 --- a/test/core/services/core-process-response.service.test.ts +++ b/test/core/services/core-process-response.service.test.ts @@ -1,5 +1,6 @@ import { CoreProcessResponseService } from '../../../src/core/core.module'; import { Response } from 'node-fetch'; +import { InvalidRequestTokenError } from '../../../src/errors'; describe('CoreProcessResponseService', () => { describe('call', () => { @@ -180,6 +181,24 @@ describe('CoreProcessResponseService', () => { }); }); }); + + describe('with invalid request token', () => { + const response = new Response(JSON.stringify({ + type: "invalid_request_token", + message: "Invalid Request Token" + }), { + headers: { 'Content-Type': 'application/json' }, + status: 422 + }) + + it('throws InvalidRequestTokenError', async () => { + await expect( + CoreProcessResponseService.call('risk', {}, response, { + info: () => {}, + }) + ).rejects.toThrow(InvalidRequestTokenError) + }) + }); }); }); });