@verii/endpoints-organizations-registrar 1.2.0 → 1.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verii/endpoints-organizations-registrar",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Organization Registrar",
5
5
  "repository": "https://github.com/LFDT-Verii/core",
6
6
  "main": "src/index.js",
@@ -29,31 +29,31 @@
29
29
  "nanoid": "^5.1.16",
30
30
  "sprintf-js": "1.1.3",
31
31
  "uuid": "~14.0.1",
32
- "@verii/blockchain-functions": "1.2.0",
33
- "@verii/common-functions": "1.2.0",
34
- "@verii/aws-clients": "1.2.0",
35
- "@verii/base-contract-io": "1.2.0",
36
- "@verii/auth": "1.2.0",
37
- "@verii/config": "1.2.0",
38
- "@verii/common-schemas": "1.2.0",
39
- "@verii/contract-permissions": "1.2.0",
40
- "@verii/country-data": "1.2.0",
41
- "@verii/crypto": "1.2.0",
42
- "@verii/db-kms": "1.2.0",
43
- "@verii/csv-parser": "1.2.0",
44
- "@verii/did-doc": "1.2.0",
45
- "@verii/did-web": "1.2.0",
46
- "@verii/endpoints-credential-types-registrar": "1.2.0",
47
- "@verii/fastify-plugins": "1.2.0",
48
- "@verii/fineract-client": "1.2.0",
49
- "@verii/image-processing": "1.2.0",
50
- "@verii/validation": "1.2.0",
51
- "@verii/vc-checks": "1.2.0",
52
- "@verii/spencer-mongo-extensions": "1.2.0",
53
- "@verii/rest-queries": "1.2.0",
54
- "@verii/jwt": "1.2.0",
55
- "@verii/verii-verification": "1.2.0",
56
- "@verii/organizations-registry": "1.2.0"
32
+ "@verii/aws-clients": "1.2.1",
33
+ "@verii/base-contract-io": "1.2.1",
34
+ "@verii/blockchain-functions": "1.2.1",
35
+ "@verii/common-functions": "1.2.1",
36
+ "@verii/common-schemas": "1.2.1",
37
+ "@verii/config": "1.2.1",
38
+ "@verii/auth": "1.2.1",
39
+ "@verii/country-data": "1.2.1",
40
+ "@verii/csv-parser": "1.2.1",
41
+ "@verii/crypto": "1.2.1",
42
+ "@verii/db-kms": "1.2.1",
43
+ "@verii/did-web": "1.2.1",
44
+ "@verii/did-doc": "1.2.1",
45
+ "@verii/endpoints-credential-types-registrar": "1.2.1",
46
+ "@verii/contract-permissions": "1.2.1",
47
+ "@verii/fastify-plugins": "1.2.1",
48
+ "@verii/fineract-client": "1.2.1",
49
+ "@verii/image-processing": "1.2.1",
50
+ "@verii/jwt": "1.2.1",
51
+ "@verii/spencer-mongo-extensions": "1.2.1",
52
+ "@verii/rest-queries": "1.2.1",
53
+ "@verii/organizations-registry": "1.2.1",
54
+ "@verii/validation": "1.2.1",
55
+ "@verii/vc-checks": "1.2.1",
56
+ "@verii/verii-verification": "1.2.1"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@spencejs/spence-mongo-repos": "^1.1.0",
@@ -76,12 +76,12 @@
76
76
  "mongodb": "^7.5.0",
77
77
  "nock": "15.0.0-beta.14",
78
78
  "prettier": "3.9.6",
79
- "@verii/common-functions": "1.2.0",
80
- "@verii/error-aggregation": "1.2.0",
81
- "@verii/sample-data": "1.2.0",
82
- "@verii/server-provider": "1.2.0",
83
- "@verii/tests-helpers": "1.2.0",
84
- "@verii/test-regexes": "1.2.0"
79
+ "@verii/error-aggregation": "1.2.1",
80
+ "@verii/server-provider": "1.2.1",
81
+ "@verii/test-regexes": "1.2.1",
82
+ "@verii/sample-data": "1.2.1",
83
+ "@verii/tests-helpers": "1.2.1",
84
+ "@verii/common-functions": "1.2.1"
85
85
  },
