@friggframework/core 2.0.0--canary.684.0da024b.0 → 2.0.0--canary.679.a2bcc2f.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 (104) hide show
  1. package/CLAUDE.md +0 -2
  2. package/README.md +0 -105
  3. package/application/commands/integration-commands.js +2 -2
  4. package/application/commands/user-commands.js +5 -17
  5. package/credential/use-cases/credential-operations-for-user.js +25 -110
  6. package/credential/use-cases/reauthorize-credential-for-user.js +8 -15
  7. package/database/documentdb-utils.js +1 -6
  8. package/generated/prisma-mongodb/edge.js +4 -15
  9. package/generated/prisma-mongodb/index-browser.js +1 -12
  10. package/generated/prisma-mongodb/index.d.ts +86 -2027
  11. package/generated/prisma-mongodb/index.js +4 -15
  12. package/generated/prisma-mongodb/package.json +1 -1
  13. package/generated/prisma-mongodb/schema.prisma +0 -47
  14. package/generated/prisma-mongodb/wasm.js +4 -15
  15. package/generated/prisma-postgresql/edge.js +4 -15
  16. package/generated/prisma-postgresql/index-browser.js +1 -12
  17. package/generated/prisma-postgresql/index.d.ts +63 -2162
  18. package/generated/prisma-postgresql/index.js +4 -15
  19. package/generated/prisma-postgresql/package.json +1 -1
  20. package/generated/prisma-postgresql/schema.prisma +0 -44
  21. package/generated/prisma-postgresql/wasm.js +4 -15
  22. package/handlers/app-definition-loader.js +1 -15
  23. package/index.js +0 -18
  24. package/integrations/integration-router.js +73 -115
  25. package/integrations/repositories/integration-repository-documentdb.js +3 -35
  26. package/integrations/repositories/integration-repository-interface.js +1 -17
  27. package/integrations/repositories/integration-repository-mongo.js +76 -59
  28. package/integrations/repositories/integration-repository-postgres.js +76 -62
  29. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  30. package/integrations/use-cases/create-integration.js +20 -55
  31. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  32. package/integrations/use-cases/index.js +1 -5
  33. package/integrations/use-cases/integration-operations-for-user.js +24 -55
  34. package/management-api/management-api-config.js +0 -3
  35. package/management-api/openapi/components.js +0 -40
  36. package/management-api/openapi/v2-operations.js +1 -13
  37. package/management-api/v2/dependencies.js +15 -40
  38. package/management-api/v2/dto.js +0 -20
  39. package/management-api/v2/handlers/authorization.js +0 -1
  40. package/management-api/v2/handlers/credentials.js +2 -6
  41. package/management-api/v2/handlers/entities.js +7 -10
  42. package/management-api/v2/handlers/integrations.js +3 -6
  43. package/management-api/v2/request.js +1 -32
  44. package/modules/index.js +0 -8
  45. package/modules/repositories/module-repository-documentdb.js +4 -48
  46. package/modules/repositories/module-repository-interface.js +2 -19
  47. package/modules/repositories/module-repository-mongo.js +73 -65
  48. package/modules/repositories/module-repository-postgres.js +73 -64
  49. package/modules/use-cases/entity-operations-for-user.js +13 -50
  50. package/modules/use-cases/entity-types.js +7 -31
  51. package/modules/use-cases/execute-entity-proxy-request.js +0 -15
  52. package/modules/use-cases/get-entities-for-user.js +5 -16
  53. package/modules/use-cases/get-entity-module-for-user.js +3 -23
  54. package/modules/use-cases/get-entity-options-by-id.js +3 -9
  55. package/modules/use-cases/get-module.js +3 -9
  56. package/modules/use-cases/multi-step-authorization.js +12 -39
  57. package/modules/use-cases/process-authorization-callback.js +18 -52
  58. package/modules/use-cases/refresh-entity-options.js +3 -9
  59. package/modules/use-cases/test-module-auth.js +3 -9
  60. package/package.json +5 -6
  61. package/prisma-mongodb/schema.prisma +0 -47
  62. package/prisma-postgresql/schema.prisma +0 -44
  63. package/types/index.d.ts +0 -1
  64. package/types/modules/index.d.ts +0 -87
  65. package/user/repositories/user-repository-documentdb.js +2 -27
  66. package/user/repositories/user-repository-interface.js +0 -18
  67. package/user/repositories/user-repository-mongo.js +0 -24
  68. package/user/repositories/user-repository-postgres.js +0 -18
  69. package/user/use-cases/authenticate-user.js +1 -7
  70. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  71. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  72. package/user/user.js +1 -64
  73. package/database/organization-where.js +0 -18
  74. package/database/unique-violation.js +0 -25
  75. package/integrations/repositories/integration-not-found.js +0 -14
  76. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  77. package/modules/entity-scope.js +0 -15
  78. package/modules/scope-config.js +0 -192
  79. package/modules/scope-policy.js +0 -112
  80. package/modules/scope-resolution.js +0 -288
  81. package/modules/scope-visibility.js +0 -239
  82. package/modules/tests/doubles/ownership-matching.js +0 -18
  83. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  84. package/modules/use-cases/create-entity-from-credential.js +0 -113
  85. package/modules/use-cases/manage-access.js +0 -21
  86. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  87. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  88. package/types/user/index.d.ts +0 -134
  89. package/user/adopter-jwt-config.js +0 -177
  90. package/user/adopter-jwt-errors.js +0 -59
  91. package/user/adopter-token-verifier.js +0 -122
  92. package/user/app-user-helpers.js +0 -97
  93. package/user/create-user-authentication.js +0 -64
  94. package/user/membership.js +0 -51
  95. package/user/repositories/organization-membership-record.js +0 -19
  96. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  97. package/user/repositories/organization-membership-repository-factory.js +0 -42
  98. package/user/repositories/organization-membership-repository-interface.js +0 -57
  99. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  100. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  101. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  102. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  103. package/user/use-cases/ensure-organization-membership.js +0 -92
  104. package/user/use-cases/find-individual-users-by-organization.js +0 -54
