@adobe/spacecat-shared-data-access 1.49.8 → 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 +7 -0
- package/package.json +1 -1
- package/src/dto/api-key.js +6 -3
- package/src/index.d.ts +26 -0
- package/src/index.js +5 -0
- package/src/models/{api-key.js → api-key/api-key.js} +72 -1
- package/src/service/api-key/accessPatterns.js +63 -4
- package/src/service/api-key/index.js +15 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
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
|
+
|
|
1
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)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
package/src/dto/api-key.js
CHANGED
|
@@ -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:
|
|
21
|
-
*
|
|
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 '
|
|
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<
|
|
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<
|
|
46
|
+
* @returns {Promise<ApiKey>}
|
|
46
47
|
*/
|
|
47
48
|
export const createNewApiKey = async (apiKey, dynamoClient, config, log) => {
|
|
48
|
-
const item =
|
|
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 {
|
|
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
|
});
|