86
86
  "scripts": {
87
87
  "test": "cross-env NODE_ENV=test node --test --test-concurrency=1 --test-timeout=900000 --experimental-test-module-mocks --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout 'test/**/*.test.js'",
@@ -33,6 +33,7 @@ const {
33
33
  initBuildOrganizationModificationsOnServiceChange,
34
34
  runAllOrgChecks,
35
35
  OrganizationErrorMessages,
36
+ softDeleteOrganization,
36
37
  OrganizationServiceErrorMessages,
37
38
  RegistrarScopes,
38
39
  initProvisionAuth0ClientGrants,
@@ -595,20 +596,7 @@ const organizationController = async (fastify) => {
595
596
  });
596
597
  }
597
598
 
598
- const groupsToModify = await repos.groups.find({
599
- filter: { dids: params.did },
600
- });
601
- const groupIds = map('_id', groupsToModify);
602
- await repos.groups.collection().updateMany(
603
- { _id: { $in: groupIds } },
604
- {
605
- $pull: { dids: orgToDelete.didDoc.id },
606
- $set: { updatedAt: new Date() },
607
- },
608
- );
609
-
610
- const modifiedProfile = { ...orgToDelete, deletedAt: new Date() };
611
- await repos.organizations.update(orgToDelete._id, modifiedProfile);
599
+ await softDeleteOrganization(orgToDelete, req);
612
600
 
613
601
  return reply.status(204).send();
614
602
  },
