@friggframework/core 2.0.0--canary.685.a13fdab.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 (106) hide show
  1. package/CLAUDE.md +0 -25
  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 -24
  23. package/index.js +0 -22
  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 +2 -71
  35. package/management-api/openapi/components.js +0 -40
  36. package/management-api/openapi/v2-operations.js +4 -16
  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 +6 -51
  46. package/modules/repositories/module-repository-interface.js +2 -19
  47. package/modules/repositories/module-repository-mongo.js +74 -67
  48. package/modules/repositories/module-repository-postgres.js +74 -66
  49. package/modules/repositories/module-repository.js +1 -2
  50. package/modules/use-cases/entity-operations-for-user.js +13 -50
  51. package/modules/use-cases/entity-types.js +7 -31
  52. package/modules/use-cases/execute-entity-proxy-request.js +3 -50
  53. package/modules/use-cases/get-entities-for-user.js +5 -16
  54. package/modules/use-cases/get-entity-module-for-user.js +5 -32
  55. package/modules/use-cases/get-entity-options-by-id.js +3 -9
  56. package/modules/use-cases/get-module.js +3 -9
  57. package/modules/use-cases/multi-step-authorization.js +12 -39
  58. package/modules/use-cases/process-authorization-callback.js +18 -52
  59. package/modules/use-cases/refresh-entity-options.js +3 -9
  60. package/modules/use-cases/test-module-auth.js +3 -9
  61. package/package.json +5 -6
  62. package/prisma-mongodb/schema.prisma +0 -47
  63. package/prisma-postgresql/schema.prisma +0 -44
  64. package/types/index.d.ts +0 -1
  65. package/types/modules/index.d.ts +0 -129
  66. package/user/repositories/user-repository-documentdb.js +2 -27
  67. package/user/repositories/user-repository-interface.js +0 -18
  68. package/user/repositories/user-repository-mongo.js +0 -24
  69. package/user/repositories/user-repository-postgres.js +0 -18
  70. package/user/use-cases/authenticate-user.js +1 -7
  71. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  72. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  73. package/user/user.js +1 -64
  74. package/database/organization-where.js +0 -18
  75. package/database/unique-violation.js +0 -25
  76. package/integrations/repositories/integration-not-found.js +0 -14
  77. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  78. package/modules/entity-scope.js +0 -15
  79. package/modules/repositories/entity-not-found.js +0 -18
  80. package/modules/scope-config.js +0 -192
  81. package/modules/scope-policy.js +0 -112
  82. package/modules/scope-resolution.js +0 -288
  83. package/modules/scope-visibility.js +0 -247
  84. package/modules/tests/doubles/ownership-matching.js +0 -18
  85. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  86. package/modules/use-cases/create-entity-from-credential.js +0 -113
  87. package/modules/use-cases/manage-access.js +0 -21
  88. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  89. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  90. package/types/user/index.d.ts +0 -134
  91. package/user/adopter-jwt-config.js +0 -177
  92. package/user/adopter-jwt-errors.js +0 -59
  93. package/user/adopter-token-verifier.js +0 -122
  94. package/user/app-user-helpers.js +0 -97
  95. package/user/create-user-authentication.js +0 -64
  96. package/user/membership.js +0 -51
  97. package/user/repositories/organization-membership-record.js +0 -19
  98. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  99. package/user/repositories/organization-membership-repository-factory.js +0 -42
  100. package/user/repositories/organization-membership-repository-interface.js +0 -57
  101. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  102. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  103. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  104. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  105. package/user/use-cases/ensure-organization-membership.js +0 -92
  106. package/user/use-cases/find-individual-users-by-organization.js +0 -54
@@ -2,17 +2,9 @@
2
2
  const {
3
3
  mapIntegrationClassToIntegrationDTO,
4
4
  } = require('../utils/map-integration-dto');
