@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.
@@ -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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"}
@@ -5,3 +5,5 @@ 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';
@@ -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,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,3 +5,5 @@ 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';
@@ -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.2",
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.1",
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",