@@ -108,26 +108,6 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
108
108
  return credentialMap;
109
109
  }
110
110
 
111
- /**
112
- * The one place a stored entity row becomes the entity the application sees.
113
- * The provider's `data` is spread FIRST and every column is pinned after it:
114
- * a stored `id`, `userId` or `organizationId` in `data` must not decide who
115
- * owns the entity or who sees it.
116
- * @private
117
- */
118
- _mapEntity(row, credential) {
119
- return {
120
- ...(row.data || {}),
121
- id: row.id.toString(),
122
- credential,
123
- userId: row.userId?.toString(),
124
- name: row.name,
125
- externalId: row.externalId,
126
- moduleName: row.moduleName,
127
- organizationId: row.organizationId?.toString() ?? null,
128
- };
129
- }
130
-
131
111
  /**
132
112
  * Find entity by ID with credential
133
113
  * Replaces: Entity.findById(entityId).populate('credential')
@@ -149,7 +129,15 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
149
129
 
150
130
  const credential = await this._fetchCredential(entity.credentialId);
151
131
 
152
- return this._mapEntity(entity, credential);
132
+ return {
133
+ id: entity.id.toString(),
134
+ credential,
135
+ userId: entity.userId?.toString(),
136
+ name: entity.name,
137
+ externalId: entity.externalId,
138
+ moduleName: entity.moduleName,
139
+ ...(entity.data || {}),
140
+ };
153
141
  }
154
142
 
155
143
  /**
@@ -169,7 +157,15 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
169
157
  const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
170
158
  const credentialMap = await this._fetchCredentialsBulk(credentialIds);
171
159
 
172
- return entities.map((e) => (this._mapEntity(e, credentialMap.get(e.credentialId) || null)));
160
+ return entities.map((e) => ({
161
+ id: e.id.toString(),
162
+ credential: credentialMap.get(e.credentialId) || null,
163
+ userId: e.userId?.toString(),
164
+ name: e.name,
165
+ externalId: e.externalId,
166
+ moduleName: e.moduleName,
167
+ ...(e.data || {}),
168
+ }));
173
169
  }
174
170
 
175
171
  /**
@@ -189,7 +185,15 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
189
185
  const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
190
186
  const credentialMap = await this._fetchCredentialsBulk(credentialIds);
191
187
 
192
- return entities.map((e) => (this._mapEntity(e, credentialMap.get(e.credentialId) || null)));
188
+ return entities.map((e) => ({
189
+ id: e.id.toString(),
190
+ credential: credentialMap.get(e.credentialId) || null,
191
+ userId: e.userId?.toString(),
192
+ name: e.name,
193
+ externalId: e.externalId,
194
+ moduleName: e.moduleName,
195
+ ...(e.data || {}),
196
+ }));
193
197
  }
194
198
 
195
199
  /**
@@ -213,32 +217,15 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
213
217
  const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
214
218
  const credentialMap = await this._fetchCredentialsBulk(credentialIds);
215
219
 
216
- return entities.map((e) => (this._mapEntity(e, credentialMap.get(e.credentialId) || null)));
217
- }
218
-
219
- /**
220
- * Find entities matching any of the owner/organization pairs, in one query.
221
- * `organizationId: null` matches rows with no organization. The caller
222
- * (a use case) decides which pairs; this only runs them.
223
- *
224
- * @param {Array<{userId: string, organizationId: (string|null)}>} clauses
225
- * @returns {Promise<Array>} Array of entity objects with string IDs
226
- */
227
- async findEntitiesByOwnership(clauses) {
228
- if (!Array.isArray(clauses) || clauses.length === 0) return [];
229
- const entities = await this.prisma.entity.findMany({
230
- where: {
231
- OR: clauses.map(({ userId, organizationId }) => ({
232
- userId: this._convertId(userId),
233
- organizationId: this._convertId(organizationId),
234
- })),
235
- },
236
- });
237
-
238
- const credentialIds = entities.map(e => e.credentialId).filter(Boolean);
239
- const credentialMap = await this._fetchCredentialsBulk(credentialIds);
240
-
241
- return entities.map((e) => (this._mapEntity(e, credentialMap.get(e.credentialId) || null)));
220
+ return entities.map((e) => ({
221
+ id: e.id.toString(),
222
+ credential: credentialMap.get(e.credentialId) || null,
223
+ userId: e.userId?.toString(),
224
+ name: e.name,
225
+ externalId: e.externalId,
226
+ moduleName: e.moduleName,
227
+ ...(e.data || {}),
228
+ }));
242
229
  }
