@friggframework/core 2.0.0--canary.684.0da024b.0 → 2.0.0--canary.679.a2bcc2f.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +0 -2
- package/README.md +0 -105
- package/application/commands/integration-commands.js +2 -2
- package/application/commands/user-commands.js +5 -17
- package/credential/use-cases/credential-operations-for-user.js +25 -110
- package/credential/use-cases/reauthorize-credential-for-user.js +8 -15
- package/database/documentdb-utils.js +1 -6
- package/generated/prisma-mongodb/edge.js +4 -15
- package/generated/prisma-mongodb/index-browser.js +1 -12
- package/generated/prisma-mongodb/index.d.ts +86 -2027
- package/generated/prisma-mongodb/index.js +4 -15
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +0 -47
- package/generated/prisma-mongodb/wasm.js +4 -15
- package/generated/prisma-postgresql/edge.js +4 -15
- package/generated/prisma-postgresql/index-browser.js +1 -12
- package/generated/prisma-postgresql/index.d.ts +63 -2162
- package/generated/prisma-postgresql/index.js +4 -15
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +0 -44
- package/generated/prisma-postgresql/wasm.js +4 -15
- package/handlers/app-definition-loader.js +1 -15
- package/index.js +0 -18
- package/integrations/integration-router.js +73 -115
- package/integrations/repositories/integration-repository-documentdb.js +3 -35
- 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/management-api/management-api-config.js +0 -3
- package/management-api/openapi/components.js +0 -40
- package/management-api/openapi/v2-operations.js +1 -13
- package/management-api/v2/dependencies.js +15 -40
- 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 -8
- 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/use-cases/entity-operations-for-user.js +13 -50
- package/modules/use-cases/entity-types.js +7 -31
- 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 +3 -9
- package/modules/use-cases/get-module.js +3 -9
- 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 +3 -9
- package/modules/use-cases/test-module-auth.js +3 -9
- package/package.json +5 -6
- package/prisma-mongodb/schema.prisma +0 -47
- package/prisma-postgresql/schema.prisma +0 -44
- package/types/index.d.ts +0 -1
- package/types/modules/index.d.ts +0 -87
- 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/use-cases/authenticate-user.js +1 -7
- package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
- package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
- package/user/user.js +1 -64
- 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/resolve-integration-ownership.js +0 -177
- package/modules/entity-scope.js +0 -15
- package/modules/scope-config.js +0 -192
- package/modules/scope-policy.js +0 -112
- package/modules/scope-resolution.js +0 -288
- package/modules/scope-visibility.js +0 -239
- package/modules/tests/doubles/ownership-matching.js +0 -18
- package/modules/use-cases/authorize-entity-for-user.js +0 -61
- package/modules/use-cases/create-entity-from-credential.js +0 -113
- package/modules/use-cases/manage-access.js +0 -21
- 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/user/index.d.ts +0 -134
- 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
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
/* eslint-disable camelcase -- JWT claim names (org_id, org_role) and reason codes are snake_case by spec */
|
|
2
|
-
const jose = require('jose');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Key material and token signing for adopter-JWT tests. Real keys and real
|
|
6
|
-
* signatures: nothing here fakes the verifier.
|
|
7
|
-
*/
|
|
8
|
-
async function createKeys() {
|
|
9
|
-
const es = await jose.generateKeyPair('ES256');
|
|
10
|
-
const rs = await jose.generateKeyPair('RS256');
|
|
11
|
-
const attacker = await jose.generateKeyPair('ES256');
|
|
12
|
-
const publicJwks = {
|
|
13
|
-
keys: [
|
|
14
|
-
{
|
|
15
|
-
...(await jose.exportJWK(es.publicKey)),
|
|
16
|
-
kid: 'es-1',
|
|
17
|
-
alg: 'ES256',
|
|
18
|
-
use: 'sig',
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
...(await jose.exportJWK(rs.publicKey)),
|
|
22
|
-
kid: 'rs-1',
|
|
23
|
-
alg: 'RS256',
|
|
24
|
-
use: 'sig',
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
};
|
|
28
|
-
return {
|
|
29
|
-
es,
|
|
30
|
-
rs,
|
|
31
|
-
attacker,
|
|
32
|
-
publicJwks,
|
|
33
|
-
localJwks: jose.createLocalJWKSet(publicJwks),
|
|
34
|
-
hmacSecret: 'a-shared-secret-that-is-long-enough-for-hs256',
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @param {Object} claims
|
|
40
|
-
* @param {Object} opts
|
|
41
|
-
* @param {Object|Uint8Array} opts.key - signing key
|
|
42
|
-
* @param {string} [opts.alg='ES256']
|
|
43
|
-
* @param {string} [opts.kid]
|
|
44
|
-
* @param {string|null} [opts.exp='5m'] - null omits exp
|
|
45
|
-
*/
|
|
46
|
-
async function signToken(
|
|
47
|
-
claims,
|
|
48
|
-
{ key, alg = 'ES256', kid, exp = '5m', iss, aud, nbf } = {}
|
|
49
|
-
) {
|
|
50
|
-
const jwt = new jose.SignJWT(claims)
|
|
51
|
-
.setProtectedHeader(kid ? { alg, kid } : { alg })
|
|
52
|
-
.setIssuedAt();
|
|
53
|
-
if (exp !== null) jwt.setExpirationTime(exp);
|
|
54
|
-
if (iss) jwt.setIssuer(iss);
|
|
55
|
-
if (aud) jwt.setAudience(aud);
|
|
56
|
-
if (nbf) jwt.setNotBefore(nbf);
|
|
57
|
-
return jwt.sign(key);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const b64url = (value) =>
|
|
61
|
-
Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
62
|
-
|
|
63
|
-
/** An unsigned token: `alg: none`, empty signature. */
|
|
64
|
-
function unsignedToken(claims) {
|
|
65
|
-
return `${b64url({ alg: 'none', typ: 'JWT' })}.${b64url({
|
|
66
|
-
...claims,
|
|
67
|
-
exp: Math.floor(Date.now() / 1000) + 300,
|
|
68
|
-
})}.`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Resolves each `wait()` only once `parties` callers have arrived, so a test
|
|
73
|
-
* can make concurrent requests all read before any of them writes.
|
|
74
|
-
*/
|
|
75
|
-
function createBarrier(parties) {
|
|
76
|
-
let arrived = 0;
|
|
77
|
-
let release;
|
|
78
|
-
const gate = new Promise((resolve) => {
|
|
79
|
-
release = resolve;
|
|
80
|
-
});
|
|
81
|
-
return {
|
|
82
|
-
async wait() {
|
|
83
|
-
arrived += 1;
|
|
84
|
-
if (arrived >= parties) release();
|
|
85
|
-
await gate;
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Stateful fake of the user repository, built from jest.fn so every call is
|
|
92
|
-
* observable. Individuals carry `organizationId`, as the real repositories
|
|
93
|
-
* return it. Like the database, it refuses a second individual with the same
|
|
94
|
-
* `appUserId` (a Prisma-shaped `P2002`), which is what a first-login race hits.
|
|
95
|
-
*
|
|
96
|
-
* @param {Object} [options]
|
|
97
|
-
* @param {number} [options.findBarrier] Make the first N calls to
|
|
98
|
-
* `findIndividualUserByAppUserId` wait for each other (see createBarrier).
|
|
99
|
-
*/
|
|
100
|
-
function createUserRepositoryDouble({
|
|
101
|
-
individuals = [],
|
|
102
|
-
organizations = [],
|
|
103
|
-
findBarrier = 0,
|
|
104
|
-
} = {}) {
|
|
105
|
-
const individualUsers = new Map(
|
|
106
|
-
individuals.map((u) => [u.appUserId, { ...u }])
|
|
107
|
-
);
|
|
108
|
-
const organizationUsers = new Map(
|
|
109
|
-
organizations.map((o) => [o.appOrgId, { ...o }])
|
|
110
|
-
);
|
|
111
|
-
const barrier = findBarrier > 1 ? createBarrier(findBarrier) : null;
|
|
112
|
-
let barrierCalls = 0;
|
|
113
|
-
let seq = 0;
|
|
114
|
-
|
|
115
|
-
const repo = {
|
|
116
|
-
findIndividualUserByAppUserId: jest.fn(async (appUserId) => {
|
|
117
|
-
const found = individualUsers.get(appUserId) ?? null;
|
|
118
|
-
if (barrier && barrierCalls < findBarrier) {
|
|
119
|
-
barrierCalls += 1;
|
|
120
|
-
await barrier.wait();
|
|
121
|
-
}
|
|
122
|
-
return found && { ...found };
|
|
123
|
-
}),
|
|
124
|
-
findOrganizationUserByAppOrgId: jest.fn(
|
|
125
|
-
async (appOrgId) => organizationUsers.get(appOrgId) ?? null
|
|
126
|
-
),
|
|
127
|
-
createIndividualUser: jest.fn(async (params) => {
|
|
128
|
-
if (individualUsers.has(params.appUserId)) {
|
|
129
|
-
throw Object.assign(
|
|
130
|
-
new Error('Unique constraint failed on appUserId'),
|
|
131
|
-
{ code: 'P2002' }
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
const created = {
|
|
135
|
-
id: `ind-${++seq}`,
|
|
136
|
-
organizationId: undefined,
|
|
137
|
-
...params,
|
|
138
|
-
};
|
|
139
|
-
individualUsers.set(created.appUserId, created);
|
|
140
|
-
return { ...created };
|
|
141
|
-
}),
|
|
142
|
-
createOrganizationUser: jest.fn(async (params) => {
|
|
143
|
-
const created = { id: `org-${++seq}`, ...params };
|
|
144
|
-
organizationUsers.set(created.appOrgId, created);
|
|
145
|
-
return created;
|
|
146
|
-
}),
|
|
147
|
-
findIndividualUserById: jest.fn(async (id) => {
|
|
148
|
-
for (const u of individualUsers.values()) {
|
|
149
|
-
if (u.id === id) return { ...u };
|
|
150
|
-
}
|
|
151
|
-
return null;
|
|
152
|
-
}),
|
|
153
|
-
// Like the database: sets the pointer only while it is unset.
|
|
154
|
-
linkIndividualToOrganizationIfUnset: jest.fn(
|
|
155
|
-
async (individualId, organizationId) => {
|
|
156
|
-
for (const u of individualUsers.values()) {
|
|
157
|
-
if (u.id !== individualId) continue;
|
|
158
|
-
const unset =
|
|
159
|
-
u.organizationId === undefined ||
|
|
160
|
-
u.organizationId === null ||
|
|
161
|
-
u.organizationId === '';
|
|
162
|
-
if (unset) u.organizationId = organizationId;
|
|
163
|
-
return { ...u };
|
|
164
|
-
}
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
),
|
|
168
|
-
};
|
|
169
|
-
repo.state = { individualUsers, organizationUsers };
|
|
170
|
-
return repo;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* In-memory organization membership repository: atomic, like the real ones. The
|
|
175
|
-
* pair is unique and `addMembership` is an upsert. It knows nothing of the
|
|
176
|
-
* legacy `organizationId` pointer; the domain (user/membership.js) does.
|
|
177
|
-
*/
|
|
178
|
-
function createMembershipRepositoryDouble() {
|
|
179
|
-
const { toMembership } = require('../../repositories/organization-membership-record');
|
|
180
|
-
const rows = new Map();
|
|
181
|
-
const key = (i, o) => `${i}|${o}`;
|
|
182
|
-
let seq = 0;
|
|
183
|
-
|
|
184
|
-
const repo = {
|
|
185
|
-
findMembership: jest.fn(async (individualId, organizationId) => {
|
|
186
|
-
const row = rows.get(key(individualId, organizationId));
|
|
187
|
-
return row ? toMembership(row) : null;
|
|
188
|
-
}),
|
|
189
|
-
addMembership: jest.fn(
|
|
190
|
-
async (individualId, organizationId, { orgRole } = {}) => {
|
|
191
|
-
const k = key(individualId, organizationId);
|
|
192
|
-
if (!rows.has(k)) {
|
|
193
|
-
rows.set(k, {
|
|
194
|
-
id: `mem-${++seq}`,
|
|
195
|
-
individualUserId: individualId,
|
|
196
|
-
organizationUserId: organizationId,
|
|
197
|
-
orgRole: orgRole ?? 'MEMBER',
|
|
198
|
-
createdAt: new Date(),
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
return toMembership(rows.get(k));
|
|
202
|
-
}
|
|
203
|
-
),
|
|
204
|
-
listMembershipsForOrganization: jest.fn(async (organizationId) =>
|
|
205
|
-
[...rows.values()]
|
|
206
|
-
.filter((r) => r.organizationUserId === organizationId)
|
|
207
|
-
.map(toMembership)
|
|
208
|
-
),
|
|
209
|
-
};
|
|
210
|
-
repo.rows = rows;
|
|
211
|
-
return repo;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
module.exports = {
|
|
215
|
-
createKeys,
|
|
216
|
-
signToken,
|
|
217
|
-
unsignedToken,
|
|
218
|
-
createUserRepositoryDouble,
|
|
219
|
-
createMembershipRepositoryDouble,
|
|
220
|
-
};
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
const { getLogger } = require('../../logs');
|
|
2
|
-
const { isPresent, implicitMembership } = require('../membership');
|
|
3
|
-
|
|
4
|
-
const log = getLogger('frigg.user.membership');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Makes sure a person is a member of the organization a request is acting in
|
|
8
|
-
* (ADR-056 §3.6). Shared by the adopter-token and x-frigg header paths.
|
|
9
|
-
*
|
|
10
|
-
* - Already a member (a stored row, or the legacy `organizationId` pointer
|
|
11
|
-
* read as an implicit membership): nothing is written.
|
|
12
|
-
* - Not a member: a membership is added, unless the caller asks for existing
|
|
13
|
-
* memberships only (`requireExisting`), in which case nothing is written and
|
|
14
|
-
* `membership` is null so the caller can refuse.
|
|
15
|
-
* - The legacy `organizationId` is set when the person has none (their first
|
|
16
|
-
* link) and never changed afterwards. It is a pointer for old readers, not
|
|
17
|
-
* the active organization: that is whichever organization the request names.
|
|
18
|
-
*
|
|
19
|
-
* Adding is an idempotent upsert, so two first logins racing for different
|
|
20
|
-
* organizations both succeed.
|
|
21
|
-
*
|
|
22
|
-
* @class EnsureOrganizationMembership
|
|
23
|
-
*/
|
|
24
|
-
class EnsureOrganizationMembership {
|
|
25
|
-
/**
|
|
26
|
-
* @param {Object} params
|
|
27
|
-
* @param {import('../repositories/user-repository-interface').UserRepositoryInterface} params.userRepository
|
|
28
|
-
* @param {import('../repositories/organization-membership-repository-interface').OrganizationMembershipRepositoryInterface} params.organizationMembershipRepository
|
|
29
|
-
*/
|
|
30
|
-
constructor({ userRepository, organizationMembershipRepository }) {
|
|
31
|
-
this.userRepository = userRepository;
|
|
32
|
-
this.organizationMembershipRepository = organizationMembershipRepository;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @param {Object} params
|
|
37
|
-
* @param {Object|null} params.individual Individual user as the repository returns it.
|
|
38
|
-
* @param {Object|null} params.organization The active organization user.
|
|
39
|
-
* @param {string|null} [params.orgRole] Role to store with a new membership.
|
|
40
|
-
* @param {boolean} [params.requireExisting=false] Refuse to add.
|
|
41
|
-
* @returns {Promise<{individual: Object|null, membership: Object|null}>}
|
|
42
|
-
* `individual` may be the updated record (legacy pointer set).
|
|
43
|
-
*/
|
|
44
|
-
async execute({
|
|
45
|
-
individual,
|
|
46
|
-
organization,
|
|
47
|
-
orgRole = null,
|
|
48
|
-
requireExisting = false,
|
|
49
|
-
}) {
|
|
50
|
-
// A request that names no organization, or no person (organization-only
|
|
51
|
-
// auth), has no membership to check or add.
|
|
52
|
-
if (!individual || !organization) {
|
|
53
|
-
return { individual, membership: null };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const existing =
|
|
57
|
-
(await this.organizationMembershipRepository.findMembership(
|
|
58
|
-
individual.id,
|
|
59
|
-
organization.id
|
|
60
|
-
)) ?? implicitMembership(individual, organization.id);
|
|
61
|
-
if (existing) {
|
|
62
|
-
return { individual, membership: existing };
|
|
63
|
-
}
|
|
64
|
-
if (requireExisting) {
|
|
65
|
-
return { individual, membership: null };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const membership =
|
|
69
|
-
await this.organizationMembershipRepository.addMembership(
|
|
70
|
-
individual.id,
|
|
71
|
-
organization.id,
|
|
72
|
-
{ orgRole }
|
|
73
|
-
);
|
|
74
|
-
log.debug('Organization membership added', {
|
|
75
|
-
eventName: `${log.name}.added`,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// The database repeats this condition in the update itself, so a
|
|
79
|
-
// concurrent first link by another request cannot be overwritten.
|
|
80
|
-
let updated = individual;
|
|
81
|
-
if (!isPresent(individual.organizationId)) {
|
|
82
|
-
updated =
|
|
83
|
-
(await this.userRepository.linkIndividualToOrganizationIfUnset(
|
|
84
|
-
individual.id,
|
|
85
|
-
organization.id
|
|
86
|
-
)) ?? individual;
|
|
87
|
-
}
|
|
88
|
-
return { individual: updated, membership };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
module.exports = { EnsureOrganizationMembership };
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
const { sameId } = require('../membership');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The individual users who belong to an organization (ADR-056 §3.6): the
|
|
5
|
-
* people with a stored membership, plus those the legacy
|
|
6
|
-
* `IndividualUser.organizationId` pointer places there, each once.
|
|
7
|
-
*
|
|
8
|
-
* Both repositories stay atomic; the union is composed here. Reading only the
|
|
9
|
-
* legacy pointer would list a person in the first organization they ever
|
|
10
|
-
* joined and in no other.
|
|
11
|
-
*
|
|
12
|
-
* @class FindIndividualUsersByOrganization
|
|
13
|
-
*/
|
|
14
|
-
class FindIndividualUsersByOrganization {
|
|
15
|
-
/**
|
|
16
|
-
* @param {Object} params
|
|
17
|
-
* @param {import('../repositories/user-repository-interface').UserRepositoryInterface} params.userRepository
|
|
18
|
-
* @param {import('../repositories/organization-membership-repository-interface').OrganizationMembershipRepositoryInterface} params.organizationMembershipRepository
|
|
19
|
-
*/
|
|
20
|
-
constructor({ userRepository, organizationMembershipRepository }) {
|
|
21
|
-
this.userRepository = userRepository;
|
|
22
|
-
this.organizationMembershipRepository = organizationMembershipRepository;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @param {string|number} organizationUserId
|
|
27
|
-
* @returns {Promise<Object[]>} Individual users, legacy-pointer people first.
|
|
28
|
-
*/
|
|
29
|
-
async execute(organizationUserId) {
|
|
30
|
-
const [legacy, memberships] = await Promise.all([
|
|
31
|
-
this.userRepository.findIndividualUsersByOrganizationId(
|
|
32
|
-
organizationUserId
|
|
33
|
-
),
|
|
34
|
-
this.organizationMembershipRepository.listMembershipsForOrganization(
|
|
35
|
-
organizationUserId
|
|
36
|
-
),
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
const users = [...(legacy || [])];
|
|
40
|
-
for (const { individualUserId } of memberships) {
|
|
41
|
-
if (users.some((u) => sameId(u.id ?? u._id, individualUserId))) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
const individual = await this.userRepository.findIndividualUserById(
|
|
45
|
-
individualUserId
|
|
46
|
-
);
|
|
47
|
-
// A membership whose person was deleted is skipped, not an error.
|
|
48
|
-
if (individual) users.push(individual);
|
|
49
|
-
}
|
|
50
|
-
return users;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
module.exports = { FindIndividualUsersByOrganization };
|