@friggframework/core 2.0.0--canary.684.9d19f43.0 → 2.0.0--canary.681.2df9ef0.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/README.md +1 -37
- package/application/commands/integration-commands.js +10 -20
- package/application/index.js +2 -6
- package/credential/use-cases/credential-operations-for-user.js +25 -110
- package/credential/use-cases/reauthorize-credential-for-user.js +8 -15
- package/database/encryption/encryption-schema-registry.js +0 -7
- package/generated/prisma-mongodb/edge.js +3 -5
- package/generated/prisma-mongodb/index-browser.js +0 -2
- package/generated/prisma-mongodb/index.d.ts +2 -115
- package/generated/prisma-mongodb/index.js +3 -5
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +0 -15
- package/generated/prisma-mongodb/wasm.js +3 -5
- package/generated/prisma-postgresql/edge.js +3 -5
- package/generated/prisma-postgresql/index-browser.js +0 -2
- package/generated/prisma-postgresql/index.d.ts +2 -131
- package/generated/prisma-postgresql/index.js +3 -5
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +0 -15
- package/generated/prisma-postgresql/wasm.js +3 -5
- package/handlers/app-definition-loader.js +8 -25
- package/handlers/backend-utils.js +25 -31
- package/handlers/routers/health.js +4 -1
- package/handlers/workers/integration-defined-workers.js +2 -5
- package/index.js +2 -10
- package/integrations/index.js +4 -0
- package/integrations/integration-router.js +52 -101
- package/integrations/repositories/integration-repository-documentdb.js +3 -34
- package/integrations/repositories/integration-repository-interface.js +1 -17
- package/integrations/repositories/integration-repository-mongo.js +76 -59
- package/integrations/repositories/integration-repository-postgres.js +76 -62
- package/integrations/tests/doubles/test-integration-repository.js +1 -12
- package/integrations/use-cases/create-integration.js +20 -55
- package/integrations/use-cases/get-integrations-for-user.js +40 -80
- package/integrations/use-cases/index.js +1 -5
- package/integrations/use-cases/integration-operations-for-user.js +24 -55
- package/integrations/utils/map-integration-dto.js +15 -1
- package/management-api/management-api-config.js +0 -3
- package/management-api/openapi/components.js +1 -47
- package/management-api/openapi/v2-operations.js +1 -17
- package/management-api/v2/dependencies.js +14 -58
- package/management-api/v2/dto.js +0 -20
- package/management-api/v2/handlers/authorization.js +0 -1
- package/management-api/v2/handlers/credentials.js +2 -6
- package/management-api/v2/handlers/entities.js +7 -10
- package/management-api/v2/handlers/integrations.js +3 -6
- package/management-api/v2/request.js +1 -32
- package/modules/index.js +0 -14
- package/modules/repositories/module-repository-documentdb.js +1 -41
- package/modules/repositories/module-repository-interface.js +2 -19
- package/modules/repositories/module-repository-mongo.js +73 -64
- package/modules/repositories/module-repository-postgres.js +73 -63
- package/modules/use-cases/entity-operations-for-user.js +13 -50
- package/modules/use-cases/entity-types.js +14 -62
- package/modules/use-cases/execute-entity-proxy-request.js +0 -15
- package/modules/use-cases/get-entities-for-user.js +5 -16
- package/modules/use-cases/get-entity-module-for-user.js +3 -23
- package/modules/use-cases/get-entity-options-by-id.js +2 -5
- package/modules/use-cases/get-module.js +2 -5
- package/modules/use-cases/multi-step-authorization.js +12 -39
- package/modules/use-cases/process-authorization-callback.js +18 -52
- package/modules/use-cases/refresh-entity-options.js +2 -5
- package/modules/use-cases/test-module-auth.js +2 -5
- package/package.json +5 -5
- package/prisma-mongodb/schema.prisma +0 -15
- package/prisma-postgresql/schema.prisma +0 -15
- package/types/index.d.ts +0 -1
- package/types/user/index.d.ts +0 -7
- package/user/repositories/organization-membership-repository-documentdb.js +4 -2
- package/user/repositories/user-repository-documentdb.js +4 -2
- package/user/use-cases/authenticate-user.js +1 -7
- package/user/user.js +1 -24
- package/database/organization-where.js +0 -18
- package/integrations/repositories/integration-not-found.js +0 -14
- package/integrations/use-cases/resolve-integration-ownership.js +0 -177
- package/modules/entity-scope.js +0 -15
- package/modules/entity-type-registry.js +0 -311
- package/modules/scope-config.js +0 -177
- package/modules/scope-policy.js +0 -111
- package/modules/scope-resolution.js +0 -288
- package/modules/scope-visibility.js +0 -212
- package/modules/tests/doubles/ownership-matching.js +0 -18
- package/modules/use-cases/authorize-entity-for-user.js +0 -61
- package/modules/use-cases/manage-access.js +0 -21
- package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
- package/types/modules/index.d.ts +0 -145
package/README.md
CHANGED
|
@@ -393,7 +393,7 @@ value that is not a three-part JWT is not offered to the verifier at all; it fal
|
|
|
393
393
|
| `clockToleranceSec` | `30` | Skew allowed on `exp` and `nbf`. |
|
|
394
394
|
| `userIdClaim` | `sub` | App user id (required in the token). |
|
|
395
395
|
| `orgIdClaim` | `org_id` | App organization id (optional). |
|
|
396
|
-
| `roleClaim` | `org_role` | Exposed as `user.getOrgRole()` for the request, and stored as the `orgRole` of a person's membership the first time they join that organization (`MEMBER` when the token names none).
|
|
396
|
+
| `roleClaim` | `org_role` | Exposed as `user.getOrgRole()` for the request, and stored as the `orgRole` of a person's membership the first time they join that organization (`MEMBER` when the token names none). |
|
|
397
397
|
|
|
398
398
|
Rules worth knowing:
|
|
399
399
|
|
|
@@ -421,42 +421,6 @@ Rules worth knowing:
|
|
|
421
421
|
- Two first logins for the same new person or organization, even in different organizations, both
|
|
422
422
|
succeed: a create that loses to a unique constraint finds the winner's row.
|
|
423
423
|
|
|
424
|
-
#### Entity scopes and ownership (ADR-056 §3)
|
|
425
|
-
|
|
426
|
-
Who owns an entity (a connected account) or an integration follows its **scope**, not `user.primary`:
|
|
427
|
-
|
|
428
|
-
| Scope | Owned by | Created with |
|
|
429
|
-
|---|---|---|
|
|
430
|
-
| `individual` | the person (`individualUser`) | the active organization (`organizationId`), or none under `individualAcross: 'all'` |
|
|
431
|
-
| `organization` | the active organization's user | that organization |
|
|
432
|
-
| `global` | nobody (the app owner) | none; refused by the tenant API (admin routes only) |
|
|
433
|
-
|
|
434
|
-
The scope of a new connection is the first of: the request (`scope` on `POST /api/v2/authorize`, only from
|
|
435
|
-
the deciding level's `allow` list), the integration binding (`Definition.modules[key].scope` or
|
|
436
|
-
`Definition.entities[key].scope`), `Definition.ownership.scope`, the entity type
|
|
437
|
-
(`appDefinition.entities[name].scope`), `appDefinition.ownership.scope`, then `organization` for an
|
|
438
|
-
organization-primary app and `individual` otherwise. A setting is `'organization'` or
|
|
439
|
-
`{ default: 'individual', allow: ['individual', 'organization'] }`.
|
|
440
|
-
|
|
441
|
-
```javascript
|
|
442
|
-
const appDefinition = {
|
|
443
|
-
user: { primary: 'individual', authModes: { adopterJwt: true } },
|
|
444
|
-
ownership: { scope: 'individual', individualAcross: 'organization' }, // the defaults
|
|
445
|
-
entities: {
|
|
446
|
-
google: { definition: google, scope: 'individual' },
|
|
447
|
-
xero: { definition: xero, scope: { default: 'organization', allow: ['organization', 'individual'] } },
|
|
448
|
-
},
|
|
449
|
-
integrations: [Sync], // Sync.Definition.ownership = { owner: 'derive' }
|
|
450
|
-
};
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
`GET /api/v2/entities` and `/integrations` return, for the active organization, its own
|
|
454
|
-
rows and the caller's, each with `scope`, `owner`, `organizationId` and `access`, and take `?scope=` and
|
|
455
|
-
`?owner=me|org`. An individual entity is only visible and usable in the organization it was connected in
|
|
456
|
-
(or in all of them, with `individualAcross: 'all'`). Rows written before this existed keep working: see
|
|
457
|
-
[Entity scopes](../../docs/guides/entity-scopes.md), which also covers the schema (`organizationId` on
|
|
458
|
-
`Entity` and `Integration`, no `scope` column) and what is deferred.
|
|
459
|
-
|
|
460
424
|
### 8. Lambda Utilities (`/lambda`)
|
|
461
425
|
|
|
462
426
|
AWS Lambda-specific utilities and helpers.
|
|
@@ -18,7 +18,7 @@ const {
|
|
|
18
18
|
ListIntegrationsByEntityExternalIdUseCase,
|
|
19
19
|
} = require('../../integrations/use-cases/list-integrations-by-entity-external-id');
|
|
20
20
|
const {
|
|
21
|
-
|
|
21
|
+
GetIntegrationsForUser,
|
|
22
22
|
} = require('../../integrations/use-cases/get-integrations-for-user');
|
|
23
23
|
const {
|
|
24
24
|
CreateIntegration,
|
|
@@ -30,9 +30,8 @@ const {
|
|
|
30
30
|
PatchIntegrationConfig,
|
|
31
31
|
} = require('../../integrations/use-cases/patch-integration-config');
|
|
32
32
|
const {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} = require('../../modules/entity-type-registry');
|
|
33
|
+
getModulesDefinitionFromIntegrationClasses,
|
|
34
|
+
} = require('../../integrations/utils/map-integration-dto');
|
|
36
35
|
|
|
37
36
|
const ERROR_CODE_MAP = {
|
|
38
37
|
ENTITY_NOT_FOUND: 401,
|
|
@@ -52,16 +51,7 @@ function mapErrorToResponse(error) {
|
|
|
52
51
|
};
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
* @param {Object} [params]
|
|
57
|
-
* @param {Function} [params.integrationClass]
|
|
58
|
-
* @param {Array<object>} [params.moduleDefinitions] - the app's offered module
|
|
59
|
-
* definitions (`loadAppDefinition().moduleDefinitions`). Integration code
|
|
60
|
-
* calls this from a constructor with no app definition in hand, so when it
|
|
61
|
-
* is omitted the commands hydrate with this integration's own modules only
|
|
62
|
-
* (a registry built from `{ integrations: [integrationClass] }`).
|
|
63
|
-
*/
|
|
64
|
-
function createIntegrationCommands({ integrationClass, moduleDefinitions } = {}) {
|
|
54
|
+
function createIntegrationCommands({ integrationClass } = {}) {
|
|
65
55
|
// Always use Frigg's default repositories and use cases
|
|
66
56
|
const integrationRepository = createIntegrationRepository();
|
|
67
57
|
|
|
@@ -118,13 +108,13 @@ function createIntegrationCommands({ integrationClass, moduleDefinitions } = {})
|
|
|
118
108
|
|
|
119
109
|
const moduleRepository = createModuleRepository();
|
|
120
110
|
|
|
111
|
+
const moduleDefinitions = getModulesDefinitionFromIntegrationClasses([
|
|
112
|
+
integrationClass,
|
|
113
|
+
]);
|
|
114
|
+
|
|
121
115
|
const moduleFactory = new ModuleFactory({
|
|
122
116
|
moduleRepository,
|
|
123
|
-
moduleDefinitions
|
|
124
|
-
moduleDefinitions ??
|
|
125
|
-
getEntityTypeDefinitions(
|
|
126
|
-
getEntityTypeRegistry({ integrations: [integrationClass] })
|
|
127
|
-
),
|
|
117
|
+
moduleDefinitions,
|
|
128
118
|
});
|
|
129
119
|
|
|
130
120
|
const loadIntegrationContextUseCase = new LoadIntegrationContextUseCase({
|
|
@@ -152,7 +142,7 @@ function createIntegrationCommands({ integrationClass, moduleDefinitions } = {})
|
|
|
152
142
|
moduleRepository,
|
|
153
143
|
});
|
|
154
144
|
|
|
155
|
-
const getIntegrationsForUserUseCase = new
|
|
145
|
+
const getIntegrationsForUserUseCase = new GetIntegrationsForUser({
|
|
156
146
|
integrationRepository,
|
|
157
147
|
integrationClasses: [integrationClass],
|
|
158
148
|
moduleFactory,
|
package/application/index.js
CHANGED
|
@@ -17,7 +17,6 @@ const { createUsageCommands } = require('./commands/usage-commands');
|
|
|
17
17
|
*
|
|
18
18
|
* @param {Object} params
|
|
19
19
|
* @param {Object} params.integrationClass - Integration class (required)
|
|
20
|
-
* @param {Array<object>} [params.moduleDefinitions] - the app's offered module definitions; defaults to the integration's own modules
|
|
21
20
|
* @returns {Object} Unified commands object with all CRUD operations
|
|
22
21
|
*
|
|
23
22
|
* @example
|
|
@@ -25,12 +24,9 @@ const { createUsageCommands } = require('./commands/usage-commands');
|
|
|
25
24
|
* const user = await commands.createUser({ username: 'user@example.com' });
|
|
26
25
|
* const credential = await commands.createCredential({ userId: user.id, ... });
|
|
27
26
|
*/
|
|
28
|
-
function createFriggCommands({ integrationClass
|
|
27
|
+
function createFriggCommands({ integrationClass }) {
|
|
29
28
|
// All commands use Frigg's default repositories and use cases
|
|
30
|
-
const integrationCommands = createIntegrationCommands({
|
|
31
|
-
integrationClass,
|
|
32
|
-
moduleDefinitions,
|
|
33
|
-
});
|
|
29
|
+
const integrationCommands = createIntegrationCommands({ integrationClass });
|
|
34
30
|
|
|
35
31
|
const userCommands = createUserCommands();
|
|
36
32
|
|
|
@@ -1,28 +1,10 @@
|
|
|
1
1
|
const Boom = require('@hapi/boom');
|
|
2
|
-
const {
|
|
3
|
-
createScopePolicy,
|
|
4
|
-
describeRecord,
|
|
5
|
-
visibilityClauses,
|
|
6
|
-
matchesListFilters,
|
|
7
|
-
SCOPES,
|
|
8
|
-
} = require('../../modules/entity-scope');
|
|
9
|
-
const { assertCanManage } = require('../../modules/use-cases/manage-access');
|
|
2
|
+
const { userOwns } = require('../../modules/use-cases/get-entity-module-for-user');
|
|
10
3
|
|
|
11
4
|
/**
|
|
12
|
-
* Management API v2 credential operations
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - its `type` and `entityIds` come from the entities that use it that the
|
|
16
|
-
* caller may see, so the same person's account connected in two
|
|
17
|
-
* organizations (one credential, two entities) lists in each organization
|
|
18
|
-
* with that organization's entity only;
|
|
19
|
-
* - who it belongs to is its OWNER (`credential.userId`), never the entities:
|
|
20
|
-
* a credential an individual entity and an organization entity both point at
|
|
21
|
-
* (possible only through legacy data) is described by whoever owns the
|
|
22
|
-
* credential, so the answer cannot change with the order the entities load;
|
|
23
|
-
* - a credential whose entities are all out of sight (another organization's)
|
|
24
|
-
* is a 404, and one with no entities at all (its entity was deleted) is
|
|
25
|
-
* visible to its owner only, as it was before scoping.
|
|
5
|
+
* Management API v2 credential operations. A credential has no module name
|
|
6
|
+
* of its own; its `type` and the entities that use it come from the
|
|
7
|
+
* caller's entities. A missing or foreign credential is a 404.
|
|
26
8
|
*/
|
|
27
9
|
|
|
28
10
|
const credentialIdOf = (entity) => {
|
|
@@ -55,82 +37,31 @@ function withEntityUsage(credentials, entities) {
|
|
|
55
37
|
});
|
|
56
38
|
}
|
|
57
39
|
|
|
58
|
-
/**
|
|
59
|
-
* `{scope, owner, organizationId, access}` of a credential for this caller, or
|
|
60
|
-
* null when its owner is neither the caller nor the active organization. Built
|
|
61
|
-
* by `describeRecord` over the row a connection by this owner has: the
|
|
62
|
-
* organization of a visible entity, else the one a new entity would get.
|
|
63
|
-
*/
|
|
64
|
-
function credentialTenancy(credential, visibleEntities, ctx) {
|
|
65
|
-
const entityOrg = visibleEntities.find(
|
|
66
|
-
(entity) => entity.organizationId !== undefined && entity.organizationId !== null
|
|
67
|
-
)?.organizationId;
|
|
68
|
-
const organizationId =
|
|
69
|
-
entityOrg ?? (ctx.individualAcross === 'all' ? null : ctx.activeOrgId);
|
|
70
|
-
return describeRecord({ userId: credential.userId, organizationId }, ctx, 'entity');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** The entities of `entities` the caller may see, each with its `tenancy`. */
|
|
74
|
-
function visibleOf(entities, ctx) {
|
|
75
|
-
const visible = [];
|
|
76
|
-
for (const entity of entities || []) {
|
|
77
|
-
const tenancy = describeRecord(entity, ctx, 'entity');
|
|
78
|
-
if (tenancy) visible.push({ ...entity, tenancy });
|
|
79
|
-
}
|
|
80
|
-
return visible;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* The caller's credentials with their type, the entities of them they may see
|
|
85
|
-
* and `tenancy`. Credentials with no entity at all are listed too, for the
|
|
86
|
-
* person and the active organization that own them.
|
|
87
|
-
*/
|
|
40
|
+
/** The caller's credentials with their type and entity usage. */
|
|
88
41
|
class ListCredentialsForUser {
|
|
89
|
-
constructor({ credentialRepository, moduleRepository
|
|
42
|
+
constructor({ credentialRepository, moduleRepository }) {
|
|
90
43
|
this.credentialRepository = credentialRepository;
|
|
91
44
|
this.moduleRepository = moduleRepository;
|
|
92
|
-
this.scopePolicy = scopePolicy || createScopePolicy();
|
|
93
45
|
}
|
|
94
46
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.moduleRepository.findEntitiesByOwnership(visibilityClauses(ctx, 'entity')),
|
|
101
|
-
Promise.all(owners.map((id) => this.credentialRepository.findCredentialsByUserId(id))),
|
|
102
|
-
// Every entity of those owners, in any organization: only to tell a credential
|
|
103
|
-
// with no entity at all from one used elsewhere.
|
|
104
|
-
Promise.all(owners.map((id) => this.moduleRepository.findEntitiesByUserId(id))),
|
|
47
|
+
async execute(user) {
|
|
48
|
+
const userId = user.getId();
|
|
49
|
+
const [credentials, entities] = await Promise.all([
|
|
50
|
+
this.credentialRepository.findCredentialsByUserId(userId),
|
|
51
|
+
this.moduleRepository.findEntitiesByUserId(userId),
|
|
105
52
|
]);
|
|
106
|
-
|
|
107
|
-
const usedAnywhere = new Set(owned.flat().map(credentialIdOf).filter(Boolean));
|
|
108
|
-
const listed = [];
|
|
109
|
-
for (const credential of perOwner.flat().filter(Boolean)) {
|
|
110
|
-
const id = String(credential.id);
|
|
111
|
-
const referencing = visible.filter((entity) => credentialIdOf(entity) === id);
|
|
112
|
-
if (referencing.length === 0 && usedAnywhere.has(id)) continue;
|
|
113
|
-
const tenancy = credentialTenancy(credential, referencing, ctx);
|
|
114
|
-
if (!tenancy || !matchesListFilters(tenancy, filters)) continue;
|
|
115
|
-
listed.push({ ...withEntityUsage([credential], referencing)[0], tenancy });
|
|
116
|
-
}
|
|
117
|
-
return listed;
|
|
53
|
+
return withEntityUsage(credentials || [], entities);
|
|
118
54
|
}
|
|
119
55
|
}
|
|
120
56
|
|
|
121
|
-
/** One credential the caller
|
|
57
|
+
/** One credential the caller owns, with its type and entity usage. */
|
|
122
58
|
class GetCredentialForUserV2 {
|
|
123
|
-
constructor({ credentialRepository, moduleRepository
|
|
59
|
+
constructor({ credentialRepository, moduleRepository }) {
|
|
124
60
|
this.credentialRepository = credentialRepository;
|
|
125
61
|
this.moduleRepository = moduleRepository;
|
|
126
|
-
this.scopePolicy = scopePolicy || createScopePolicy();
|
|
127
62
|
}
|
|
128
63
|
|
|
129
64
|
async execute(credentialId, user) {
|
|
130
|
-
const notFound = () =>
|
|
131
|
-
Boom.notFound(`Credential ${credentialId} not found`, {
|
|
132
|
-
code: 'CREDENTIAL_NOT_FOUND',
|
|
133
|
-
});
|
|
134
65
|
let credential = null;
|
|
135
66
|
try {
|
|
136
67
|
credential = await this.credentialRepository.findCredentialById(
|
|
@@ -141,29 +72,22 @@ class GetCredentialForUserV2 {
|
|
|
141
72
|
throw error;
|
|
142
73
|
}
|
|
143
74
|
}
|
|
144
|
-
if (!credential
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return { ...withEntityUsage([credential], visible)[0], tenancy };
|
|
75
|
+
if (!credential || !userOwns(user, credential.userId)) {
|
|
76
|
+
throw Boom.notFound(`Credential ${credentialId} not found`, {
|
|
77
|
+
code: 'CREDENTIAL_NOT_FOUND',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const entities = await this.moduleRepository.findEntitiesByUserId(
|
|
81
|
+
credential.userId
|
|
82
|
+
);
|
|
83
|
+
return withEntityUsage([credential], entities)[0];
|
|
154
84
|
}
|
|
155
85
|
}
|
|
156
86
|
|
|
157
87
|
/**
|
|
158
|
-
* Deletes a credential the caller
|
|
159
|
-
*
|
|
88
|
+
* Deletes a credential the caller owns. The entities that used it stay, with
|
|
89
|
+
* their credential unset, so they show as needing re-authorization rather
|
|
160
90
|
* than disappearing from integrations.
|
|
161
|
-
*
|
|
162
|
-
* Refuses with 409 `CREDENTIAL_SHARED` while an entity the caller cannot see
|
|
163
|
-
* still uses it: one credential row serves the same provider account in every
|
|
164
|
-
* organization, and deleting it from one would break the other. The message
|
|
165
|
-
* says nothing about the other connections (not even how many): remove the
|
|
166
|
-
* entities that use it, and it goes with the last one.
|
|
167
91
|
*/
|
|
168
92
|
class DeleteCredentialForUser {
|
|
169
93
|
constructor({ credentialRepository, moduleRepository, getCredentialForUser }) {
|
|
@@ -177,15 +101,6 @@ class DeleteCredentialForUser {
|
|
|
177
101
|
credentialId,
|
|
178
102
|
user
|
|
179
103
|
);
|
|
180
|
-
assertCanManage(credential.tenancy.access, `credential ${credential.id}`);
|
|
181
|
-
const usedBy =
|
|
182
|
-
(await this.moduleRepository.findEntities({ credentialId: credential.id })) || [];
|
|
183
|
-
if (usedBy.length > credential.entityIds.length) {
|
|
184
|
-
throw Boom.conflict(
|
|
185
|
-
`Credential ${credential.id} is still in use by other connections and cannot be deleted from here`,
|
|
186
|
-
{ code: 'CREDENTIAL_SHARED' }
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
104
|
for (const entityId of credential.entityIds) {
|
|
190
105
|
await this.moduleRepository.unsetCredential(entityId);
|
|
191
106
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const Boom = require('@hapi/boom');
|
|
2
|
-
const { assertCanManage } = require('../../modules/use-cases/manage-access');
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Re-authorization of a credential the caller owns (Management API v2). It
|
|
@@ -15,13 +14,7 @@ async function credentialEntityType(getCredentialForUser, credentialId, user) {
|
|
|
15
14
|
{ code: 'CREDENTIAL_TYPE_UNKNOWN' }
|
|
16
15
|
);
|
|
17
16
|
}
|
|
18
|
-
return {
|
|
19
|
-
entityType: credential.type,
|
|
20
|
-
credentialId: String(credential.id),
|
|
21
|
-
// A re-authorization keeps the scope the connection already has.
|
|
22
|
-
fixedScope: credential.tenancy?.scope,
|
|
23
|
-
access: credential.tenancy?.access,
|
|
24
|
-
};
|
|
17
|
+
return { entityType: credential.type, credentialId: String(credential.id) };
|
|
25
18
|
}
|
|
26
19
|
|
|
27
20
|
/** GET /api/v2/credentials/:credentialId/reauthorize */
|
|
@@ -32,12 +25,14 @@ class GetCredentialReauthorization {
|
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
async execute(credentialId, user, { step = 1, sessionId, state } = {}) {
|
|
35
|
-
const
|
|
36
|
-
|
|
28
|
+
const binding = await credentialEntityType(
|
|
29
|
+
this.getCredentialForUser,
|
|
30
|
+
credentialId,
|
|
31
|
+
user
|
|
32
|
+
);
|
|
37
33
|
return this.getAuthorizationStep.execute({
|
|
38
34
|
user,
|
|
39
|
-
|
|
40
|
-
credentialId: boundCredentialId,
|
|
35
|
+
...binding,
|
|
41
36
|
step,
|
|
42
37
|
sessionId,
|
|
43
38
|
state,
|
|
@@ -53,13 +48,11 @@ class ReauthorizeCredentialForUser {
|
|
|
53
48
|
}
|
|
54
49
|
|
|
55
50
|
async execute(credentialId, user, { data, step = 1, sessionId } = {}) {
|
|
56
|
-
const
|
|
51
|
+
const binding = await credentialEntityType(
|
|
57
52
|
this.getCredentialForUser,
|
|
58
53
|
credentialId,
|
|
59
54
|
user
|
|
60
55
|
);
|
|
61
|
-
// A credential an organization owns: the organization's admins only.
|
|
62
|
-
assertCanManage(access, `credential ${credentialId}`);
|
|
63
56
|
return this.submitAuthorizationStep.execute({
|
|
64
57
|
user,
|
|
65
58
|
...binding,
|
|
@@ -180,13 +180,6 @@ function loadModuleEncryptionSchemas(integrations) {
|
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
// NOTE: this path does not exist ('../integrations' resolves to
|
|
184
|
-
// database/integrations), so today this function throws MODULE_NOT_FOUND,
|
|
185
|
-
// loadCustomEncryptionSchema swallows it, and module credentialFields are
|
|
186
|
-
// effectively NOT registered. Fixing it changes which fields are encrypted
|
|
187
|
-
// and needs a decrypt that tolerates plaintext, so it ships separately:
|
|
188
|
-
// branch claude/internal-mcp-03b-module-credential-encryption. Left as is
|
|
189
|
-
// here on purpose (ADR-059 must not change what gets encrypted).
|
|
190
183
|
const { getModulesDefinitionFromIntegrationClasses } = require('../integrations/utils/map-integration-dto');
|
|
191
184
|
|
|
192
185
|
const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrations);
|