243
230
 
244
231
  /**
@@ -277,7 +264,15 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
277
264
 
278
265
  const credential = await this._fetchCredential(entity.credentialId);
279
266
 
280
- return this._mapEntity(entity, credential);
267
+ return {
268
+ id: entity.id.toString(),
269
+ credential,
270
+ userId: entity.userId?.toString(),
271
+ name: entity.name,
272
+ externalId: entity.externalId,
273
+ moduleName: entity.moduleName,
274
+ ...(entity.data || {}),
275
+ };
281
276
  }
282
277
 
283
278
  /**
@@ -297,7 +292,15 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
297
292
  .filter(Boolean);
298
293
  const credentialMap = await this._fetchCredentialsBulk(credentialIds);
299
294
 
300
- return entities.map((e) => (this._mapEntity(e, credentialMap.get(e.credentialId) || null)));
295
+ return entities.map((e) => ({
296
+ id: e.id.toString(),
297
+ credential: credentialMap.get(e.credentialId) || null,
298
+ userId: e.userId?.toString(),
299
+ name: e.name,
300
+ externalId: e.externalId,
301
+ moduleName: e.moduleName,
302
+ ...(e.data || {}),
303
+ }));
301
304
  }
302
305
 
303
306
  /**
@@ -305,13 +308,9 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
305
308
  * Replaces: Entity.create(entityData)
306
309
  *
307
310
  * @param {Object} entityData - Entity data (with string IDs from application layer)
308
- * @param {{organizationId?: (string|null)}} [tenancy] - the organization the entity
309
- * belongs to (ADR-056 §3.6). A separate argument, not a field of `entityData`,
310
- * because `entityData` carries provider fields and one named `organizationId`
311
- * must not be taken for ours.
312
311
  * @returns {Promise<Object>} Created entity object with string IDs
313
312
  */
