@cheqd/studio 3.15.0-develop.1 → 3.15.0-develop.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/app.js +4 -0
  3. package/dist/app.js.map +1 -1
  4. package/dist/controllers/api/accreditation.d.ts.map +1 -1
  5. package/dist/controllers/api/accreditation.js +36 -9
  6. package/dist/controllers/api/accreditation.js.map +1 -1
  7. package/dist/controllers/api/credential-status.d.ts +94 -0
  8. package/dist/controllers/api/credential-status.d.ts.map +1 -1
  9. package/dist/controllers/api/credential-status.js +220 -506
  10. package/dist/controllers/api/credential-status.js.map +1 -1
  11. package/dist/controllers/api/credential.d.ts +66 -0
  12. package/dist/controllers/api/credential.d.ts.map +1 -1
  13. package/dist/controllers/api/credential.js +117 -1
  14. package/dist/controllers/api/credential.js.map +1 -1
  15. package/dist/controllers/api/did.js.map +1 -1
  16. package/dist/controllers/api/key.d.ts +71 -1
  17. package/dist/controllers/api/key.d.ts.map +1 -1
  18. package/dist/controllers/api/key.js +113 -2
  19. package/dist/controllers/api/key.js.map +1 -1
  20. package/dist/controllers/api/presentation.js +22 -21
  21. package/dist/controllers/api/presentation.js.map +1 -1
  22. package/dist/controllers/api/providers.controller.js.map +1 -1
  23. package/dist/controllers/api/resource.js.map +1 -1
  24. package/dist/controllers/validator/did.js +1 -1
  25. package/dist/controllers/validator/did.js.map +1 -1
  26. package/dist/database/entities/issued-credential.entity.d.ts +10 -1
  27. package/dist/database/entities/issued-credential.entity.d.ts.map +1 -1
  28. package/dist/database/entities/issued-credential.entity.js +22 -0
  29. package/dist/database/entities/issued-credential.entity.js.map +1 -1
  30. package/dist/database/entities/status-registry.entity.d.ts +60 -0
  31. package/dist/database/entities/status-registry.entity.d.ts.map +1 -0
  32. package/dist/database/entities/status-registry.entity.js +145 -0
  33. package/dist/database/entities/status-registry.entity.js.map +1 -0
  34. package/dist/database/migrations/1761834657128-studio-migrations.d.ts +7 -0
  35. package/dist/database/migrations/1761834657128-studio-migrations.d.ts.map +1 -0
  36. package/dist/database/migrations/1761834657128-studio-migrations.js +14 -0
  37. package/dist/database/migrations/1761834657128-studio-migrations.js.map +1 -0
  38. package/dist/database/migrations/1762775396083-MigrateStatusLists.d.ts +25 -0
  39. package/dist/database/migrations/1762775396083-MigrateStatusLists.d.ts.map +1 -0
  40. package/dist/database/migrations/1762775396083-MigrateStatusLists.js +243 -0
  41. package/dist/database/migrations/1762775396083-MigrateStatusLists.js.map +1 -0
  42. package/dist/database/migrations/1762775500000-UpdateWriteCursors.d.ts +23 -0
  43. package/dist/database/migrations/1762775500000-UpdateWriteCursors.d.ts.map +1 -0
  44. package/dist/database/migrations/1762775500000-UpdateWriteCursors.js +181 -0
  45. package/dist/database/migrations/1762775500000-UpdateWriteCursors.js.map +1 -0
  46. package/dist/database/types/types.d.ts.map +1 -1
  47. package/dist/database/types/types.js +10 -0
  48. package/dist/database/types/types.js.map +1 -1
  49. package/dist/helpers/mailchimp.d.ts.map +1 -1
  50. package/dist/helpers/mailchimp.js +1 -3
  51. package/dist/helpers/mailchimp.js.map +1 -1
  52. package/dist/middleware/auth/routes/api/credential-status-auth.d.ts.map +1 -1
  53. package/dist/middleware/auth/routes/api/credential-status-auth.js +2 -0
  54. package/dist/middleware/auth/routes/api/credential-status-auth.js.map +1 -1
  55. package/dist/middleware/auth/routes/api/key-auth.d.ts.map +1 -1
  56. package/dist/middleware/auth/routes/api/key-auth.js +1 -0
  57. package/dist/middleware/auth/routes/api/key-auth.js.map +1 -1
  58. package/dist/services/api/credential-status.d.ts +107 -0
  59. package/dist/services/api/credential-status.d.ts.map +1 -0
  60. package/dist/services/api/credential-status.js +923 -0
  61. package/dist/services/api/credential-status.js.map +1 -0
  62. package/dist/services/api/credentials.d.ts +12 -0
  63. package/dist/services/api/credentials.d.ts.map +1 -1
  64. package/dist/services/api/credentials.js +330 -117
  65. package/dist/services/api/credentials.js.map +1 -1
  66. package/dist/services/connectors/resource.js.map +1 -1
  67. package/dist/services/identity/abstract.d.ts +1 -1
  68. package/dist/services/identity/abstract.d.ts.map +1 -1
  69. package/dist/services/identity/abstract.js +1 -1
  70. package/dist/services/identity/abstract.js.map +1 -1
  71. package/dist/services/identity/default.d.ts +1 -1
  72. package/dist/services/identity/default.d.ts.map +1 -1
  73. package/dist/services/identity/default.js +2 -2
  74. package/dist/services/identity/default.js.map +1 -1
  75. package/dist/services/identity/index.d.ts +1 -1
  76. package/dist/services/identity/index.d.ts.map +1 -1
  77. package/dist/services/identity/providers/studio/agent.d.ts +6 -6
  78. package/dist/services/identity/providers/studio/agent.d.ts.map +1 -1
  79. package/dist/services/identity/providers/studio/agent.js +11 -2
  80. package/dist/services/identity/providers/studio/agent.js.map +1 -1
  81. package/dist/services/identity/providers/studio/local.d.ts +3 -3
  82. package/dist/services/identity/providers/studio/postgres.d.ts +3 -3
  83. package/dist/services/identity/providers/studio/postgres.d.ts.map +1 -1
  84. package/dist/services/identity/providers/studio/postgres.js +0 -12
  85. package/dist/services/identity/providers/studio/postgres.js.map +1 -1
  86. package/dist/services/track/api/credential-status-subscriber.d.ts +4 -4
  87. package/dist/services/track/api/credential-status-subscriber.d.ts.map +1 -1
  88. package/dist/services/track/api/credential-status-subscriber.js +18 -1
  89. package/dist/services/track/api/credential-status-subscriber.js.map +1 -1
  90. package/dist/services/track/api/credential-subscriber.d.ts +4 -4
  91. package/dist/services/track/api/credential-subscriber.d.ts.map +1 -1
  92. package/dist/services/track/api/credential-subscriber.js.map +1 -1
  93. package/dist/services/track/api/did-subscriber.d.ts +4 -4
  94. package/dist/services/track/api/did-subscriber.d.ts.map +1 -1
  95. package/dist/services/track/api/did-subscriber.js.map +1 -1
  96. package/dist/services/track/api/key-subscriber.d.ts +4 -4
  97. package/dist/services/track/api/key-subscriber.d.ts.map +1 -1
  98. package/dist/services/track/api/key-subscriber.js.map +1 -1
  99. package/dist/services/track/api/presentation-subscriber.d.ts +4 -4
  100. package/dist/services/track/api/presentation-subscriber.d.ts.map +1 -1
  101. package/dist/services/track/api/presentation-subscriber.js.map +1 -1
  102. package/dist/services/track/api/resource-subscriber.d.ts +4 -4
  103. package/dist/services/track/api/resource-subscriber.d.ts.map +1 -1
  104. package/dist/services/track/api/resource-subscriber.js.map +1 -1
  105. package/dist/services/track/base.d.ts +2 -2
  106. package/dist/services/track/base.d.ts.map +1 -1
  107. package/dist/services/track/base.js.map +1 -1
  108. package/dist/services/track/operation-subscriber.d.ts +4 -4
  109. package/dist/services/track/operation-subscriber.d.ts.map +1 -1
  110. package/dist/services/track/operation-subscriber.js.map +1 -1
  111. package/dist/services/track/tracker.d.ts +2 -2
  112. package/dist/services/track/tracker.d.ts.map +1 -1
  113. package/dist/services/track/tracker.js.map +1 -1
  114. package/dist/services/track/types.d.ts +2 -2
  115. package/dist/services/track/types.d.ts.map +1 -1
  116. package/dist/static/swagger-api.json +684 -67
  117. package/dist/types/accreditation.d.ts +3 -0
  118. package/dist/types/accreditation.d.ts.map +1 -1
  119. package/dist/types/constants.d.ts +2 -0
  120. package/dist/types/constants.d.ts.map +1 -1
  121. package/dist/types/constants.js +2 -0
  122. package/dist/types/constants.js.map +1 -1
  123. package/dist/types/credential-status.d.ts +39 -1
  124. package/dist/types/credential-status.d.ts.map +1 -1
  125. package/dist/types/credential-status.js +7 -0
  126. package/dist/types/credential-status.js.map +1 -1
  127. package/dist/types/credential.d.ts +21 -3
  128. package/dist/types/credential.d.ts.map +1 -1
  129. package/dist/types/credential.js +5 -0
  130. package/dist/types/credential.js.map +1 -1
  131. package/dist/types/swagger-api-types.d.ts +242 -39
  132. package/dist/types/swagger-api-types.d.ts.map +1 -1
  133. package/dist/types/swagger-api-types.js +242 -39
  134. package/dist/types/swagger-api-types.js.map +1 -1
  135. package/dist/types/track.d.ts +8 -6
  136. package/dist/types/track.d.ts.map +1 -1
  137. package/dist/types/track.js.map +1 -1
  138. package/package.json +2 -2
