perimeterx-js-core 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/activities/HttpActivityClient.js +4 -4
- package/lib/cjs/activities/HttpBatchedActivityClient.js +7 -7
- package/lib/cjs/additional_activity_handler/AdditionalActivityHandlerUtils.js +2 -2
- package/lib/cjs/config/ConfigurationBase.js +4 -1
- package/lib/cjs/config/remote_config/DefaultRemoteConfigUpdater.js +25 -30
- package/lib/cjs/config/remote_config/RemoteConfigUtils.js +14 -0
- package/lib/cjs/config/remote_config/index.js +1 -0
- package/lib/cjs/context/DefaultContext.js +18 -7
- package/lib/cjs/custom_parameters/CustomParametersUtils.js +1 -1
- package/lib/cjs/enforcer/EnforcerBase.js +40 -31
- package/lib/cjs/graphql/DefaultGraphQLParser.js +20 -22
- package/lib/cjs/impl/url/DefaultUrlUtils.js +7 -1
- package/lib/cjs/logger/DefaultLogger.js +2 -2
- package/lib/cjs/logger/HttpLogServiceClient.js +32 -27
- package/lib/cjs/logger/LoggerBase.js +5 -1
- package/lib/cjs/monitored_request/MonitoredRequestUtils.js +8 -8
- package/lib/cjs/phase/impl/CreateBlockResponsePhase.js +2 -2
- package/lib/cjs/phase/impl/DecideActionPhase.js +3 -4
- package/lib/cjs/phase/impl/EnrichContextFromRequestPhase.js +3 -2
- package/lib/cjs/phase/impl/EnrichContextFromResponsePhase.js +1 -1
- package/lib/cjs/phase/impl/FirstPartyPhase.js +4 -4
- package/lib/cjs/phase/impl/RiskApiPhase.js +1 -1
- package/lib/cjs/phase/impl/SendLogsPhase.js +2 -2
- package/lib/cjs/phase/impl/UpdateRemoteConfigPhase.js +0 -1
- package/lib/cjs/products/account_defender/AccountDefender.js +9 -8
- package/lib/cjs/products/bot_defender/filter/DefaultBotDefenderFilter.js +10 -10
- package/lib/cjs/products/bot_defender/first_party/DefaultBotDefenderFirstParty.js +17 -17
- package/lib/cjs/products/credential_intelligence/endpoint/CredentialEndpoint.js +8 -9
- package/lib/cjs/products/credential_intelligence/endpoint/CredentialEndpointManager.js +13 -15
- package/lib/cjs/pxde/DefaultDataEnrichment.js +11 -11
- package/lib/cjs/risk_api/client/PostRiskApiClientBase.js +5 -5
- package/lib/cjs/risk_token/parser/TokenParserBase.js +12 -13
- package/lib/cjs/risk_token/token/v2/DefaultTokenV2.js +5 -5
- package/lib/cjs/risk_token/token/v3/DefaultTokenV3.js +11 -11
- package/lib/cjs/sensitive_request/SensitiveRequestUtils.js +4 -4
- package/lib/cjs/telemetry/DefaultTelemetry.js +7 -7
- package/lib/cjs/utils/constants.js +1 -1
- package/lib/cjs/utils/timestamp_hmac_header_validator/DefaultTimestampHmacHeaderValidator.js +7 -7
- package/lib/esm/activities/HttpActivityClient.js +4 -4
- package/lib/esm/activities/HttpBatchedActivityClient.js +7 -7
- package/lib/esm/additional_activity_handler/AdditionalActivityHandlerUtils.js +2 -2
- package/lib/esm/config/ConfigurationBase.js +4 -1
- package/lib/esm/config/remote_config/DefaultRemoteConfigUpdater.js +21 -23
- package/lib/esm/config/remote_config/RemoteConfigUtils.js +9 -0
- package/lib/esm/config/remote_config/index.js +1 -0
- package/lib/esm/context/DefaultContext.js +18 -8
- package/lib/esm/custom_parameters/CustomParametersUtils.js +1 -1
- package/lib/esm/enforcer/EnforcerBase.js +18 -7
- package/lib/esm/graphql/DefaultGraphQLParser.js +9 -10
- package/lib/esm/impl/url/DefaultUrlUtils.js +7 -1
- package/lib/esm/logger/DefaultLogger.js +2 -2
- package/lib/esm/logger/HttpLogServiceClient.js +25 -27
- package/lib/esm/logger/LoggerBase.js +6 -1
- package/lib/esm/monitored_request/MonitoredRequestUtils.js +8 -8
- package/lib/esm/phase/impl/CreateBlockResponsePhase.js +2 -2
- package/lib/esm/phase/impl/DecideActionPhase.js +2 -6
- package/lib/esm/phase/impl/EnrichContextFromRequestPhase.js +4 -2
- package/lib/esm/phase/impl/EnrichContextFromResponsePhase.js +1 -1
- package/lib/esm/phase/impl/FirstPartyPhase.js +4 -4
- package/lib/esm/phase/impl/RiskApiPhase.js +1 -1
- package/lib/esm/phase/impl/SendLogsPhase.js +2 -2
- package/lib/esm/phase/impl/UpdateRemoteConfigPhase.js +0 -1
- package/lib/esm/products/account_defender/AccountDefender.js +9 -8
- package/lib/esm/products/bot_defender/filter/DefaultBotDefenderFilter.js +8 -8
- package/lib/esm/products/bot_defender/first_party/DefaultBotDefenderFirstParty.js +16 -15
- package/lib/esm/products/credential_intelligence/endpoint/CredentialEndpoint.js +8 -10
- package/lib/esm/products/credential_intelligence/endpoint/CredentialEndpointManager.js +5 -5
- package/lib/esm/pxde/DefaultDataEnrichment.js +11 -11
- package/lib/esm/risk_api/client/PostRiskApiClientBase.js +5 -5
- package/lib/esm/risk_token/parser/TokenParserBase.js +12 -11
- package/lib/esm/risk_token/token/v2/DefaultTokenV2.js +5 -5
- package/lib/esm/risk_token/token/v3/DefaultTokenV3.js +11 -11
- package/lib/esm/sensitive_request/SensitiveRequestUtils.js +4 -4
- package/lib/esm/telemetry/DefaultTelemetry.js +7 -7
- package/lib/esm/utils/constants.js +1 -1
- package/lib/esm/utils/timestamp_hmac_header_validator/DefaultTimestampHmacHeaderValidator.js +7 -7
- package/lib/types/activities/HttpActivityClient.d.ts +4 -3
- package/lib/types/activities/HttpBatchedActivityClient.d.ts +8 -7
- package/lib/types/activities/utils.d.ts +270 -0
- package/lib/types/blocker/utils.d.ts +27 -0
- package/lib/types/config/ConfigurationBase.d.ts +6 -5
- package/lib/types/config/remote_config/DefaultRemoteConfigUpdater.d.ts +5 -5
- package/lib/types/config/remote_config/RemoteConfigUtils.d.ts +4 -0
- package/lib/types/config/remote_config/index.d.ts +1 -0
- package/lib/types/context/DefaultContext.d.ts +7 -2
- package/lib/types/context/interfaces/IContext.d.ts +11 -2
- package/lib/types/enforcer/EnforcerBase.d.ts +2 -1
- package/lib/types/graphql/DefaultGraphQLParser.d.ts +1 -2
- package/lib/types/logger/DefaultLogger.d.ts +1 -1
- package/lib/types/logger/HttpLogServiceClient.d.ts +5 -10
- package/lib/types/logger/ILogServiceClient.d.ts +1 -2
- package/lib/types/logger/LoggerBase.d.ts +2 -1
- package/lib/types/logger/model/LogMetadata.d.ts +1 -0
- package/lib/types/monitored_request/MonitoredRequestUtils.d.ts +1353 -2
- package/lib/types/phase/impl/DecideActionPhase.d.ts +0 -3
- package/lib/types/phase/impl/EnrichContextFromRequestPhase.d.ts +1 -0
- package/lib/types/phase/impl/FirstPartyPhase.d.ts +1 -1
- package/lib/types/products/account_defender/AccountDefender.d.ts +4 -5
- package/lib/types/products/bot_defender/filter/DefaultBotDefenderFilter.d.ts +2 -3
- package/lib/types/products/bot_defender/first_party/DefaultBotDefenderFirstParty.d.ts +3 -4
- package/lib/types/products/credential_intelligence/endpoint/CredentialEndpoint.d.ts +3 -5
- package/lib/types/products/credential_intelligence/endpoint/CredentialEndpointManager.d.ts +2 -2
- package/lib/types/products/credential_intelligence/endpoint/ICredentialEndpoint.d.ts +3 -4
- package/lib/types/pxhd/PXHDUtils.d.ts +54 -0
- package/lib/types/risk_api/client/PostRiskApiClientBase.d.ts +1 -1
- package/lib/types/risk_token/parser/TokenParserBase.d.ts +4 -5
- package/lib/types/risk_token/token/v3/DefaultTokenV3.d.ts +1 -1
- package/lib/types/sensitive_request/SensitiveRequestUtils.d.ts +705 -17
- package/lib/types/telemetry/DefaultTelemetry.d.ts +1 -1
- package/lib/types/utils/constants.d.ts +1 -1
- package/lib/types/utils/timestamp_hmac_header_validator/DefaultTimestampHmacHeaderValidator.d.ts +3 -2
- package/lib/types/utils/timestamp_hmac_header_validator/ITimestampHmacHeaderValidator.d.ts +2 -1
- package/package.json +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { REMOTE_CONFIG_PUSH_DATA_FEATURE_NAME, WRITE_REMOTE_CONFIG_ERROR_NAME } from './constants';
|
|
1
|
+
import { DefaultTimestampHmacHeaderValidator, PUSH_DATA_HMAC_HEADER_NAME, sleep, } from '../../utils';
|
|
2
|
+
import { WRITE_REMOTE_CONFIG_ERROR_NAME } from './constants';
|
|
4
3
|
export class DefaultRemoteConfigUpdater {
|
|
5
4
|
config;
|
|
6
5
|
serviceClient;
|
|
@@ -14,61 +13,60 @@ export class DefaultRemoteConfigUpdater {
|
|
|
14
13
|
options.timestampHmacHeaderValidator ||
|
|
15
14
|
new DefaultTimestampHmacHeaderValidator(config, config.remoteConfigSecret, options.base64Utils, options.hmacUtils);
|
|
16
15
|
}
|
|
17
|
-
isUpdateRemoteConfigRequest(
|
|
18
|
-
|
|
19
|
-
const isCorrectFeature = request.headers.get(PUSH_DATA_FEATURE_HEADER_NAME) === REMOTE_CONFIG_PUSH_DATA_FEATURE_NAME;
|
|
20
|
-
const isHmacHeaderPresent = !!request.headers.get(PUSH_DATA_HMAC_HEADER_NAME);
|
|
21
|
-
return isCorrectMethod && isCorrectFeature && isHmacHeaderPresent;
|
|
16
|
+
isUpdateRemoteConfigRequest(context) {
|
|
17
|
+
return context.isRemoteConfigUpdateRequest;
|
|
22
18
|
}
|
|
23
|
-
async updateRemoteConfig(
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
async updateRemoteConfig(context) {
|
|
20
|
+
context.logger.debug('identified an update remote config request');
|
|
21
|
+
const { requestData: { request }, } = context;
|
|
22
|
+
if (!(await this.isUpdateRequestValid(request, context))) {
|
|
26
23
|
return false;
|
|
27
24
|
}
|
|
28
|
-
const updateRequestData = await this.getUpdateRequestData(request);
|
|
25
|
+
const updateRequestData = await this.getUpdateRequestData(request, context);
|
|
29
26
|
if (!updateRequestData) {
|
|
30
27
|
return false;
|
|
31
28
|
}
|
|
32
|
-
const remoteConfig = await this.fetchRemoteConfig(updateRequestData);
|
|
29
|
+
const remoteConfig = await this.fetchRemoteConfig(updateRequestData, context);
|
|
33
30
|
if (!remoteConfig) {
|
|
34
31
|
return false;
|
|
35
32
|
}
|
|
36
33
|
await this.storageClient.save(remoteConfig);
|
|
37
|
-
|
|
34
|
+
context.logger.debug(`successfully updated with remote config version ${remoteConfig.version}`);
|
|
38
35
|
return true;
|
|
39
36
|
}
|
|
40
|
-
async isUpdateRequestValid(request) {
|
|
37
|
+
async isUpdateRequestValid(request, context) {
|
|
41
38
|
const timestampHmacHeader = request.headers.get(PUSH_DATA_HMAC_HEADER_NAME);
|
|
42
|
-
const isValid = await this.timestampHmacHeaderValidator.isValid(timestampHmacHeader);
|
|
39
|
+
const isValid = await this.timestampHmacHeaderValidator.isValid(timestampHmacHeader, context.logger);
|
|
43
40
|
if (!isValid) {
|
|
44
|
-
|
|
41
|
+
context.logger.error(`invalid timestamp hmac header received: ${timestampHmacHeader}`, {
|
|
45
42
|
errorType: WRITE_REMOTE_CONFIG_ERROR_NAME,
|
|
46
43
|
});
|
|
47
44
|
}
|
|
48
45
|
return isValid;
|
|
49
46
|
}
|
|
50
|
-
async getUpdateRequestData(request) {
|
|
47
|
+
async getUpdateRequestData(request, context) {
|
|
51
48
|
let updateRequestData;
|
|
52
49
|
try {
|
|
53
50
|
updateRequestData = await request.json();
|
|
54
51
|
}
|
|
55
52
|
catch (err) {
|
|
56
|
-
|
|
53
|
+
context.logger.error(`could not parse update request body: ${err}`, {
|
|
57
54
|
errorType: WRITE_REMOTE_CONFIG_ERROR_NAME,
|
|
58
55
|
});
|
|
59
56
|
return null;
|
|
60
57
|
}
|
|
61
58
|
if (!updateRequestData?.version) {
|
|
62
|
-
|
|
59
|
+
context.logger.error(`no version on update request body: ${updateRequestData}`, {
|
|
63
60
|
errorType: WRITE_REMOTE_CONFIG_ERROR_NAME,
|
|
64
61
|
});
|
|
65
62
|
return null;
|
|
66
63
|
}
|
|
67
64
|
return updateRequestData;
|
|
68
65
|
}
|
|
69
|
-
async fetchRemoteConfig(updateRequestData) {
|
|
66
|
+
async fetchRemoteConfig(updateRequestData, context) {
|
|
70
67
|
const { remoteConfigMaxFetchAttempts, remoteConfigRetryIntervalMs, remoteConfigVersion } = this.config;
|
|
71
|
-
let
|
|
68
|
+
let errorText;
|
|
69
|
+
let remoteConfigData;
|
|
72
70
|
for (let i = 0; i < remoteConfigMaxFetchAttempts; i++) {
|
|
73
71
|
try {
|
|
74
72
|
remoteConfigData = await this.serviceClient.fetch(updateRequestData);
|
|
@@ -86,7 +84,7 @@ export class DefaultRemoteConfigUpdater {
|
|
|
86
84
|
errorText =
|
|
87
85
|
errorText ??
|
|
88
86
|
`no config version ${updateRequestData.version} found after ${remoteConfigMaxFetchAttempts} attempts, current: ${remoteConfigVersion}, received: ${remoteConfigData?.version}`;
|
|
89
|
-
|
|
87
|
+
context.logger.error(errorText, { errorType: WRITE_REMOTE_CONFIG_ERROR_NAME });
|
|
90
88
|
}
|
|
91
89
|
isDesiredRemoteConfigVersion({ version }, remoteConfigData) {
|
|
92
90
|
return version === remoteConfigData.version;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { HttpMethod } from '../../http';
|
|
2
|
+
import { PUSH_DATA_FEATURE_HEADER_NAME, PUSH_DATA_HMAC_HEADER_NAME } from '../../utils';
|
|
3
|
+
import { REMOTE_CONFIG_PUSH_DATA_FEATURE_NAME } from './constants';
|
|
4
|
+
export var RemoteConfigUtils;
|
|
5
|
+
(function (RemoteConfigUtils) {
|
|
6
|
+
RemoteConfigUtils.isRemoteConfigUpdateRequest = (request) => request.method === HttpMethod.PATCH &&
|
|
7
|
+
request.headers.get(PUSH_DATA_FEATURE_HEADER_NAME) === REMOTE_CONFIG_PUSH_DATA_FEATURE_NAME &&
|
|
8
|
+
!!request.headers.get(PUSH_DATA_HMAC_HEADER_NAME);
|
|
9
|
+
})(RemoteConfigUtils || (RemoteConfigUtils = {}));
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import { RemoteConfigUtils } from '../config';
|
|
1
2
|
import { BlockAction } from '../blocker';
|
|
2
3
|
import { PXHDSource } from '../pxhd';
|
|
3
4
|
import { TokenOrigin, TokenParseResult } from '../risk_token';
|
|
4
5
|
import { RiskApiCallResult } from '../risk_api';
|
|
5
6
|
import { COOKIE_HEADER_NAME, toReadonlyHeaders, USER_AGENT_HEADER_NAME, } from '../http';
|
|
6
7
|
import { StringSplitCookieParser, PXHD_COOKIE_NAME, PXVID_COOKIE_NAME, X_PX_AUTHORIZATION_HEADER_NAME, } from '../utils';
|
|
7
|
-
import { X_PX_ENFORCER_LOG_HEADER } from '../logger';
|
|
8
|
+
import { DefaultLogger, X_PX_ENFORCER_LOG_HEADER } from '../logger';
|
|
8
9
|
export class DefaultContext {
|
|
9
10
|
requestId;
|
|
10
11
|
tokenOrigin;
|
|
12
|
+
isRemoteConfigUpdateRequest;
|
|
13
|
+
shouldSendLogs;
|
|
11
14
|
requestData;
|
|
12
15
|
tokenData;
|
|
13
16
|
riskApiData;
|
|
@@ -21,7 +24,6 @@ export class DefaultContext {
|
|
|
21
24
|
reasons;
|
|
22
25
|
score;
|
|
23
26
|
blockAction;
|
|
24
|
-
shouldSendLogs;
|
|
25
27
|
pxhd;
|
|
26
28
|
pxde;
|
|
27
29
|
pxdeVerified;
|
|
@@ -30,6 +32,7 @@ export class DefaultContext {
|
|
|
30
32
|
enforcerStartTime;
|
|
31
33
|
config;
|
|
32
34
|
urlUtils;
|
|
35
|
+
contextLogger;
|
|
33
36
|
constructor(config, request, options) {
|
|
34
37
|
this.enforcerStartTime = Date.now();
|
|
35
38
|
this.config = config;
|
|
@@ -46,25 +49,32 @@ export class DefaultContext {
|
|
|
46
49
|
this.requestId = options.requestIdGenerator.generateRequestId();
|
|
47
50
|
this.blockAction = BlockAction.CAPTCHA;
|
|
48
51
|
this.pxdeVerified = false;
|
|
52
|
+
this.isRemoteConfigUpdateRequest = RemoteConfigUtils.isRemoteConfigUpdateRequest(request);
|
|
53
|
+
this.shouldSendLogs = this.isRemoteConfigUpdateRequest || this.isHeaderBasedLoggerRequest(config, request);
|
|
54
|
+
this.contextLogger = this.createContextLogger(config, this.shouldSendLogs);
|
|
49
55
|
this.requestData = this.createRequestData(config, request, options.cookieParser);
|
|
50
56
|
this.tokenOrigin = this.getTokenOrigin(request);
|
|
51
57
|
if (!this.isMobile) {
|
|
52
58
|
this.setCookiesOnContext();
|
|
53
59
|
}
|
|
54
|
-
|
|
60
|
+
}
|
|
61
|
+
get logger() {
|
|
62
|
+
return this.contextLogger;
|
|
55
63
|
}
|
|
56
64
|
get isMobile() {
|
|
57
65
|
return this.tokenOrigin === TokenOrigin.HEADER;
|
|
58
66
|
}
|
|
67
|
+
createContextLogger(config, shouldSaveLogs) {
|
|
68
|
+
return new DefaultLogger(config.loggerSeverity, shouldSaveLogs);
|
|
69
|
+
}
|
|
59
70
|
createRequestData(config, request, cookieParser = new StringSplitCookieParser()) {
|
|
60
71
|
const rawUrl = request.url;
|
|
61
72
|
const url = this.normalizeUrl(rawUrl);
|
|
62
73
|
const isUrlDifferentFromRawUrl = rawUrl !== url.href;
|
|
63
74
|
const method = request.method;
|
|
64
|
-
const headers = request.headers;
|
|
65
75
|
const cookies = DefaultContext.getCookies(cookieParser, request.headers.get(COOKIE_HEADER_NAME), request.headers.get(config.customCookieHeader));
|
|
66
76
|
const requestCookieNames = Object.keys(cookies);
|
|
67
|
-
const readOnlyHeaders = toReadonlyHeaders(headers);
|
|
77
|
+
const readOnlyHeaders = toReadonlyHeaders(request.headers);
|
|
68
78
|
const userAgent = this.extractUserAgentFromHeader(config, readOnlyHeaders);
|
|
69
79
|
const ip = this.extractIpFromHeader(config, readOnlyHeaders) || request.clientIP;
|
|
70
80
|
const httpVersion = request.httpVersion;
|
|
@@ -96,7 +106,7 @@ export class DefaultContext {
|
|
|
96
106
|
url = this.urlUtils.createUrl(`${url.origin}${this.urlUtils.decodeUriComponent(url.pathname)}${url.search}`);
|
|
97
107
|
}
|
|
98
108
|
catch (e) {
|
|
99
|
-
this.
|
|
109
|
+
this.logger.debug(`unable to URL decode reserved characters: ${e}`);
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
112
|
url.pathname = url.pathname.replace(/\/+$/, '').replace(/\/+/g, '/');
|
|
@@ -133,7 +143,7 @@ export class DefaultContext {
|
|
|
133
143
|
};
|
|
134
144
|
}
|
|
135
145
|
}
|
|
136
|
-
|
|
137
|
-
return
|
|
146
|
+
isHeaderBasedLoggerRequest(config, request) {
|
|
147
|
+
return config.loggerAuthToken && config.loggerAuthToken === request.headers.get(X_PX_ENFORCER_LOG_HEADER);
|
|
138
148
|
}
|
|
139
149
|
}
|
|
@@ -8,7 +8,7 @@ export var CustomParametersUtils;
|
|
|
8
8
|
return CustomParametersUtils.normalizeCustomParams(parameters);
|
|
9
9
|
}
|
|
10
10
|
catch (e) {
|
|
11
|
-
|
|
11
|
+
context.logger.error(`unable to enrich custom params: ${e}`);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
return null;
|
|
@@ -31,6 +31,9 @@ export class EnforcerBase {
|
|
|
31
31
|
this.enforceFlow = this.createEnforceFlow(config, initializationBlock);
|
|
32
32
|
this.postEnforceFlow = this.createPostEnforceFlow(config, initializationBlock);
|
|
33
33
|
this.endEnforcerFlow = this.createEndEnforcerFlow(config, initializationBlock);
|
|
34
|
+
if (!this.config.moduleEnabled) {
|
|
35
|
+
this.config.logger.debug('HUMAN enforcer is disabled, will not enforce');
|
|
36
|
+
}
|
|
34
37
|
}
|
|
35
38
|
createFilterFlow(config, initializationBlock) {
|
|
36
39
|
return new FilterFlow(config, initializationBlock);
|
|
@@ -50,26 +53,27 @@ export class EnforcerBase {
|
|
|
50
53
|
* @returns Promise<Res|null> - A Promise resolving to a Res or null depending on the action that should be taken.
|
|
51
54
|
*/
|
|
52
55
|
async enforce(...args) {
|
|
56
|
+
let context;
|
|
53
57
|
try {
|
|
54
58
|
if (!this.config.moduleEnabled) {
|
|
55
|
-
this.config.logger.debug('module is disabled, will not enforce');
|
|
56
59
|
return null;
|
|
57
60
|
}
|
|
58
|
-
|
|
61
|
+
context = await this.constructContext(...args);
|
|
62
|
+
return await this.doEnforce(context, ...args);
|
|
59
63
|
}
|
|
60
64
|
catch (e) {
|
|
61
|
-
this.config.logger.error(`caught error in enforce - ${e}`);
|
|
65
|
+
(context || this.config).logger.error(`caught error in enforce - ${e}`);
|
|
62
66
|
return null;
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
/**
|
|
66
70
|
* Performs all enforcer functionality on the incoming request context.
|
|
71
|
+
* @param context
|
|
67
72
|
* @param args - The EnforceArgs required to enforce the incoming request.
|
|
68
73
|
* @returns Promise<Res|null> - A Promise resolving to a Res or null depending on the action that should be taken.
|
|
69
74
|
* @protected
|
|
70
75
|
*/
|
|
71
|
-
async doEnforce(...args) {
|
|
72
|
-
const context = await this.constructContext(...args);
|
|
76
|
+
async doEnforce(context, ...args) {
|
|
73
77
|
let result = await this.filterFlow.execute(context);
|
|
74
78
|
if (result.done) {
|
|
75
79
|
await this.endEnforcerFlow.execute(context);
|
|
@@ -90,8 +94,12 @@ export class EnforcerBase {
|
|
|
90
94
|
* @protected
|
|
91
95
|
*/
|
|
92
96
|
async postEnforce(...args) {
|
|
97
|
+
let context;
|
|
93
98
|
try {
|
|
94
|
-
|
|
99
|
+
if (!this.config.moduleEnabled) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
context = this.retrieveContext(...args);
|
|
95
103
|
if (context) {
|
|
96
104
|
context.response = await this.convertToOutgoingResponse(...args);
|
|
97
105
|
await this.postEnforceFlow.execute(context);
|
|
@@ -99,10 +107,13 @@ export class EnforcerBase {
|
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
catch (e) {
|
|
102
|
-
this.config.logger.error(`caught error in post enforce - ${e}`);
|
|
110
|
+
(context || this.config).logger.error(`caught error in post enforce - ${e}`);
|
|
103
111
|
}
|
|
104
112
|
}
|
|
105
113
|
async sendAdditionalS2SActivity(...args) {
|
|
114
|
+
if (!this.config.moduleEnabled) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
106
117
|
const context = this.retrieveContext(...args);
|
|
107
118
|
if (context) {
|
|
108
119
|
await this.activityClient.sendActivities(context);
|
|
@@ -2,12 +2,10 @@ import { isRouteInPatterns } from '../utils';
|
|
|
2
2
|
import { HttpMethod } from '../http';
|
|
3
3
|
import { GraphQLOperationType } from './model';
|
|
4
4
|
export class DefaultGraphQLParser {
|
|
5
|
-
config;
|
|
6
5
|
graphqlRoutes;
|
|
7
6
|
sensitiveOperationTypes;
|
|
8
7
|
sensitiveOperationNames;
|
|
9
8
|
constructor(config) {
|
|
10
|
-
this.config = config;
|
|
11
9
|
this.graphqlRoutes = config.graphqlRoutes;
|
|
12
10
|
this.sensitiveOperationNames = config.sensitiveGraphqlOperationNames;
|
|
13
11
|
this.sensitiveOperationTypes = config.sensitiveGraphqlOperationTypes;
|
|
@@ -15,27 +13,28 @@ export class DefaultGraphQLParser {
|
|
|
15
13
|
isGraphQLRequest({ requestData }) {
|
|
16
14
|
return (requestData.method === HttpMethod.POST && isRouteInPatterns(requestData.url.pathname, this.graphqlRoutes));
|
|
17
15
|
}
|
|
18
|
-
async parseGraphQLRequest(
|
|
16
|
+
async parseGraphQLRequest(context) {
|
|
19
17
|
try {
|
|
20
|
-
const
|
|
18
|
+
const { requestData } = context;
|
|
19
|
+
const graphQLOperations = await this.getGraphQLOperationsFromBody(requestData.request, context);
|
|
21
20
|
if (!graphQLOperations) {
|
|
22
|
-
|
|
21
|
+
context.logger.debug('unable to get graphql operations from request body');
|
|
23
22
|
return null;
|
|
24
23
|
}
|
|
25
24
|
const data = this.parseGraphQLOperations(graphQLOperations);
|
|
26
25
|
if (!data || data.length === 0) {
|
|
27
|
-
|
|
26
|
+
context.logger.debug('unable to parse graphql operations');
|
|
28
27
|
return null;
|
|
29
28
|
}
|
|
30
|
-
|
|
29
|
+
context.logger.debug(`${data.length} graphql operation${data.length === 1 ? '' : 's'} parsed successfully`);
|
|
31
30
|
return data;
|
|
32
31
|
}
|
|
33
32
|
catch (e) {
|
|
34
|
-
|
|
33
|
+
context.logger.debug(`unable to parse graphql request: ${e}`);
|
|
35
34
|
return null;
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
|
-
async getGraphQLOperationsFromBody(
|
|
37
|
+
async getGraphQLOperationsFromBody(request, context) {
|
|
39
38
|
try {
|
|
40
39
|
let body = await request.json();
|
|
41
40
|
if (!body) {
|
|
@@ -44,7 +43,7 @@ export class DefaultGraphQLParser {
|
|
|
44
43
|
return Array.isArray(body) ? body : [body];
|
|
45
44
|
}
|
|
46
45
|
catch (e) {
|
|
47
|
-
|
|
46
|
+
context.logger.debug(`unable to parse body to json: ${e}`);
|
|
48
47
|
return null;
|
|
49
48
|
}
|
|
50
49
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { UrlImpl } from './UrlImpl';
|
|
1
2
|
export class DefaultUrlUtils {
|
|
2
3
|
createUrl(rawUrl) {
|
|
3
|
-
|
|
4
|
+
try {
|
|
5
|
+
return new URL(rawUrl);
|
|
6
|
+
}
|
|
7
|
+
catch (e) {
|
|
8
|
+
return new UrlImpl(rawUrl);
|
|
9
|
+
}
|
|
4
10
|
}
|
|
5
11
|
createUrlSearchParams(params) {
|
|
6
12
|
return new URLSearchParams(params);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LoggerBase } from './LoggerBase';
|
|
2
2
|
export class DefaultLogger extends LoggerBase {
|
|
3
|
-
constructor(loggerSeverity) {
|
|
4
|
-
super(loggerSeverity);
|
|
3
|
+
constructor(loggerSeverity, shouldSaveLogs) {
|
|
4
|
+
super(loggerSeverity, shouldSaveLogs);
|
|
5
5
|
}
|
|
6
6
|
log(message) {
|
|
7
7
|
/* eslint-disable no-console */
|
|
@@ -2,54 +2,52 @@ import { EXTERNAL_LOGGER_SERVICE_PATH } from './constants';
|
|
|
2
2
|
import { AUTHORIZATION_HEADER_NAME, CONTENT_TYPE_HEADER_NAME, ContentType, HttpMethod, OutgoingRequestImpl, } from '../http';
|
|
3
3
|
import { getAuthorizationHeader } from '../utils';
|
|
4
4
|
export class HttpLogServiceClient {
|
|
5
|
-
|
|
6
|
-
backendUrl;
|
|
7
|
-
loggerAuthToken;
|
|
8
|
-
remoteConfigId;
|
|
9
|
-
remoteConfigVersion;
|
|
10
|
-
logger;
|
|
5
|
+
config;
|
|
11
6
|
httpClient;
|
|
12
7
|
constructor(config, httpClient) {
|
|
13
|
-
this.
|
|
14
|
-
this.backendUrl = config.backendScoreApiUrl;
|
|
15
|
-
this.loggerAuthToken = config.loggerAuthToken;
|
|
16
|
-
this.remoteConfigId = config.remoteConfigId;
|
|
17
|
-
this.remoteConfigVersion = config.remoteConfigVersion;
|
|
18
|
-
this.logger = config.logger;
|
|
8
|
+
this.config = config;
|
|
19
9
|
this.httpClient = httpClient;
|
|
20
10
|
}
|
|
21
|
-
async sendLogs(context
|
|
11
|
+
async sendLogs(context) {
|
|
12
|
+
const logs = context.logger.getLogs();
|
|
22
13
|
try {
|
|
23
|
-
const
|
|
14
|
+
const logMetadata = this.getLogMetadata(context);
|
|
15
|
+
const enrichedLogs = logs.map((log) => this.enrichLogRecord(log, logMetadata));
|
|
24
16
|
await this.postLogs(enrichedLogs);
|
|
25
17
|
}
|
|
26
18
|
catch (e) {
|
|
27
|
-
this.logger.error(`
|
|
19
|
+
this.config.logger.error(`logs for ${context.requestId}: ${JSON.stringify(logs)}`);
|
|
20
|
+
this.config.logger.error(`unable to send logs: ${e}`);
|
|
28
21
|
}
|
|
29
22
|
}
|
|
30
|
-
|
|
31
|
-
const { method, url, rawUrl } = context
|
|
32
|
-
const
|
|
23
|
+
getLogMetadata(context) {
|
|
24
|
+
const { requestId, requestData: { method, url, rawUrl }, } = context;
|
|
25
|
+
const { appId, moduleVersion, remoteConfigId, remoteConfigVersion } = this.config;
|
|
26
|
+
const logMetadata = {
|
|
33
27
|
container: 'enforcer',
|
|
34
|
-
appID:
|
|
35
|
-
method
|
|
28
|
+
appID: appId,
|
|
29
|
+
method,
|
|
36
30
|
host: url.host,
|
|
37
31
|
url: rawUrl,
|
|
38
32
|
path: url.pathname + url.search,
|
|
39
|
-
requestId
|
|
33
|
+
requestId,
|
|
34
|
+
moduleVersion,
|
|
40
35
|
};
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
if (remoteConfigId && remoteConfigVersion) {
|
|
37
|
+
logMetadata.remoteConfigID = remoteConfigId;
|
|
38
|
+
logMetadata.remoteConfigVersion = remoteConfigVersion;
|
|
44
39
|
}
|
|
45
|
-
return
|
|
40
|
+
return logMetadata;
|
|
41
|
+
}
|
|
42
|
+
enrichLogRecord(log, logMetadata) {
|
|
43
|
+
return { ...logMetadata, ...log };
|
|
46
44
|
}
|
|
47
45
|
async postLogs(logRecords) {
|
|
48
|
-
const url = `${this.
|
|
46
|
+
const url = `${this.config.backendScoreApiUrl}${EXTERNAL_LOGGER_SERVICE_PATH}`;
|
|
49
47
|
const method = HttpMethod.POST;
|
|
50
48
|
const headers = {
|
|
51
49
|
[CONTENT_TYPE_HEADER_NAME]: [ContentType.APPLICATION_JSON],
|
|
52
|
-
[AUTHORIZATION_HEADER_NAME]: [getAuthorizationHeader(this.loggerAuthToken)],
|
|
50
|
+
[AUTHORIZATION_HEADER_NAME]: [getAuthorizationHeader(this.config.loggerAuthToken)],
|
|
53
51
|
};
|
|
54
52
|
const body = JSON.stringify(logRecords);
|
|
55
53
|
const req = new OutgoingRequestImpl({ url, method, headers, body });
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { LoggerSeverity } from './LoggerSeverity';
|
|
2
2
|
export class LoggerBase {
|
|
3
3
|
loggerSeverity;
|
|
4
|
+
shouldSaveLogs;
|
|
4
5
|
logs;
|
|
5
|
-
constructor(loggerSeverity = LoggerSeverity.ERROR, logs = []) {
|
|
6
|
+
constructor(loggerSeverity = LoggerSeverity.ERROR, shouldSaveLogs, logs = []) {
|
|
6
7
|
this.loggerSeverity = loggerSeverity;
|
|
8
|
+
this.shouldSaveLogs = shouldSaveLogs;
|
|
7
9
|
this.logs = logs;
|
|
8
10
|
}
|
|
9
11
|
debug(message, metadata) {
|
|
@@ -37,6 +39,9 @@ export class LoggerBase {
|
|
|
37
39
|
return this.logs;
|
|
38
40
|
}
|
|
39
41
|
recordLog(message, loggerSeverity, metadata) {
|
|
42
|
+
if (!this.shouldSaveLogs) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
40
45
|
const logRecord = {
|
|
41
46
|
...metadata,
|
|
42
47
|
message: message,
|
|
@@ -3,9 +3,9 @@ export var MonitoredRequestUtils;
|
|
|
3
3
|
(function (MonitoredRequestUtils) {
|
|
4
4
|
MonitoredRequestUtils.isMonitoredRequest = async (config, context) => {
|
|
5
5
|
const { url, request } = context.requestData;
|
|
6
|
-
const isEnforcedRequest = MonitoredRequestUtils.isEnforcedRoute(url, config.enforcedRoutes) || (await MonitoredRequestUtils.invokeCustomIsEnforcedRequest(config,
|
|
6
|
+
const isEnforcedRequest = MonitoredRequestUtils.isEnforcedRoute(url, config.enforcedRoutes) || (await MonitoredRequestUtils.invokeCustomIsEnforcedRequest(config, context));
|
|
7
7
|
const monitoredRoute = !isEnforcedRequest &&
|
|
8
|
-
(MonitoredRequestUtils.isMonitoredRoute(url, config.monitoredRoutes) || (await MonitoredRequestUtils.invokeCustomIsMonitoredRequest(config,
|
|
8
|
+
(MonitoredRequestUtils.isMonitoredRoute(url, config.monitoredRoutes) || (await MonitoredRequestUtils.invokeCustomIsMonitoredRequest(config, context)));
|
|
9
9
|
const monitorMode = !isEnforcedRequest && config.moduleMode === ModuleMode.MONITOR;
|
|
10
10
|
return (monitorMode || monitoredRoute) && !MonitoredRequestUtils.isAllowedToBypassMonitor(config.bypassMonitorHeader, request);
|
|
11
11
|
};
|
|
@@ -15,24 +15,24 @@ export var MonitoredRequestUtils;
|
|
|
15
15
|
MonitoredRequestUtils.isEnforcedRoute = (url, enforcedRoutes) => {
|
|
16
16
|
return isRouteInPatterns(url.pathname, enforcedRoutes);
|
|
17
17
|
};
|
|
18
|
-
MonitoredRequestUtils.invokeCustomIsEnforcedRequest = async (config,
|
|
18
|
+
MonitoredRequestUtils.invokeCustomIsEnforcedRequest = async (config, context) => {
|
|
19
19
|
if (config.customIsEnforcedRequest && typeof config.customIsEnforcedRequest === 'function') {
|
|
20
20
|
try {
|
|
21
|
-
return await config.customIsEnforcedRequest(request.getUnderlyingRequest());
|
|
21
|
+
return await config.customIsEnforcedRequest(context.requestData.request.getUnderlyingRequest());
|
|
22
22
|
}
|
|
23
23
|
catch (err) {
|
|
24
|
-
|
|
24
|
+
context.logger.debug(`caught custom enforced request error - ${err}`);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
return false;
|
|
28
28
|
};
|
|
29
|
-
MonitoredRequestUtils.invokeCustomIsMonitoredRequest = async (config,
|
|
29
|
+
MonitoredRequestUtils.invokeCustomIsMonitoredRequest = async (config, context) => {
|
|
30
30
|
if (config.customIsMonitoredRequest && typeof config.customIsMonitoredRequest === 'function') {
|
|
31
31
|
try {
|
|
32
|
-
return await config.customIsMonitoredRequest(request.getUnderlyingRequest());
|
|
32
|
+
return await config.customIsMonitoredRequest(context.requestData.request.getUnderlyingRequest());
|
|
33
33
|
}
|
|
34
34
|
catch (err) {
|
|
35
|
-
|
|
35
|
+
context.logger.debug(`caught custom monitored request error - ${err}`);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
return false;
|
|
@@ -15,7 +15,7 @@ export class CreateBlockResponsePhase {
|
|
|
15
15
|
if (context.action !== Action.BLOCK) {
|
|
16
16
|
return { done: false };
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
context.logger.debug('create block response phase');
|
|
19
19
|
let response = this.createBlockResponse(context);
|
|
20
20
|
if (!response) {
|
|
21
21
|
return { done: false };
|
|
@@ -29,7 +29,7 @@ export class CreateBlockResponsePhase {
|
|
|
29
29
|
if (context.reasons[name]) {
|
|
30
30
|
const blocker = this.blockers[name];
|
|
31
31
|
if (blocker?.shouldBlock(context)) {
|
|
32
|
-
|
|
32
|
+
context.logger.debug(`returning ${name} block response`);
|
|
33
33
|
response = blocker.createBlockResponse(context);
|
|
34
34
|
break;
|
|
35
35
|
}
|
|
@@ -2,17 +2,13 @@ import { LoggerSeverity } from '../../logger';
|
|
|
2
2
|
import { Action, getDecisionFromContext } from '../../action';
|
|
3
3
|
import { BlockAction } from '../../blocker';
|
|
4
4
|
export class DecideActionPhase {
|
|
5
|
-
config;
|
|
6
|
-
constructor(config) {
|
|
7
|
-
this.config = config;
|
|
8
|
-
}
|
|
9
5
|
async updateContextDecision(context) {
|
|
10
6
|
const { action, reasons } = getDecisionFromContext(context);
|
|
11
|
-
if (
|
|
7
|
+
if (context.logger.getLoggerSeverity() === LoggerSeverity.DEBUG) {
|
|
12
8
|
const productReasons = Object.entries(reasons)
|
|
13
9
|
.map(([prod, reason]) => `${prod} -> ${reason}`)
|
|
14
10
|
.join(', ');
|
|
15
|
-
|
|
11
|
+
context.logger.debug(`decision: ${action}, reasons: ${productReasons}`);
|
|
16
12
|
}
|
|
17
13
|
context.action = action;
|
|
18
14
|
context.reasons = reasons;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { CustomParametersUtils } from '../../custom_parameters';
|
|
2
2
|
import { DecideActionPhase } from './DecideActionPhase';
|
|
3
3
|
export class EnrichContextFromRequestPhase extends DecideActionPhase {
|
|
4
|
+
config;
|
|
4
5
|
products;
|
|
5
6
|
dataEnrichment;
|
|
6
7
|
graphQLParser;
|
|
7
8
|
constructor(config, products, dataEnrichment, graphQLParser) {
|
|
8
|
-
super(
|
|
9
|
+
super();
|
|
10
|
+
this.config = config;
|
|
9
11
|
this.products = products;
|
|
10
12
|
this.dataEnrichment = dataEnrichment;
|
|
11
13
|
this.graphQLParser = graphQLParser;
|
|
12
14
|
}
|
|
13
15
|
async execute(context) {
|
|
14
|
-
|
|
16
|
+
context.logger.debug('enrich context from request phase');
|
|
15
17
|
await this.addPxdeToContext(context);
|
|
16
18
|
await this.addGraphQLDataToContext(context);
|
|
17
19
|
await this.addCustomParametersToContext(context);
|
|
@@ -6,7 +6,7 @@ export class EnrichContextFromResponsePhase {
|
|
|
6
6
|
this.products = products;
|
|
7
7
|
}
|
|
8
8
|
async execute(context) {
|
|
9
|
-
|
|
9
|
+
context.logger.debug('enrich context from response phase');
|
|
10
10
|
await this.addProductDataToContext(context);
|
|
11
11
|
return { done: false };
|
|
12
12
|
}
|
|
@@ -12,15 +12,15 @@ export class FirstPartyPhase {
|
|
|
12
12
|
for (const firstParty of this.firstParties) {
|
|
13
13
|
const firstPartyData = await firstParty.handleFirstPartyRequest(context);
|
|
14
14
|
if (firstPartyData) {
|
|
15
|
-
const response = await this.sendFirstPartyRequest(firstPartyData);
|
|
15
|
+
const response = await this.sendFirstPartyRequest(firstPartyData, context);
|
|
16
16
|
return { done: true, response };
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
return { done: false };
|
|
20
20
|
}
|
|
21
|
-
async sendFirstPartyRequest({ request, defaultResponse }) {
|
|
21
|
+
async sendFirstPartyRequest({ request, defaultResponse }, context) {
|
|
22
22
|
if (!request) {
|
|
23
|
-
|
|
23
|
+
context.logger.debug('no first party request provided, returning default response');
|
|
24
24
|
return defaultResponse;
|
|
25
25
|
}
|
|
26
26
|
try {
|
|
@@ -28,7 +28,7 @@ export class FirstPartyPhase {
|
|
|
28
28
|
return MinimalResponseUtils.from(response);
|
|
29
29
|
}
|
|
30
30
|
catch (e) {
|
|
31
|
-
|
|
31
|
+
context.logger.debug(`caught error, returning default first party response: ${e}`);
|
|
32
32
|
return defaultResponse;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -7,9 +7,9 @@ export class SendLogsPhase {
|
|
|
7
7
|
}
|
|
8
8
|
async execute(context) {
|
|
9
9
|
if (context.shouldSendLogs) {
|
|
10
|
-
await this.logServiceClient?.sendLogs(context
|
|
10
|
+
await this.logServiceClient?.sendLogs(context);
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
context.logger.clearLogs();
|
|
13
13
|
return { done: false };
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -7,7 +7,6 @@ export class UpdateRemoteConfigPhase {
|
|
|
7
7
|
}
|
|
8
8
|
async execute(context) {
|
|
9
9
|
if (this.remoteConfigUpdater.isUpdateRemoteConfigRequest(context)) {
|
|
10
|
-
context.shouldSendLogs = true;
|
|
11
10
|
const isSuccessful = await this.remoteConfigUpdater.updateRemoteConfig(context);
|
|
12
11
|
return { done: true, response: this.getUpdateConfigResponse(isSuccessful) };
|
|
13
12
|
}
|