@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.
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +4 -0
- package/dist/app.js.map +1 -1
- package/dist/controllers/api/accreditation.d.ts.map +1 -1
- package/dist/controllers/api/accreditation.js +36 -9
- package/dist/controllers/api/accreditation.js.map +1 -1
- package/dist/controllers/api/credential-status.d.ts +94 -0
- package/dist/controllers/api/credential-status.d.ts.map +1 -1
- package/dist/controllers/api/credential-status.js +220 -506
- package/dist/controllers/api/credential-status.js.map +1 -1
- package/dist/controllers/api/credential.d.ts +66 -0
- package/dist/controllers/api/credential.d.ts.map +1 -1
- package/dist/controllers/api/credential.js +117 -1
- package/dist/controllers/api/credential.js.map +1 -1
- package/dist/controllers/api/did.js.map +1 -1
- package/dist/controllers/api/key.d.ts +71 -1
- package/dist/controllers/api/key.d.ts.map +1 -1
- package/dist/controllers/api/key.js +113 -2
- package/dist/controllers/api/key.js.map +1 -1
- package/dist/controllers/api/presentation.js +22 -21
- package/dist/controllers/api/presentation.js.map +1 -1
- package/dist/controllers/api/providers.controller.js.map +1 -1
- package/dist/controllers/api/resource.js.map +1 -1
- package/dist/controllers/validator/did.js +1 -1
- package/dist/controllers/validator/did.js.map +1 -1
- package/dist/database/entities/issued-credential.entity.d.ts +10 -1
- package/dist/database/entities/issued-credential.entity.d.ts.map +1 -1
- package/dist/database/entities/issued-credential.entity.js +22 -0
- package/dist/database/entities/issued-credential.entity.js.map +1 -1
- package/dist/database/entities/status-registry.entity.d.ts +60 -0
- package/dist/database/entities/status-registry.entity.d.ts.map +1 -0
- package/dist/database/entities/status-registry.entity.js +145 -0
- package/dist/database/entities/status-registry.entity.js.map +1 -0
- package/dist/database/migrations/1761834657128-studio-migrations.d.ts +7 -0
- package/dist/database/migrations/1761834657128-studio-migrations.d.ts.map +1 -0
- package/dist/database/migrations/1761834657128-studio-migrations.js +14 -0
- package/dist/database/migrations/1761834657128-studio-migrations.js.map +1 -0
- package/dist/database/migrations/1762775396083-MigrateStatusLists.d.ts +25 -0
- package/dist/database/migrations/1762775396083-MigrateStatusLists.d.ts.map +1 -0
- package/dist/database/migrations/1762775396083-MigrateStatusLists.js +243 -0
- package/dist/database/migrations/1762775396083-MigrateStatusLists.js.map +1 -0
- package/dist/database/migrations/1762775500000-UpdateWriteCursors.d.ts +23 -0
- package/dist/database/migrations/1762775500000-UpdateWriteCursors.d.ts.map +1 -0
- package/dist/database/migrations/1762775500000-UpdateWriteCursors.js +181 -0
- package/dist/database/migrations/1762775500000-UpdateWriteCursors.js.map +1 -0
- package/dist/database/types/types.d.ts.map +1 -1
- package/dist/database/types/types.js +10 -0
- package/dist/database/types/types.js.map +1 -1
- package/dist/helpers/mailchimp.d.ts.map +1 -1
- package/dist/helpers/mailchimp.js +1 -3
- package/dist/helpers/mailchimp.js.map +1 -1
- package/dist/middleware/auth/routes/api/credential-status-auth.d.ts.map +1 -1
- package/dist/middleware/auth/routes/api/credential-status-auth.js +2 -0
- package/dist/middleware/auth/routes/api/credential-status-auth.js.map +1 -1
- package/dist/middleware/auth/routes/api/key-auth.d.ts.map +1 -1
- package/dist/middleware/auth/routes/api/key-auth.js +1 -0
- package/dist/middleware/auth/routes/api/key-auth.js.map +1 -1
- package/dist/services/api/credential-status.d.ts +107 -0
- package/dist/services/api/credential-status.d.ts.map +1 -0
- package/dist/services/api/credential-status.js +923 -0
- package/dist/services/api/credential-status.js.map +1 -0
- package/dist/services/api/credentials.d.ts +12 -0
- package/dist/services/api/credentials.d.ts.map +1 -1
- package/dist/services/api/credentials.js +330 -117
- package/dist/services/api/credentials.js.map +1 -1
- package/dist/services/connectors/resource.js.map +1 -1
- package/dist/services/identity/abstract.d.ts +1 -1
- package/dist/services/identity/abstract.d.ts.map +1 -1
- package/dist/services/identity/abstract.js +1 -1
- package/dist/services/identity/abstract.js.map +1 -1
- package/dist/services/identity/default.d.ts +1 -1
- package/dist/services/identity/default.d.ts.map +1 -1
- package/dist/services/identity/default.js +2 -2
- package/dist/services/identity/default.js.map +1 -1
- package/dist/services/identity/index.d.ts +1 -1
- package/dist/services/identity/index.d.ts.map +1 -1
- package/dist/services/identity/providers/studio/agent.d.ts +6 -6
- package/dist/services/identity/providers/studio/agent.d.ts.map +1 -1
- package/dist/services/identity/providers/studio/agent.js +11 -2
- package/dist/services/identity/providers/studio/agent.js.map +1 -1
- package/dist/services/identity/providers/studio/local.d.ts +3 -3
- package/dist/services/identity/providers/studio/postgres.d.ts +3 -3
- package/dist/services/identity/providers/studio/postgres.d.ts.map +1 -1
- package/dist/services/identity/providers/studio/postgres.js +0 -12
- package/dist/services/identity/providers/studio/postgres.js.map +1 -1
- package/dist/services/track/api/credential-status-subscriber.d.ts +4 -4
- package/dist/services/track/api/credential-status-subscriber.d.ts.map +1 -1
- package/dist/services/track/api/credential-status-subscriber.js +18 -1
- package/dist/services/track/api/credential-status-subscriber.js.map +1 -1
- package/dist/services/track/api/credential-subscriber.d.ts +4 -4
- package/dist/services/track/api/credential-subscriber.d.ts.map +1 -1
- package/dist/services/track/api/credential-subscriber.js.map +1 -1
- package/dist/services/track/api/did-subscriber.d.ts +4 -4
- package/dist/services/track/api/did-subscriber.d.ts.map +1 -1
- package/dist/services/track/api/did-subscriber.js.map +1 -1
- package/dist/services/track/api/key-subscriber.d.ts +4 -4
- package/dist/services/track/api/key-subscriber.d.ts.map +1 -1
- package/dist/services/track/api/key-subscriber.js.map +1 -1
- package/dist/services/track/api/presentation-subscriber.d.ts +4 -4
- package/dist/services/track/api/presentation-subscriber.d.ts.map +1 -1
- package/dist/services/track/api/presentation-subscriber.js.map +1 -1
- package/dist/services/track/api/resource-subscriber.d.ts +4 -4
- package/dist/services/track/api/resource-subscriber.d.ts.map +1 -1
- package/dist/services/track/api/resource-subscriber.js.map +1 -1
- package/dist/services/track/base.d.ts +2 -2
- package/dist/services/track/base.d.ts.map +1 -1
- package/dist/services/track/base.js.map +1 -1
- package/dist/services/track/operation-subscriber.d.ts +4 -4
- package/dist/services/track/operation-subscriber.d.ts.map +1 -1
- package/dist/services/track/operation-subscriber.js.map +1 -1
- package/dist/services/track/tracker.d.ts +2 -2
- package/dist/services/track/tracker.d.ts.map +1 -1
- package/dist/services/track/tracker.js.map +1 -1
- package/dist/services/track/types.d.ts +2 -2
- package/dist/services/track/types.d.ts.map +1 -1
- package/dist/static/swagger-api.json +684 -67
- package/dist/types/accreditation.d.ts +3 -0
- package/dist/types/accreditation.d.ts.map +1 -1
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/constants.js +2 -0
- package/dist/types/constants.js.map +1 -1
- package/dist/types/credential-status.d.ts +39 -1
- package/dist/types/credential-status.d.ts.map +1 -1
- package/dist/types/credential-status.js +7 -0
- package/dist/types/credential-status.js.map +1 -1
- package/dist/types/credential.d.ts +21 -3
- package/dist/types/credential.d.ts.map +1 -1
- package/dist/types/credential.js +5 -0
- package/dist/types/credential.js.map +1 -1
- package/dist/types/swagger-api-types.d.ts +242 -39
- package/dist/types/swagger-api-types.d.ts.map +1 -1
- package/dist/types/swagger-api-types.js +242 -39
- package/dist/types/swagger-api-types.js.map +1 -1
- package/dist/types/track.d.ts +8 -6
- package/dist/types/track.d.ts.map +1 -1
- package/dist/types/track.js.map +1 -1
- 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
|