@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.
Files changed (48) hide show
  1. package/CLAUDE.md +41 -1
  2. package/application/commands/README.md +17 -11
  3. package/application/commands/entity-commands.js +18 -60
  4. package/application/commands/integration-commands.js +63 -0
  5. package/database/documentdb-encryption-service.js +2 -33
  6. package/database/documentdb-utils.js +4 -1
  7. package/database/encryption/README.md +3 -3
  8. package/database/encryption/ciphertext-format.js +24 -0
  9. package/database/encryption/encryption-schema-registry.js +109 -29
  10. package/database/encryption/field-encryption-service.js +13 -16
  11. package/database/prisma.js +10 -1
  12. package/errors/index.js +4 -0
  13. package/errors/mapping-already-exists-error.js +14 -0
  14. package/handlers/WEBHOOKS.md +29 -0
  15. package/index.js +2 -0
  16. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +67 -13
  17. package/integrations/integration-base.js +29 -0
  18. package/integrations/integration-router.js +22 -18
  19. package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
  20. package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
  21. package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
  22. package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
  23. package/integrations/repositories/integration-mapping-repository.js +29 -0
  24. package/integrations/repositories/integration-repository-documentdb.js +32 -0
  25. package/integrations/repositories/integration-repository-interface.js +30 -0
  26. package/integrations/repositories/integration-repository-mongo.js +53 -1
  27. package/integrations/repositories/integration-repository-postgres.js +58 -0
  28. package/integrations/repositories/process-repository-mongo.js +1 -1
  29. package/integrations/use-cases/add-entities-to-integration.js +92 -0
  30. package/integrations/use-cases/create-integration.js +57 -19
  31. package/integrations/use-cases/remove-entities-from-integration.js +70 -0
  32. package/integrations/utils/assert-entity-ids-input.js +42 -0
  33. package/jest-setup.js +13 -2
  34. package/jest-teardown.js +5 -2
  35. package/jest.config.js +4 -0
  36. package/jest.db.config.js +23 -0
  37. package/modules/use-cases/get-entity-options-by-id.js +5 -7
  38. package/modules/use-cases/get-module.js +8 -29
  39. package/modules/use-cases/refresh-entity-options.js +5 -7
  40. package/modules/use-cases/test-module-auth.js +5 -7
  41. package/modules/utils/map-module-dto.js +5 -3
  42. package/modules/utils/to-credential-id.js +14 -0
  43. package/package.json +7 -5
  44. package/queues/queuer-util.js +6 -5
  45. package/user/tests/doubles/test-user-repository.js +1 -1
  46. package/user/use-cases/get-user-from-bearer-token.js +7 -2
  47. package/modules/use-cases/create-entity-from-credential.js +0 -105
  48. 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 = findModuleDefinitionForEntity(
52
- this.moduleDefinitions,
53
- entity
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?._id?.toString(),
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.667.f74cf3b.0",
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.667.f74cf3b.0",
51
- "@friggframework/prettier-config": "2.0.0--canary.667.f74cf3b.0",
52
- "@friggframework/test": "2.0.0--canary.667.f74cf3b.0",
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": "f74cf3b1f04195508cb0ee6d4086b1cb2d70d913"
94
+ "gitHead": "7cfcf1267a412053163b99b7f9a123f7e4e1bde3"
93
95
  }
@@ -97,16 +97,17 @@ const QueuerUtil = {
97
97
  Id: uuid(),
98
98
  MessageBody: JSON.stringify(entry),
99
99
  });
100
- // Sends 10, then purges the buffer
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: buffer,
106
+ Entries: batch,
104
107
  QueueUrl: queueUrl,
105
108
  });
106
109
  const result = await sqs.send(command);
107
- inspectBatchResult(result, queueUrl, buffer);
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, { user: userId, rawToken });
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(sessionToken.user);
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(sessionToken.user);
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 };