@friggframework/core 2.0.0--canary.667.f74cf3b.0 → 2.0.0--canary.710.7cfcf12.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/CLAUDE.md +41 -1
- package/application/commands/README.md +17 -11
- package/application/commands/entity-commands.js +18 -60
- package/application/commands/integration-commands.js +63 -0
- package/database/documentdb-encryption-service.js +2 -33
- package/database/documentdb-utils.js +4 -1
- package/database/encryption/README.md +3 -3
- package/database/encryption/ciphertext-format.js +24 -0
- package/database/encryption/encryption-schema-registry.js +109 -29
- package/database/encryption/field-encryption-service.js +13 -16
- package/database/prisma.js +10 -1
- package/errors/index.js +4 -0
- package/errors/mapping-already-exists-error.js +14 -0
- package/handlers/WEBHOOKS.md +29 -0
- package/index.js +2 -0
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +67 -13
- package/integrations/integration-base.js +29 -0
- package/integrations/integration-router.js +22 -18
- package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
- package/integrations/repositories/integration-mapping-repository.js +29 -0
- package/integrations/repositories/integration-repository-documentdb.js +32 -0
- package/integrations/repositories/integration-repository-interface.js +30 -0
- package/integrations/repositories/integration-repository-mongo.js +53 -1
- package/integrations/repositories/integration-repository-postgres.js +58 -0
- package/integrations/repositories/process-repository-mongo.js +1 -1
- package/integrations/use-cases/add-entities-to-integration.js +92 -0
- package/integrations/use-cases/create-integration.js +57 -19
- package/integrations/use-cases/remove-entities-from-integration.js +70 -0
- package/integrations/utils/assert-entity-ids-input.js +42 -0
- package/jest-setup.js +13 -2
- package/jest-teardown.js +5 -2
- package/jest.config.js +4 -0
- package/jest.db.config.js +23 -0
- package/modules/use-cases/get-entity-options-by-id.js +5 -7
- package/modules/use-cases/get-module.js +8 -29
- package/modules/use-cases/refresh-entity-options.js +5 -7
- package/modules/use-cases/test-module-auth.js +5 -7
- package/modules/utils/map-module-dto.js +5 -3
- package/modules/utils/to-credential-id.js +14 -0
- package/package.json +7 -5
- package/queues/queuer-util.js +6 -5
- package/user/tests/doubles/test-user-repository.js +1 -1
- package/user/use-cases/get-user-from-bearer-token.js +7 -2
- package/modules/use-cases/create-entity-from-credential.js +0 -105
- package/modules/utils/find-module-definition.js +0 -23
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
const { Module } = require('../module');
|
|
2
|
-
const {
|
|
3
|
-
findModuleDefinitionForEntity,
|
|
4
|
-
} = require('../utils/find-module-definition');
|
|
5
2
|
|
|
6
3
|
class TestModuleAuth {
|
|
7
4
|
/**
|
|
@@ -48,10 +45,11 @@ class TestModuleAuth {
|
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
const entityType = entity.moduleName;
|
|
51
|
-
const moduleDefinition =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
const moduleDefinition = this.moduleDefinitions.find((def) => {
|
|
49
|
+
const modelName =
|
|
50
|
+
Module.getEntityModelFromDefinition(def).modelName;
|
|
51
|
+
return entityType === modelName;
|
|
52
|
+
});
|
|
55
53
|
|
|
56
54
|
if (!moduleDefinition) {
|
|
57
55
|
throw new Error(
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { toCredentialId } = require('./to-credential-id');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @param {import('../module').Module} moduleInstance
|
|
3
5
|
* Convert a Module domain instance to a plain DTO suitable for JSON responses.
|
|
@@ -10,9 +12,9 @@ function mapModuleClassToModuleDTO(moduleInstance) {
|
|
|
10
12
|
name: moduleInstance.name,
|
|
11
13
|
userId: moduleInstance.userId,
|
|
12
14
|
entity: moduleInstance.entity,
|
|
13
|
-
credentialId: moduleInstance.credential
|
|
14
|
-
type: moduleInstance.getName()
|
|
15
|
+
credentialId: toCredentialId(moduleInstance.credential),
|
|
16
|
+
type: moduleInstance.getName(),
|
|
15
17
|
};
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
module.exports = { mapModuleClassToModuleDTO };
|
|
20
|
+
module.exports = { mapModuleClassToModuleDTO };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the credential id from an entity's or module's `credential`, which
|
|
3
|
+
* repositories return as the decrypted credential object (or a bare id).
|
|
4
|
+
* @param {Object|string|number|null|undefined} credential
|
|
5
|
+
* @returns {string|null}
|
|
6
|
+
*/
|
|
7
|
+
function toCredentialId(credential) {
|
|
8
|
+
if (credential && typeof credential === 'object') {
|
|
9
|
+
return credential.id?.toString() ?? null;
|
|
10
|
+
}
|
|
11
|
+
return credential?.toString() ?? null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = { toCredentialId };
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.
|
|
4
|
+
"version": "2.0.0--canary.710.7cfcf12.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
8
8
|
"@aws-sdk/client-lambda": "^3.714.0",
|
|
9
9
|
"@aws-sdk/client-s3": "^3.588.0",
|
|
10
|
+
"@aws-sdk/client-scheduler": "^3.588.0",
|
|
10
11
|
"@aws-sdk/client-sqs": "^3.588.0",
|
|
11
12
|
"@aws-sdk/client-ssm": "^3.588.0",
|
|
12
13
|
"@aws-sdk/s3-request-presigner": "^3.588.0",
|
|
@@ -47,9 +48,9 @@
|
|
|
47
48
|
}
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
51
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
52
|
-
"@friggframework/test": "2.0.0--canary.
|
|
51
|
+
"@friggframework/eslint-config": "2.0.0--canary.710.7cfcf12.0",
|
|
52
|
+
"@friggframework/prettier-config": "2.0.0--canary.710.7cfcf12.0",
|
|
53
|
+
"@friggframework/test": "2.0.0--canary.710.7cfcf12.0",
|
|
53
54
|
"@prisma/client": "^6.19.3",
|
|
54
55
|
"@types/lodash": "4.17.15",
|
|
55
56
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -67,6 +68,7 @@
|
|
|
67
68
|
"scripts": {
|
|
68
69
|
"lint:fix": "prettier --write --loglevel error . && eslint . --fix",
|
|
69
70
|
"test": "jest --passWithNoTests # TODO",
|
|
71
|
+
"test:db": "jest --config jest.db.config.js --runInBand",
|
|
70
72
|
"prisma:generate:mongo": "npx prisma generate --schema ./prisma-mongodb/schema.prisma",
|
|
71
73
|
"prisma:generate:postgres": "npx prisma generate --schema ./prisma-postgresql/schema.prisma",
|
|
72
74
|
"prisma:generate": "npm run prisma:generate:mongo && npm run prisma:generate:postgres",
|
|
@@ -89,5 +91,5 @@
|
|
|
89
91
|
"publishConfig": {
|
|
90
92
|
"access": "public"
|
|
91
93
|
},
|
|
92
|
-
"gitHead": "
|
|
94
|
+
"gitHead": "7cfcf1267a412053163b99b7f9a123f7e4e1bde3"
|
|
93
95
|
}
|
package/queues/queuer-util.js
CHANGED
|
@@ -97,16 +97,17 @@ const QueuerUtil = {
|
|
|
97
97
|
Id: uuid(),
|
|
98
98
|
MessageBody: JSON.stringify(entry),
|
|
99
99
|
});
|
|
100
|
-
// Sends 10, then
|
|
100
|
+
// Sends 10, then starts a fresh buffer. Each command gets its own
|
|
101
|
+
// array: clearing the shared buffer in place would also empty the
|
|
102
|
+
// Entries of a command that was already sent.
|
|
101
103
|
if (buffer.length === batchSize) {
|
|
104
|
+
const batch = buffer.splice(0, buffer.length);
|
|
102
105
|
const command = new SendMessageBatchCommand({
|
|
103
|
-
Entries:
|
|
106
|
+
Entries: batch,
|
|
104
107
|
QueueUrl: queueUrl,
|
|
105
108
|
});
|
|
106
109
|
const result = await sqs.send(command);
|
|
107
|
-
inspectBatchResult(result, queueUrl,
|
|
108
|
-
// Purge the buffer
|
|
109
|
-
buffer.splice(0, buffer.length);
|
|
110
|
+
inspectBatchResult(result, queueUrl, batch);
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -23,7 +23,7 @@ class TestUserRepository {
|
|
|
23
23
|
|
|
24
24
|
async createToken(userId, rawToken, minutes = 120) {
|
|
25
25
|
const token = `token-for-${userId}-for-${minutes}-mins`;
|
|
26
|
-
this.tokens.set(token, {
|
|
26
|
+
this.tokens.set(token, { id: token, userId, rawToken });
|
|
27
27
|
return token;
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -40,8 +40,13 @@ class GetUserFromBearerToken {
|
|
|
40
40
|
throw Boom.unauthorized('Session Token Not Found');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
const tokenUserId = sessionToken.userId;
|
|
44
|
+
if (!tokenUserId) {
|
|
45
|
+
throw Boom.unauthorized('Session Token Has No User');
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
if (this.userConfig.primary === 'organization') {
|
|
44
|
-
const organizationUserData = await this.userRepository.findOrganizationUserById(
|
|
49
|
+
const organizationUserData = await this.userRepository.findOrganizationUserById(tokenUserId);
|
|
45
50
|
|
|
46
51
|
if (!organizationUserData) {
|
|
47
52
|
throw Boom.unauthorized('Organization User Not Found');
|
|
@@ -57,7 +62,7 @@ class GetUserFromBearerToken {
|
|
|
57
62
|
);
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
const individualUserData = await this.userRepository.findIndividualUserById(
|
|
65
|
+
const individualUserData = await this.userRepository.findIndividualUserById(tokenUserId);
|
|
61
66
|
|
|
62
67
|
if (!individualUserData) {
|
|
63
68
|
throw Boom.unauthorized('Individual User Not Found');
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
const Boom = require('@hapi/boom');
|
|
2
|
-
const { Module } = require('../module');
|
|
3
|
-
|
|
4
|
-
const sameId = (a, b) =>
|
|
5
|
-
a !== undefined &&
|
|
6
|
-
a !== null &&
|
|
7
|
-
b !== undefined &&
|
|
8
|
-
b !== null &&
|
|
9
|
-
String(a) === String(b);
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Creates (or finds) the entity for a credential the caller already holds:
|
|
13
|
-
* the v1 `POST /api/entity`.
|
|
14
|
-
*
|
|
15
|
-
* The module is built with the stored credential loaded, so the API can call
|
|
16
|
-
* the provider and a token refresh during `getEntityDetails` updates that
|
|
17
|
-
* credential. Persistence reuses `ProcessAuthorizationCallback.findOrCreateEntity`,
|
|
18
|
-
* the same find-or-create (and credential repointing) the authorization flow
|
|
19
|
-
* uses, so the two paths cannot drift.
|
|
20
|
-
*/
|
|
21
|
-
class CreateEntityFromCredential {
|
|
22
|
-
/**
|
|
23
|
-
* @param {Object} params
|
|
24
|
-
* @param {import('./process-authorization-callback').ProcessAuthorizationCallback} params.processAuthorizationCallback
|
|
25
|
-
* @param {Object} params.credentialRepository
|
|
26
|
-
* @param {Array<Object>} params.moduleDefinitions
|
|
27
|
-
* @param {Function} [params.createModule] - builds a Module; injectable for tests
|
|
28
|
-
*/
|
|
29
|
-
constructor({
|
|
30
|
-
processAuthorizationCallback,
|
|
31
|
-
credentialRepository,
|
|
32
|
-
moduleDefinitions,
|
|
33
|
-
createModule,
|
|
34
|
-
}) {
|
|
35
|
-
this.processAuthorizationCallback = processAuthorizationCallback;
|
|
36
|
-
this.credentialRepository = credentialRepository;
|
|
37
|
-
this.moduleDefinitions = moduleDefinitions || [];
|
|
38
|
-
this.createModule = createModule || ((params) => new Module(params));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @param {Object} params
|
|
43
|
-
* @param {Object} params.user - authenticated User
|
|
44
|
-
* @param {string} params.entityType
|
|
45
|
-
* @param {string} params.credentialId
|
|
46
|
-
* @returns {Promise<Object>} the persisted entity
|
|
47
|
-
*/
|
|
48
|
-
async execute({ user, entityType, credentialId }) {
|
|
49
|
-
const userId = user?.getId?.();
|
|
50
|
-
if (userId === undefined || userId === null) {
|
|
51
|
-
throw Boom.unauthorized('No authenticated user');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const definition = this.moduleDefinitions.find(
|
|
55
|
-
(def) => def && def.moduleName === entityType
|
|
56
|
-
);
|
|
57
|
-
if (!definition) {
|
|
58
|
-
throw Boom.badRequest(`Unknown entity type ${entityType}`, {
|
|
59
|
-
code: 'ENTITY_TYPE_NOT_FOUND',
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const credential =
|
|
64
|
-
await this.credentialRepository.findCredentialById(credentialId);
|
|
65
|
-
// A missing credential and someone else's get the same answer, so
|
|
66
|
-
// credential ids cannot be probed.
|
|
67
|
-
if (!credential || !sameId(credential.userId, userId)) {
|
|
68
|
-
throw Boom.badRequest('Invalid credential ID', {
|
|
69
|
-
code: 'INVALID_CREDENTIAL',
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// The repository returns the tokens flat; a Module reads them from
|
|
74
|
-
// `credential.data`.
|
|
75
|
-
const { id, userId: ownerId, externalId, authIsValid, ...data } =
|
|
76
|
-
credential;
|
|
77
|
-
const module = this.createModule({
|
|
78
|
-
userId,
|
|
79
|
-
definition,
|
|
80
|
-
entity: {
|
|
81
|
-
credential: { id, userId: ownerId, externalId, authIsValid, data },
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const entityDetails =
|
|
86
|
-
await definition.requiredAuthMethods.getEntityDetails(
|
|
87
|
-
module.api,
|
|
88
|
-
null,
|
|
89
|
-
null,
|
|
90
|
-
userId
|
|
91
|
-
);
|
|
92
|
-
Object.assign(
|
|
93
|
-
entityDetails.details,
|
|
94
|
-
module.apiParamsFromEntity(module.api)
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
return this.processAuthorizationCallback.findOrCreateEntity(
|
|
98
|
-
entityDetails,
|
|
99
|
-
definition.moduleName,
|
|
100
|
-
credential.id
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
module.exports = { CreateEntityFromCredential };
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The API module definition a stored entity belongs to.
|
|
3
|
-
*
|
|
4
|
-
* Entities are persisted with `moduleName` set to the definition's
|
|
5
|
-
* `moduleName` (see ProcessAuthorizationCallback.findOrCreateEntity). A
|
|
6
|
-
* definition's legacy `modelName` is accepted as well, for entities written
|
|
7
|
-
* before the two were the same.
|
|
8
|
-
*
|
|
9
|
-
* @param {Array<Object>} moduleDefinitions
|
|
10
|
-
* @param {Object} entity - entity record with a `moduleName`
|
|
11
|
-
* @returns {Object|undefined}
|
|
12
|
-
*/
|
|
13
|
-
function findModuleDefinitionForEntity(moduleDefinitions, entity) {
|
|
14
|
-
const entityType = entity?.moduleName;
|
|
15
|
-
if (!entityType) return undefined;
|
|
16
|
-
return (moduleDefinitions || []).find(
|
|
17
|
-
(def) =>
|
|
18
|
-
def &&
|
|
19
|
-
(def.moduleName === entityType || def.modelName === entityType)
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
module.exports = { findModuleDefinitionForEntity };
|