@@ -69,7 +69,7 @@ const initAddServiceToOrganization = async (fastify) => {
69
69
  if (isNewNodeOperatorService(organization, createdService)) {
70
70
  const stakesAccountId = await createStakesAccount(
71
71
  organization.ids.fineractClientId,
72
- organization.didDoc.id,
72
+ organization._id,
73
73
  context,
74
74
  );
75
75
 
@@ -6,6 +6,10 @@ const buildProfileResponse = (organization, includeTimestamps = false) => {
6
6
  ...organization.profile,
7
7
  description: organization.profile.description || '',
8
8
  id: organization.didDoc.id,
9
+ // Registrations interrupted before soft deletion of failed provisioning was
10
+ // introduced may still lack the derived profile fields
11
+ permittedVelocityServiceCategory:
12
+ organization.profile.permittedVelocityServiceCategory ?? [],
9
13
  verifiableCredentialJwt: organization.verifiableCredentialJwt,
10
14
  };
11
15
  if (includeTimestamps) {
@@ -2,5 +2,6 @@ module.exports = {
2
2
  ...require('./init-create-organization'),
3
3
  ...require('./add-primary-permissions'),
4
4
  ...require('./init-provision-group'),
5
+ ...require('./soft-delete-organization'),
5
6
  ...require('./verify-profile-website-unique'),
6
7
  };
@@ -32,6 +32,7 @@ const {
32
32
  } = require('./build-non-custodied-organization');
33
33
  const { initProvisionGroup } = require('./init-provision-group');
34
34
  const { addPrimaryPermissions } = require('./add-primary-permissions');
35
+ const { softDeleteOrganization } = require('./soft-delete-organization');
35
36
  const {
36
37
  getServiceConsentType,
37
38
  } = require('../../organization-services/domains/get-service-consent-type');
@@ -44,30 +45,12 @@ const initCreateOrganization = async (fastify) => {
44
45
  const auth0Provisioner = await initAuth0Provisioner(fastify.config);
45
46
  const provisionAuth0Clients = initProvisionAuth0Clients(auth0Provisioner);
46
47
 
47
- return async (
48
- { byoDid, byoKeys, serviceEndpoints, profile, invitationCode },
48
+ const provisionOrganization = async (
49
+ { organization, invitation, newKeys, newKeyPairs, caoServiceRefs },
49
50
  context,
50
51
  ) => {
51
52
  const { repos, kms, user } = context;
52
53
 
53
- const invitation = await acceptInvitation(invitationCode, context);
54
- const buildOrganization =
55
- byoDid == null
56
- ? buildCustodiedOrganization
57
- : buildNonCustodiedOrganization;
58
- const { newOrganization, newKeys, newKeyPairs, caoServiceRefs } =
59
- await buildOrganization(
60
- {
61
- byoDid,
62
- byoKeys,
63
- serviceEndpoints,
64
- profile,
65
- invitation,
66
- },
67
- context,
68
- );
69
-
70
- const organization = await repos.organizations.insert(newOrganization);
71
54
  await provisionGroup(organization, context);
72
55
 
73
56
  const activatedServiceIds = activateServices(
@@ -216,6 +199,65 @@ const initCreateOrganization = async (fastify) => {
216
199
  authClients,
217
200
  };
218
201
  };
202
+
203
+ return async (
204
+ { byoDid, byoKeys, serviceEndpoints, profile, invitationCode },
205
+ context,
206
+ ) => {
207
+ const { repos } = context;
208
+
209
+ const invitation = await acceptInvitation(invitationCode, context);
210
+ const buildOrganization =
211
+ byoDid == null
212
+ ? buildCustodiedOrganization
213
+ : buildNonCustodiedOrganization;
214
+ const { newOrganization, newKeys, newKeyPairs, caoServiceRefs } =
215
+ await buildOrganization(
216
+ {
217
+ byoDid,
218
+ byoKeys,
219
+ serviceEndpoints,
220
+ profile,
221
+ invitation,
222
+ },
223
+ context,
224
+ );
225
+
226
+ const organization = await repos.organizations.insert(newOrganization);
227
+
228
+ // Provisioning talks to external systems (Auth0, Fineract, KMS, blockchain).
229
+ // If any of them fails, the organization must not linger as a partial record:
230
+ // soft delete it so it is hidden from every finder and the registration can be retried.
231
+ try {
232
+ return await provisionOrganization(
233
+ { organization, invitation, newKeys, newKeyPairs, caoServiceRefs },
234
+ context,
235
+ );
236
+ } catch (error) {
237
+ await softDeleteFailedOrganization(organization, error, context);
238
+ throw error;
239
+ }
240
+ };
241
+ };
242
+
243
+ const softDeleteFailedOrganization = async (organization, error, context) => {
244
+ const { log } = context;
245
+ const logContext = {
246
+ organizationId: organization._id,
247
+ did: organization.didDoc.id,
248
+ };
249
+ log.error(
250
+ { err: error, ...logContext },
251
+ 'Organization provisioning failed. Soft deleting the organization',
252
+ );
253
+ try {
254
+ await softDeleteOrganization(organization, context);
255
+ } catch (softDeleteError) {
256
+ log.error(
257
+ { err: softDeleteError, ...logContext },
258
+ 'Failed to soft delete the organization after provisioning failure',
259
+ );
260
+ }
219
261
  };
220
262
 
221
263
  module.exports = { initCreateOrganization };
@@ -0,0 +1,34 @@
1
+ const softDeleteOrganization = async (organization, { repos }) => {
2
+ const did = organization.didDoc.id;
3
+ const now = new Date();
4
+
5
+ // hide the organization first: every organization finder excludes deleted
6
+ // records, so a failure in the group cleanup below leaves nothing visible
7
+ const deletedOrganization = await repos.organizations.update(
8
+ organization._id,
9
+ { deletedAt: now },
10
+ );
11
+
12
+ await repos.groups
13
+ .collection()
14
+ .updateMany(
15
+ { dids: did },
16
+ { $pull: { dids: did }, $set: { updatedAt: now } },
17
+ );
18
+
19
+ // the group provisioned for the organization itself is keyed by its did;
20
+ // once it holds no other dids and no client admins, remove it so a retried
21
+ // registration can create it again instead of inserting a duplicate
22
+ await repos.groups.collection().deleteMany({
23
+ groupId: did,
24
+ dids: { $size: 0 },
25
+ $or: [
26
+ { clientAdminIds: { $exists: false } },
27
+ { clientAdminIds: { $size: 0 } },
28
+ ],
29
+ });
30
+
31
+ return deletedOrganization;
32
+ };
33
+
34
+ module.exports = { softDeleteOrganization };