@adobe/spacecat-shared-data-access 1.49.7 → 1.50.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.50.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.49.8...@adobe/spacecat-shared-data-access-v1.50.0) (2024-11-08)
2
+
3
+
4
+ ### Features
5
+
6
+ * support queries to generate self-service api-keys ([#428](https://github.com/adobe/spacecat-shared/issues/428)) ([90a46c2](https://github.com/adobe/spacecat-shared/commit/90a46c2f095e07d77636189c04f52ba31a626c7b))
7
+
8
+ # [@adobe/spacecat-shared-data-access-v1.49.8](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.49.7...@adobe/spacecat-shared-data-access-v1.49.8) (2024-11-08)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * engine spec ([#433](https://github.com/adobe/spacecat-shared/issues/433)) ([19dd309](https://github.com/adobe/spacecat-shared/commit/19dd30956c0a9d35ea343c580e589205bfdbdfd8))
14
+
1
15
  # [@adobe/spacecat-shared-data-access-v1.49.7](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.49.6...@adobe/spacecat-shared-data-access-v1.49.7) (2024-11-08)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "1.49.7",
3
+ "version": "1.50.0",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -33,10 +33,10 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@adobe/spacecat-shared-dynamo": "1.3.45",
36
+ "@adobe/spacecat-shared-dynamo": "1.3.46",
37
37
  "@adobe/spacecat-shared-utils": "1.22.3",
38
- "@aws-sdk/client-dynamodb": "3.682.0",
39
- "@aws-sdk/lib-dynamodb": "3.685.0",
38
+ "@aws-sdk/client-dynamodb": "3.687.0",
39
+ "@aws-sdk/lib-dynamodb": "3.687.0",
40
40
  "@types/joi": "17.2.3",
41
41
  "joi": "17.13.3",
42
42
  "uuid": "11.0.2"
@@ -10,15 +10,16 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { createApiKey } from '../models/api-key.js';
13
+ import { createApiKey } from '../models/api-key/api-key.js';
14
14
 
15
15
  export const ApiKeyDto = {
16
16
 
17
17
  /**
18
18
  * Converts an ApiKey object into a DynamoDB item.
19
19
  * @param apiKey
20
- * @returns {{createdAt: *, name: *, imsUserId, scopes, revokedAt, key: *,
21
- * imsOrgId: *, expiresAt: *}}
20
+ * @returns {{createdAt: string, name: string, imsUserId: string,
21
+ * scopes: array<object>, revokedAt: string, deletedAt: string,
22
+ * hashedApiKey: string, imsOrgId: string, expiresAt: string}}
22
23
  */
23
24
  toDynamoItem: (apiKey) => ({
24
25
  id: apiKey.getId(),
@@ -29,6 +30,7 @@ export const ApiKeyDto = {
29
30
  createdAt: apiKey.getCreatedAt(),
30
31
  expiresAt: apiKey.getExpiresAt(),
31
32
  revokedAt: apiKey.getRevokedAt(),
33
+ deletedAt: apiKey.getDeletedAt(),
32
34
  scopes: apiKey.getScopes(),
33
35
  }),
34
36
 
@@ -47,6 +49,7 @@ export const ApiKeyDto = {
47
49
  createdAt: dynamoItem.createdAt,
48
50
  expiresAt: dynamoItem.expiresAt,
49
51
  revokedAt: dynamoItem.revokedAt,
52
+ deletedAt: dynamoItem.deletedAt,
50
53
  scopes: dynamoItem.scopes,
51
54
  };
52
55
 
package/src/index.d.ts CHANGED
@@ -635,11 +635,26 @@ export interface ApiKey {
635
635
  */
636
636
  getRevokedAt: () => string;
637
637
 
638
+ /**
639
+ * Retrieves the deletedAt of the API Key.
640
+ */
641
+ getDeletedAt: () => string;
642
+
638
643
  /**
639
644
  * Retrieves the scopes of the API Key.
640
645
  */
641
646
  getScopes: () => Array<string>;
642
647
 
648
+ /**
649
+ * Updates the deletedAt attribute of the API Key.
650
+ */
651
+ updateDeletedAt: (deletedAt: string) => ApiKey;
652
+
653
+ /**
654
+ * Indicates whether the API Key is valid.
655
+ */
656
+ isValid: () => boolean;
657
+
643
658
  }
644
659
 
645
660
  /**
@@ -839,6 +854,16 @@ export interface DataAccess {
839
854
  createNewApiKey: (
840
855
  apiKeyData: object,
841
856
  ) => Promise<ApiKey>;
857
+ updateApiKey: (
858
+ apiKey: ApiKey,
859
+ ) => Promise<ApiKey>;
860
+ getApiKeysByImsUserIdAndImsOrgId: (
861
+ imsUserId: string,
862
+ imsOrgId: string,
863
+ ) => Promise<ApiKey[] | null>;
864
+ getApiKeyById: (
865
+ id: string,
866
+ ) => Promise<ApiKey | null>;
842
867
 
843
868
  // site candidate functions
844
869
  getSiteCandidateByBaseURL: (baseURL: string) => Promise<SiteCandidate>;
@@ -893,6 +918,7 @@ interface DataAccessConfig {
893
918
  indexNameAllImportJobsByDateRange: string,
894
919
  indexNameImportUrlsByJobIdAndStatus: string,
895
920
  indexNameApiKeyByHashedApiKey: string,
921
+ indexNameApiKeyByImsUserIdAndImsOrgId: string,
896
922
  pkAllSites: string;
897
923
  pkAllLatestAudits: string;
898
924
  pkAllOrganizations: string;
package/src/index.js CHANGED
@@ -38,6 +38,7 @@ const INDEX_NAME_ALL_IMPORT_JOBS_BY_STATUS = 'spacecat-services-all-import-jobs-
38
38
  const INDEX_NAME_ALL_IMPORT_JOBS_BY_DATE_RANGE = 'spacecat-services-all-import-jobs-by-date-range-dev';
39
39
  const INDEX_NAME_ALL_IMPORT_URLS_BY_JOB_ID_AND_STATUS = 'spacecat-services-all-import-urls-by-job-id-and-status-dev';
40
40
  const INDEX_NAME_API_KEY_BY_HASHED_API_KEY = 'spacecat-services-api-key-by-hashed-api-key-dev';
41
+ const INDEX_NAME_API_KEY_BY_IMS_USER_ID_AND_IMS_ORG_ID = 'spacecat-services-api-key-by-ims-user-id-and-ims-org-id-dev';
41
42
 
42
43
  const PK_ALL_SITES = 'ALL_SITES';
43
44
  const PK_ALL_CONFIGURATIONS = 'ALL_CONFIGURATIONS';
@@ -76,6 +77,8 @@ export default function dataAccessWrapper(fn) {
76
77
  DYNAMO_INDEX_NAME_ALL_IMPORT_URLS_BY_JOB_ID_AND_STATUS =
77
78
  INDEX_NAME_ALL_IMPORT_URLS_BY_JOB_ID_AND_STATUS,
78
79
  DYNAMO_INDEX_NAME_API_KEY_BY_HASHED_API_KEY = INDEX_NAME_API_KEY_BY_HASHED_API_KEY,
80
+ DYNAMO_INDEX_NAME_API_KEY_BY_IMS_USER_ID_AND_IMS_ORG_ID =
81
+ INDEX_NAME_API_KEY_BY_IMS_USER_ID_AND_IMS_ORG_ID,
79
82
  } = context.env;
80
83
 
81
84
  context.dataAccess = createDataAccess({
@@ -102,6 +105,8 @@ export default function dataAccessWrapper(fn) {
102
105
  indexNameAllImportJobsByDateRange: DYNAMO_INDEX_NAME_ALL_IMPORT_JOBS_BY_DATE_RANGE,
103
106
  indexNameImportUrlsByJobIdAndStatus: DYNAMO_INDEX_NAME_ALL_IMPORT_URLS_BY_JOB_ID_AND_STATUS,
104
107
  indexNameApiKeyByHashedApiKey: DYNAMO_INDEX_NAME_API_KEY_BY_HASHED_API_KEY,
108
+ indexNameApiKeyByImsUserIdAndImsOrgId:
109
+ DYNAMO_INDEX_NAME_API_KEY_BY_IMS_USER_ID_AND_IMS_ORG_ID,
105
110
  pkAllSites: PK_ALL_SITES,
106
111
  pkAllOrganizations: PK_ALL_ORGANIZATIONS,
107
112
  pkAllLatestAudits: PK_ALL_LATEST_AUDITS,
@@ -13,7 +13,7 @@
13
13
  import {
14
14
  hasText, isIsoDate, isObject, isValidUrl,
15
15
  } from '@adobe/spacecat-shared-utils';
16
- import { Base } from './base.js';
16
+ import { Base } from '../base.js';
17
17
 
18
18
  // List of known scope names that can be used with scoped API keys
19
19
  const scopeNames = [
@@ -40,8 +40,79 @@ const ApiKey = (data) => {
40
40
  self.getCreatedAt = () => self.state.createdAt;
41
41
  self.getExpiresAt = () => self.state.expiresAt;
42
42
  self.getRevokedAt = () => self.state.revokedAt;
43
+ self.getDeletedAt = () => self.state.deletedAt;
43
44
  self.getScopes = () => self.state.scopes;
44
45
 
46
+ /**
47
+ * Checks if the apiKey is valid.
48
+ * @returns {boolean} True if the apiKey is valid, false otherwise
49
+ */
50
+ self.isValid = () => {
51
+ const now = new Date();
52
+
53
+ if (self.state.deletedAt && new Date(self.state.deletedAt) < now) {
54
+ return false;
55
+ }
56
+
57
+ if (self.state.revokedAt && new Date(self.state.revokedAt) < now) {
58
+ return false;
59
+ }
60
+
61
+ if (self.state.expiresAt && new Date(self.state.expiresAt) < now) {
62
+ return false;
63
+ }
64
+
65
+ return true;
66
+ };
67
+
68
+ /**
69
+ * Updates the state of the ApiKey.
70
+ * @param key - The key to update.
71
+ * @param value - The new value.
72
+ * @param validator - An optional validation function to use before updating the value.
73
+ * @returns {ApiKey} The updated ApiKey object.
74
+ */
75
+ const updateState = (key, value, validator) => {
76
+ if (validator && typeof validator === 'function') {
77
+ validator(value);
78
+ }
79
+
80
+ self.state[key] = value;
81
+ self.touch();
82
+
83
+ return self;
84
+ };
85
+
86
+ /**
87
+ * Updates the deletedAt attribute of the ApiKey.
88
+ * @param {string} deletedAt - The deletedAt timestamp - ISO 8601 date string.
89
+ */
90
+ self.updateDeletedAt = (deletedAt) => updateState('deletedAt', deletedAt, (value) => {
91
+ if (!isIsoDate(value)) {
92
+ throw new Error(`Invalid deletedAt during update: ${value}. Must be a valid ISO 8601 date string.`);
93
+ }
94
+ });
95
+
96
+ /**
97
+ * Updates the expiresAt attribute of the ApiKey.
98
+ * @param {string} expiresAt - The expiresAt timestamp - ISO 8601 date string.
99
+ */
100
+ self.updateExpiresAt = (expiresAt) => updateState('expiresAt', expiresAt, (value) => {
101
+ if (!isIsoDate(value)) {
102
+ throw new Error(`Invalid expiresAt during update: ${value}. Must be a valid ISO 8601 date string.`);
103
+ }
104
+ });
105
+
106
+ /**
107
+ * Updates the revokedAt attribute of the ApiKey.
108
+ * @param {string} revokedAt - The revokedAt timestamp - ISO 8601 date string.
109
+ */
110
+ self.updateRevokedAt = (revokedAt) => updateState('revokedAt', revokedAt, (value) => {
111
+ if (!isIsoDate(value)) {
112
+ throw new Error(`Invalid revokedAt during update: ${value}. Must be a valid ISO 8601 date string.`);
113
+ }
114
+ });
115
+
45
116
  return Object.freeze(self);
46
117
  };
47
118
 
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { ApiKeyDto } from '../../dto/api-key.js';
14
+ import { createApiKey } from '../../models/api-key/api-key.js';
14
15
 
15
16
  /**
16
17
  * Get ApiKey by Hashed Key
@@ -18,7 +19,7 @@ import { ApiKeyDto } from '../../dto/api-key.js';
18
19
  * @param {DynamoClient} dynamoClient
19
20
  * @param {Object} config
20
21
  * @param {Logger} log
21
- * @returns {Promise<ApiKeyDto>}
22
+ * @returns {Promise<ApiKey>}
22
23
  */
23
24
  export const getApiKeyByHashedApiKey = async (hashedApiKey, dynamoClient, log, config) => {
24
25
  const items = await dynamoClient.query({
@@ -42,10 +43,68 @@ export const getApiKeyByHashedApiKey = async (hashedApiKey, dynamoClient, log, c
42
43
  * @param {DynamoClient} dynamoClient
43
44
  * @param {Object} config
44
45
  * @param {Logger} log
45
- * @returns {Promise<ApiKeyDto>}
46
+ * @returns {Promise<ApiKey>}
46
47
  */
47
48
  export const createNewApiKey = async (apiKey, dynamoClient, config, log) => {
48
- const item = ApiKeyDto.toDynamoItem(apiKey);
49
- await dynamoClient.putItem(config.tableNameApiKeys, item, log);
49
+ const item = createApiKey(apiKey);
50
+ await dynamoClient.putItem(config.tableNameApiKeys, ApiKeyDto.toDynamoItem(item), log);
50
51
  return item;
51
52
  };
53
+
54
+ /**
55
+ * Get ApiKeys by IMS User ID and IMS Org ID
56
+ * @param imsUserId
57
+ * @param imsOrgId
58
+ * @param dynamoClient
59
+ * @param config
60
+ * @returns {Promise<ApiKey[]>}
61
+ */
62
+ export const getApiKeysByImsUserIdAndImsOrgId = async (
63
+ imsUserId,
64
+ imsOrgId,
65
+ dynamoClient,
66
+ config,
67
+ ) => {
68
+ const items = await dynamoClient.query({
69
+ TableName: config.tableNameApiKeys,
70
+ IndexName: config.indexNameApiKeyByImsUserIdAndImsOrgId,
71
+ KeyConditionExpression: '#imsUserId = :imsUserId AND #imsOrgId = :imsOrgId',
72
+ ExpressionAttributeNames: {
73
+ '#imsUserId': 'imsUserId',
74
+ '#imsOrgId': 'imsOrgId',
75
+ },
76
+ ExpressionAttributeValues: {
77
+ ':imsUserId': imsUserId,
78
+ ':imsOrgId': imsOrgId,
79
+ },
80
+ });
81
+ return items.map((item) => ApiKeyDto.fromDynamoItem(item));
82
+ };
83
+
84
+ /**
85
+ * Get ApiKey by ID
86
+ * @param id
87
+ * @param dynamoClient
88
+ * @param config
89
+ * @returns {Promise<ApiKey|null>}
90
+ */
91
+ export const getApiKeyById = async (id, dynamoClient, config) => {
92
+ const item = await dynamoClient.getItem(config.tableNameApiKeys, { id });
93
+ return item ? ApiKeyDto.fromDynamoItem(item) : null;
94
+ };
95
+
96
+ /**
97
+ * Update ApiKey
98
+ * @param apiKey
99
+ * @param dynamoClient
100
+ * @param config
101
+ * @returns {Promise<ApiKey>}
102
+ */
103
+ export const updateApiKey = async (apiKey, dynamoClient, config) => {
104
+ const existingApiKey = await getApiKeyById(apiKey.getId(), dynamoClient, config);
105
+ if (!existingApiKey) {
106
+ throw new Error(`API Key with id ${apiKey.getId()} not found`);
107
+ }
108
+ await dynamoClient.putItem(config.tableNameApiKeys, ApiKeyDto.toDynamoItem(apiKey));
109
+ return apiKey;
110
+ };
@@ -10,7 +10,13 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { createNewApiKey, getApiKeyByHashedApiKey } from './accessPatterns.js';
13
+ import {
14
+ createNewApiKey,
15
+ getApiKeyByHashedApiKey,
16
+ getApiKeyById,
17
+ getApiKeysByImsUserIdAndImsOrgId,
18
+ updateApiKey,
19
+ } from './accessPatterns.js';
14
20
 
15
21
  export const apiKeyFunctions = (dynamoClient, config, log) => ({
16
22
  getApiKeyByHashedApiKey: (hashedApiKey) => getApiKeyByHashedApiKey(
@@ -20,4 +26,12 @@ export const apiKeyFunctions = (dynamoClient, config, log) => ({
20
26
  config,
21
27
  ),
22
28
  createNewApiKey: (apiKey) => createNewApiKey(apiKey, dynamoClient, config, log),
29
+ getApiKeysByImsUserIdAndImsOrgId: (imsUserId, imsOrgId) => getApiKeysByImsUserIdAndImsOrgId(
30
+ imsUserId,
31
+ imsOrgId,
32
+ dynamoClient,
33
+ config,
34
+ ),
35
+ getApiKeyById: (id) => getApiKeyById(id, dynamoClient, config),
36
+ updateApiKey: (apiKey) => updateApiKey(apiKey, dynamoClient, config),
23
37
  });