@@ -0,0 +1,923 @@
1
+ import { fromString } from 'uint8arrays';
2
+ import { IdentityServiceStrategySetup } from '../identity/index.js';
3
+ import { DefaultStatusActionPurposeMap, StatusRegistryState, StatusListType } from '../../types/credential-status.js';
4
+ import { Cheqd, } from '@cheqd/did-provider-cheqd';
5
+ import { toNetwork } from '../../helpers/helpers.js';
6
+ import { eventTracker } from '../track/tracker.js';
7
+ import { OperationCategoryNameEnum, OperationNameEnum } from '../../types/constants.js';
8
+ import { FeeAnalyzer } from '../../helpers/fee-analyzer.js';
9
+ import { Like } from 'typeorm';
10
+ import { StatusRegistryEntity } from '../../database/entities/status-registry.entity.js';
11
+ import { Connection } from '../../database/connection/connection.js';
12
+ import { v4 } from 'uuid';
13
+ import { IdentifierService } from './identifier.js';
14
+ import { CredentialCategory } from '../../types/credential.js';
15
+ import { StatusCodes } from 'http-status-codes';
16
+ export class CredentialStatusService {
17
+ static instance = new CredentialStatusService();
18
+ repository;
19
+ constructor() {
20
+ this.repository = Connection.instance.dbConnection.getRepository(StatusRegistryEntity);
21
+ }
22
+ async createUnencryptedStatusList(body, query, customer, user) {
23
+ const { did, encodedList, statusListName, alsoKnownAs, statusListVersion = '1.0', length, encoding, statusSize: size, ttl, statusMessages, state, credentialCategory = CredentialCategory.CREDENTIAL, } = body;
24
+ const { listType, statusPurpose } = query;
25
+ const data = encodedList ? fromString(encodedList, encoding) : undefined;
26
+ const identityServiceStrategySetup = new IdentityServiceStrategySetup(customer.customerId);
27
+ try {
28
+ // validate identifier
29
+ const identifier = await IdentifierService.instance.get(did, customer);
30
+ if (!identifier) {
31
+ return {
32
+ success: false,
33
+ statusCode: StatusCodes.NOT_FOUND,
34
+ error: `Identifier ${did} not found for the customer ${customer.customerId}`,
35
+ };
36
+ }
37
+ if (data) {
38
+ let result;
39
+ if (listType === StatusListType.Bitstring) {
40
+ result = await identityServiceStrategySetup.agent.broadcastBitstringStatusList(did, {
41
+ data,
42
+ name: statusListName,
43
+ alsoKnownAs,
44
+ version: statusListVersion,
45
+ }, customer);
46
+ }
47
+ else {
48
+ result = await identityServiceStrategySetup.agent.broadcastStatusList2021(did, {
49
+ data,
50
+ name: statusListName,
51
+ alsoKnownAs,
52
+ version: statusListVersion,
53
+ }, { encoding, statusPurpose: statusPurpose }, customer);
54
+ }
55
+ return {
56
+ success: result,
57
+ statusCode: StatusCodes.OK,
58
+ data,
59
+ };
60
+ }
61
+ let result;
62
+ let statusSize;
63
+ if (listType === StatusListType.Bitstring) {
64
+ result = (await identityServiceStrategySetup.agent.createUnencryptedBitstringStatusList(did, {
65
+ name: statusListName,
66
+ alsoKnownAs,
67
+ version: statusListVersion,
68
+ }, {
69
+ length,
70
+ size,
71
+ statusMessages,
72
+ ttl,
73
+ encoding,
74
+ statusPurpose,
75
+ }, customer));
76
+ statusSize = result.resource.metadata.length;
77
+ }
78
+ else {
79
+ result = (await identityServiceStrategySetup.agent.createUnencryptedStatusList2021(did, {
80
+ name: statusListName,
81
+ alsoKnownAs,
82
+ version: statusListVersion,
83
+ }, {
84
+ length,
85
+ encoding,
86
+ statusPurpose: statusPurpose,
87
+ }, customer));
88
+ statusSize = length || Cheqd.defaultStatusList2021Length;
89
+ }
90
+ if (result.error) {
91
+ return {
92
+ success: false,
93
+ statusCode: StatusCodes.BAD_REQUEST,
94
+ error: result.error?.message || result.error.toString(),
95
+ };
96
+ }
97
+ // persist status list in the db
98
+ const statusRegistry = new StatusRegistryEntity({
99
+ registryId: v4(),
100
+ uri: `${did}?resourceName=${result.resourceMetadata.resourceName}&resourceType=${result.resourceMetadata.resourceType}`,
101
+ registryType: result.resourceMetadata.resourceType,
102
+ registryName: statusListName,
103
+ credentialCategory,
104
+ version: 1,
105
+ registrySize: statusSize,
106
+ writeCursor: 0,
107
+ state: state || StatusRegistryState.Active,
108
+ storageType: 'cheqd',
109
+ identifier,
110
+ customer,
111
+ metadata: {
112
+ statusPurpose,
113
+ },
114
+ });
115
+ await this.repository.save(statusRegistry);
116
+ const trackInfo = {
117
+ category: OperationCategoryNameEnum.CREDENTIAL_STATUS,
118
+ name: OperationNameEnum.CREDENTIAL_STATUS_CREATE_UNENCRYPTED,
119
+ customer,
120
+ user,
121
+ data: {
122
+ did,
123
+ registryId: statusRegistry.registryId,
124
+ resource: result.resourceMetadata,
125
+ encrypted: result.resource?.metadata?.encrypted,
126
+ symmetricKey: '',
127
+ },
128
+ };
129
+ eventTracker.emit('track', trackInfo);
130
+ return {
131
+ success: true,
132
+ statusCode: StatusCodes.OK,
133
+ data: result,
134
+ };
135
+ }
136
+ catch (error) {
137
+ return {
138
+ success: false,
139
+ statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
140
+ error: `Internal error: ${error?.message || error}`,
141
+ };
142
+ }
143
+ }
144
+ async createEncryptedStatusList(body, query, customer, user) {
145
+ const { did, statusListName, alsoKnownAs, statusListVersion = '1.0', length, statusSize: size, ttl, statusMessages, encoding, paymentConditions, feePaymentAddress, feePaymentAmount, feePaymentWindow, state, credentialCategory = CredentialCategory.CREDENTIAL, } = body;
146
+ const { listType, statusPurpose } = query;
147
+ const identityServiceStrategySetup = new IdentityServiceStrategySetup(customer.customerId);
148
+ try {
149
+ let result;
150
+ let statusSize;
151
+ // validate identifier
152
+ const identifier = await IdentifierService.instance.get(did, customer);
153
+ if (!identifier) {
154
+ return {
155
+ success: false,
156
+ statusCode: StatusCodes.NOT_FOUND,
157
+ error: `Identifier ${did} not found for the customer ${customer.customerId}`,
158
+ };
159
+ }
160
+ if (listType === StatusListType.Bitstring) {
161
+ result = (await identityServiceStrategySetup.agent.createEncryptedBitstringStatusList(did, {
162
+ name: statusListName,
163
+ alsoKnownAs,
164
+ version: statusListVersion,
165
+ }, {
166
+ length,
167
+ size,
168
+ statusMessages,
169
+ ttl,
170
+ encoding,
171
+ statusPurpose,
172
+ paymentConditions,
173
+ feePaymentAddress,
174
+ feePaymentAmount,
175
+ feePaymentWindow,
176
+ }, customer));
177
+ statusSize = result.resource.metadata.length;
178
+ }
179
+ else {
180
+ result = (await identityServiceStrategySetup.agent.createEncryptedStatusList2021(did, {
181
+ name: statusListName,
182
+ alsoKnownAs,
183
+ version: statusListVersion,
184
+ }, {
185
+ length,
186
+ encoding,
187
+ statusPurpose: statusPurpose,
188
+ paymentConditions,
189
+ feePaymentAddress,
190
+ feePaymentAmount,
191
+ feePaymentWindow,
192
+ }, customer));
193
+ statusSize = size || Cheqd.defaultStatusList2021Length;
194
+ }
195
+ if (result.error) {
196
+ return {
197
+ success: false,
198
+ statusCode: StatusCodes.BAD_REQUEST,
199
+ error: result.error?.message || result.error.toString(),
200
+ };
201
+ }
202
+ // persist status list in the db
203
+ const statusRegistry = new StatusRegistryEntity({
204
+ registryId: v4(),
205
+ uri: `${did}?resourceName=${result.resourceMetadata.resourceName}&resourceType=${result.resourceMetadata.resourceType}`,
206
+ registryType: result.resourceMetadata.resourceType,
207
+ registryName: statusListName,
208
+ credentialCategory,
209
+ version: 1,
210
+ registrySize: statusSize,
211
+ writeCursor: 0,
212
+ state: state || StatusRegistryState.Active,
213
+ storageType: 'cheqd',
214
+ identifier,
215
+ customer,
216
+ encrypted: true,
217
+ metadata: {
218
+ statusPurpose,
219
+ paymentConditions,
220
+ feePaymentAddress,
221
+ feePaymentAmount,
222
+ feePaymentWindow,
223
+ },
224
+ });
225
+ await this.repository.save(statusRegistry);
226
+ const trackInfo = {
227
+ name: OperationNameEnum.CREDENTIAL_STATUS_CREATE_ENCRYPTED,
228
+ category: OperationCategoryNameEnum.CREDENTIAL_STATUS,
229
+ customer,
230
+ user,
231
+ data: {
232
+ did,
233
+ registryId: statusRegistry.registryId,
234
+ resource: result.resourceMetadata,
235
+ encrypted: true,
236
+ symmetricKey: '',
237
+ },
238
+ feePaymentOptions: [],
239
+ };
240
+ eventTracker.emit('track', trackInfo);
241
+ return {
242
+ success: true,
243
+ statusCode: StatusCodes.OK,
244
+ data: result,
245
+ };
246
+ }
247
+ catch (error) {
248
+ return {
249
+ success: false,
250
+ statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
251
+ error: `Internal error: ${error?.message || error}`,
252
+ };
253
+ }
254
+ }
255
+ async updateUnencryptedStatusList(body, query, customer, user) {
256
+ const { did, statusListName, statusListVersion = '1.0', indices } = body;
257
+ const { statusAction, listType } = query;
258
+ const identityServiceStrategySetup = new IdentityServiceStrategySetup(customer.customerId);
259
+ const unencrypted = await identityServiceStrategySetup.agent.searchStatusList(did, statusListName, listType, DefaultStatusActionPurposeMap[statusAction]);
260
+ if (unencrypted.error) {
261
+ if (unencrypted.error === 'notFound') {
262
+ return {
263
+ success: false,
264
+ statusCode: StatusCodes.NOT_FOUND,
265
+ error: `update: error: status list '${statusListName}' not found`,
266
+ };
267
+ }
268
+ return {
269
+ success: false,
270
+ statusCode: StatusCodes.BAD_REQUEST,
271
+ error: `update: error: ${unencrypted.error}`,
272
+ };
273
+ }
274
+ if (unencrypted.resource?.metadata?.encrypted) {
275
+ return {
276
+ success: false,
277
+ statusCode: StatusCodes.BAD_REQUEST,
278
+ error: `update: error: status list '${statusListName}' is encrypted`,
279
+ };
280
+ }
281
+ try {
282
+ const result = (await identityServiceStrategySetup.agent.updateUnencryptedStatusList(did, listType, {
283
+ indices: typeof indices === 'number' ? [indices] : indices,
284
+ statusListName,
285
+ statusListVersion,
286
+ statusAction,
287
+ }, customer));
288
+ result.updated = (function (that) {
289
+ if (that?.revoked?.every((item) => !!item) &&
290
+ that?.revoked?.length !== 0)
291
+ return true;
292
+ if (that?.suspended?.every((item) => !!item) &&
293
+ that?.suspended?.length !== 0)
294
+ return true;
295
+ if (that?.unsuspended?.every((item) => !!item) &&
296
+ that?.unsuspended?.length !== 0)
297
+ return true;
298
+ return false;
299
+ })(result);
300
+ if (result.error) {
301
+ return {
302
+ success: false,
303
+ statusCode: StatusCodes.BAD_REQUEST,
304
+ error: result.error?.message || result.error.toString(),
305
+ };
306
+ }
307
+ const formatted = {
308
+ updated: true,
309
+ revoked: result?.revoked || undefined,
310
+ suspended: result?.suspended || undefined,
311
+ unsuspended: result?.unsuspended || undefined,
312
+ resource: result.statusList,
313
+ resourceMetadata: result.resourceMetadata,
314
+ };
315
+ if (result.resourceMetadata) {
316
+ const trackInfo = {
317
+ category: OperationCategoryNameEnum.CREDENTIAL_STATUS,
318
+ name: OperationNameEnum.CREDENTIAL_STATUS_UPDATE_UNENCRYPTED,
319
+ customer,
320
+ user,
321
+ data: {
322
+ did,
323
+ resource: result.resourceMetadata,
324
+ encrypted: false,
325
+ symmetricKey: '',
326
+ },
327
+ };
328
+ eventTracker.emit('track', trackInfo);
329
+ }
330
+ return {
331
+ success: true,
332
+ statusCode: StatusCodes.OK,
333
+ data: formatted,
334
+ };
335
+ }
336
+ catch (error) {
337
+ return {
338
+ success: false,
339
+ statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
340
+ error: `Internal error: ${error?.message || error}`,
341
+ };
342
+ }
343
+ }
344
+ async updateEncryptedStatusList(body, query, customer, user) {
345
+ const { did, statusListName, statusListVersion, indices, symmetricKey, paymentConditions, feePaymentAddress, feePaymentAmount, feePaymentWindow, } = body;
346
+ const { statusAction, listType } = query;
347
+ const identityServiceStrategySetup = new IdentityServiceStrategySetup(customer.customerId);
348
+ const encrypted = await identityServiceStrategySetup.agent.searchStatusList(did, statusListName, listType, DefaultStatusActionPurposeMap[statusAction]);
349
+ if (encrypted.error) {
350
+ if (encrypted.error === 'notFound') {
351
+ return {
352
+ success: false,
353
+ statusCode: StatusCodes.NOT_FOUND,
354
+ error: `update: error: status list '${statusListName}' not found`,
355
+ };
356
+ }
357
+ return {
358
+ success: false,
359
+ statusCode: StatusCodes.BAD_REQUEST,
360
+ error: `update: error: ${encrypted.error}`,
361
+ };
362
+ }
363
+ if (!encrypted.resource?.metadata?.encrypted) {
364
+ return {
365
+ success: false,
366
+ statusCode: StatusCodes.BAD_REQUEST,
367
+ error: `update: error: status list '${statusListName}' is unencrypted`,
368
+ };
369
+ }
370
+ try {
371
+ const result = (await identityServiceStrategySetup.agent.updateEncryptedStatusList(did, listType, {
372
+ indices: typeof indices === 'number' ? [indices] : indices,
373
+ statusListName,
374
+ statusListVersion,
375
+ statusAction,
376
+ paymentConditions,
377
+ symmetricKey,
378
+ feePaymentAddress,
379
+ feePaymentAmount,
380
+ feePaymentWindow,
381
+ }, customer));
382
+ result.updated = (function (that) {
383
+ if (that?.revoked?.every((item) => !!item) &&
384
+ that?.revoked?.length !== 0)
385
+ return true;
386
+ if (that?.suspended?.every((item) => !!item) &&
387
+ that?.suspended?.length !== 0)
388
+ return true;
389
+ if (that?.unsuspended?.every((item) => !!item) &&
390
+ that?.unsuspended?.length !== 0)
391
+ return true;
392
+ return false;
393
+ })(result);
394
+ if (result.error) {
395
+ return {
396
+ success: false,
397
+ statusCode: StatusCodes.BAD_REQUEST,
398
+ error: result.error?.message || result.error.toString(),
399
+ };
400
+ }
401
+ const formatted = {
402
+ updated: true,
403
+ revoked: result?.revoked || undefined,
404
+ suspended: result?.suspended || undefined,
405
+ unsuspended: result?.unsuspended || undefined,
406
+ resource: result.statusList,
407
+ resourceMetadata: result.resourceMetadata,
408
+ symmetricKey: result.symmetricKey,
409
+ };
410
+ if (result.resourceMetadata) {
411
+ const trackInfo = {
412
+ category: OperationCategoryNameEnum.CREDENTIAL_STATUS,
413
+ name: OperationNameEnum.CREDENTIAL_STATUS_UPDATE_ENCRYPTED,
414
+ customer,
415
+ user,
416
+ data: {
417
+ did,
418
+ resource: result.resourceMetadata,
419
+ encrypted: true,
420
+ symmetricKey: '',
421
+ },
422
+ feePaymentOptions: [],
423
+ };
424
+ eventTracker.emit('track', trackInfo);
425
+ }
426
+ return {
427
+ success: true,
428
+ statusCode: StatusCodes.OK,
429
+ data: formatted,
430
+ };
431
+ }
432
+ catch (error) {
433
+ return {
434
+ success: false,
435
+ statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
436
+ error: `Internal error: ${error?.message || error}`,
437
+ };
438
+ }
439
+ }
440
+ async checkStatusList(body, query, customer, user) {
441
+ const feePaymentOptions = [];
442
+ // collect request parameters - case: body
443
+ const { did, statusListName, index, makeFeePayment, statusListCredential, statusSize, statusMessage } = body;
444
+ // collect request parameters - case: query
445
+ const { statusPurpose, listType } = query;
446
+ // Make the base body for tracking
447
+ const trackInfo = {
448
+ name: OperationNameEnum.CREDENTIAL_STATUS_CHECK,
449
+ category: OperationCategoryNameEnum.CREDENTIAL_STATUS,
450
+ customer,
451
+ user,
452
+ successful: false,
453
+ data: {
454
+ did,
455
+ registryId: '',
456
+ },
457
+ };
458
+ if (listType === StatusListType.Bitstring) {
459
+ if (!statusListCredential)
460
+ return {
461
+ statusCode: StatusCodes.BAD_REQUEST,
462
+ success: false,
463
+ error: `check: error: 'statusListCredential' is required for BitstringStatusList type`,
464
+ };
465
+ if (statusSize && statusSize > 1 && !statusMessage)
466
+ return {
467
+ statusCode: StatusCodes.BAD_REQUEST,
468
+ success: false,
469
+ error: `check: error: 'statusMessage' is required when 'statusSize' is greater than 1 for BitstringStatusList type`,
470
+ };
471
+ }
472
+ const identityServiceStrategySetup = new IdentityServiceStrategySetup(customer.customerId);
473
+ const statusList = await identityServiceStrategySetup.agent.searchStatusList(did, statusListName, listType, statusPurpose);
474
+ if (statusList.error) {
475
+ if (statusList.error === 'notFound') {
476
+ return {
477
+ success: false,
478
+ statusCode: StatusCodes.NOT_FOUND,
479
+ error: `check: error: status list '${statusListName}' not found`,
480
+ };
481
+ }
482
+ return {
483
+ success: false,
484
+ statusCode: StatusCodes.BAD_REQUEST,
485
+ error: `check: error: ${statusList.error}`,
486
+ };
487
+ }
488
+ try {
489
+ // make fee payment, if defined
490
+ if (makeFeePayment && statusList?.resource?.metadata?.encrypted) {
491
+ const feePaymentResult = await Promise.all(statusList?.resource?.metadata?.paymentConditions?.map(async (condition) => {
492
+ return await identityServiceStrategySetup.agent.remunerateStatusList2021({
493
+ feePaymentAddress: condition.feePaymentAddress,
494
+ feePaymentAmount: condition.feePaymentAmount,
495
+ feePaymentNetwork: toNetwork(did),
496
+ memo: 'Automated status check fee payment, orchestrated by CaaS.',
497
+ }, customer);
498
+ }) || []);
499
+ // Track the operation
500
+ await Promise.all(feePaymentResult.map(async (result) => {
501
+ const portion = await FeeAnalyzer.getPaymentTrack(result, toNetwork(did));
502
+ feePaymentOptions.push(...portion);
503
+ }));
504
+ // handle error
505
+ if (feePaymentResult.some((result) => result.error)) {
506
+ trackInfo.data = {
507
+ did: did,
508
+ resource: statusList.resourceMetadata,
509
+ encrypted: statusList.resource?.metadata?.encrypted,
510
+ };
511
+ trackInfo.successful = false;
512
+ trackInfo.feePaymentOptions = feePaymentOptions;
513
+ eventTracker.emit('track', trackInfo);
514
+ return {
515
+ success: false,
516
+ statusCode: StatusCodes.BAD_REQUEST,
517
+ error: `check: payment: error: ${feePaymentResult.find((result) => result.error)?.error}`,
518
+ };
519
+ }
520
+ }
521
+ // check status list
522
+ let result;
523
+ if (listType === StatusListType.Bitstring) {
524
+ result = await identityServiceStrategySetup.agent.checkBitstringStatusList(did, {
525
+ id: statusListCredential + '#' + index,
526
+ type: 'BitstringStatusListEntry',
527
+ statusPurpose,
528
+ statusListIndex: index.toString(),
529
+ statusListCredential: statusListCredential || '',
530
+ statusSize: statusSize,
531
+ statusMessage: statusMessage,
532
+ }, customer);
533
+ }
534
+ else {
535
+ result = await identityServiceStrategySetup.agent.checkStatusList2021(did, {
536
+ statusListIndex: index,
537
+ statusListName,
538
+ statusPurpose,
539
+ }, customer);
540
+ }
541
+ if ('error' in result && result.error) {
542
+ return {
543
+ success: false,
544
+ statusCode: StatusCodes.BAD_REQUEST,
545
+ error: typeof result.error === 'string'
546
+ ? result.error
547
+ : result.error.message || result.error.toString(),
548
+ };
549
+ }
550
+ trackInfo.data = {
551
+ did: did,
552
+ resource: statusList.resourceMetadata,
553
+ encrypted: statusList.resource?.metadata?.encrypted,
554
+ };
555
+ trackInfo.successful = true;
556
+ trackInfo.feePaymentOptions = feePaymentOptions;
557
+ eventTracker.emit('track', trackInfo);
558
+ return {
559
+ success: true,
560
+ statusCode: StatusCodes.OK,
561
+ data: result,
562
+ };
563
+ }
564
+ catch (error) {
565
+ const errorRef = error;
566
+ if (errorRef?.errorCode === 'NodeAccessControlConditionsReturnedNotAuthorized') {
567
+ return {
568
+ success: false,
569
+ statusCode: StatusCodes.UNAUTHORIZED,
570
+ error: `check: error: ${errorRef?.message
571
+ ? 'unauthorized: decryption conditions are not met'
572
+ : error.toString()}`,
573
+ };
574
+ }
575
+ if (errorRef?.errorCode === 'incorrect_access_control_conditions') {
576
+ return {
577
+ success: false,
578
+ statusCode: StatusCodes.BAD_REQUEST,
579
+ error: `check: error: ${errorRef?.message
580
+ ? 'incorrect access control conditions'
581
+ : error.toString()}`,
582
+ };
583
+ }
584
+ return {
585
+ success: false,
586
+ statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
587
+ error: `Internal error: ${errorRef?.message || errorRef.toString()}`,
588
+ };
589
+ }
590
+ }
591
+ async searchStatusList(query) {
592
+ const { did, statusListName, listType, statusPurpose } = query;
593
+ try {
594
+ // find in registry and retreive uri
595
+ const result = await new IdentityServiceStrategySetup().agent.searchStatusList(did, statusListName, listType, statusPurpose);
596
+ if (result.error) {
597
+ if (result.error === 'notFound') {
598
+ return {
599
+ success: false,
600
+ statusCode: StatusCodes.NOT_FOUND,
601
+ error: `search: error: status list '${statusListName}' not found`,
602
+ };
603
+ }
604
+ return {
605
+ success: false,
606
+ statusCode: StatusCodes.BAD_REQUEST,
607
+ error: `search: error: ${result.error}`,
608
+ };
609
+ }
610
+ return {
611
+ success: true,
612
+ statusCode: StatusCodes.OK,
613
+ data: result,
614
+ };
615
+ }
616
+ catch (error) {
617
+ return {
618
+ success: false,
619
+ statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
620
+ error: `Internal error: ${error?.message || error}`,
621
+ };
622
+ }
623
+ }
624
+ /**
625
+ * Helper method to determine the next resource name in the registry chain
626
+ * Examples:
627
+ * "test-list" -> "test-list-ext1"
628
+ * "test-list-ext1" -> "test-list-ext2"
629
+ * "test-list-ext5" -> "test-list-ext6"
630
+ */
631
+ getNextResourceName(currentRegistryName) {
632
+ // Check if already has extension
633
+ const extMatch = currentRegistryName.match(/^(.+)-ext(\d+)$/);
634
+ if (extMatch) {
635
+ // Already has extension, increment it
636
+ const baseName = extMatch[1];
637
+ const currentExt = parseInt(extMatch[2], 10);
638
+ return `${baseName}-ext${currentExt + 1}`;
639
+ }
640
+ else {
641
+ // First extension
642
+ return `${currentRegistryName}-ext1`;
643
+ }
644
+ }
645
+ /**
646
+ * Update registry using CAS (Compare-And-Swap) pattern for optimistic locking
647
+ * @returns true if update succeeded, false if concurrent modification detected
648
+ */
649
+ async updateRegistryWithCAS(registryId, currentVersion, updates) {
650
+ const result = await this.repository
651
+ .createQueryBuilder()
652
+ .update(StatusRegistryEntity)
653
+ .set({
654
+ ...updates,
655
+ version: currentVersion + 1,
656
+ updatedAt: new Date(),
657
+ })
658
+ .where('registryId = :registryId', { registryId })
659
+ .andWhere('version = :version', { version: currentVersion })
660
+ .execute();
661
+ return (result.affected ?? 0) === 1;
662
+ }
663
+ /**
664
+ * Find registry by URI
665
+ */
666
+ async findRegistryByUri(uri, customer) {
667
+ return await this.repository.findOne({
668
+ where: {
669
+ uri,
670
+ customer,
671
+ },
672
+ relations: ['identifier'],
673
+ });
674
+ }
675
+ /**
676
+ * Create STANDBY registry linked to the active registry
677
+ */
678
+ async createStandbyRegistry(activeRegistry, customer) {
679
+ const nextResourceName = this.getNextResourceName(activeRegistry.registryName);
680
+ const nextUri = `${activeRegistry.identifier.did}?resourceName=${nextResourceName}&resourceType=${activeRegistry.registryType}`;
681
+ // Create new STANDBY registry
682
+ const newRegistryRequestBody = {
683
+ ...activeRegistry.metadata,
684
+ encoding: activeRegistry.metadata?.encoding,
685
+ did: activeRegistry.identifier.did,
686
+ statusListName: nextResourceName,
687
+ statusListVersion: '1',
688
+ length: activeRegistry.registrySize,
689
+ state: StatusRegistryState.Standby,
690
+ credentialCategory: activeRegistry.credentialCategory,
691
+ prev_uri: activeRegistry.uri,
692
+ };
693
+ const newRegistryQuery = {
694
+ listType: activeRegistry.registryType,
695
+ statusPurpose: activeRegistry.metadata?.statusPurpose,
696
+ };
697
+ const res = activeRegistry.encrypted
698
+ ? await this.createEncryptedStatusList(newRegistryRequestBody, newRegistryQuery, customer)
699
+ : await this.createUnencryptedStatusList(newRegistryRequestBody, newRegistryQuery, customer);
700
+ if (res.error) {
701
+ throw new Error(`Failed to create STANDBY registry: ${res.error}`);
702
+ }
703
+ // Update current registry with next_uri using CAS
704
+ const updated = await this.updateRegistryWithCAS(activeRegistry.registryId, activeRegistry.version, {
705
+ next_uri: nextUri,
706
+ });
707
+ if (!updated) {
708
+ throw new Error('Failed to update next_uri - concurrent modification detected');
709
+ }
710
+ }
711
+ /**
712
+ * Rotate status list based on capacity threshold
713
+ * This function handles:
714
+ * 1. Creating STANDBY when threshold is reached
715
+ * 2. Promoting STANDBY to ACTIVE when registry is FULL
716
+ * 3. Marking current registry as FULL
717
+ */
718
+ async rotateStatusList(statusListId, customer) {
719
+ // Find existing registry
720
+ const registry = await this.repository.findOne({
721
+ where: {
722
+ registryId: statusListId,
723
+ customer,
724
+ },
725
+ relations: ['identifier'],
726
+ });
727
+ if (!registry) {
728
+ throw new Error('Registry not found');
729
+ }
730
+ // Calculate utilization
731
+ const utilizationPercent = (registry.writeCursor / registry.registrySize) * 100;
732
+ const isFull = registry.writeCursor >= registry.registrySize;
733
+ const isAtThreshold = utilizationPercent >= registry.threshold_percentage;
734
+ if (isFull) {
735
+ // Registry is FULL - promote STANDBY to ACTIVE
736
+ await this.handleFullRegistry(registry, customer);
737
+ }
738
+ else if (isAtThreshold) {
739
+ // Threshold reached - create STANDBY if not exists
740
+ await this.handleThresholdReached(registry, customer);
741
+ }
742
+ else {
743
+ throw new Error(`Registry is not at threshold (${utilizationPercent.toFixed(2)}% < ${registry.threshold_percentage}%)`);
744
+ }
745
+ }
746
+ /**
747
+ * Handle registry that has reached threshold
748
+ * Creates STANDBY if one doesn't exist
749
+ */
750
+ async handleThresholdReached(registry, customer) {
751
+ // Check if STANDBY already exists
752
+ if (registry.next_uri) {
753
+ // STANDBY already exists, nothing to do
754
+ console.log(`STANDBY already exists for registry ${registry.registryId}`);
755
+ return;
756
+ }
757
+ // Create STANDBY registry
758
+ await this.createStandbyRegistry(registry, customer);
759
+ }
760
+ /**
761
+ * Handle registry that is FULL
762
+ * Promotes STANDBY to ACTIVE and creates new STANDBY
763
+ */
764
+ async handleFullRegistry(registry, customer) {
765
+ // Check if next_uri exists (STANDBY)
766
+ if (!registry.next_uri) {
767
+ throw new Error('Registry is FULL but no STANDBY exists. Create STANDBY first.');
768
+ }
769
+ // Mark current as FULL using CAS
770
+ const markedFull = await this.updateRegistryWithCAS(registry.registryId, registry.version, {
771
+ state: StatusRegistryState.Full,
772
+ sealedAt: new Date(), // TODO include sealed commitment
773
+ });
774
+ if (!markedFull) {
775
+ throw new Error('Failed to mark registry as FULL - concurrent modification detected');
776
+ }
777
+ // Find STANDBY registry by next_uri
778
+ const standbyRegistry = await this.findRegistryByUri(registry.next_uri, customer);
779
+ if (!standbyRegistry) {
780
+ throw new Error('STANDBY registry not found at next_uri');
781
+ }
782
+ // Promote STANDBY to ACTIVE using CAS
783
+ const promoted = await this.updateRegistryWithCAS(standbyRegistry.registryId, standbyRegistry.version, {
784
+ state: StatusRegistryState.Active,
785
+ });
786
+ if (!promoted) {
787
+ throw new Error('Failed to promote STANDBY to ACTIVE - concurrent modification detected');
788
+ }
789
+ // Create new STANDBY for the newly promoted ACTIVE registry
790
+ await this.createStandbyRegistry(standbyRegistry, customer);
791
+ }
792
+ async listStatusList(query, customer) {
793
+ const { deprecated, did, state, statusListName, listType, credentialCategory } = query;
794
+ try {
795
+ const where = {
796
+ customer: { customerId: customer.customerId },
797
+ };
798
+ const relations = {
799
+ identifier: true, // Check performance for this JOIN operation
800
+ };
801
+ if (deprecated) {
802
+ where.deprecated = deprecated;
803
+ }
804
+ if (did) {
805
+ where.identifier = { did };
806
+ }
807
+ if (state) {
808
+ where.state = state;
809
+ }
810
+ if (listType) {
811
+ if (listType === StatusListType.Bitstring) {
812
+ where.registryType = Like('%Bitstring%');
813
+ }
814
+ else {
815
+ where.registryType = Like('%StatusList2021%');
816
+ }
817
+ }
818
+ if (statusListName) {
819
+ where.registryName = statusListName;
820
+ }
821
+ if (credentialCategory) {
822
+ where.credentialCategory = credentialCategory;
823
+ }
824
+ const [data, count] = await this.repository.findAndCount({ where, relations });
825
+ return {
826
+ success: true,
827
+ statusCode: StatusCodes.OK,
828
+ data: {
829
+ records: data.map((item) => ({
830
+ statusListId: item.registryId,
831
+ statusListName: item.registryName,
832
+ uri: item.uri,
833
+ issuerId: item.identifier.did,
834
+ previousUri: item.prev_uri,
835
+ nextUri: item.next_uri,
836
+ listType: item.registryType,
837
+ storageType: item.storageType,
838
+ encrypted: item.encrypted || false,
839
+ credentialCategory: item.credentialCategory,
840
+ size: item.registrySize,
841
+ writeCursor: item.writeCursor,
842
+ state: item.state,
843
+ createdAt: item.createdAt.toISOString(),
844
+ updatedAt: item.updatedAt.toISOString(),
845
+ sealedAt: item.sealedAt ? item.sealedAt.toISOString() : undefined,
846
+ statusPurpose: item.metadata?.statusPurpose,
847
+ deprecated: item.deprecated,
848
+ })),
849
+ total: count,
850
+ },
851
+ };
852
+ }
853
+ catch (error) {
854
+ return {
855
+ success: false,
856
+ statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
857
+ error: `Internal error: ${error?.message || error}`,
858
+ };
859
+ }
860
+ }
861
+ async getStatusList(statusOptions, customer, lock = false) {
862
+ const where = {
863
+ customer: { customerId: customer.customerId },
864
+ };
865
+ if (statusOptions.statusListId) {
866
+ where.registryId = statusOptions.statusListId;
867
+ }
868
+ else if (statusOptions.statusListName && statusOptions.listType) {
869
+ where.registryName = statusOptions.statusListName;
870
+ where.registryType = statusOptions.listType;
871
+ }
872
+ else {
873
+ return {
874
+ success: false,
875
+ statusCode: StatusCodes.BAD_REQUEST,
876
+ error: 'Either statusListId or statusListName and listType must be provided',
877
+ };
878
+ }
879
+ try {
880
+ const item = await this.repository.findOne({
881
+ where,
882
+ relations: ['identifier'],
883
+ lock: lock ? { mode: 'pessimistic_write' } : undefined,
884
+ });
885
+ if (!item) {
886
+ return {
887
+ success: false,
888
+ statusCode: StatusCodes.NOT_FOUND,
889
+ error: `Status list not found`,
890
+ };
891
+ }
892
+ return {
893
+ success: true,
894
+ statusCode: StatusCodes.OK,
895
+ data: {
896
+ statusListId: item.registryId,
897
+ statusListName: item.registryName,
898
+ uri: item.uri,
899
+ issuerId: item.identifier.did,
900
+ previousUri: item.prev_uri,
901
+ nextUri: item.next_uri,
902
+ listType: item.registryType,
903
+ storageType: item.storageType,
904
+ encrypted: item.encrypted || false,
905
+ credentialCategory: item.credentialCategory,
906
+ size: item.registrySize,
907
+ writeCursor: item.writeCursor,
908
+ state: item.state,
909
+ createdAt: item.createdAt.toISOString(),
910
+ updatedAt: item.updatedAt.toISOString(),
911
+ sealedAt: item.sealedAt ? item.sealedAt.toISOString() : undefined,
912
+ deprecated: item.deprecated,
913
+ statusPurpose: item.metadata?.statusPurpose,
914
+ additionalUsedIndexes: item.metadata?.additionalUsedIndexes || [],
915
+ },
916
+ };
917
+ }
918
+ catch (error) {
919
+ throw new Error(`Internal error: ${error?.message || error}`);
920
+ }
921
+ }
922
+ }
923
+ //# sourceMappingURL=credential-status.js.map