@friggframework/core 2.0.0--canary.682.451c957.0 → 2.0.0--canary.666.89f6545.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +41 -3
- package/README.md +0 -105
- package/application/commands/README.md +17 -11
- package/application/commands/entity-commands.js +18 -60
- package/application/commands/integration-commands.js +73 -20
- package/application/commands/user-commands.js +5 -17
- package/application/index.js +2 -6
- package/credential/repositories/credential-repository-documentdb.js +0 -34
- package/credential/repositories/credential-repository-interface.js +0 -15
- package/credential/repositories/credential-repository-mongo.js +0 -23
- package/credential/repositories/credential-repository-postgres.js +0 -28
- package/database/documentdb-encryption-service.js +2 -33
- package/database/documentdb-utils.js +0 -2
- package/database/encryption/README.md +3 -3
- package/database/encryption/ciphertext-format.js +24 -0
- package/database/encryption/encryption-schema-registry.js +109 -42
- package/database/encryption/field-encryption-service.js +13 -16
- package/database/prisma.js +10 -1
- package/errors/index.js +4 -0
- package/errors/mapping-already-exists-error.js +14 -0
- package/generated/prisma-mongodb/edge.js +4 -31
- package/generated/prisma-mongodb/index-browser.js +1 -28
- package/generated/prisma-mongodb/index.d.ts +74 -3463
- package/generated/prisma-mongodb/index.js +4 -31
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +0 -74
- package/generated/prisma-mongodb/wasm.js +4 -31
- package/generated/prisma-postgresql/edge.js +4 -31
- package/generated/prisma-postgresql/index-browser.js +1 -28
- package/generated/prisma-postgresql/index.d.ts +51 -3631
- package/generated/prisma-postgresql/index.js +4 -31
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +0 -70
- package/generated/prisma-postgresql/wasm.js +4 -31
- package/handlers/WEBHOOKS.md +29 -0
- package/handlers/app-definition-loader.js +16 -34
- package/handlers/app-handler-helpers.js +3 -25
- package/handlers/backend-utils.js +25 -31
- package/handlers/middleware/admin-auth.js +1 -14
- package/handlers/routers/auth.js +3 -11
- package/handlers/routers/health.js +4 -11
- package/handlers/workers/integration-defined-workers.js +2 -5
- package/index.js +2 -23
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +67 -13
- package/integrations/index.js +0 -1
- package/integrations/integration-base.js +29 -0
- package/integrations/integration-router.js +87 -129
- package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
- package/integrations/repositories/integration-mapping-repository.js +29 -0
- package/integrations/repositories/integration-repository-documentdb.js +35 -35
- package/integrations/repositories/integration-repository-interface.js +31 -17
- package/integrations/repositories/integration-repository-mongo.js +129 -60
- package/integrations/repositories/integration-repository-postgres.js +134 -62
- package/integrations/repositories/process-repository-mongo.js +1 -1
- package/integrations/tests/doubles/test-integration-repository.js +1 -12
- package/integrations/use-cases/add-entities-to-integration.js +92 -0
- package/integrations/use-cases/create-integration.js +75 -72
- package/integrations/use-cases/get-integrations-for-user.js +40 -80
- package/integrations/use-cases/index.js +1 -5
- package/integrations/use-cases/remove-entities-from-integration.js +70 -0
- package/integrations/utils/assert-entity-ids-input.js +42 -0
- package/integrations/utils/map-integration-dto.js +15 -47
- package/jest-setup.js +13 -2
- package/jest-teardown.js +5 -2
- package/jest.config.js +4 -0
- package/jest.db.config.js +23 -0
- package/modules/index.js +0 -14
- package/modules/repositories/module-repository-documentdb.js +4 -48
- package/modules/repositories/module-repository-interface.js +2 -19
- package/modules/repositories/module-repository-mongo.js +73 -65
- package/modules/repositories/module-repository-postgres.js +73 -64
- package/modules/requester/requester.js +2 -27
- package/modules/use-cases/create-entity-from-credential.js +28 -36
- package/modules/use-cases/get-entities-for-user.js +5 -16
- package/modules/use-cases/get-entity-options-by-id.js +3 -9
- package/modules/use-cases/get-module.js +24 -19
- package/modules/use-cases/process-authorization-callback.js +18 -52
- package/modules/use-cases/refresh-entity-options.js +3 -9
- package/modules/use-cases/test-module-auth.js +3 -9
- package/modules/utils/map-module-dto.js +6 -10
- package/modules/utils/to-credential-id.js +14 -0
- package/package.json +8 -6
- package/prisma-mongodb/schema.prisma +0 -74
- package/prisma-postgresql/schema.prisma +0 -70
- package/queues/queuer-util.js +6 -5
- package/types/index.d.ts +0 -2
- package/user/repositories/user-repository-documentdb.js +2 -27
- package/user/repositories/user-repository-interface.js +0 -18
- package/user/repositories/user-repository-mongo.js +0 -24
- package/user/repositories/user-repository-postgres.js +0 -18
- package/user/tests/doubles/test-user-repository.js +1 -1
- package/user/use-cases/get-user-from-adopter-jwt.js +125 -86
- package/user/use-cases/get-user-from-bearer-token.js +7 -2
- package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
- package/user/user.js +0 -27
- package/credential/use-cases/credential-operations-for-user.js +0 -117
- package/credential/use-cases/reauthorize-credential-for-user.js +0 -66
- package/database/organization-where.js +0 -18
- package/database/unique-violation.js +0 -25
- package/integrations/repositories/integration-not-found.js +0 -14
- package/integrations/use-cases/integration-operations-for-user.js +0 -246
- package/integrations/use-cases/resolve-integration-ownership.js +0 -177
- package/management-api/create-management-api-router.js +0 -49
- package/management-api/error-response.js +0 -116
- package/management-api/management-api-config.js +0 -85
- package/management-api/meta-router.js +0 -57
- package/management-api/openapi/build-openapi.js +0 -144
- package/management-api/openapi/components.js +0 -407
- package/management-api/openapi/v1-operations.js +0 -83
- package/management-api/openapi/v2-operations.js +0 -261
- package/management-api/route-registry.js +0 -294
- package/management-api/test-support/http-client.js +0 -39
- package/management-api/use-cases/get-management-api-meta.js +0 -57
- package/management-api/v1-deprecation.js +0 -120
- package/management-api/v2/create-v2-router.js +0 -85
- package/management-api/v2/dependencies.js +0 -279
- package/management-api/v2/dto.js +0 -143
- package/management-api/v2/handlers/authorization.js +0 -94
- package/management-api/v2/handlers/credentials.js +0 -29
- package/management-api/v2/handlers/entities.js +0 -77
- package/management-api/v2/handlers/integrations.js +0 -128
- package/management-api/v2/handlers/proxy.js +0 -34
- package/management-api/v2/request.js +0 -85
- package/management-api/v2/test-support/fake-dependencies.js +0 -103
- package/modules/domain/authorization-session.js +0 -144
- package/modules/entity-scope.js +0 -15
- package/modules/entity-type-registry.js +0 -311
- package/modules/proxy/proxy-errors.js +0 -20
- package/modules/proxy/proxy-path.js +0 -179
- package/modules/proxy/proxy-policy.js +0 -248
- package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
- package/modules/repositories/authorization-session-repository-factory.js +0 -31
- package/modules/repositories/authorization-session-repository-interface.js +0 -47
- package/modules/repositories/authorization-session-repository-prisma.js +0 -87
- package/modules/requester/proxy-request.js +0 -244
- package/modules/scope-config.js +0 -155
- package/modules/scope-policy.js +0 -80
- package/modules/scope-resolution.js +0 -277
- package/modules/scope-visibility.js +0 -165
- package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
- package/modules/tests/doubles/ownership-matching.js +0 -18
- package/modules/use-cases/authorize-entity-for-user.js +0 -59
- package/modules/use-cases/entity-operations-for-user.js +0 -157
- package/modules/use-cases/entity-types.js +0 -139
- package/modules/use-cases/execute-entity-proxy-request.js +0 -254
- package/modules/use-cases/get-authorization-requirements.js +0 -100
- package/modules/use-cases/get-entity-module-for-user.js +0 -106
- package/modules/use-cases/multi-step-authorization.js +0 -326
- package/modules/utils/public-entity.js +0 -59
- package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
- package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
- package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
- package/types/modules/index.d.ts +0 -155
- package/types/user/index.d.ts +0 -117
- package/user/adopter-jwt-config.js +0 -177
- package/user/adopter-jwt-errors.js +0 -59
- package/user/adopter-token-verifier.js +0 -122
- package/user/app-user-helpers.js +0 -97
- package/user/create-user-authentication.js +0 -64
- package/user/membership.js +0 -51
- package/user/repositories/organization-membership-record.js +0 -19
- package/user/repositories/organization-membership-repository-documentdb.js +0 -106
- package/user/repositories/organization-membership-repository-factory.js +0 -42
- package/user/repositories/organization-membership-repository-interface.js +0 -57
- package/user/repositories/organization-membership-repository-mongo.js +0 -13
- package/user/repositories/organization-membership-repository-postgres.js +0 -29
- package/user/repositories/organization-membership-repository-prisma.js +0 -81
- package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
- package/user/use-cases/ensure-organization-membership.js +0 -92
- package/user/use-cases/find-individual-users-by-organization.js +0 -54
package/CLAUDE.md
CHANGED
|
@@ -206,11 +206,51 @@ packages/core/
|
|
|
206
206
|
- `delete-integration-for-user.js` - Remove integration
|
|
207
207
|
- `get-integration-instance.js` - Load integration with modules
|
|
208
208
|
- `load-integration-context.js` - Full integration context loading
|
|
209
|
+
- `add-entities-to-integration.js` / `remove-entities-from-integration.js` - Attach or detach entities on an existing integration
|
|
209
210
|
|
|
210
211
|
**Repositories**:
|
|
211
212
|
- `integration-repository-factory.js` - Creates database-specific repositories
|
|
212
213
|
- `integration-repository-mongo.js` - MongoDB implementation
|
|
213
214
|
- `integration-repository-postgres.js` - PostgreSQL implementation
|
|
215
|
+
- `addEntitiesToIntegration(integrationId, entityIds)` /
|
|
216
|
+
`removeEntitiesFromIntegration(integrationId, entityIds)` change the entity
|
|
217
|
+
set of an existing integration (all three adapters; `null` for an unknown
|
|
218
|
+
integration). Reach them through the `addEntitiesToIntegration` /
|
|
219
|
+
`removeEntitiesFromIntegration` commands. The add use case
|
|
220
|
+
(`add-entities-to-integration.js`) rejects unknown entities
|
|
221
|
+
(`ENTITIES_NOT_FOUND`, 404), entities of another user
|
|
222
|
+
(`ENTITY_USER_MISMATCH`, 403) and entities of a module the integration does
|
|
223
|
+
not already hold (`ENTITY_MODULE_MISMATCH`, 400). The remove use case
|
|
224
|
+
(`remove-entities-from-integration.js`) refuses to leave a module with no
|
|
225
|
+
entity (`LAST_ENTITY_OF_MODULE`, 409). Re-adding an attached entity or
|
|
226
|
+
removing a detached one changes nothing; a detached entity is not deleted.
|
|
227
|
+
`integration-repository-entities-parity.db.test.js` runs every adapter against
|
|
228
|
+
real databases when `INTEGRATION_ENTITIES_PARITY_MONGO_URL` /
|
|
229
|
+
`INTEGRATION_ENTITIES_PARITY_POSTGRES_URL` are set. The DocumentDB adapter
|
|
230
|
+
writes only `Integration.entityIds` (as its `createIntegration` does), not
|
|
231
|
+
`Entity.integrationIds`.
|
|
232
|
+
**One credential, many accounts**: when one credential covers several
|
|
233
|
+
provider accounts, give each account its own entity (externalId = account
|
|
234
|
+
id) and attach them all to one integration; webhooks then route with
|
|
235
|
+
`findIntegrationByEntityExternalId`. Traps:
|
|
236
|
+
(1) `IntegrationBase` keys modules by module name, so `this.<moduleKey>`
|
|
237
|
+
holds only one entity's module, and on the queue/webhook path
|
|
238
|
+
(`get-integration-instance.js`) `this.entities` holds entity **ids**, not
|
|
239
|
+
records. For per-account work (a webhook for one account, teardown of every
|
|
240
|
+
account in `onDelete`), load
|
|
241
|
+
`this.commands.loadIntegrationContextById(this.id)` and use
|
|
242
|
+
`context.modules`: one module per entity, each with its record on
|
|
243
|
+
`module.entity` (pick by `module.entity.externalId`).
|
|
244
|
+
(2) `findIntegrationByEntityExternalId` counts every entity with that
|
|
245
|
+
externalId, attached or not, across users: reuse an existing entity
|
|
246
|
+
(`findEntity`) when an account comes back, or delete it when it is revoked,
|
|
247
|
+
never create a second one.
|
|
248
|
+
(3) `createIntegration` reuses an existing integration of the same user
|
|
249
|
+
and type whose entities include all requested ones (an exact match wins),
|
|
250
|
+
so a reconnect that sends the original entities finds the integration that
|
|
251
|
+
now holds more.
|
|
252
|
+
(4) Every module's delegate is the integration: an auth failure on any
|
|
253
|
+
account marks the shared credential invalid and the integration ERROR.
|
|
214
254
|
- `integration-mapping-repository-*.js` - Mapping data persistence.
|
|
215
255
|
`queryMappings(integrationId, { where, orderBy, skip, take, omit })`
|
|
216
256
|
returns `{ mappings, total }`: one filtered, ordered page of an
|
|
@@ -226,7 +266,7 @@ packages/core/
|
|
|
226
266
|
mapping keys to leave out of the rows; never write such rows back. Path
|
|
227
267
|
segments must match `^[A-Za-z_][A-Za-z0-9_]*$`. The PostgreSQL, MongoDB and
|
|
228
268
|
DocumentDB adapters give the same pages and totals;
|
|
229
|
-
`integration-mapping-repository-query-parity.test.js` checks that against
|
|
269
|
+
`integration-mapping-repository-query-parity.db.test.js` checks that against
|
|
230
270
|
real databases when `QUERY_MAPPINGS_PARITY_MONGO_URL` /
|
|
231
271
|
`QUERY_MAPPINGS_PARITY_POSTGRES_URL` are set. Two orderings still differ:
|
|
232
272
|
strings compare by the database collation on PostgreSQL and by code point
|
|
@@ -332,14 +372,12 @@ class MyIntegration extends IntegrationBase {
|
|
|
332
372
|
- Password-based (bcrypt hashed)
|
|
333
373
|
- Token-based (Bearer tokens)
|
|
334
374
|
- App-based (external app user IDs)
|
|
335
|
-
- Adopter JWT (`authModes.adopterJwt`, `jwtConfig`; `jose`, JWKS or HMAC; see `README.md`)
|
|
336
375
|
|
|
337
376
|
**Use Cases**:
|
|
338
377
|
- `login-user.js` - User authentication
|
|
339
378
|
- `create-individual-user.js` - Create personal account
|
|
340
379
|
- `create-organization-user.js` - Create business account
|
|
341
380
|
- `get-user-from-bearer-token.js` - Token authentication
|
|
342
|
-
- `get-user-from-adopter-jwt.js` - Adopter JWT verification, find-or-create (ADR-056 §2); `adopter-jwt-config.js` validates `jwtConfig` at load
|
|
343
381
|
|
|
344
382
|
**Repositories**:
|
|
345
383
|
- `user-repository-factory.js` - Creates database-specific repositories
|
package/README.md
CHANGED
|
@@ -305,7 +305,6 @@ Comprehensive user authentication and authorization system supporting both indiv
|
|
|
305
305
|
- **Password-based**: Traditional username/password authentication
|
|
306
306
|
- **Token-based**: Bearer token authentication with session management
|
|
307
307
|
- **App-based**: External app user ID authentication (passwordless)
|
|
308
|
-
- **Adopter token (JWT)**: A token issued by your own identity system, verified by Frigg and safe to send from a browser (see below)
|
|
309
308
|
|
|
310
309
|
**Usage:**
|
|
311
310
|
```javascript
|
|
@@ -353,110 +352,6 @@ console.log('Individual user:', user.getIndividualUser());
|
|
|
353
352
|
console.log('Organization user:', user.getOrganizationUser());
|
|
354
353
|
```
|
|
355
354
|
|
|
356
|
-
#### Adopter token (JWT) authentication
|
|
357
|
-
|
|
358
|
-
For a multi-tenant product (ADR-056 §2), let the browser send the token your identity system
|
|
359
|
-
already issues. Frigg verifies the signature and claims, then finds or creates the individual
|
|
360
|
-
user and the organization user the token names. Unlike `sharedSecret` (`x-frigg-*` headers), a
|
|
361
|
-
token cannot name a user other than the one it was issued for, so it is safe in a browser.
|
|
362
|
-
|
|
363
|
-
```javascript
|
|
364
|
-
// backend/index.js
|
|
365
|
-
const appDefinition = {
|
|
366
|
-
user: {
|
|
367
|
-
primary: 'organization',
|
|
368
|
-
individualUserRequired: true,
|
|
369
|
-
organizationUserRequired: true,
|
|
370
|
-
authModes: { adopterJwt: true, friggToken: false, sharedSecret: false },
|
|
371
|
-
jwtConfig: {
|
|
372
|
-
jwksUri: 'https://login.example.com/.well-known/jwks.json',
|
|
373
|
-
issuer: 'https://login.example.com/',
|
|
374
|
-
audience: 'my-frigg-api', // required with jwksUri
|
|
375
|
-
},
|
|
376
|
-
},
|
|
377
|
-
};
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
The client sends `Authorization: Bearer <token>`. `x-frigg-appuserid` / `x-frigg-apporgid`, if
|
|
381
|
-
also sent, must match the token or the request is refused with 403. That match rule lives in
|
|
382
|
-
`user/use-cases/authenticate-user.js` (`validateUserMatch`), not in the token verifier. A bearer
|
|
383
|
-
value that is not a three-part JWT is not offered to the verifier at all; it falls through to
|
|
384
|
-
`friggToken` (or is a 401 when that mode is off).
|
|
385
|
-
|
|
386
|
-
| `jwtConfig` field | Default | Meaning |
|
|
387
|
-
|---|---|---|
|
|
388
|
-
| `jwksUri` | | Remote JWKS (rotating keys, one per verifier, cached by jose). Must be `https`; `http` only for `localhost`, `127.0.0.0/8` or `::1`. Set exactly one of this and `secret`. |
|
|
389
|
-
| `secret` | | Shared HMAC secret. Read it from an environment variable. |
|
|
390
|
-
| `issuer` | not checked | Required `iss`. With `jwksUri`, a startup warning is logged when it is unset. |
|
|
391
|
-
| `audience` | not checked with `secret`; **required with `jwksUri`** | Accepted `aud` (a string or a list). |
|
|
392
|
-
| `algorithms` | `['ES256','RS256']` with `jwksUri`, `['HS256']` with `secret` | Allow-list. HS* is only accepted with `secret`, never with `jwksUri`. The older single `algorithm` is a deprecated alias; setting both is an error. |
|
|
393
|
-
| `clockToleranceSec` | `30` | Skew allowed on `exp` and `nbf`. |
|
|
394
|
-
| `userIdClaim` | `sub` | App user id (required in the token). |
|
|
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). |
|
|
397
|
-
|
|
398
|
-
Rules worth knowing:
|
|
399
|
-
|
|
400
|
-
- Tokens must carry `exp`. `alg: none` and any algorithm outside the allow-list are refused.
|
|
401
|
-
- Startup fails (`loadAppDefinition` throws) when `adopterJwt` is on and the config is unusable:
|
|
402
|
-
neither or both of `jwksUri` and `secret`, a `jwksUri` that is not https (or loopback http),
|
|
403
|
-
no `audience` with `jwksUri`, or an algorithm list that does not fit the key type.
|
|
404
|
-
- Failures are `401` with a stable `data.reason`: `token_expired`, `token_not_yet_valid`,
|
|
405
|
-
`token_invalid_signature`, `token_wrong_issuer`, `token_wrong_audience`,
|
|
406
|
-
`token_missing_user_claim`, `token_malformed`. The message never contains the token or its claims.
|
|
407
|
-
Any other verification failure, such as an unreachable or unresolvable JWKS, is `503`
|
|
408
|
-
`jwks_unavailable`, not a 401, so clients do not drop good sessions.
|
|
409
|
-
- **The token's organization is the request's active organization**, and a person can belong to
|
|
410
|
-
several (ADR-056 §3.6). The first time a person presents a token for an organization they are
|
|
411
|
-
not a member of, Frigg adds an `OrganizationMembership` and the request runs as that
|
|
412
|
-
organization (`user.getActiveOrganizationId()`; `user.getId()` under `primary: 'organization'`).
|
|
413
|
-
Nothing is moved: `IndividualUser.organizationId` is a legacy pointer, set only on a person's
|
|
414
|
-
first link and read as an implicit membership. The same applies to `x-frigg-apporgid` with
|
|
415
|
-
`sharedSecret`. With `user.strictMembership: true` those headers cannot add anyone to an organization
|
|
416
|
-
that already existed (400 for a non-member, a brand-new `appUserId` included); only an organization
|
|
417
|
-
the request itself creates is allowed. `strictUserValidation` is a deprecated alias with that same
|
|
418
|
-
meaning. See
|
|
419
|
-
[Organization memberships](../../docs/guides/organization-memberships.md). A token naming a second
|
|
420
|
-
organization is no longer a `403` (`multi_org_unsupported` is gone).
|
|
421
|
-
- Two first logins for the same new person or organization, even in different organizations, both
|
|
422
|
-
succeed: a create that loses to a unique constraint finds the winner's row.
|
|
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
355
|
### 8. Lambda Utilities (`/lambda`)
|
|
461
356
|
|
|
462
357
|
AWS Lambda-specific utilities and helpers.
|
|
@@ -67,9 +67,10 @@ class MyIntegration extends IntegrationBase {
|
|
|
67
67
|
async hydrateFromExternalUser(externalUserId) {
|
|
68
68
|
// Find integration context by external entity ID
|
|
69
69
|
const result =
|
|
70
|
-
await this.commands.findIntegrationContextByExternalEntityId(
|
|
71
|
-
externalUserId
|
|
72
|
-
|
|
70
|
+
await this.commands.findIntegrationContextByExternalEntityId({
|
|
71
|
+
externalId: externalUserId,
|
|
72
|
+
type: 'my-integration', // integration config.type to match
|
|
73
|
+
});
|
|
73
74
|
|
|
74
75
|
if (result.error) {
|
|
75
76
|
return { error: result.error };
|
|
@@ -231,9 +232,10 @@ Manage integration records and load full integration contexts.
|
|
|
231
232
|
```javascript
|
|
232
233
|
// Find integration context by external entity ID
|
|
233
234
|
// Returns { context, error } where context includes record + hydrated modules
|
|
234
|
-
const result = await commands.findIntegrationContextByExternalEntityId(
|
|
235
|
-
'external-user-or-workspace-id'
|
|
236
|
-
|
|
235
|
+
const result = await commands.findIntegrationContextByExternalEntityId({
|
|
236
|
+
externalId: 'external-user-or-workspace-id',
|
|
237
|
+
type: 'my-integration', // integration config.type to match
|
|
238
|
+
});
|
|
237
239
|
|
|
238
240
|
if (!result.error) {
|
|
239
241
|
integration.setIntegrationRecord(result.context);
|
|
@@ -299,7 +301,10 @@ try {
|
|
|
299
301
|
For integration context operations, errors are returned in the result:
|
|
300
302
|
|
|
301
303
|
```javascript
|
|
302
|
-
const result = await commands.findIntegrationContextByExternalEntityId(
|
|
304
|
+
const result = await commands.findIntegrationContextByExternalEntityId({
|
|
305
|
+
externalId: userId,
|
|
306
|
+
type: 'my-integration',
|
|
307
|
+
});
|
|
303
308
|
|
|
304
309
|
if (result.error) {
|
|
305
310
|
return { error: result.error };
|
|
@@ -349,9 +354,10 @@ const result =
|
|
|
349
354
|
const { createFriggCommands } = require('@friggframework/core');
|
|
350
355
|
|
|
351
356
|
this.commands = createFriggCommands({ integrationClass: MyIntegration });
|
|
352
|
-
const result = await this.commands.findIntegrationContextByExternalEntityId(
|
|
353
|
-
userId
|
|
354
|
-
|
|
357
|
+
const result = await this.commands.findIntegrationContextByExternalEntityId({
|
|
358
|
+
externalId: userId,
|
|
359
|
+
type: 'my-integration',
|
|
360
|
+
});
|
|
355
361
|
```
|
|
356
362
|
|
|
357
363
|
## Best Practices
|
|
@@ -433,7 +439,7 @@ if (!user) {
|
|
|
433
439
|
| | `findEntitiesByIds(ids)` | Find multiple by IDs |
|
|
434
440
|
| | `updateEntity(id, updates)` | Update entity properties |
|
|
435
441
|
| | `deleteEntity(id)` | Delete entity |
|
|
436
|
-
| **Integration** | `findIntegrationContextByExternalEntityId(externalId)`
|
|
442
|
+
| **Integration** | `findIntegrationContextByExternalEntityId({ externalId, type })` | Load integration + modules by external ID |
|
|
437
443
|
| | `loadIntegrationContextById(integrationId)` | Load integration + modules by ID |
|
|
438
444
|
|
|
439
445
|
## Support
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
const {
|
|
2
2
|
createModuleRepository,
|
|
3
3
|
} = require('../../modules/repositories/module-repository-factory');
|
|
4
|
+
const { toCredentialId } = require('../../modules/utils/to-credential-id');
|
|
4
5
|
|
|
5
6
|
const ERROR_CODE_MAP = {
|
|
6
7
|
ENTITY_NOT_FOUND: 404,
|
|
7
8
|
INVALID_ENTITY_DATA: 400,
|
|
8
9
|
};
|
|
9
10
|
|
|
11
|
+
function toEntityDto(entity) {
|
|
12
|
+
return {
|
|
13
|
+
id: entity.id,
|
|
14
|
+
userId: entity.userId,
|
|
15
|
+
externalId: entity.externalId,
|
|
16
|
+
name: entity.name,
|
|
17
|
+
moduleName: entity.moduleName,
|
|
18
|
+
credentialId: toCredentialId(entity.credential),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
function mapErrorToResponse(error) {
|
|
11
23
|
const status = ERROR_CODE_MAP[error?.code] || 500;
|
|
12
24
|
return {
|
|
@@ -66,16 +78,7 @@ function createEntityCommands() {
|
|
|
66
78
|
|
|
67
79
|
const entity = await moduleRepo.createEntity(entityData);
|
|
68
80
|
|
|
69
|
-
return
|
|
70
|
-
id: entity.id,
|
|
71
|
-
userId: entity.userId,
|
|
72
|
-
externalId: entity.externalId,
|
|
73
|
-
name: entity.name,
|
|
74
|
-
moduleName: entity.moduleName,
|
|
75
|
-
credentialId: entity.credential?._id
|
|
76
|
-
? entity.credential._id.toString()
|
|
77
|
-
: entity.credential,
|
|
78
|
-
};
|
|
81
|
+
return toEntityDto(entity);
|
|
79
82
|
} catch (error) {
|
|
80
83
|
return mapErrorToResponse(error);
|
|
81
84
|
}
|
|
@@ -109,16 +112,7 @@ function createEntityCommands() {
|
|
|
109
112
|
return null;
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
return
|
|
113
|
-
id: entity.id,
|
|
114
|
-
userId: entity.userId,
|
|
115
|
-
externalId: entity.externalId,
|
|
116
|
-
name: entity.name,
|
|
117
|
-
moduleName: entity.moduleName,
|
|
118
|
-
credentialId: entity.credential?._id
|
|
119
|
-
? entity.credential._id.toString()
|
|
120
|
-
: entity.credential,
|
|
121
|
-
};
|
|
115
|
+
return toEntityDto(entity);
|
|
122
116
|
} catch (error) {
|
|
123
117
|
return mapErrorToResponse(error);
|
|
124
118
|
}
|
|
@@ -139,16 +133,7 @@ function createEntityCommands() {
|
|
|
139
133
|
|
|
140
134
|
const entities = await moduleRepo.findEntitiesByUserId(userId);
|
|
141
135
|
|
|
142
|
-
return entities.map((entity) => (
|
|
143
|
-
id: entity.id,
|
|
144
|
-
userId: entity.userId,
|
|
145
|
-
externalId: entity.externalId,
|
|
146
|
-
name: entity.name,
|
|
147
|
-
moduleName: entity.moduleName,
|
|
148
|
-
credentialId: entity.credential?._id
|
|
149
|
-
? entity.credential._id.toString()
|
|
150
|
-
: entity.credential,
|
|
151
|
-
}));
|
|
136
|
+
return entities.map((entity) => toEntityDto(entity));
|
|
152
137
|
} catch (error) {
|
|
153
138
|
if (error.code) {
|
|
154
139
|
return mapErrorToResponse(error);
|
|
@@ -180,16 +165,7 @@ function createEntityCommands() {
|
|
|
180
165
|
moduleName
|
|
181
166
|
);
|
|
182
167
|
|
|
183
|
-
return entities.map((entity) => (
|
|
184
|
-
id: entity.id,
|
|
185
|
-
userId: entity.userId,
|
|
186
|
-
externalId: entity.externalId,
|
|
187
|
-
name: entity.name,
|
|
188
|
-
moduleName: entity.moduleName,
|
|
189
|
-
credentialId: entity.credential?._id
|
|
190
|
-
? entity.credential._id.toString()
|
|
191
|
-
: entity.credential,
|
|
192
|
-
}));
|
|
168
|
+
return entities.map((entity) => toEntityDto(entity));
|
|
193
169
|
} catch (error) {
|
|
194
170
|
if (error.code) {
|
|
195
171
|
return mapErrorToResponse(error);
|
|
@@ -213,16 +189,7 @@ function createEntityCommands() {
|
|
|
213
189
|
|
|
214
190
|
const entity = await moduleRepo.findEntityById(entityId);
|
|
215
191
|
|
|
216
|
-
return
|
|
217
|
-
id: entity.id,
|
|
218
|
-
userId: entity.userId,
|
|
219
|
-
externalId: entity.externalId,
|
|
220
|
-
name: entity.name,
|
|
221
|
-
moduleName: entity.moduleName,
|
|
222
|
-
credentialId: entity.credential?._id
|
|
223
|
-
? entity.credential._id.toString()
|
|
224
|
-
: entity.credential,
|
|
225
|
-
};
|
|
192
|
+
return toEntityDto(entity);
|
|
226
193
|
} catch (error) {
|
|
227
194
|
return mapErrorToResponse(error);
|
|
228
195
|
}
|
|
@@ -250,16 +217,7 @@ function createEntityCommands() {
|
|
|
250
217
|
throw error;
|
|
251
218
|
}
|
|
252
219
|
|
|
253
|
-
return
|
|
254
|
-
id: entity.id,
|
|
255
|
-
userId: entity.userId,
|
|
256
|
-
externalId: entity.externalId,
|
|
257
|
-
name: entity.name,
|
|
258
|
-
moduleName: entity.moduleName,
|
|
259
|
-
credentialId: entity.credential?._id
|
|
260
|
-
? entity.credential._id.toString()
|
|
261
|
-
: entity.credential,
|
|
262
|
-
};
|
|
220
|
+
return toEntityDto(entity);
|
|
263
221
|
} catch (error) {
|
|
264
222
|
return mapErrorToResponse(error);
|
|
265
223
|
}
|
|
@@ -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,14 @@ const {
|
|
|
30
30
|
PatchIntegrationConfig,
|
|
31
31
|
} = require('../../integrations/use-cases/patch-integration-config');
|
|
32
32
|
const {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
AddEntitiesToIntegration,
|
|
34
|
+
} = require('../../integrations/use-cases/add-entities-to-integration');
|
|
35
|
+
const {
|
|
36
|
+
RemoveEntitiesFromIntegration,
|
|
37
|
+
} = require('../../integrations/use-cases/remove-entities-from-integration');
|
|
38
|
+
const {
|
|
39
|
+
getModulesDefinitionFromIntegrationClasses,
|
|
40
|
+
} = require('../../integrations/utils/map-integration-dto');
|
|
36
41
|
|
|
37
42
|
const ERROR_CODE_MAP = {
|
|
38
43
|
ENTITY_NOT_FOUND: 401,
|
|
@@ -41,6 +46,11 @@ const ERROR_CODE_MAP = {
|
|
|
41
46
|
EXTERNAL_ID_REQUIRED: 400,
|
|
42
47
|
TYPE_REQUIRED: 400,
|
|
43
48
|
INTEGRATION_RECORD_NOT_FOUND: 404,
|
|
49
|
+
INVALID_INTEGRATION_DATA: 400,
|
|
50
|
+
ENTITIES_NOT_FOUND: 404,
|
|
51
|
+
ENTITY_USER_MISMATCH: 403,
|
|
52
|
+
ENTITY_MODULE_MISMATCH: 400,
|
|
53
|
+
LAST_ENTITY_OF_MODULE: 409,
|
|
44
54
|
};
|
|
45
55
|
|
|
46
56
|
function mapErrorToResponse(error) {
|
|
@@ -52,16 +62,7 @@ function mapErrorToResponse(error) {
|
|
|
52
62
|
};
|
|
53
63
|
}
|
|
54
64
|
|
|
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 } = {}) {
|
|
65
|
+
function createIntegrationCommands({ integrationClass } = {}) {
|
|
65
66
|
// Always use Frigg's default repositories and use cases
|
|
66
67
|
const integrationRepository = createIntegrationRepository();
|
|
67
68
|
|
|
@@ -118,13 +119,13 @@ function createIntegrationCommands({ integrationClass, moduleDefinitions } = {})
|
|
|
118
119
|
|
|
119
120
|
const moduleRepository = createModuleRepository();
|
|
120
121
|
|
|
122
|
+
const moduleDefinitions = getModulesDefinitionFromIntegrationClasses([
|
|
123
|
+
integrationClass,
|
|
124
|
+
]);
|
|
125
|
+
|
|
121
126
|
const moduleFactory = new ModuleFactory({
|
|
122
127
|
moduleRepository,
|
|
123
|
-
moduleDefinitions
|
|
124
|
-
moduleDefinitions ??
|
|
125
|
-
getEntityTypeDefinitions(
|
|
126
|
-
getEntityTypeRegistry({ integrations: [integrationClass] })
|
|
127
|
-
),
|
|
128
|
+
moduleDefinitions,
|
|
128
129
|
});
|
|
129
130
|
|
|
130
131
|
const loadIntegrationContextUseCase = new LoadIntegrationContextUseCase({
|
|
@@ -152,7 +153,7 @@ function createIntegrationCommands({ integrationClass, moduleDefinitions } = {})
|
|
|
152
153
|
moduleRepository,
|
|
153
154
|
});
|
|
154
155
|
|
|
155
|
-
const getIntegrationsForUserUseCase = new
|
|
156
|
+
const getIntegrationsForUserUseCase = new GetIntegrationsForUser({
|
|
156
157
|
integrationRepository,
|
|
157
158
|
integrationClasses: [integrationClass],
|
|
158
159
|
moduleFactory,
|
|
@@ -173,6 +174,17 @@ function createIntegrationCommands({ integrationClass, moduleDefinitions } = {})
|
|
|
173
174
|
integrationRepository,
|
|
174
175
|
});
|
|
175
176
|
|
|
177
|
+
const addEntitiesToIntegrationUseCase = new AddEntitiesToIntegration({
|
|
178
|
+
integrationRepository,
|
|
179
|
+
moduleRepository,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const removeEntitiesFromIntegrationUseCase =
|
|
183
|
+
new RemoveEntitiesFromIntegration({
|
|
184
|
+
integrationRepository,
|
|
185
|
+
moduleRepository,
|
|
186
|
+
});
|
|
187
|
+
|
|
176
188
|
return {
|
|
177
189
|
findIntegrationById,
|
|
178
190
|
listIntegrations,
|
|
@@ -317,6 +329,47 @@ function createIntegrationCommands({ integrationClass, moduleDefinitions } = {})
|
|
|
317
329
|
}
|
|
318
330
|
},
|
|
319
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Attach entities to an existing integration. Every entity must
|
|
334
|
+
* exist, belong to the integration's user, and be of a module the
|
|
335
|
+
* integration already holds; entities that are already attached are
|
|
336
|
+
* left as they are.
|
|
337
|
+
* @param {Object} params
|
|
338
|
+
* @param {string} params.integrationId - Integration ID
|
|
339
|
+
* @param {Array<string>} params.entityIds - Entity IDs to attach
|
|
340
|
+
* @returns {Promise<Object>} Updated integration
|
|
341
|
+
*/
|
|
342
|
+
async addEntitiesToIntegration({ integrationId, entityIds }) {
|
|
343
|
+
try {
|
|
344
|
+
return await addEntitiesToIntegrationUseCase.execute(
|
|
345
|
+
integrationId,
|
|
346
|
+
entityIds
|
|
347
|
+
);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
return mapErrorToResponse(error);
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Detach entities from an integration. Entities that are not
|
|
355
|
+
* attached are ignored; detached entities are not deleted. Each
|
|
356
|
+
* module must keep at least one entity.
|
|
357
|
+
* @param {Object} params
|
|
358
|
+
* @param {string} params.integrationId - Integration ID
|
|
359
|
+
* @param {Array<string>} params.entityIds - Entity IDs to detach
|
|
360
|
+
* @returns {Promise<Object>} Updated integration
|
|
361
|
+
*/
|
|
362
|
+
async removeEntitiesFromIntegration({ integrationId, entityIds }) {
|
|
363
|
+
try {
|
|
364
|
+
return await removeEntitiesFromIntegrationUseCase.execute(
|
|
365
|
+
integrationId,
|
|
366
|
+
entityIds
|
|
367
|
+
);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return mapErrorToResponse(error);
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
|
|
320
373
|
/**
|
|
321
374
|
* Delete an integration by ID
|
|
322
375
|
* @param {string} integrationId - Integration ID to delete
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
const {
|
|
2
2
|
createUserRepository,
|
|
3
3
|
} = require('../../user/repositories/user-repository-factory');
|
|
4
|
-
const {
|
|
5
|
-
createOrganizationMembershipRepository,
|
|
6
|
-
} = require('../../user/repositories/organization-membership-repository-factory');
|
|
7
|
-
const {
|
|
8
|
-
FindIndividualUsersByOrganization,
|
|
9
|
-
} = require('../../user/use-cases/find-individual-users-by-organization');
|
|
10
4
|
|
|
11
5
|
const ERROR_CODE_MAP = {
|
|
12
6
|
USER_NOT_FOUND: 404,
|
|
@@ -32,12 +26,6 @@ function mapErrorToResponse(error) {
|
|
|
32
26
|
*/
|
|
33
27
|
function createUserCommands() {
|
|
34
28
|
const userRepository = createUserRepository();
|
|
35
|
-
const findIndividualUsersByOrganization =
|
|
36
|
-
new FindIndividualUsersByOrganization({
|
|
37
|
-
userRepository,
|
|
38
|
-
organizationMembershipRepository:
|
|
39
|
-
createOrganizationMembershipRepository(),
|
|
40
|
-
});
|
|
41
29
|
|
|
42
30
|
return {
|
|
43
31
|
/**
|
|
@@ -182,8 +170,7 @@ function createUserCommands() {
|
|
|
182
170
|
},
|
|
183
171
|
|
|
184
172
|
/**
|
|
185
|
-
* Find all individual users
|
|
186
|
-
* a membership and those the legacy organizationId pointer places there.
|
|
173
|
+
* Find all individual users linked to an organization user
|
|
187
174
|
* @param {string} organizationUserId - Organization user ID to search for
|
|
188
175
|
* @returns {Promise<Object[]>} Array of individual user objects (empty if none)
|
|
189
176
|
*/
|
|
@@ -195,9 +182,10 @@ function createUserCommands() {
|
|
|
195
182
|
throw error;
|
|
196
183
|
}
|
|
197
184
|
|
|
198
|
-
const users =
|
|
199
|
-
|
|
200
|
-
|
|
185
|
+
const users =
|
|
186
|
+
await userRepository.findIndividualUsersByOrganizationId(
|
|
187
|
+
organizationUserId
|
|
188
|
+
);
|
|
201
189
|
|
|
202
190
|
return (users || []).map((user) => ({
|
|
203
191
|
id: user._id?.toString() || user.id,
|
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
|
|
|
@@ -186,40 +186,6 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
186
186
|
return this._mapCredential(decryptedCredential);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
/**
|
|
190
|
-
* All credentials owned by a user, newest first.
|
|
191
|
-
*
|
|
192
|
-
* @param {string} userId - User ID
|
|
193
|
-
* @returns {Promise<Array<Object>>} Credentials with string IDs
|
|
194
|
-
*/
|
|
195
|
-
async findCredentialsByUserId(userId) {
|
|
196
|
-
const userObjectId = toObjectId(userId);
|
|
197
|
-
if (!userObjectId) return [];
|
|
198
|
-
const docs = await findManyDrained(
|
|
199
|
-
this.prisma,
|
|
200
|
-
'Credential',
|
|
201
|
-
{ userId: userObjectId },
|
|
202
|
-
{ sort: { createdAt: -1 } }
|
|
203
|
-
);
|
|
204
|
-
const credentials = [];
|
|
205
|
-
for (const doc of docs) {
|
|
206
|
-
const decrypted = await this.encryptionService.decryptFields(
|
|
207
|
-
'Credential',
|
|
208
|
-
doc
|
|
209
|
-
);
|
|
210
|
-
credentials.push({
|
|
211
|
-
...this._mapCredential(decrypted),
|
|
212
|
-
id: fromObjectId(doc._id),
|
|
213
|
-
userId: fromObjectId(doc.userId) ?? doc.userId,
|
|
214
|
-
externalId: doc.externalId ?? null,
|
|
215
|
-
authIsValid: doc.authIsValid ?? null,
|
|
216
|
-
createdAt: doc.createdAt ?? null,
|
|
217
|
-
updatedAt: doc.updatedAt ?? null,
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
return credentials;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
189
|
async updateCredential(credentialId, updates) {
|
|
224
190
|
const objectId = toObjectId(credentialId);
|
|
225
191
|
if (!objectId) return null;
|
|
@@ -80,21 +80,6 @@ class CredentialRepositoryInterface {
|
|
|
80
80
|
);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
/**
|
|
84
|
-
* All credentials owned by a user, newest first.
|
|
85
|
-
*
|
|
86
|
-
* @param {string|number} userId - User ID (string from application layer)
|
|
87
|
-
* @returns {Promise<Array<Object>>} Credentials with string IDs, `createdAt`
|
|
88
|
-
* and `updatedAt`, and the (decrypted) data fields spread in, as
|
|
89
|
-
* findCredentialById returns them. Empty when the user has none.
|
|
90
|
-
* @abstract
|
|
91
|
-
*/
|
|
92
|
-
async findCredentialsByUserId(userId) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
'Method findCredentialsByUserId must be implemented by subclass'
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
83
|
/**
|
|
99
84
|
* Update a credential by ID
|
|
100
85
|
*
|