314
- async createEntity(entityData, tenancy = {}) {
313
+ async createEntity(entityData) {
315
314
  const {
316
315
  user,
317
316
  userId,
@@ -329,7 +328,6 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
329
328
  name,
330
329
  moduleName,
331
330
  externalId,
332
- organizationId: this._convertId(tenancy.organizationId ?? null),
333
331
  data: dynamicData,
334
332
  };
335
333
 
@@ -339,7 +337,15 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
339
337
 
340
338
  const credentialObj = await this._fetchCredential(entity.credentialId);
341
339
 
342
- return this._mapEntity(entity, credentialObj);
340
+ return {
341
+ id: entity.id.toString(),
342
+ credential: credentialObj,
343
+ userId: entity.userId?.toString(),
344
+ name: entity.name,
345
+ externalId: entity.externalId,
346
+ moduleName: entity.moduleName,
347
+ ...(entity.data || {}),
348
+ };
343
349
  }
344
350
 
345
351
  /**
@@ -395,7 +401,15 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
395
401
 
396
402
  const credentialObj = await this._fetchCredential(entity.credentialId);
397
403
 
398
- return this._mapEntity(entity, credentialObj);
404
+ return {
405
+ id: entity.id.toString(),
406
+ credential: credentialObj,
407
+ userId: entity.userId?.toString(),
408
+ name: entity.name,
409
+ externalId: entity.externalId,
410
+ moduleName: entity.moduleName,
411
+ ...(entity.data || {}),
412
+ };
399
413
  } catch (error) {
400
414
  if (error.code === 'P2025') {
401
415
  return null;
@@ -460,11 +474,6 @@ class ModuleRepositoryPostgres extends ModuleRepositoryInterface {
460
474
  if (filter.moduleName) where.moduleName = filter.moduleName;
461
475
  if (filter.externalId) where.externalId = this._toString(filter.externalId);
462
476
 
463
- // `null` means "no organization"; absent means "any".
464
- if (filter.organizationId !== undefined) {
465
- where.organizationId = this._convertId(filter.organizationId);
466
- }
467
-
468
477
  return where;
469
478
  }
470
479
  }
@@ -1,11 +1,4 @@
1
1
  const Boom = require('@hapi/boom');
2
- const {
3
- describeRecord,
4
- visibilityClauses,
5
- matchesListFilters,
6
- } = require('../scope-visibility');
7
- const { DEFAULT_SCOPE_POLICY } = require('../scope-policy');
8
- const { assertCanManage } = require('./manage-access');
9
2
 
10
3
  /**
11
4
  * Management API v2 entity operations. Each one starts from
@@ -13,55 +6,29 @@ const { assertCanManage } = require('./manage-access');
13
6
  * (missing or foreign entity = 404).
14
7
  */
15
8
 
16
- /**
17
- * The entities the caller may see in the active organization (ADR-056 §3.4,
18
- * §3.6): the organization's own, and the caller's individual ones by the
19
- * visibility rule. Each carries `tenancy` ({scope, owner, organizationId,
20
- * access}); the stored entity is otherwise untouched.
21
- */
9
+ /** The caller's connected entities. */
22
10
  class ListEntitiesForUser {
23
- constructor({ moduleRepository, scopePolicy }) {
11
+ constructor({ moduleRepository }) {
24
12
  this.moduleRepository = moduleRepository;
25
- this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
26
13
  }
27
14
 
28
- /**
29
- * @param {Object} user - authenticated User
30
- * @param {{scope?: string, owner?: string}} [filters] - `?scope=` and `?owner=`
31
- */
32
- async execute(user, filters = {}) {
33
- const ctx = this.scopePolicy.accessContext(user);
34
- const entities = await this.moduleRepository.findEntitiesByOwnership(
35
- visibilityClauses(ctx, 'entity')
15
+ /** @param {Object} user - authenticated User */
16
+ async execute(user) {
17
+ const entities = await this.moduleRepository.findEntitiesByUserId(
18
+ user.getId()
36
19
  );
37
- const listed = [];
38
- for (const entity of Array.isArray(entities) ? entities : []) {
39
- // The query is the filter; this is the same rule applied again,
40
- // so a repository that returns too much cannot widen the list.
41
- const tenancy = describeRecord(entity, ctx, 'entity');
42
- if (tenancy && matchesListFilters(tenancy, filters)) {
43
- listed.push({ ...entity, tenancy });
44
- }
45
- }
46
- return listed;
20
+ return Array.isArray(entities) ? entities : [];
47
21
  }
48
22
  }
49
23
 
50
- /** One entity the caller may use, with its `tenancy`. */
24
+ /** One entity the caller owns. */
51
25
  class GetEntityForUser {
52
26
  constructor({ getEntityModuleForUser }) {
53
27
  this.getEntityModuleForUser = getEntityModuleForUser;
54
28
  }
55
29
 
56
30
  async execute(entityId, user) {
57
- const entity = await this.getEntityModuleForUser.loadOwnedEntity(
58
- entityId,
59
- user
60
- );
61
- return {
62
- ...entity,
63
- tenancy: this.getEntityModuleForUser.describe(entity, user),
64
- };
31
+ return this.getEntityModuleForUser.loadOwnedEntity(entityId, user);
65
32
  }
66
33
  }
67
34
 
@@ -112,10 +79,10 @@ class RefreshEntityOptionsForUser {
112
79
  }
113
80
 
114
81
  /**
115
- * Deletes an entity the caller may manage (the owner; for an organization
116
- * entity, an organization admin). Refuses with 409 while integrations still
117
- * use it, so an integration never points at a missing entity. The credential
118
- * is left alone (others may share it); delete it through /api/v2/credentials.
82
+ * Deletes an entity the caller owns. Refuses with 409 while integrations
83
+ * still use it, so an integration never points at a missing entity. The
84
+ * credential is left alone (others may share it); delete it through
85
+ * /api/v2/credentials.
119
86
  */
120
87
  class DeleteEntityForUser {
121
88
  constructor({ getEntityModuleForUser, moduleRepository, integrationRepository }) {
@@ -129,10 +96,6 @@ class DeleteEntityForUser {
129
96
  entityId,
130
97
  user
131
98
  );
132
- assertCanManage(
133
- this.getEntityModuleForUser.describe(entity, user)?.access,
134
- `entity ${entity.id}`
135
- );
136
99
  const integrations =
137
100
  (await this.integrationRepository.findIntegrationsByEntityId(
138
101
  entity.id
@@ -59,31 +59,10 @@ function findDefinition(moduleDefinitions, entityType) {
59
59
  return definition;
60
60
  }
61
61
 
62
- /**
63
- * With a scope policy the entry also says which scope a new connection gets
64
- * and which a request may choose (ADR-056 §3.1), so a UI can offer the choice.
65
- * `global` is never offered: a tenant request cannot create one.
66
- */
67
- const scopeOf = (scopePolicy, name, user) => {
68
- if (!scopePolicy) return {};
69
- const { scope, source, allow } = scopePolicy.resolveScope({
70
- entityType: name,
71
- user,
72
- });
73
- return {
74
- scope: {
75
- default: scope,
76
- allow: allow.filter((value) => value !== 'global'),
77
- source,
78
- },
79
- };
80
- };
81
-
82
- const describeEntry = (name, entry, scopePolicy, user) => ({
62
+ const describeEntry = (name, entry) => ({
83
63
  // The registry key is the name the API uses, whatever the definition spells.
84
64
  ...describeEntityType(entry.definition, name),
85
65
  usedBy: usedByOf(entry),
86
- ...scopeOf(scopePolicy, name, user),
87
66
  });
88
67
 
89
68
  function requireRegistry(entityTypeRegistry) {
@@ -99,34 +78,31 @@ function requireRegistry(entityTypeRegistry) {
99
78
  * bind each type; it is empty for a registry-only type.
100
79
  */
101
80
  class ListEntityTypes {
102
- constructor({ entityTypeRegistry, scopePolicy }) {
81
+ constructor({ entityTypeRegistry }) {
103
82
  this.registry = requireRegistry(entityTypeRegistry);
104
- this.scopePolicy = scopePolicy;
105
83
  }
106
84
 
107
- /** @param {{user?: Object}} [params] - the caller, for the level-6 scope default */
108
- async execute({ user } = {}) {
85
+ async execute() {
109
86
  return [...this.registry]
110
- .map(([name, entry]) => describeEntry(name, entry, this.scopePolicy, user))
87
+ .map(([name, entry]) => describeEntry(name, entry))
111
88
  .sort((a, b) => a.name.localeCompare(b.name));
112
89
  }
113
90
  }
114
91
 
115
92
  /** Describes one entity type, looked up by its registry key; 404 when absent. */
116
93
  class GetEntityType {
117
- constructor({ entityTypeRegistry, scopePolicy }) {
94
+ constructor({ entityTypeRegistry }) {
118
95
  this.registry = requireRegistry(entityTypeRegistry);
119
- this.scopePolicy = scopePolicy;
120
96
  }
121
97
 
122
- async execute(entityType, { user } = {}) {
98
+ async execute(entityType) {
123
99
  const entry = this.registry.get(entityType);
124
100
  if (!entry) {
125
101
  throw Boom.notFound(`Entity type '${entityType}' not found`, {
126
102
  code: 'ENTITY_TYPE_NOT_FOUND',
127
103
  });
128
104
  }
129
- return describeEntry(entityType, entry, this.scopePolicy, user);
105
+ return describeEntry(entityType, entry);
130
106
  }
131
107
  }
132
108
 
@@ -98,24 +98,9 @@ class ExecuteEntityProxyRequest {
98
98
  audit.requestBytes = validated.requestBytes;
99
99
  const path = normalizeProxyPath(validated.path);
100
100
 
101
- // Access is `use` in the active organization (ADR-056 §3.5, §3.6): the
102
- // owner's entity there, or the organization's. A `global` entity is
103
- // never the tenant's.
104
101
  const { entity, definition, module } =
105
102
  await this.getEntityModuleForUser.execute(entityId, user);
106
103
  audit.module = definition.moduleName;
107
- // Narrowest by default (§3.5): proxying through a shared credential
108
- // lets any member act on the organization's account, so only an
109
- // individual entity may be proxied until `managementApi.proxy.scopes`
110
- // lets an app opt in to `organization`.
111
- // TODO(F6): read proxy.scopes here instead of refusing outright.
112
- if (this.getEntityModuleForUser.describe(entity, user)?.scope !== 'individual') {
113
- throw new ProxyRequestError(
114
- 'PROXY_NOT_ALLOWED',
115
- 'The entity proxy only accepts individual entities; organization-level entities are not proxied',
116
- { module: definition.moduleName, reason: 'scope' }
117
- );
118
- }
119
104
  const credentialRef = entity.credential;
120
105
  const credentialId =
121
106
  credentialRef && typeof credentialRef === 'object'
@@ -1,11 +1,8 @@
1
1
  const { Module } = require('../module');
2
2
  const { mapModuleClassToModuleDTO } = require('../utils/map-module-dto');
3
- const { describeRecord, visibilityClauses } = require('../scope-visibility');
4
- const { DEFAULT_SCOPE_POLICY } = require('../scope-policy');
5
3
 
6
4
  class GetEntitiesForUser {
7
- constructor({ moduleRepository, moduleDefinitions, scopePolicy }) {
8
- this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
5
+ constructor({ moduleRepository, moduleDefinitions }) {
9
6
  this.moduleRepository = moduleRepository;
10
7
 
11
8
  this.definitionMap = new Map();
@@ -14,24 +11,16 @@ class GetEntitiesForUser {
14
11
  }
15
12
  }
16
13
 
17
- /**
18
- * The entities a `User` may see in the active organization (ADR-056 §3.6).
19
- * @param {Object} user - authenticated User
20
- */
21
- async execute(user) {
22
- const ctx = this.scopePolicy.accessContext(user);
23
- const found = await this.moduleRepository.findEntitiesByOwnership(
24
- visibilityClauses(ctx, 'entity')
25
- );
26
- const entities = (found || []).filter((entity) =>
27
- describeRecord(entity, ctx, 'entity')
14
+ async execute(userId) {
15
+ const entities = await this.moduleRepository.findEntitiesByUserId(
16
+ userId
28
17
  );
29
18
 
30
19
  return entities.map((entity) => {
31
20
  const definition = this.definitionMap.get(entity.moduleName);
32
21
 
33
22
  const moduleInstance = new Module({
34
- userId: entity.userId,
23
+ userId,
35
24
  definition: definition,
36
25
  entity: entity,
37
26
  });
@@ -1,13 +1,8 @@
1
1
  const Boom = require('@hapi/boom');
2
- const { describeRecord } = require('../scope-visibility');
3
- const { DEFAULT_SCOPE_POLICY } = require('../scope-policy');
4
2
 
5
3
  /**
6
4
  * True when `user` may act on records owned by `ownerId`. Uses the User
7
5
  * domain object's rule (individual or organisation user model) when present.
8
- * This is the owner-only rule: it ignores the organization a record belongs
9
- * to, so it no longer decides access to an entity or an integration
10
- * (modules/scope-visibility.js does). Credentials still use it.
11
6
  * @param {Object} user - authenticated User (or a plain { getId } double)
12
7
  * @param {string|number} ownerId
13
8
  */
@@ -35,11 +30,8 @@ class GetEntityModuleForUser {
35
30
  * @param {import('../repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
36
31
  * @param {Array<Object>} params.moduleDefinitions
37
32
  * @param {Function} [params.createModule] - builds a Module; injectable for tests.
38
- * @param {Object} [params.scopePolicy] - from createScopePolicy; with none the
39
- * app configures nothing (ADR-056 §3.1).
40
33
  */
41
- constructor({ moduleRepository, moduleDefinitions, createModule, scopePolicy }) {
42
- this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
34
+ constructor({ moduleRepository, moduleDefinitions, createModule }) {
43
35
  this.moduleRepository = moduleRepository;
44
36
  this.moduleDefinitions = moduleDefinitions || [];
45
37
  this.createModule = createModule || defaultCreateModule;
@@ -69,11 +61,7 @@ class GetEntityModuleForUser {
69
61
  return { entity, definition, module };
70
62
  }
71
63
 
72
- /**
73
- * The entity record, or a 404 when it is missing or the caller may not use
74
- * it: not theirs, or paired with another organization (ADR-056 §3.6). A
75
- * `global` entity is never the tenant's to load.
76
- */
64
+ /** The entity record, or a 404 when it is missing or not the caller's. */
77
65
  async loadOwnedEntity(entityId, user) {
78
66
  let entity = null;
79
67
  try {
@@ -86,21 +74,13 @@ class GetEntityModuleForUser {
86
74
  throw error;
87
75
  }
88
76
  }
89
- if (!entity || !this.describe(entity, user)) {
77
+ if (!entity || !userOwns(user, entity.userId)) {
90
78
  throw Boom.notFound(`Entity ${entityId} not found`, {
91
79
  code: 'ENTITY_NOT_FOUND',
92
80
  });
93
81
  }
94
82
  return entity;
95
83
  }
96
-
97
- /**
98
- * `{ scope, owner, organizationId, access }` of an entity to this caller,
99
- * or null when they may not see it (§3.4).
100
- */
101
- describe(entity, user) {
102
- return describeRecord(entity, this.scopePolicy.accessContext(user), 'entity');
103
- }
104
84
  }
105
85
 
106
86
  module.exports = { GetEntityModuleForUser, userOwns };
@@ -1,5 +1,4 @@
1
1
  const { Module } = require('../module');
2
- const { DEFAULT_SCOPE_POLICY } = require('../scope-policy');
3
2
 
4
3
  class GetEntityOptionsById {
5
4
  /**
@@ -7,11 +6,9 @@ class GetEntityOptionsById {
7
6
  * @param {import('../repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
8
7
  * @param {} params.moduleDefinitions
9
8
  */
10
- constructor({ moduleRepository, moduleDefinitions, scopePolicy }) {
9
+ constructor({ moduleRepository, moduleDefinitions }) {
11
10
  this.moduleRepository = moduleRepository;
12
11
  this.moduleDefinitions = moduleDefinitions;
13
- // Who may load an entity follows its scope (ADR-056 §3.6).
14
- this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
15
12
  }
16
13
 
17
14
  /**
@@ -38,7 +35,7 @@ class GetEntityOptionsById {
38
35
 
39
36
  // Validate entity ownership
40
37
  const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
41
- ? this.scopePolicy.canUseEntity(userIdOrUser, entity)
38
+ ? userIdOrUser.ownsUserId(entity.userId)
42
39
  : entity.userId?.toString() === userId?.toString();
43
40
 
44
41
  if (!isOwned) {
@@ -60,11 +57,8 @@ class GetEntityOptionsById {
60
57
  );
61
58
  }
62
59
 
63
- // Built for the entity's OWNER, not the caller: a caller may use an
64
- // organization's entity, and a token refresh during this call must
65
- // update the owner's credential, not create one under the caller.
66
60
  const module = new Module({
67
- userId: entity.userId ?? userId,
61
+ userId,
68
62
  entity,
69
63
  definition: moduleDefinition,
70
64
  });
@@ -1,12 +1,9 @@
1
1
  const { Module } = require('../module');
2
- const { DEFAULT_SCOPE_POLICY } = require('../scope-policy');
3
2
 
4
3
  class GetModule {
5
- constructor({ moduleRepository, moduleDefinitions, scopePolicy }) {
4
+ constructor({ moduleRepository, moduleDefinitions }) {
6
5
  this.moduleRepository = moduleRepository;
7
6
  this.moduleDefinitions = moduleDefinitions;
8
- // Who may load an entity follows its scope (ADR-056 §3.6).
9
- this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
10
7
  }
11
8
 
12
9
  /**
@@ -35,7 +32,7 @@ class GetModule {
35
32
  // If User object provided, use ownsUserId to check linked users
36
33
  // Otherwise fall back to simple equality check
37
34
  const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
38
- ? this.scopePolicy.canUseEntity(userIdOrUser, entity)
35
+ ? userIdOrUser.ownsUserId(entity.userId)
39
36
  : entity.userId?.toString() === userId?.toString();
40
37
 
41
38
  if (!isOwned) {
@@ -56,11 +53,8 @@ class GetModule {
56
53
  );
57
54
  }
58
55
 
59
- // Built for the entity's OWNER, not the caller: a caller may use an
60
- // organization's entity, and a token refresh during this call must
61
- // update the owner's credential, not create one under the caller.
62
56
  const module = new Module({
63
- userId: entity.userId ?? userId,
57
+ userId,
64
58
  entity,
65
59
  definition: moduleDefinition,
66
60
  });