@adobe/spacecat-shared-data-access 1.38.1 → 1.39.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-data-access-v1.39.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.38.2...@adobe/spacecat-shared-data-access-v1.39.0) (2024-07-31)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add API Key data attributes ([#306](https://github.com/adobe/spacecat-shared/issues/306)) ([37ddf55](https://github.com/adobe/spacecat-shared/commit/37ddf55f73acb9e6848dbe6a9292496a15d77ff0))
7
+
8
+ # [@adobe/spacecat-shared-data-access-v1.38.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.38.1...@adobe/spacecat-shared-data-access-v1.38.2) (2024-07-27)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update external fixes ([#304](https://github.com/adobe/spacecat-shared/issues/304)) ([c6c56a7](https://github.com/adobe/spacecat-shared/commit/c6c56a72897acb60fb042215b708816ec16a5870))
14
+
1
15
  # [@adobe/spacecat-shared-data-access-v1.38.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.38.0...@adobe/spacecat-shared-data-access-v1.38.1) (2024-07-24)
2
16
 
3
17
 
package/migration.sh CHANGED
@@ -86,6 +86,7 @@ echo "$ORGANIZATIONS" | jq -c '.Items[]' | while read -r org; do
86
86
  GSI1PK=$(echo $org | jq -r '.GSI1PK.S')
87
87
  CREATED_AT=$(echo $org | jq -r '.createdAt.S')
88
88
  UPDATED_AT=$(echo $org | jq -r '.updatedAt.S')
89
+ FULLFILLABLE_ITEMS=$(echo $org | jq -r '.fulfillableItems // {"M": {}}')
89
90
  SLACK=$(echo $org | jq -r '.config.M.slack // {"M": {}}')
90
91
  IMPORTS=$(echo $org | jq -r '.config.M.imports // {"L": []}')
91
92
 
@@ -112,6 +113,7 @@ echo "$ORGANIZATIONS" | jq -c '.Items[]' | while read -r org; do
112
113
  "GSI1PK": {"S": "$GSI1PK"},
113
114
  "createdAt": {"S": "$CREATED_AT"},
114
115
  "updatedAt": {"S": "$UPDATED_AT"},
116
+ "fulfillableItems": $FULLFILLABLE_ITEMS,
115
117
  "config": {
116
118
  "M": {
117
119
  "slack": $SLACK,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "1.38.1",
3
+ "version": "1.39.0",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -31,14 +31,14 @@
31
31
  "dependencies": {
32
32
  "@adobe/spacecat-shared-dynamo": "1.2.5",
33
33
  "@adobe/spacecat-shared-utils": "1.2.0",
34
- "@aws-sdk/client-dynamodb": "3.616.0",
35
- "@aws-sdk/lib-dynamodb": "3.616.0",
34
+ "@aws-sdk/client-dynamodb": "3.620.0",
35
+ "@aws-sdk/lib-dynamodb": "3.620.0",
36
36
  "@types/joi": "17.2.3",
37
37
  "joi": "17.13.3",
38
38
  "uuid": "10.0.0"
39
39
  },
40
40
  "devDependencies": {
41
- "chai": "4.4.1",
41
+ "chai": "4.5.0",
42
42
  "chai-as-promised": "8.0.0",
43
43
  "dynamo-db-local": "8.0.0",
44
44
  "sinon": "18.0.0",
@@ -0,0 +1,53 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { createApiKey } from '../models/api-key.js';
14
+
15
+ export const ApiKeyDto = {
16
+
17
+ /**
18
+ * Converts an ApiKey object into a DynamoDB item.
19
+ * @param apiKey
20
+ * @returns {{createdAt: *, name: *, imsUserId, scopes, revokedAt, key: *,
21
+ * imsOrgId: *, expiresAt: *}}
22
+ */
23
+ toDynamoItem: (apiKey) => ({
24
+ hashedKey: apiKey.getHashedKey(),
25
+ name: apiKey.getName(),
26
+ imsUserId: apiKey.getImsUserId(),
27
+ imsOrgId: apiKey.getImsOrgId(),
28
+ createdAt: apiKey.getCreatedAt(),
29
+ expiresAt: apiKey.getExpiresAt(),
30
+ revokedAt: apiKey.getRevokedAt(),
31
+ scopes: apiKey.getScopes(),
32
+ }),
33
+
34
+ /**
35
+ * Converts a DynamoDB item into an ApiKey object.
36
+ * @param dynamoItem
37
+ * @returns {ApiKey}
38
+ */
39
+ fromDynamoItem: (dynamoItem) => {
40
+ const apiKeyData = {
41
+ hashedKey: dynamoItem.hashedKey,
42
+ name: dynamoItem.name,
43
+ imsUserId: dynamoItem.imsUserId,
44
+ imsOrgId: dynamoItem.imsOrgId,
45
+ createdAt: dynamoItem.createdAt,
46
+ expiresAt: dynamoItem.expiresAt,
47
+ revokedAt: dynamoItem.revokedAt,
48
+ scopes: dynamoItem.scopes,
49
+ };
50
+
51
+ return createApiKey(apiKeyData);
52
+ },
53
+ };
package/src/index.d.ts CHANGED
@@ -534,6 +534,57 @@ export interface ImportUrl {
534
534
 
535
535
  }
536
536
 
537
+ /**
538
+ * Represents an API Key entity.
539
+ */
540
+ export interface ApiKey {
541
+ /**
542
+ * Retrieves the ID of the API Key.
543
+ */
544
+ getId: () => string;
545
+
546
+ /**
547
+ * Retrieves the hashed key value of the API Key.
548
+ */
549
+ getHashedKey: () => string;
550
+
551
+ /**
552
+ * Retrieves the name of the API Key.
553
+ */
554
+ getName: () => string;
555
+
556
+ /**
557
+ * Retrieves the imsUserId of the API Key.
558
+ */
559
+ getImsUserId: () => string;
560
+
561
+ /**
562
+ * Retrieves the imsOrgId of the API key
563
+ */
564
+ getImsOrgId: () => string;
565
+
566
+ /**
567
+ * Retrieves the createdAt of the API Key.
568
+ */
569
+ getCreatedAt: () => string;
570
+
571
+ /**
572
+ * Retrieves the expiresAt of the API Key.
573
+ */
574
+ getExpiresAt: () => string;
575
+
576
+ /**
577
+ * Retrieves the revokedAt of the API Key.
578
+ */
579
+ getRevokedAt: () => string;
580
+
581
+ /**
582
+ * Retrieves the scopes of the API Key.
583
+ */
584
+ getScopes: () => string;
585
+
586
+ }
587
+
537
588
  /**
538
589
  * Represents an experiment entity.
539
590
  */
@@ -722,6 +773,12 @@ export interface DataAccess {
722
773
  jobId: string,
723
774
  status: string,
724
775
  ) => Promise<ImportUrl[]>;
776
+ getApiKeyByHashedKey: (
777
+ hashedKey: string,
778
+ ) => Promise<ApiKey | null>;
779
+ createNewApiKey: (
780
+ apiKeyData: object,
781
+ ) => Promise<ApiKey>;
725
782
 
726
783
  // site candidate functions
727
784
  getSiteCandidateByBaseURL: (baseURL: string) => Promise<SiteCandidate>;
@@ -764,6 +821,7 @@ interface DataAccessConfig {
764
821
  tableNameImportJobs: string;
765
822
  tableNameImportUrls: string;
766
823
  tableNameExperiments: string;
824
+ tableNameApiKeys: string;
767
825
  indexNameAllKeyEventsBySiteId: string,
768
826
  indexNameAllSites: string;
769
827
  indexNameAllSitesOrganizations: string,
@@ -774,6 +832,7 @@ interface DataAccessConfig {
774
832
  indexNameAllImportJobsByStatus: string,
775
833
  indexNameAllImportJobsByDateRange: string,
776
834
  indexNameAllImportUrlsByJobIdAndStatus: string,
835
+ indexNameApiKeyByHashedKey: string,
777
836
  pkAllSites: string;
778
837
  pkAllLatestAudits: string;
779
838
  pkAllOrganizations: string;
package/src/index.js CHANGED
@@ -25,6 +25,7 @@ const TABLE_NAME_SITE_TOP_PAGES = 'spacecat-services-site-top-pages-dev';
25
25
  const TABLE_NAME_IMPORT_JOBS = 'spacecat-services-import-jobs-dev';
26
26
  const TABLE_NAME_IMPORT_URLS = 'spacecat-services-import-urls-dev';
27
27
  const TABLE_NAME_EXPERIMENTS = 'spacecat-services-experiments-dev';
28
+ const TABLE_NAME_API_KEYS = 'spacecat-services-api-keys-dev';
28
29
 
29
30
  const INDEX_NAME_ALL_KEY_EVENTS_BY_SITE_ID = 'spacecat-services-key-events-by-site-id';
30
31
  const INDEX_NAME_ALL_SITES = 'spacecat-services-all-sites-dev';
@@ -36,6 +37,7 @@ const INDEX_NAME_ALL_SITES_ORGANIZATIONS = 'spacecat-services-all-sites-organiza
36
37
  const INDEX_NAME_ALL_IMPORT_JOBS_BY_STATUS = 'spacecat-services-all-import-jobs-by-status-dev';
37
38
  const INDEX_NAME_ALL_IMPORT_JOBS_BY_DATE_RANGE = 'spacecat-services-all-import-jobs-by-date-range-dev';
38
39
  const INDEX_NAME_ALL_IMPORT_URLS_BY_JOB_ID_AND_STATUS = 'spacecat-services-all-import-urls-by-job-id-and-status-dev';
40
+ const INDEX_NAME_API_KEY_BY_HASHED_KEY = 'spacecat-services-api-key-by-hashed-key-dev';
39
41
 
40
42
  const PK_ALL_SITES = 'ALL_SITES';
41
43
  const PK_ALL_CONFIGURATIONS = 'ALL_CONFIGURATIONS';
@@ -60,6 +62,7 @@ export default function dataAccessWrapper(fn) {
60
62
  DYNAMO_TABLE_NAME_IMPORT_JOBS = TABLE_NAME_IMPORT_JOBS,
61
63
  DYNAMO_TABLE_NAME_IMPORT_URLS = TABLE_NAME_IMPORT_URLS,
62
64
  DYNAMO_TABLE_NAME_EXPERIMENTS = TABLE_NAME_EXPERIMENTS,
65
+ DYNAMO_TABLE_NAME_API_KEYS = TABLE_NAME_API_KEYS,
63
66
  DYNAMO_INDEX_NAME_ALL_KEY_EVENTS_BY_SITE_ID = INDEX_NAME_ALL_KEY_EVENTS_BY_SITE_ID,
64
67
  DYNAMO_INDEX_NAME_ALL_SITES = INDEX_NAME_ALL_SITES,
65
68
  DYNAMO_INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE = INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE,
@@ -72,6 +75,7 @@ export default function dataAccessWrapper(fn) {
72
75
  DYNAMO_INDEX_NAME_ALL_IMPORT_JOBS_BY_DATE_RANGE = INDEX_NAME_ALL_IMPORT_JOBS_BY_DATE_RANGE,
73
76
  DYNAMO_INDEX_NAME_ALL_IMPORT_URLS_BY_JOB_ID_AND_STATUS =
74
77
  INDEX_NAME_ALL_IMPORT_URLS_BY_JOB_ID_AND_STATUS,
78
+ DYNAMO_INDEX_NAME_API_KEY_BY_HASHED_KEY = INDEX_NAME_API_KEY_BY_HASHED_KEY,
75
79
  } = context.env;
76
80
 
77
81
  context.dataAccess = createDataAccess({
@@ -86,6 +90,7 @@ export default function dataAccessWrapper(fn) {
86
90
  tableNameImportJobs: DYNAMO_TABLE_NAME_IMPORT_JOBS,
87
91
  tableNameImportUrls: DYNAMO_TABLE_NAME_IMPORT_URLS,
88
92
  tableNameExperiments: DYNAMO_TABLE_NAME_EXPERIMENTS,
93
+ tableNameApiKeys: DYNAMO_TABLE_NAME_API_KEYS,
89
94
  indexNameAllKeyEventsBySiteId: DYNAMO_INDEX_NAME_ALL_KEY_EVENTS_BY_SITE_ID,
90
95
  indexNameAllSites: DYNAMO_INDEX_NAME_ALL_SITES,
91
96
  indexNameAllOrganizations: DYNAMO_INDEX_NAME_ALL_ORGANIZATIONS,
@@ -96,6 +101,7 @@ export default function dataAccessWrapper(fn) {
96
101
  indexNameAllImportJobsByStatus: DYNAMO_INDEX_NAME_ALL_IMPORT_JOBS_BY_STATUS,
97
102
  indexNameAllImportJobsByDateRange: DYNAMO_INDEX_NAME_ALL_IMPORT_JOBS_BY_DATE_RANGE,
98
103
  indexNameImportUrlsByJobIdAndStatus: DYNAMO_INDEX_NAME_ALL_IMPORT_URLS_BY_JOB_ID_AND_STATUS,
104
+ indexNameApiKeyByHashedKey: DYNAMO_INDEX_NAME_API_KEY_BY_HASHED_KEY,
99
105
  pkAllSites: PK_ALL_SITES,
100
106
  pkAllOrganizations: PK_ALL_ORGANIZATIONS,
101
107
  pkAllLatestAudits: PK_ALL_LATEST_AUDITS,
@@ -0,0 +1,96 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {
14
+ hasText, isIsoDate, isObject, isValidUrl,
15
+ } from '@adobe/spacecat-shared-utils';
16
+ import { Base } from './base.js';
17
+
18
+ const scopeNames = ['sites.read_all', 'sites.write_all', 'organizations.read_all', 'organizations.write_all',
19
+ 'audits.read_all', 'audits.write_all', 'imports.read', 'imports.write', 'imports.read_all'];
20
+
21
+ const ApiKey = (data) => {
22
+ const self = Base(data);
23
+
24
+ self.getHashedKey = () => self.state.hashedKey;
25
+ self.getName = () => self.state.name;
26
+ self.getImsUserId = () => self.state.imsUserId;
27
+ self.getImsOrgId = () => self.state.imsOrgId;
28
+ self.getCreatedAt = () => self.state.createdAt;
29
+ self.getExpiresAt = () => self.state.expiresAt;
30
+ self.getRevokedAt = () => self.state.revokedAt;
31
+ self.getScopes = () => self.state.scopes;
32
+
33
+ return Object.freeze(self);
34
+ };
35
+
36
+ /**
37
+ * Creates a new ApiKey object.
38
+ * @param {Object} apiKeyData - The data for the ApiKey object.
39
+ * @returns {ApiKey} The new ApiKey object.
40
+ */
41
+ export const createApiKey = (data) => {
42
+ const newState = { ...data };
43
+
44
+ if (!hasText(newState.hashedKey)) {
45
+ throw new Error(`Invalid Hashed Key: ${newState.hashedKey}`);
46
+ }
47
+
48
+ if (!hasText(newState.name)) {
49
+ throw new Error(`Invalid Name: ${newState.name}`);
50
+ }
51
+
52
+ if (hasText(newState.createdAt) && !isIsoDate(newState.createdAt)) {
53
+ throw new Error(`createdAt should be a valid ISO 8601 string: ${newState.createdAt}`);
54
+ }
55
+
56
+ if (!hasText(newState.createdAt)) {
57
+ newState.createdAt = new Date().toISOString();
58
+ }
59
+
60
+ if (hasText(newState.expiresAt) && !isIsoDate(newState.expiresAt)) {
61
+ throw new Error(`expiresAt should be a valid ISO 8601 string: ${newState.expiresAt}`);
62
+ }
63
+
64
+ if (hasText(newState.revokedAt) && !isIsoDate(newState.revokedAt)) {
65
+ throw new Error(`revokedAt should be a valid ISO 8601 string: ${newState.revokedAt}`);
66
+ }
67
+
68
+ if (!Array.isArray(newState.scopes)) {
69
+ throw new Error(`Invalid scopes: ${newState.scopes}`);
70
+ }
71
+
72
+ for (const scope of newState.scopes) {
73
+ if (!isObject(scope)) {
74
+ throw new Error(`Invalid scope: ${scope}`);
75
+ }
76
+
77
+ if (!hasText(scope.name)) {
78
+ throw new Error(`Invalid scope name: ${scope.name}`);
79
+ }
80
+
81
+ if (!scopeNames.includes(scope.name)) {
82
+ throw new Error(`Scope name is not part of the pre-defined scopes: ${scope.name}`);
83
+ }
84
+
85
+ if (hasText(scope.domains) && !Array.isArray(scope.domains)) {
86
+ throw new Error(`Scope domains should be an array: ${scope.domains}`);
87
+ }
88
+ for (const domain of scope.domains) {
89
+ if (!isValidUrl(domain)) {
90
+ throw new Error(`Invalid domain: ${domain}`);
91
+ }
92
+ }
93
+ }
94
+
95
+ return ApiKey(newState);
96
+ };
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { ApiKeyDto } from '../../dto/api-key.js';
14
+
15
+ /**
16
+ * Get ApiKey by Hashed Key
17
+ * @param {string} hashedKey
18
+ * @param {DynamoClient} dynamoClient
19
+ * @param {Object} config
20
+ * @param {Logger} log
21
+ * @returns {Promise<ApiKeyDto>}
22
+ */
23
+ export const getApiKeyByHashedKey = async (hashedKey, dynamoClient, log, config) => {
24
+ const item = await dynamoClient.query({
25
+ TableName: config.tableNameApiKeys,
26
+ IndexName: config.indexNameApiKeyByHashedKey,
27
+ KeyConditionExpression: '#hashedKey = :hashedKey',
28
+ ExpressionAttributeNames: {
29
+ '#hashedKey': 'hashedKey',
30
+ },
31
+ ExpressionAttributeValues: {
32
+ ':gsi1pk': config.pkApiKey,
33
+ ':hashedKey': hashedKey,
34
+ },
35
+ });
36
+ ApiKeyDto.fromDynamoItem(item);
37
+ };
38
+
39
+ /**
40
+ * Create new ApiKey
41
+ * @param {ApiKey} apiKey
42
+ * @param {DynamoClient} dynamoClient
43
+ * @param {Object} config
44
+ * @param {Logger} log
45
+ * @returns {Promise<ApiKeyDto>}
46
+ */
47
+ export const createNewApiKey = async (apiKey, dynamoClient, config, log) => {
48
+ const item = ApiKeyDto.toDynamoItem(apiKey);
49
+ await dynamoClient.putItem(config.tableNameApiKeys, item, log);
50
+ return item;
51
+ };
@@ -0,0 +1,18 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { createNewApiKey, getApiKeyByHashedKey } from './accessPatterns.js';
14
+
15
+ export const apiKeyFunctions = (dynamoClient, config, log) => ({
16
+ getApiKeyByHashedKey: (hashedKey) => getApiKeyByHashedKey(hashedKey, dynamoClient, log, config),
17
+ createNewApiKey: (apiKey) => createNewApiKey(apiKey, dynamoClient, config, log),
18
+ });
@@ -21,6 +21,7 @@ import { siteTopPagesFunctions } from './site-top-pages/index.js';
21
21
  import { importJobFunctions } from './import-job/index.js';
22
22
  import { importUrlFunctions } from './import-url/index.js';
23
23
  import { experimentFunctions } from './experiments/index.js';
24
+ import { apiKeyFunctions } from './api-key/index.js';
24
25
 
25
26
  /**
26
27
  * Creates a data access object.
@@ -48,6 +49,7 @@ export const createDataAccess = (config, log = console) => {
48
49
  const importJobFuncs = importJobFunctions(dynamoClient, config, log);
49
50
  const importUrlFuncs = importUrlFunctions(dynamoClient, config, log);
50
51
  const experimentFuncs = experimentFunctions(dynamoClient, config, log);
52
+ const apiKeyFuncs = apiKeyFunctions(dynamoClient, config, log);
51
53
 
52
54
  return {
53
55
  ...auditFuncs,
@@ -60,5 +62,6 @@ export const createDataAccess = (config, log = console) => {
60
62
  ...importJobFuncs,
61
63
  ...importUrlFuncs,
62
64
  ...experimentFuncs,
65
+ ...apiKeyFuncs,
63
66
  };
64
67
  };