5
- const {
6
- describeRecord,
7
- visibilityClauses,
8
- matchesListFilters,
9
- } = require('../../modules/scope-visibility');
10
- const { DEFAULT_SCOPE_POLICY } = require('../../modules/scope-policy');
11
5
 
12
6
  /**
13
- * Use case for retrieving the integrations a caller may see: the ones they own
14
- * or their active organization owns, created in that organization (ADR-056
15
- * §3.6), each with `tenancy` ({scope, owner, organizationId, access}).
7
+ * Use case for retrieving all integrations for a specific user.
16
8
  * @class GetIntegrationsForUser
17
9
  */
18
10
  class GetIntegrationsForUser {
@@ -23,16 +15,13 @@ class GetIntegrationsForUser {
23
15
  * @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes.
24
16
  * @param {import('../../modules/module-factory').ModuleFactory} params.moduleFactory - Service for module instantiation and management.
25
17
  * @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module and entity data operations.
26
- * @param {Object} [params.scopePolicy] - from createScopePolicy
27
18
  */
28
19
  constructor({
29
20
  integrationRepository,
30
21
  integrationClasses,
31
22
  moduleFactory,
32
23
  moduleRepository,
33
- scopePolicy,
34
24
  }) {
35
- this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
36
25
  /**
37
26
  * @type {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface}
38
27
  */
@@ -43,86 +32,57 @@ class GetIntegrationsForUser {
43
32
  }
44
33
 
45
34
  /**
35
+ * Executes the retrieval of all integrations for a user.
46
36
  * @async
47
- * @param {Object} user - authenticated User
48
- * @param {{scope?: string, owner?: string}} [filters] - `?scope=` and `?owner=`
49
- * @returns {Promise<Object[]>} Array of integration DTOs.
37
+ * @param {string} userId - ID of the user whose integrations to retrieve.
38
+ * @returns {Promise<Object[]>} Array of integration DTOs for the specified user.
50
39
  */
51
- async execute(user, filters = {}) {
52
- const ctx = this.scopePolicy.accessContext(user);
53
- const records = await this.integrationRepository.findIntegrationsByOwnership(
54
- visibilityClauses(ctx, 'integration')
55
- );
40
+ async execute(userId) {
41
+ const integrationRecords =
42
+ await this.integrationRepository.findIntegrationsByUserId(userId);
56
43
 
57
44
  const integrations = [];
58
- for (const record of records) {
59
- // The query is the filter; the same rule again means a repository
60
- // that returns too much cannot widen the list.
61
- const tenancy = describeRecord(record, ctx, 'integration');
62
- if (!tenancy || !matchesListFilters(tenancy, filters)) continue;
63
- integrations.push({ ...(await this.toDto(record)), tenancy });
64
- }
65
- return integrations;
66
- }
67
45
 
68
- /** Hydrates one stored integration record into its DTO. */
69
- async toDto(integrationRecord) {
70
- const entities = await this.moduleRepository.findEntitiesByIds(
71
- integrationRecord.entitiesIds
72
- );
73
-
74
- const integrationClass = this.integrationClasses.find(
75
- (integrationClass) =>
76
- integrationClass.Definition.name ===
77
- integrationRecord.config.type
78
- );
46
+ for (const integrationRecord of integrationRecords) {
47
+ const entities = await this.moduleRepository.findEntitiesByIds(
48
+ integrationRecord.entitiesIds
49
+ );
79
50
 
80
- const modules = [];
81
- for (const entity of entities) {
82
- const moduleInstance = await this.moduleFactory.getModuleInstance(
83
- entity.id,
84
- integrationRecord.userId
51
+ const integrationClass = this.integrationClasses.find(
52
+ (integrationClass) =>
53
+ integrationClass.Definition.name ===
54
+ integrationRecord.config.type
85
55
  );
86
- modules.push(moduleInstance);
87
- }
88
56
 
89
- return mapIntegrationClassToIntegrationDTO({
90
- id: integrationRecord.id,
91
- userId: integrationRecord.userId,
92
- entities: entities,
93
- config: integrationRecord.config,
94
- status: integrationRecord.status,
95
- version: integrationRecord.version,
96
- messages: integrationRecord.messages || { errors: [], warnings: [] },
97
- modules,
98
- options: integrationClass.getOptionDetails(),
99
- });
100
- }
101
- }
57
+ const modules = [];
58
+ for (const entity of entities) {
59
+ const moduleInstance =
60
+ await this.moduleFactory.getModuleInstance(
61
+ entity.id,
62
+ integrationRecord.userId
63
+ );
64
+ modules.push(moduleInstance);
65
+ }
102
66
 
103
- /**
104
- * Every integration one user id owns, with no organization rules: the lookup
105
- * behind the app-level `friggCommands.findIntegrationsByUserId`, which has a
106
- * user id and no request.
107
- */
108
- class GetIntegrationsByUserId {
109
- /** @param {ConstructorParameters<typeof GetIntegrationsForUser>[0]} params */
110
- constructor(params) {
111
- this.integrationRepository = params.integrationRepository;
112
- this.hydrator = new GetIntegrationsForUser(params);
113
- }
67
+ const integrationData = {
68
+ id: integrationRecord.id,
69
+ userId: integrationRecord.userId,
70
+ entities: entities,
71
+ config: integrationRecord.config,
72
+ status: integrationRecord.status,
73
+ version: integrationRecord.version,
74
+ messages: integrationRecord.messages || { errors: [], warnings: [] },
75
+ modules,
76
+ options: integrationClass.getOptionDetails(),
77
+ };
114
78
 
115
- /** @param {string} userId */
116
- async execute(userId) {
117
- const records = await this.integrationRepository.findIntegrationsByUserId(
118
- userId
119
- );
120
- const integrations = [];
121
- for (const record of records) {
122
- integrations.push(await this.hydrator.toDto(record));
79
+ integrations.push(
80
+ mapIntegrationClassToIntegrationDTO(integrationData)
81
+ );
123
82
  }
83
+
124
84
  return integrations;
125
85
  }
126
86
  }
127
87
 
128
- module.exports = { GetIntegrationsForUser, GetIntegrationsByUserId };
88
+ module.exports = { GetIntegrationsForUser };
@@ -1,7 +1,4 @@
1
- const {
2
- GetIntegrationsForUser,
3
- GetIntegrationsByUserId,
4
- } = require('./get-integrations-for-user');
1
+ const { GetIntegrationsForUser } = require('./get-integrations-for-user');
5
2
  const { DeleteIntegrationForUser } = require('./delete-integration-for-user');
6
3
  const { CreateIntegration } = require('./create-integration');
7
4
  const { GetIntegration } = require('./get-integration');
@@ -12,7 +9,6 @@ const { GetProcess } = require('./get-process');
12
9
 
13
10
  module.exports = {
14
11
  GetIntegrationsForUser,
15
- GetIntegrationsByUserId,
16
12
  DeleteIntegrationForUser,
17
13
  CreateIntegration,
18
14
  GetIntegration,
@@ -1,15 +1,11 @@
1
1
  const Boom = require('@hapi/boom');
2
- const { describeRecord } = require('../../modules/scope-visibility');
3
- const { DEFAULT_SCOPE_POLICY } = require('../../modules/scope-policy');
4
- const { assertCanManage } = require('../../modules/use-cases/manage-access');
5
- const { isIntegrationNotFound } = require('../repositories/integration-not-found');
2
+ const { userOwns } = require('../../modules/use-cases/get-entity-module-for-user');
6
3
 
7
4
  /**
8
- * Management API v2 integration operations. They put an access check
9
- * (missing or unavailable integration = 404) in front of the existing
10
- * integration use cases, and pass the record's owner id on. Access follows
11
- * the owner and the organization the integration was created in (ADR-056
12
- * §3.6), not the owner alone.
5
+ * Management API v2 integration operations. They put an ownership check
6
+ * (missing or foreign integration = 404) in front of the existing
7
+ * integration use cases, and pass the record's owner id on, so the
8
+ * organisation user model works the same as for entities.
13
9
  */
14
10
 
15
11
  const notFound = (integrationId) =>
@@ -17,61 +13,40 @@ const notFound = (integrationId) =>
17
13
  code: 'INTEGRATION_NOT_FOUND',
18
14
  });
19
15
 
20
- /**
21
- * The integration record, or 404 when it is missing or the caller may not
22
- * use it: not theirs or their active organization's, or created in another
23
- * organization.
24
- */
16
+ /** The integration record, or 404 when it is missing or not the caller's. */
25
17
  class GetOwnedIntegrationRecord {
26
- constructor({ integrationRepository, scopePolicy }) {
18
+ constructor({ integrationRepository }) {
27
19
  this.integrationRepository = integrationRepository;
28
- this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
29
20
  }
30
21
 
31
- /** `{ scope, owner, organizationId, access }` of a record to this caller, or null. */
32
- describe(record, user) {
33
- return describeRecord(record, this.scopePolicy.accessContext(user), 'integration');
34
- }
35
-
36
- /**
37
- * @param {string} integrationId
38
- * @param {Object} user - authenticated User
39
- * @param {{manage?: boolean}} [options] - `manage: true` for changing or
40
- * deleting: a person may use an organization's integration but only an
41
- * admin may change it (403 ORG_ADMIN_REQUIRED)
42
- */
43
- async execute(integrationId, user, { manage = false } = {}) {
22
+ async execute(integrationId, user) {
44
23
  let record = null;
45
24
  try {
46
25
  record = await this.integrationRepository.findIntegrationById(
47
26
  integrationId
48
27
  );
49
28
  } catch (error) {
50
- if (
51
- /Invalid ID|ObjectId|Malformed/i.test(error?.message || '') ||
52
- isIntegrationNotFound(error)
53
- ) {
29
+ if (/Invalid ID|ObjectId|Malformed/i.test(error?.message || '')) {
54
30
  throw notFound(integrationId);
55
31
  }
56
32
  throw error;
57
33
  }
58
- const tenancy = record && this.describe(record, user);
59
- if (!tenancy) throw notFound(integrationId);
60
- if (manage) assertCanManage(tenancy.access, `integration ${integrationId}`);
34
+ if (!record || !userOwns(user, record.userId)) {
35
+ throw notFound(integrationId);
36
+ }
61
37
  return record;
62
38
  }
63
39
  }
64
40
 
65
41
  /**
66
- * Creates (or reuses) an integration between entities the caller may use.
67
- * The entities' access, the binding scopes and the integration type are
68
- * checked before anything is written, and the integration is owned and
69
- * paired with an organization by `resolveIntegrationOwnership` (§3, §3.6).
42
+ * Creates (or reuses) an integration between entities the caller owns.
43
+ * Unlike v1, the entities' ownership and the integration type are checked
44
+ * before anything is written.
70
45
  */
71
46
  class CreateIntegrationForUser {
72
- constructor({ createIntegration, resolveIntegrationOwnership, integrationClasses }) {
47
+ constructor({ createIntegration, getEntityModuleForUser, integrationClasses }) {
73
48
  this.createIntegration = createIntegration;
74
- this.resolveIntegrationOwnership = resolveIntegrationOwnership;
49
+ this.getEntityModuleForUser = getEntityModuleForUser;
75
50
  this.integrationClasses = integrationClasses || [];
76
51
  }
77
52
 
@@ -99,17 +74,13 @@ class CreateIntegrationForUser {
99
74
  details: { field: 'entities' },
100
75
  });
101
76
  }
102
- const { ownerId, organizationId, adoptUnscoped } =
103
- await this.resolveIntegrationOwnership.execute({
104
- entityIds: entities,
105
- config,
106
- user,
107
- });
77
+ for (const entityId of entities) {
78
+ await this.getEntityModuleForUser.loadOwnedEntity(String(entityId), user);
79
+ }
108
80
  return this.createIntegration.execute(
109
81
  entities.map(String),
110
- ownerId,
111
- config,
112
- { organizationId, adoptUnscoped }
82
+ user.getId(),
83
+ config
113
84
  );
114
85
  }
115
86
  }
@@ -146,8 +117,7 @@ class UpdateIntegrationForUser {
146
117
  }
147
118
  const record = await this.getOwnedIntegrationRecord.execute(
148
119
  integrationId,
149
- user,
150
- { manage: true }
120
+ user
151
121
  );
152
122
  return this.updateIntegration.execute(record.id, record.userId, config);
153
123
  }
@@ -163,8 +133,7 @@ class RemoveIntegrationForUser {
163
133
  async execute(integrationId, user) {
164
134
  const record = await this.getOwnedIntegrationRecord.execute(
165
135
  integrationId,
166
- user,
167
- { manage: true }
136
+ user
168
137
  );
169
138
  await this.deleteIntegrationForUser.execute(record.id, record.userId);
170
139
  }
@@ -9,7 +9,6 @@
9
9
  * timeoutMs: 25000, // whole upstream call, lower only
10
10
  * maxRequestBytes: 1048576,
11
11
  * maxResponseBytes: 5242880, // lower only
12
- * scopes: ['individual'], // entity scopes the proxy accepts (ADR-056 §3.5)
13
12
  * modules: { // per-module allow-list override
14
13
  * hubspot: { allow: 'module' },
15
14
  * erp: { allow: [{ method: 'GET', path: '/v1/customers/**' }] },
@@ -17,36 +16,7 @@
17
16
  * },
18
17
  * }
19
18
  * ```
20
- *
21
- * Limits that are out of range fall back to their defaults. `proxy.scopes` is a
22
- * security setting, so it does not fall back: a value that is not a list of
23
- * known tenant scopes throws ManagementApiConfigError, and loadAppDefinition
24
- * calls this so the app stops at load.
25
- */
26
-
27
- const { BaseError } = require('../errors/base-error');
28
- const { SCOPES } = require('../modules/scope-resolution');
29
-
30
- /** Raised for an invalid `appDefinition.managementApi` setting. */
31
- class ManagementApiConfigError extends BaseError {
32
- constructor(message, options) {
33
- super(message, options);
34
- this.code = 'MANAGEMENT_API_CONFIG_INVALID';
35
- }
36
- }
37
-
38
- /**
39
- * The scopes `managementApi.proxy.scopes` may list. `global` is not one of
40
- * them: a global entity is the app owner's and is never a tenant's to proxy.
41
19
  */
42
- const PROXY_SCOPE_VALUES = Object.freeze([SCOPES.INDIVIDUAL, SCOPES.ORGANIZATION]);
43
-
44
- /**
45
- * Narrowest by default (ADR-056 §3.5): proxying through a shared credential
46
- * lets any member act on the organization's account, so an app opts in to
47
- * `organization` explicitly.
48
- */
49
- const DEFAULT_PROXY_SCOPES = Object.freeze([SCOPES.INDIVIDUAL]);
50
20
 
51
21
  // ADR-052 §7. API Gateway HTTP APIs time out at 30s and Lambda responses cap
52
22
  // at 6MB, so the timeout and response defaults are also ceilings: an app may
@@ -74,41 +44,9 @@ function positiveOr(value, fallback, ceiling) {
74
44
  return isPositiveNumber(value) ? Math.min(value, ceiling) : fallback;
75
45
  }
76
46
 
77
- const proxyScopesError = (message) =>
78
- new ManagementApiConfigError(`managementApi.proxy.scopes: ${message}`);
79
-
80
- /**
81
- * `managementApi.proxy.scopes`: absent means the default; anything else must be
82
- * a non-empty list of PROXY_SCOPE_VALUES, each once.
83
- * @returns {ReadonlyArray<string>}
84
- * @throws {ManagementApiConfigError}
85
- */
86
- function resolveProxyScopes(scopes) {
87
- if (scopes === undefined) return DEFAULT_PROXY_SCOPES;
88
- const allowed = PROXY_SCOPE_VALUES.join(', ');
89
- if (!Array.isArray(scopes) || scopes.length === 0) {
90
- throw proxyScopesError(`must be a non-empty list drawn from ${allowed}`);
91
- }
92
- for (const scope of scopes) {
93
- if (scope === SCOPES.GLOBAL) {
94
- throw proxyScopesError(
95
- "'global' is not allowed: global entities belong to the app owner and are never proxied for a tenant"
96
- );
97
- }
98
- if (!PROXY_SCOPE_VALUES.includes(scope)) {
99
- throw proxyScopesError(`unknown scope ${JSON.stringify(scope)}; use ${allowed}`);
100
- }
101
- }
102
- if (new Set(scopes).size !== scopes.length) {
103
- throw proxyScopesError('lists a scope more than once');
104
- }
105
- return Object.freeze([...scopes]);
106
- }
107
-
108
47
  /**
109
48
  * @param {Object} [managementApi] - `appDefinition.managementApi`
110
- * @returns {{ v1: boolean, proxy: { enable: boolean, timeoutMs: number, maxRequestBytes: number, maxResponseBytes: number, scopes: ReadonlyArray<string>, modules: Object } }}
111
- * @throws {ManagementApiConfigError} for an invalid `proxy.scopes`
49
+ * @returns {{ v1: boolean, proxy: { enable: boolean, timeoutMs: number, maxRequestBytes: number, maxResponseBytes: number, modules: Object } }}
112
50
  */
113
51
  function resolveManagementApiConfig(managementApi) {
114
52
  const config =
@@ -133,7 +71,6 @@ function resolveManagementApiConfig(managementApi) {
133
71
  proxy.maxResponseBytes,
134
72
  DEFAULT_PROXY_LIMITS.maxResponseBytes
135
73
  ),
136
- scopes: resolveProxyScopes(proxy.scopes),
137
74
  modules:
138
75
  proxy.modules && typeof proxy.modules === 'object'
139
76
  ? proxy.modules
@@ -142,10 +79,4 @@ function resolveManagementApiConfig(managementApi) {
142
79
  };
143
80
  }
144
81
 
145
- module.exports = {
146
- resolveManagementApiConfig,
147
- DEFAULT_PROXY_LIMITS,
148
- DEFAULT_PROXY_SCOPES,
149
- PROXY_SCOPE_VALUES,
150
- ManagementApiConfigError,
151
- };
82
+ module.exports = { resolveManagementApiConfig, DEFAULT_PROXY_LIMITS };
@@ -8,27 +8,6 @@ const ref = (name) => ({ $ref: `#/components/schemas/${name}` });
8
8
 
9
9
  const nullable = (schema) => ({ ...schema, nullable: true });
10
10
 
11
- // Whose a record is and what the caller may do with it (ADR-056 §3.4). Added to
12
- // entities, credentials and integrations by the list and get use cases.
13
- const tenancyProperties = {
14
- scope: { type: 'string', enum: ['individual', 'organization'], description: 'Whose it is. `global` records are not shown to tenants.' },
15
- owner: {
16
- type: 'object',
17
- properties: { type: { type: 'string', enum: ['individual', 'organization'] }, id: { type: 'string' } },
18
- description: "The user that owns it: the person, or the organization's user",
19
- },
20
- organizationId: nullable({
21
- type: 'string',
22
- description:
23
- 'The organization it belongs to. Null for an individual entity connected outside an organization, or under ownership.individualAcross "all".',
24
- }),
25
- access: {
26
- type: 'string',
27
- enum: ['manage', 'use', 'view'],
28
- description: 'manage (create, reauthorize, delete), use (bind it, proxy through it) or view (metadata only)',
29
- },
30
- };
31
-
32
11
  const errorBody = {
33
12
  type: 'object',
34
13
  required: ['error'],
@@ -90,7 +69,6 @@ const SCHEMAS = {
90
69
  config: { type: 'object', additionalProperties: true },
91
70
  version: nullable({ type: 'string' }),
92
71
  messages: { type: 'object', additionalProperties: true },
93
- ...tenancyProperties,
94
72
  },
95
73
  },
96
74
  ListIntegrationsResponse: {
@@ -151,7 +129,6 @@ const SCHEMAS = {
151
129
  credentialId: nullable({ type: 'string' }),
152
130
  userId: nullable({ type: 'string' }),
153
131
  authIsValid: nullable({ type: 'boolean' }),
154
- ...tenancyProperties,
155
132
  },
156
133
  },
157
134
  ListEntitiesResponse: {
@@ -176,16 +153,6 @@ const SCHEMAS = {
176
153
  isMultiStep: { type: 'boolean' },
177
154
  stepCount: { type: 'integer', minimum: 1 },
178
155
  capabilities: {},
179
- scope: {
180
- type: 'object',
181
- description:
182
- 'The scope a new connection of this type gets and the scopes a request may choose (ADR-056 §3.1). `global` is never offered.',
183
- properties: {
184
- default: { type: 'string', enum: ['individual', 'organization', 'global'] },
185
- allow: { type: 'array', items: { type: 'string', enum: ['individual', 'organization'] } },
186
- source: { type: 'string', enum: ['binding', 'integration', 'entityType', 'app', 'default'] },
187
- },
188
- },
189
156
  },
190
157
  },
191
158
  ListEntityTypesResponse: {
@@ -206,7 +173,6 @@ const SCHEMAS = {
206
173
  authIsValid: nullable({ type: 'boolean' }),
207
174
  entityIds: { type: 'array', items: { type: 'string' } },
208
175
  entityCount: { type: 'integer', minimum: 0 },
209
- ...tenancyProperties,
210
176
  createdAt: nullable({ type: 'string', format: 'date-time' }),
211
177
  updatedAt: nullable({ type: 'string', format: 'date-time' }),
212
178
  data: {
@@ -231,12 +197,6 @@ const SCHEMAS = {
231
197
  data: { type: 'object', additionalProperties: true, description: 'OAuth callback params or step form data' },
232
198
  step: { type: 'integer', minimum: 1, default: 1 },
233
199
  sessionId: { type: 'string', description: 'Required for step 2 and later' },
234
- scope: {
235
- type: 'string',
236
- enum: ['individual', 'organization'],
237
- description:
238
- "The scope to connect at, from the entity type's allow list (ADR-056 §3.1). Omit to take the type's default. `global` is refused (403).",
239
- },
240
200
  },
241
201
  },
242
202
  ReauthorizeRequest: {
@@ -34,11 +34,6 @@ const stepQuery = [
34
34
  query('step', 'Step number, 1-based (default 1)', { type: 'integer', minimum: 1 }),
35
35
  query('sessionId', 'Session from step 1; required for later steps'),
36
36
  ];
37
- // ADR-056 §3.4: the same two filters on every list that mixes ownership.
38
- const listFilters = [
39
- query('scope', 'Only records of this scope', { type: 'string', enum: ['individual', 'organization'] }),
40
- query('owner', 'Only the caller\'s own (`me`) or the active organization\'s (`org`)', { type: 'string', enum: ['me', 'org'] }),
41
- ];
42
37
  const anyJson = { description: 'Defined by the integration or module' };
43
38
 
44
39
  const V2_OPERATIONS = {
@@ -46,8 +41,7 @@ const V2_OPERATIONS = {
46
41
  listIntegrations: {
47
42
  summary: "List the caller's integrations",
48
43
  description:
49
- 'Returns only `{ integrations }`: the caller\'s and the active organization\'s, created in that organization (ADR-056 §3.6). Each carries `scope`, `owner`, `organizationId` and `access`. Integration types are at /api/v2/integrations/options and connected accounts at /api/v2/entities.',
50
- parameters: listFilters,
44
+ 'Returns only `{ integrations }`. Integration types are at /api/v2/integrations/options and connected accounts at /api/v2/entities.',
51
45
  responses: { 200: ok('Integrations', ref('ListIntegrationsResponse')), ...errors('Unauthorized') },
52
46
  },
53
47
  listIntegrationOptions: {
@@ -114,9 +108,6 @@ const V2_OPERATIONS = {
114
108
  // Entities
115
109
  listEntities: {
116
110
  summary: "List the caller's connected entities",
117
- description:
118
- "The active organization's entities and the caller's individual ones (ADR-056 §3.6): an individual entity belongs to the organization it was connected in, unless `ownership.individualAcross` is `all`. Each carries `scope`, `owner`, `organizationId` and `access`.",
119
- parameters: listFilters,
120
111
  responses: { 200: ok('Entities', ref('ListEntitiesResponse')), ...errors('Unauthorized') },
121
112
  },
122
113
  listEntityTypes: {
@@ -165,14 +156,14 @@ const V2_OPERATIONS = {
165
156
  proxyEntityRequest: {
166
157
  summary: "Call the entity's API through Frigg (beta)",
167
158
  description:
168
- "ADR-052. Off unless the app sets managementApi.proxy.enable. Path-only input relative to the module's base URL; deny by default, only operations in the module's (or the app's) allow-list. ADR-056 §3.5: the caller needs use access to the entity in the active organization (an entity they cannot see there is 404), and the entity's scope must be in managementApi.proxy.scopes (default individual only; 403 PROXY_NOT_ALLOWED with details.reason 'scope', or 'access' for view-only access). The HTTP status follows the upstream status; upstream 5xx is 502 UPSTREAM_ERROR, 429 keeps Retry-After.",
159
+ "ADR-052. Off unless the app sets managementApi.proxy.enable. Path-only input relative to the module's base URL; deny by default, only operations in the module's (or the app's) allow-list. The HTTP status follows the upstream status; upstream 5xx is 502 UPSTREAM_ERROR, 429 keeps Retry-After.",
169
160
  requestBody: body(ref('ProxyRequest')),
170
161
  responses: {
171
162
  200: ok('Upstream answer (the status mirrors the upstream status)', ref('ProxyResponse')),
172
163
  400: ok('INVALID_PROXY_REQUEST', ref('ProxyError')),
173
164
  401: ok('INVALID_CREDENTIALS or unauthenticated', ref('ProxyError')),
174
- 403: ok("PROXY_NOT_ALLOWED: not in the allow-list, or details.reason 'scope' / 'access'", ref('ProxyError')),
175
- 404: ok('Entity not found, or not visible in the active organization', ref('Error')),
165
+ 403: ok('PROXY_NOT_ALLOWED', ref('ProxyError')),
166
+ 404: ok('Entity not found', ref('Error')),
176
167
  413: ok('PAYLOAD_TOO_LARGE', ref('ProxyError')),
177
168
  415: ok('UNSUPPORTED_MEDIA_TYPE', ref('ProxyError')),
178
169
  429: ok('RATE_LIMITED (Retry-After passed through)', ref('ProxyError')),
@@ -184,9 +175,6 @@ const V2_OPERATIONS = {
184
175
  // Credentials
185
176
  listCredentials: {
186
177
  summary: "List the caller's credentials (masked)",
187
- description:
188
- 'A credential is listed through the entities that use it that the caller may see, so each organization lists only its own connections; one no entity uses is listed to its owner. Each carries `scope`, `owner`, `organizationId` and `access`, taken from who owns the credential.',
189
- parameters: listFilters,
190
178
  responses: { 200: ok('Credentials', ref('ListCredentialsResponse')), ...errors('Unauthorized') },
191
179
  },
192
180
  getCredential: {