@adtrackify/at-service-common 3.17.2 → 3.17.4
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/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -0
- package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.js +75 -0
- package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.js.map +1 -0
- package/dist/cjs/helpers/api-key-auth-helper.d.ts +9 -0
- package/dist/cjs/helpers/api-key-auth-helper.js +41 -0
- package/dist/cjs/helpers/api-key-auth-helper.js.map +1 -0
- package/dist/cjs/helpers/api-key-authorizer-helper.d.ts +36 -0
- package/dist/cjs/helpers/api-key-authorizer-helper.js +78 -0
- package/dist/cjs/helpers/api-key-authorizer-helper.js.map +1 -0
- package/dist/cjs/helpers/index.d.ts +2 -0
- package/dist/cjs/helpers/index.js +2 -0
- package/dist/cjs/helpers/index.js.map +1 -1
- package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -0
- package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.js +73 -0
- package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.js.map +1 -0
- package/dist/esm/helpers/api-key-auth-helper.d.ts +9 -0
- package/dist/esm/helpers/api-key-auth-helper.js +36 -0
- package/dist/esm/helpers/api-key-auth-helper.js.map +1 -0
- package/dist/esm/helpers/api-key-authorizer-helper.d.ts +36 -0
- package/dist/esm/helpers/api-key-authorizer-helper.js +74 -0
- package/dist/esm/helpers/api-key-authorizer-helper.js.map +1 -0
- package/dist/esm/helpers/index.d.ts +2 -0
- package/dist/esm/helpers/index.js +2 -0
- package/dist/esm/helpers/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
jest.mock('../../helpers/logging-helper', () => ({
|
|
4
|
+
Logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() },
|
|
5
|
+
}));
|
|
6
|
+
jest.mock('../../libs/http-error', () => ({
|
|
7
|
+
HttpError: {
|
|
8
|
+
unauthorized: () => Object.assign(new Error('Unauthorized'), { statusCode: 401 }),
|
|
9
|
+
forbidden: (msg) => Object.assign(new Error(msg), { statusCode: 403 }),
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
const at_tracking_event_types_1 = require("@adtrackify/at-tracking-event-types");
|
|
13
|
+
const api_key_auth_helper_1 = require("../../helpers/api-key-auth-helper");
|
|
14
|
+
const makeEvent = (authorizer) => ({ requestContext: { authorizer } });
|
|
15
|
+
describe('extractApiKeyAuth', () => {
|
|
16
|
+
it('returns pixelId, accountId, scopes, and apiKeyPk from authorizer context', () => {
|
|
17
|
+
const event = makeEvent({
|
|
18
|
+
pixelId: 'px_1',
|
|
19
|
+
accountId: 'acc_1',
|
|
20
|
+
apiKeyPk: 'px_1_abc',
|
|
21
|
+
scopes: 'analytics:read,dashboards:write',
|
|
22
|
+
});
|
|
23
|
+
expect((0, api_key_auth_helper_1.extractApiKeyAuth)(event)).toEqual({
|
|
24
|
+
pixelId: 'px_1',
|
|
25
|
+
accountId: 'acc_1',
|
|
26
|
+
apiKeyPk: 'px_1_abc',
|
|
27
|
+
scopes: ['analytics:read', 'dashboards:write'],
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
it('expands legacy db_query scope to analytics:read', () => {
|
|
31
|
+
const event = makeEvent({
|
|
32
|
+
pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc', scopes: 'db_query',
|
|
33
|
+
});
|
|
34
|
+
expect((0, api_key_auth_helper_1.extractApiKeyAuth)(event).scopes).toEqual([at_tracking_event_types_1.API_KEY_SCOPE.ANALYTICS_READ]);
|
|
35
|
+
});
|
|
36
|
+
it('expands legacy attribution_query scope to analytics:read', () => {
|
|
37
|
+
const event = makeEvent({
|
|
38
|
+
pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc', scopes: 'attribution_query',
|
|
39
|
+
});
|
|
40
|
+
expect((0, api_key_auth_helper_1.extractApiKeyAuth)(event).scopes).toEqual([at_tracking_event_types_1.API_KEY_SCOPE.ANALYTICS_READ]);
|
|
41
|
+
});
|
|
42
|
+
it('deduplicates when both legacy values are present', () => {
|
|
43
|
+
const event = makeEvent({
|
|
44
|
+
pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc',
|
|
45
|
+
scopes: 'db_query,attribution_query',
|
|
46
|
+
});
|
|
47
|
+
expect((0, api_key_auth_helper_1.extractApiKeyAuth)(event).scopes).toEqual([at_tracking_event_types_1.API_KEY_SCOPE.ANALYTICS_READ]);
|
|
48
|
+
});
|
|
49
|
+
it('passes modern scopes through unchanged', () => {
|
|
50
|
+
const event = makeEvent({
|
|
51
|
+
pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc',
|
|
52
|
+
scopes: 'analytics:read,dashboards:read,dashboards:write',
|
|
53
|
+
});
|
|
54
|
+
expect((0, api_key_auth_helper_1.extractApiKeyAuth)(event).scopes).toEqual([
|
|
55
|
+
'analytics:read', 'dashboards:read', 'dashboards:write',
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
it('returns empty scopes array when scopes header is missing', () => {
|
|
59
|
+
const event = makeEvent({ pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc' });
|
|
60
|
+
expect((0, api_key_auth_helper_1.extractApiKeyAuth)(event).scopes).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
it('throws Unauthorized when pixelId is missing from authorizer context', () => {
|
|
63
|
+
const event = makeEvent({ accountId: 'acc_1' });
|
|
64
|
+
expect(() => (0, api_key_auth_helper_1.extractApiKeyAuth)(event)).toThrow('Unauthorized');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('requireScope', () => {
|
|
68
|
+
it('allows when the required scope is present', () => {
|
|
69
|
+
expect(() => (0, api_key_auth_helper_1.requireScope)(['analytics:read', 'dashboards:read'], at_tracking_event_types_1.API_KEY_SCOPE.ANALYTICS_READ)).not.toThrow();
|
|
70
|
+
});
|
|
71
|
+
it('throws Forbidden when the required scope is missing', () => {
|
|
72
|
+
expect(() => (0, api_key_auth_helper_1.requireScope)(['analytics:read'], at_tracking_event_types_1.API_KEY_SCOPE.DASHBOARDS_WRITE)).toThrow('API key does not have the dashboards:write scope');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=api-key-auth-helper.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-auth-helper.spec.js","sourceRoot":"","sources":["../../../../src/__tests__/helpers/api-key-auth-helper.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { API_KEY_SCOPE } from '@adtrackify/at-tracking-event-types';
|
|
2
|
+
export interface ApiKeyAuthContext {
|
|
3
|
+
pixelId: string;
|
|
4
|
+
accountId: string;
|
|
5
|
+
scopes: string[];
|
|
6
|
+
apiKeyPk: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const extractApiKeyAuth: (event: import("aws-lambda").APIGatewayProxyEvent) => ApiKeyAuthContext;
|
|
9
|
+
export declare const requireScope: (scopes: string[], required: API_KEY_SCOPE) => void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireScope = exports.extractApiKeyAuth = void 0;
|
|
4
|
+
const at_tracking_event_types_1 = require("@adtrackify/at-tracking-event-types");
|
|
5
|
+
const logging_helper_js_1 = require("./logging-helper.js");
|
|
6
|
+
const http_error_js_1 = require("../libs/http-error.js");
|
|
7
|
+
const expandLegacyScopes = (rawScopes) => {
|
|
8
|
+
const expanded = new Set();
|
|
9
|
+
for (const scope of rawScopes) {
|
|
10
|
+
const replacement = at_tracking_event_types_1.LEGACY_API_KEY_SCOPE_EXPANSION[scope];
|
|
11
|
+
if (replacement) {
|
|
12
|
+
for (const modern of replacement)
|
|
13
|
+
expanded.add(modern);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
expanded.add(scope);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return [...expanded];
|
|
20
|
+
};
|
|
21
|
+
const extractApiKeyAuth = (event) => {
|
|
22
|
+
const authorizer = event.requestContext.authorizer;
|
|
23
|
+
const pixelId = authorizer?.pixelId;
|
|
24
|
+
const accountId = authorizer?.accountId;
|
|
25
|
+
const apiKeyPk = authorizer?.apiKeyPk;
|
|
26
|
+
const rawScopes = (authorizer?.scopes || '').split(',').filter(Boolean);
|
|
27
|
+
const scopes = expandLegacyScopes(rawScopes);
|
|
28
|
+
if (!pixelId) {
|
|
29
|
+
throw http_error_js_1.HttpError.unauthorized();
|
|
30
|
+
}
|
|
31
|
+
return { pixelId, accountId, scopes, apiKeyPk };
|
|
32
|
+
};
|
|
33
|
+
exports.extractApiKeyAuth = extractApiKeyAuth;
|
|
34
|
+
const requireScope = (scopes, required) => {
|
|
35
|
+
if (!scopes.includes(required)) {
|
|
36
|
+
logging_helper_js_1.Logger.info('requireScope: missing required scope', { scopes, required });
|
|
37
|
+
throw http_error_js_1.HttpError.forbidden(`API key does not have the ${required} scope`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.requireScope = requireScope;
|
|
41
|
+
//# sourceMappingURL=api-key-auth-helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-auth-helper.js","sourceRoot":"","sources":["../../../src/helpers/api-key-auth-helper.ts"],"names":[],"mappings":";;;AAUA,iFAAoG;AACpG,2DAA6C;AAC7C,yDAAkD;AAOlD,MAAM,kBAAkB,GAAG,CAAC,SAAmB,EAAY,EAAE;IAC3D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE;QAC7B,MAAM,WAAW,GAAG,wDAA8B,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,WAAW,EAAE;YACf,KAAK,MAAM,MAAM,IAAI,WAAW;gBAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;SACxD;aAAM;YACL,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;SACrB;KACF;IACD,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;AACvB,CAAC,CAAC;AAcK,MAAM,iBAAiB,GAAG,CAAC,KAAsB,EAAqB,EAAE;IAC7E,MAAM,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC;IACnD,MAAM,OAAO,GAAG,UAAU,EAAE,OAAiB,CAAC;IAC9C,MAAM,SAAS,GAAG,UAAU,EAAE,SAAmB,CAAC;IAClD,MAAM,QAAQ,GAAG,UAAU,EAAE,QAAkB,CAAC;IAChD,MAAM,SAAS,GAAG,CAAE,UAAU,EAAE,MAAiB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpF,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAE7C,IAAI,CAAC,OAAO,EAAE;QACZ,MAAM,yBAAS,CAAC,YAAY,EAAE,CAAC;KAChC;IAED,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAClD,CAAC,CAAC;AAbW,QAAA,iBAAiB,qBAa5B;AAGK,MAAM,YAAY,GAAG,CAAC,MAAgB,EAAE,QAAuB,EAAQ,EAAE;IAC9E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAC9B,0BAAM,CAAC,IAAI,CAAC,sCAAsC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC1E,MAAM,yBAAS,CAAC,SAAS,CAAC,6BAA6B,QAAQ,QAAQ,CAAC,CAAC;KAC1E;AACH,CAAC,CAAC;AALW,QAAA,YAAY,gBAKvB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Context } from 'aws-lambda';
|
|
2
|
+
import { type ApiKeyRecord, type Pixel } from '@adtrackify/at-tracking-event-types';
|
|
3
|
+
export interface ApiKeyAuthorizerDeps {
|
|
4
|
+
getApiKeyByPk: (pk: string) => Promise<ApiKeyRecord | null>;
|
|
5
|
+
getPixelById: (pixelId: string) => Promise<Pixel | null | undefined>;
|
|
6
|
+
}
|
|
7
|
+
interface AllowPolicy {
|
|
8
|
+
principalId: string;
|
|
9
|
+
policyDocument: {
|
|
10
|
+
Version: string;
|
|
11
|
+
Statement: {
|
|
12
|
+
Action: string;
|
|
13
|
+
Effect: 'Allow';
|
|
14
|
+
Resource: string;
|
|
15
|
+
}[];
|
|
16
|
+
};
|
|
17
|
+
context: {
|
|
18
|
+
pixelId: string;
|
|
19
|
+
accountId: string;
|
|
20
|
+
scopes: string;
|
|
21
|
+
apiKeyPk: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
interface DenyPolicy {
|
|
25
|
+
principalId: string;
|
|
26
|
+
policyDocument: {
|
|
27
|
+
Version: string;
|
|
28
|
+
Statement: {
|
|
29
|
+
Action: string;
|
|
30
|
+
Effect: 'Deny';
|
|
31
|
+
Resource: string;
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export declare const createApiKeyAuthorizerHandler: (deps: ApiKeyAuthorizerDeps) => (event: any, context: Context) => Promise<AllowPolicy | DenyPolicy>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createApiKeyAuthorizerHandler = void 0;
|
|
4
|
+
const at_tracking_event_types_1 = require("@adtrackify/at-tracking-event-types");
|
|
5
|
+
const logging_helper_js_1 = require("./logging-helper.js");
|
|
6
|
+
const crypto_js_1 = require("../libs/crypto.js");
|
|
7
|
+
const toWildcardArn = (methodArn) => {
|
|
8
|
+
const parts = methodArn.split('/');
|
|
9
|
+
return `${parts[0]}/${parts[1]}/*`;
|
|
10
|
+
};
|
|
11
|
+
const generateDenyPolicy = (methodArn) => ({
|
|
12
|
+
principalId: 'anonymous',
|
|
13
|
+
policyDocument: {
|
|
14
|
+
Version: '2012-10-17',
|
|
15
|
+
Statement: [{ Action: 'execute-api:Invoke', Effect: 'Deny', Resource: methodArn }],
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const createApiKeyAuthorizerHandler = (deps) => {
|
|
19
|
+
return async function handler(event, context) {
|
|
20
|
+
const methodArn = event.methodArn;
|
|
21
|
+
try {
|
|
22
|
+
(0, logging_helper_js_1.configureLogger)(event, context);
|
|
23
|
+
const apiKey = event.headers?.['x-api-key'] || event.headers?.['X-Api-Key'];
|
|
24
|
+
const pixelId = event.headers?.['x-pixel-id'] || event.headers?.['X-Pixel-Id'];
|
|
25
|
+
if (!apiKey || !pixelId) {
|
|
26
|
+
logging_helper_js_1.Logger.info('ApiKeyAuthorizer: missing x-api-key or x-pixel-id header');
|
|
27
|
+
return generateDenyPolicy(methodArn);
|
|
28
|
+
}
|
|
29
|
+
const keyHash = (0, crypto_js_1.generateSha256Hash)(apiKey);
|
|
30
|
+
const pk = `${pixelId}_${keyHash}`;
|
|
31
|
+
const apiKeyRecord = await deps.getApiKeyByPk(pk);
|
|
32
|
+
if (!apiKeyRecord) {
|
|
33
|
+
logging_helper_js_1.Logger.info('ApiKeyAuthorizer: no matching key found');
|
|
34
|
+
return generateDenyPolicy(methodArn);
|
|
35
|
+
}
|
|
36
|
+
if (apiKeyRecord.status !== at_tracking_event_types_1.API_KEY_STATUS.ACTIVE) {
|
|
37
|
+
logging_helper_js_1.Logger.info('ApiKeyAuthorizer: key is not active', { status: apiKeyRecord.status });
|
|
38
|
+
return generateDenyPolicy(methodArn);
|
|
39
|
+
}
|
|
40
|
+
if (apiKeyRecord.expiresAt && new Date(apiKeyRecord.expiresAt) < new Date()) {
|
|
41
|
+
logging_helper_js_1.Logger.info('ApiKeyAuthorizer: key has expired', { expiresAt: apiKeyRecord.expiresAt });
|
|
42
|
+
return generateDenyPolicy(methodArn);
|
|
43
|
+
}
|
|
44
|
+
const pixel = await deps.getPixelById(pixelId);
|
|
45
|
+
if (!pixel || pixel.status !== at_tracking_event_types_1.PIXEL_STATUS.ACTIVE) {
|
|
46
|
+
logging_helper_js_1.Logger.info('ApiKeyAuthorizer: pixel is not active', { pixelId, status: pixel?.status });
|
|
47
|
+
return generateDenyPolicy(methodArn);
|
|
48
|
+
}
|
|
49
|
+
logging_helper_js_1.Logger.info('ApiKeyAuthorizer: key authorized', {
|
|
50
|
+
pixelId: apiKeyRecord.pixelId,
|
|
51
|
+
apiKeyPk: apiKeyRecord.pk,
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
principalId: apiKeyRecord.pixelId,
|
|
55
|
+
policyDocument: {
|
|
56
|
+
Version: '2012-10-17',
|
|
57
|
+
Statement: [{
|
|
58
|
+
Action: 'execute-api:Invoke',
|
|
59
|
+
Effect: 'Allow',
|
|
60
|
+
Resource: toWildcardArn(methodArn),
|
|
61
|
+
}],
|
|
62
|
+
},
|
|
63
|
+
context: {
|
|
64
|
+
pixelId: apiKeyRecord.pixelId,
|
|
65
|
+
accountId: apiKeyRecord.accountId,
|
|
66
|
+
scopes: apiKeyRecord.scopes.join(','),
|
|
67
|
+
apiKeyPk: apiKeyRecord.pk,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
logging_helper_js_1.Logger.error('ApiKeyAuthorizer: error', { error });
|
|
73
|
+
return generateDenyPolicy(methodArn);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
exports.createApiKeyAuthorizerHandler = createApiKeyAuthorizerHandler;
|
|
78
|
+
//# sourceMappingURL=api-key-authorizer-helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-authorizer-helper.js","sourceRoot":"","sources":["../../../src/helpers/api-key-authorizer-helper.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,4 +21,6 @@ __exportStar(require("./shopify-helper.js"), exports);
|
|
|
21
21
|
__exportStar(require("./identity-cache-helper.js"), exports);
|
|
22
22
|
__exportStar(require("./account-users-helper.js"), exports);
|
|
23
23
|
__exportStar(require("./sqs-utils.js"), exports);
|
|
24
|
+
__exportStar(require("./api-key-auth-helper.js"), exports);
|
|
25
|
+
__exportStar(require("./api-key-authorizer-helper.js"), exports);
|
|
24
26
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/helpers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+DAA6C;AAC7C,sDAAoC;AACpC,uDAAqC;AACrC,sDAAoC;AACpC,6DAA2C;AAC3C,4DAA0C;AAC1C,iDAA+B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/helpers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+DAA6C;AAC7C,sDAAoC;AACpC,uDAAqC;AACrC,sDAAoC;AACpC,6DAA2C;AAC3C,4DAA0C;AAC1C,iDAA+B;AAC/B,2DAAyC;AACzC,iEAA+C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
jest.mock('../../helpers/logging-helper', () => ({
|
|
2
|
+
Logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() },
|
|
3
|
+
}));
|
|
4
|
+
jest.mock('../../libs/http-error', () => ({
|
|
5
|
+
HttpError: {
|
|
6
|
+
unauthorized: () => Object.assign(new Error('Unauthorized'), { statusCode: 401 }),
|
|
7
|
+
forbidden: (msg) => Object.assign(new Error(msg), { statusCode: 403 }),
|
|
8
|
+
},
|
|
9
|
+
}));
|
|
10
|
+
import { API_KEY_SCOPE } from '@adtrackify/at-tracking-event-types';
|
|
11
|
+
import { extractApiKeyAuth, requireScope } from '../../helpers/api-key-auth-helper';
|
|
12
|
+
const makeEvent = (authorizer) => ({ requestContext: { authorizer } });
|
|
13
|
+
describe('extractApiKeyAuth', () => {
|
|
14
|
+
it('returns pixelId, accountId, scopes, and apiKeyPk from authorizer context', () => {
|
|
15
|
+
const event = makeEvent({
|
|
16
|
+
pixelId: 'px_1',
|
|
17
|
+
accountId: 'acc_1',
|
|
18
|
+
apiKeyPk: 'px_1_abc',
|
|
19
|
+
scopes: 'analytics:read,dashboards:write',
|
|
20
|
+
});
|
|
21
|
+
expect(extractApiKeyAuth(event)).toEqual({
|
|
22
|
+
pixelId: 'px_1',
|
|
23
|
+
accountId: 'acc_1',
|
|
24
|
+
apiKeyPk: 'px_1_abc',
|
|
25
|
+
scopes: ['analytics:read', 'dashboards:write'],
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it('expands legacy db_query scope to analytics:read', () => {
|
|
29
|
+
const event = makeEvent({
|
|
30
|
+
pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc', scopes: 'db_query',
|
|
31
|
+
});
|
|
32
|
+
expect(extractApiKeyAuth(event).scopes).toEqual([API_KEY_SCOPE.ANALYTICS_READ]);
|
|
33
|
+
});
|
|
34
|
+
it('expands legacy attribution_query scope to analytics:read', () => {
|
|
35
|
+
const event = makeEvent({
|
|
36
|
+
pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc', scopes: 'attribution_query',
|
|
37
|
+
});
|
|
38
|
+
expect(extractApiKeyAuth(event).scopes).toEqual([API_KEY_SCOPE.ANALYTICS_READ]);
|
|
39
|
+
});
|
|
40
|
+
it('deduplicates when both legacy values are present', () => {
|
|
41
|
+
const event = makeEvent({
|
|
42
|
+
pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc',
|
|
43
|
+
scopes: 'db_query,attribution_query',
|
|
44
|
+
});
|
|
45
|
+
expect(extractApiKeyAuth(event).scopes).toEqual([API_KEY_SCOPE.ANALYTICS_READ]);
|
|
46
|
+
});
|
|
47
|
+
it('passes modern scopes through unchanged', () => {
|
|
48
|
+
const event = makeEvent({
|
|
49
|
+
pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc',
|
|
50
|
+
scopes: 'analytics:read,dashboards:read,dashboards:write',
|
|
51
|
+
});
|
|
52
|
+
expect(extractApiKeyAuth(event).scopes).toEqual([
|
|
53
|
+
'analytics:read', 'dashboards:read', 'dashboards:write',
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
it('returns empty scopes array when scopes header is missing', () => {
|
|
57
|
+
const event = makeEvent({ pixelId: 'px_1', accountId: 'acc_1', apiKeyPk: 'px_1_abc' });
|
|
58
|
+
expect(extractApiKeyAuth(event).scopes).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
it('throws Unauthorized when pixelId is missing from authorizer context', () => {
|
|
61
|
+
const event = makeEvent({ accountId: 'acc_1' });
|
|
62
|
+
expect(() => extractApiKeyAuth(event)).toThrow('Unauthorized');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('requireScope', () => {
|
|
66
|
+
it('allows when the required scope is present', () => {
|
|
67
|
+
expect(() => requireScope(['analytics:read', 'dashboards:read'], API_KEY_SCOPE.ANALYTICS_READ)).not.toThrow();
|
|
68
|
+
});
|
|
69
|
+
it('throws Forbidden when the required scope is missing', () => {
|
|
70
|
+
expect(() => requireScope(['analytics:read'], API_KEY_SCOPE.DASHBOARDS_WRITE)).toThrow('API key does not have the dashboards:write scope');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=api-key-auth-helper.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-auth-helper.spec.js","sourceRoot":"","sources":["../../../../src/__tests__/helpers/api-key-auth-helper.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { API_KEY_SCOPE } from '@adtrackify/at-tracking-event-types';
|
|
2
|
+
export interface ApiKeyAuthContext {
|
|
3
|
+
pixelId: string;
|
|
4
|
+
accountId: string;
|
|
5
|
+
scopes: string[];
|
|
6
|
+
apiKeyPk: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const extractApiKeyAuth: (event: import("aws-lambda").APIGatewayProxyEvent) => ApiKeyAuthContext;
|
|
9
|
+
export declare const requireScope: (scopes: string[], required: API_KEY_SCOPE) => void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { LEGACY_API_KEY_SCOPE_EXPANSION } from '@adtrackify/at-tracking-event-types';
|
|
2
|
+
import { Logger } from './logging-helper.js';
|
|
3
|
+
import { HttpError } from '../libs/http-error.js';
|
|
4
|
+
const expandLegacyScopes = (rawScopes) => {
|
|
5
|
+
const expanded = new Set();
|
|
6
|
+
for (const scope of rawScopes) {
|
|
7
|
+
const replacement = LEGACY_API_KEY_SCOPE_EXPANSION[scope];
|
|
8
|
+
if (replacement) {
|
|
9
|
+
for (const modern of replacement)
|
|
10
|
+
expanded.add(modern);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
expanded.add(scope);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return [...expanded];
|
|
17
|
+
};
|
|
18
|
+
export const extractApiKeyAuth = (event) => {
|
|
19
|
+
const authorizer = event.requestContext.authorizer;
|
|
20
|
+
const pixelId = authorizer?.pixelId;
|
|
21
|
+
const accountId = authorizer?.accountId;
|
|
22
|
+
const apiKeyPk = authorizer?.apiKeyPk;
|
|
23
|
+
const rawScopes = (authorizer?.scopes || '').split(',').filter(Boolean);
|
|
24
|
+
const scopes = expandLegacyScopes(rawScopes);
|
|
25
|
+
if (!pixelId) {
|
|
26
|
+
throw HttpError.unauthorized();
|
|
27
|
+
}
|
|
28
|
+
return { pixelId, accountId, scopes, apiKeyPk };
|
|
29
|
+
};
|
|
30
|
+
export const requireScope = (scopes, required) => {
|
|
31
|
+
if (!scopes.includes(required)) {
|
|
32
|
+
Logger.info('requireScope: missing required scope', { scopes, required });
|
|
33
|
+
throw HttpError.forbidden(`API key does not have the ${required} scope`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=api-key-auth-helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-auth-helper.js","sourceRoot":"","sources":["../../../src/helpers/api-key-auth-helper.ts"],"names":[],"mappings":"AAUA,OAAO,EAAiB,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AACpG,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAOlD,MAAM,kBAAkB,GAAG,CAAC,SAAmB,EAAY,EAAE;IAC3D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE;QAC7B,MAAM,WAAW,GAAG,8BAA8B,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,WAAW,EAAE;YACf,KAAK,MAAM,MAAM,IAAI,WAAW;gBAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;SACxD;aAAM;YACL,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;SACrB;KACF;IACD,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;AACvB,CAAC,CAAC;AAcF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAsB,EAAqB,EAAE;IAC7E,MAAM,UAAU,GAAG,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC;IACnD,MAAM,OAAO,GAAG,UAAU,EAAE,OAAiB,CAAC;IAC9C,MAAM,SAAS,GAAG,UAAU,EAAE,SAAmB,CAAC;IAClD,MAAM,QAAQ,GAAG,UAAU,EAAE,QAAkB,CAAC;IAChD,MAAM,SAAS,GAAG,CAAE,UAAU,EAAE,MAAiB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpF,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAE7C,IAAI,CAAC,OAAO,EAAE;QACZ,MAAM,SAAS,CAAC,YAAY,EAAE,CAAC;KAChC;IAED,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAClD,CAAC,CAAC;AAGF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAgB,EAAE,QAAuB,EAAQ,EAAE;IAC9E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAC9B,MAAM,CAAC,IAAI,CAAC,sCAAsC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC1E,MAAM,SAAS,CAAC,SAAS,CAAC,6BAA6B,QAAQ,QAAQ,CAAC,CAAC;KAC1E;AACH,CAAC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Context } from 'aws-lambda';
|
|
2
|
+
import { type ApiKeyRecord, type Pixel } from '@adtrackify/at-tracking-event-types';
|
|
3
|
+
export interface ApiKeyAuthorizerDeps {
|
|
4
|
+
getApiKeyByPk: (pk: string) => Promise<ApiKeyRecord | null>;
|
|
5
|
+
getPixelById: (pixelId: string) => Promise<Pixel | null | undefined>;
|
|
6
|
+
}
|
|
7
|
+
interface AllowPolicy {
|
|
8
|
+
principalId: string;
|
|
9
|
+
policyDocument: {
|
|
10
|
+
Version: string;
|
|
11
|
+
Statement: {
|
|
12
|
+
Action: string;
|
|
13
|
+
Effect: 'Allow';
|
|
14
|
+
Resource: string;
|
|
15
|
+
}[];
|
|
16
|
+
};
|
|
17
|
+
context: {
|
|
18
|
+
pixelId: string;
|
|
19
|
+
accountId: string;
|
|
20
|
+
scopes: string;
|
|
21
|
+
apiKeyPk: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
interface DenyPolicy {
|
|
25
|
+
principalId: string;
|
|
26
|
+
policyDocument: {
|
|
27
|
+
Version: string;
|
|
28
|
+
Statement: {
|
|
29
|
+
Action: string;
|
|
30
|
+
Effect: 'Deny';
|
|
31
|
+
Resource: string;
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export declare const createApiKeyAuthorizerHandler: (deps: ApiKeyAuthorizerDeps) => (event: any, context: Context) => Promise<AllowPolicy | DenyPolicy>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { API_KEY_STATUS, PIXEL_STATUS } from '@adtrackify/at-tracking-event-types';
|
|
2
|
+
import { Logger, configureLogger } from './logging-helper.js';
|
|
3
|
+
import { generateSha256Hash } from '../libs/crypto.js';
|
|
4
|
+
const toWildcardArn = (methodArn) => {
|
|
5
|
+
const parts = methodArn.split('/');
|
|
6
|
+
return `${parts[0]}/${parts[1]}/*`;
|
|
7
|
+
};
|
|
8
|
+
const generateDenyPolicy = (methodArn) => ({
|
|
9
|
+
principalId: 'anonymous',
|
|
10
|
+
policyDocument: {
|
|
11
|
+
Version: '2012-10-17',
|
|
12
|
+
Statement: [{ Action: 'execute-api:Invoke', Effect: 'Deny', Resource: methodArn }],
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
export const createApiKeyAuthorizerHandler = (deps) => {
|
|
16
|
+
return async function handler(event, context) {
|
|
17
|
+
const methodArn = event.methodArn;
|
|
18
|
+
try {
|
|
19
|
+
configureLogger(event, context);
|
|
20
|
+
const apiKey = event.headers?.['x-api-key'] || event.headers?.['X-Api-Key'];
|
|
21
|
+
const pixelId = event.headers?.['x-pixel-id'] || event.headers?.['X-Pixel-Id'];
|
|
22
|
+
if (!apiKey || !pixelId) {
|
|
23
|
+
Logger.info('ApiKeyAuthorizer: missing x-api-key or x-pixel-id header');
|
|
24
|
+
return generateDenyPolicy(methodArn);
|
|
25
|
+
}
|
|
26
|
+
const keyHash = generateSha256Hash(apiKey);
|
|
27
|
+
const pk = `${pixelId}_${keyHash}`;
|
|
28
|
+
const apiKeyRecord = await deps.getApiKeyByPk(pk);
|
|
29
|
+
if (!apiKeyRecord) {
|
|
30
|
+
Logger.info('ApiKeyAuthorizer: no matching key found');
|
|
31
|
+
return generateDenyPolicy(methodArn);
|
|
32
|
+
}
|
|
33
|
+
if (apiKeyRecord.status !== API_KEY_STATUS.ACTIVE) {
|
|
34
|
+
Logger.info('ApiKeyAuthorizer: key is not active', { status: apiKeyRecord.status });
|
|
35
|
+
return generateDenyPolicy(methodArn);
|
|
36
|
+
}
|
|
37
|
+
if (apiKeyRecord.expiresAt && new Date(apiKeyRecord.expiresAt) < new Date()) {
|
|
38
|
+
Logger.info('ApiKeyAuthorizer: key has expired', { expiresAt: apiKeyRecord.expiresAt });
|
|
39
|
+
return generateDenyPolicy(methodArn);
|
|
40
|
+
}
|
|
41
|
+
const pixel = await deps.getPixelById(pixelId);
|
|
42
|
+
if (!pixel || pixel.status !== PIXEL_STATUS.ACTIVE) {
|
|
43
|
+
Logger.info('ApiKeyAuthorizer: pixel is not active', { pixelId, status: pixel?.status });
|
|
44
|
+
return generateDenyPolicy(methodArn);
|
|
45
|
+
}
|
|
46
|
+
Logger.info('ApiKeyAuthorizer: key authorized', {
|
|
47
|
+
pixelId: apiKeyRecord.pixelId,
|
|
48
|
+
apiKeyPk: apiKeyRecord.pk,
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
principalId: apiKeyRecord.pixelId,
|
|
52
|
+
policyDocument: {
|
|
53
|
+
Version: '2012-10-17',
|
|
54
|
+
Statement: [{
|
|
55
|
+
Action: 'execute-api:Invoke',
|
|
56
|
+
Effect: 'Allow',
|
|
57
|
+
Resource: toWildcardArn(methodArn),
|
|
58
|
+
}],
|
|
59
|
+
},
|
|
60
|
+
context: {
|
|
61
|
+
pixelId: apiKeyRecord.pixelId,
|
|
62
|
+
accountId: apiKeyRecord.accountId,
|
|
63
|
+
scopes: apiKeyRecord.scopes.join(','),
|
|
64
|
+
apiKeyPk: apiKeyRecord.pk,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
Logger.error('ApiKeyAuthorizer: error', { error });
|
|
70
|
+
return generateDenyPolicy(methodArn);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=api-key-authorizer-helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-authorizer-helper.js","sourceRoot":"","sources":["../../../src/helpers/api-key-authorizer-helper.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,4 +5,6 @@ export * from './shopify-helper.js';
|
|
|
5
5
|
export * from './identity-cache-helper.js';
|
|
6
6
|
export * from './account-users-helper.js';
|
|
7
7
|
export * from './sqs-utils.js';
|
|
8
|
+
export * from './api-key-auth-helper.js';
|
|
9
|
+
export * from './api-key-authorizer-helper.js';
|
|
8
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/helpers/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adtrackify/at-service-common",
|
|
3
|
-
"version": "3.17.
|
|
3
|
+
"version": "3.17.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/*"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@adtrackify/at-shared-utils": "^3.2.23",
|
|
40
|
-
"@adtrackify/at-tracking-event-types": "^4.68.
|
|
40
|
+
"@adtrackify/at-tracking-event-types": "^4.68.3",
|
|
41
41
|
"@aws-sdk/util-utf8": "^3.374.0",
|
|
42
42
|
"@shopify/admin-api-client": "^1.0.4",
|
|
43
43
|
"@shopify/shopify-api": "^11.6.1",
|