Skip to content

Commit d518cce

Browse files
committed
Add did export api
1 parent dd027b1 commit d518cce

File tree

15 files changed

+503
-144
lines changed

15 files changed

+503
-144
lines changed

src/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,10 @@ class App {
194194
app.post('/did/create', DIDController.createDIDValidator, new DIDController().createDid);
195195
app.post('/did/update', DIDController.updateDIDValidator, new DIDController().updateDid);
196196
app.post('/did/import', DIDController.importDIDValidator, new DIDController().importDid);
197-
app.post('/did/deactivate/:did', DIDController.deactivateDIDValidator, new DIDController().deactivateDid);
197+
app.post('/did/deactivate/:did', DIDController.didPathValidator, new DIDController().deactivateDid);
198198
app.get('/did/list', DIDController.listDIDValidator, new DIDController().getDids);
199199
app.get('/did/search/:did', new DIDController().resolveDidUrl);
200+
app.post('/did/export', DIDController.didPathValidator, new DIDController().exportDid);
200201

201202
// Trust Registry API
202203
app.post(

src/controllers/api/did.ts

Lines changed: 195 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
CheqdNetwork,
55
DIDDocument,
66
MethodSpecificIdAlgo,
7-
Service,
87
VerificationMethods,
98
createDidVerificationMethod,
109
} from '@cheqd/sdk';
@@ -32,6 +31,7 @@ import type {
3231
GetDIDRequestParams,
3332
ResolveDIDRequestParams,
3433
DeactivateDIDRequestBody,
34+
ExportDidResponse,
3535
} from '../../types/did.js';
3636
import { check, param } from '../validator/index.js';
3737
import type { IIdentifier, IKey, RequireOnly } from '@veramo/core';
@@ -124,7 +124,7 @@ export class DIDController {
124124
check('publicKeyHexs').optional().isArray().withMessage('publicKeyHexs should be an array of strings').bail(),
125125
];
126126

127-
public static deactivateDIDValidator = [param('did').exists().isString().isDID().bail()];
127+
public static didPathValidator = [param('did').exists().isString().isDID().bail()];
128128

129129
public static importDIDValidator = [
130130
check('did').isDID().bail(),
@@ -211,112 +211,109 @@ export class DIDController {
211211
// Get strategy e.g. postgres or local
212212
const identityServiceStrategySetup = new IdentityServiceStrategySetup(response.locals.customer.customerId);
213213
try {
214-
if (request.body.didDocument) {
215-
didDocument = request.body.didDocument;
216-
if (verificationMethodType) {
217-
const publicKeyHex =
218-
key ||
219-
(
220-
await identityServiceStrategySetup.agent.createKey(
221-
SupportedKeyTypes.Ed25519,
222-
response.locals.customer
223-
)
224-
).publicKeyHex;
225-
226-
const pkBase64 = toString(fromString(publicKeyHex, 'hex'), 'base64');
227-
didDocument.verificationMethod = createDidVerificationMethod(
228-
[verificationMethodType],
229-
[
230-
{
231-
methodSpecificId: bases['base58btc'].encode(base64ToBytes(pkBase64)),
232-
didUrl: didDocument.id,
233-
keyId: `${didDocument.id}#key-1`,
234-
publicKey: pkBase64,
235-
},
236-
]
237-
);
238-
} else {
239-
return response.status(StatusCodes.BAD_REQUEST).json({
240-
error: 'Provide options section to create a DID',
241-
} satisfies UnsuccessfulCreateDidResponseBody);
242-
}
243-
} else if (verificationMethodType) {
244-
const publicKeyHex =
245-
key ||
246-
(
247-
await identityServiceStrategySetup.agent.createKey(
248-
SupportedKeyTypes.Ed25519,
249-
response.locals.customer
250-
)
251-
).publicKeyHex;
252-
didDocument = generateDidDoc({
253-
verificationMethod: verificationMethodType,
254-
verificationMethodId: 'key-1',
255-
methodSpecificIdAlgo: identifierFormatType || MethodSpecificIdAlgo.Uuid,
256-
network,
257-
publicKey: publicKeyHex,
258-
});
259-
260-
if (Array.isArray(request.body['@context'])) {
261-
didDocument['@context'] = request.body['@context'];
262-
}
263-
if (typeof request.body['@context'] === 'string') {
264-
didDocument['@context'] = [request.body['@context']];
265-
}
266-
267-
if (service) {
268-
if (Array.isArray(service)) {
269-
try {
270-
const services = service as Service[];
271-
didDocument.service = [];
272-
for (const service of services) {
273-
didDocument.service.push({
274-
id: `${didDocument.id}#${service.idFragment}`,
275-
type: service.type,
276-
serviceEndpoint: service.serviceEndpoint,
277-
recipientKeys: service.recipientKeys,
278-
routingKeys: service.routingKeys,
279-
priority: service.priority,
280-
accept: service.accept,
281-
});
282-
}
283-
} catch (e) {
284-
return response.status(StatusCodes.BAD_REQUEST).json({
285-
error: 'Provide the correct service section to create a DID',
286-
} satisfies UnsuccessfulCreateDidResponseBody);
287-
}
288-
} else {
289-
didDocument.service = [
290-
{
291-
id: `${didDocument.id}#${service.idFragment}`,
214+
let did: IIdentifier;
215+
switch (providerId) {
216+
case 'dock':
217+
did = await new DockIdentityService().createDid(network, { id: '' }, response.locals.customer);
218+
const { didDocument: didDoc } = await identityServiceStrategySetup.agent.resolveDid(did.did);
219+
console.log(didDoc);
220+
if (didDoc && service) {
221+
const services = Array.isArray(service) ? service : [service];
222+
didDoc.service = didDoc.service || [];
223+
// handle overriding
224+
const filteredServices = services.filter((s) =>
225+
didDoc.service?.every((ds) => s.idFragment != ds.id)
226+
);
227+
for (const service of filteredServices) {
228+
didDoc.service.push({
229+
id: `${didDoc.id}#${service.idFragment}`,
292230
type: service.type,
293231
serviceEndpoint: service.serviceEndpoint,
294232
recipientKeys: service.recipientKeys,
295233
routingKeys: service.routingKeys,
296234
priority: service.priority,
297235
accept: service.accept,
298-
},
299-
];
236+
});
237+
}
238+
did = await identityServiceStrategySetup.agent.updateDid(didDoc, response.locals.customer);
300239
}
301-
}
302-
} else {
303-
return response.status(StatusCodes.BAD_REQUEST).json({
304-
error: 'Provide a DID Document or the VerificationMethodType to create a DID',
305-
} satisfies UnsuccessfulCreateDidResponseBody);
306-
}
307-
308-
let did: IIdentifier;
309-
switch (providerId) {
310-
case 'dock':
311-
did = await new DockIdentityService().createDid(
312-
network || didDocument.id.split(':')[2],
313-
didDocument,
314-
response.locals.customer
315-
);
316240
break;
317241
case 'studio':
318242
case undefined:
319-
did = await new IdentityServiceStrategySetup(response.locals.customer.customerId).agent.createDid(
243+
if (request.body.didDocument) {
244+
didDocument = request.body.didDocument;
245+
if (verificationMethodType) {
246+
const publicKeyHex =
247+
key ||
248+
(
249+
await identityServiceStrategySetup.agent.createKey(
250+
SupportedKeyTypes.Ed25519,
251+
response.locals.customer
252+
)
253+
).publicKeyHex;
254+
255+
const pkBase64 = toString(fromString(publicKeyHex, 'hex'), 'base64');
256+
didDocument.verificationMethod = createDidVerificationMethod(
257+
[verificationMethodType],
258+
[
259+
{
260+
methodSpecificId: bases['base58btc'].encode(base64ToBytes(pkBase64)),
261+
didUrl: didDocument.id,
262+
keyId: `${didDocument.id}#key-1`,
263+
publicKey: pkBase64,
264+
},
265+
]
266+
);
267+
} else {
268+
return response.status(StatusCodes.BAD_REQUEST).json({
269+
error: 'Provide options section to create a DID',
270+
} satisfies UnsuccessfulCreateDidResponseBody);
271+
}
272+
} else if (verificationMethodType) {
273+
const publicKeyHex =
274+
key ||
275+
(
276+
await identityServiceStrategySetup.agent.createKey(
277+
SupportedKeyTypes.Ed25519,
278+
response.locals.customer
279+
)
280+
).publicKeyHex;
281+
282+
didDocument = generateDidDoc({
283+
verificationMethod: verificationMethodType,
284+
verificationMethodId: 'key-1',
285+
methodSpecificIdAlgo: identifierFormatType || MethodSpecificIdAlgo.Uuid,
286+
network,
287+
publicKey: publicKeyHex,
288+
});
289+
290+
// populate default assertionMethod to support JSON-LD
291+
didDocument.assertionMethod = didDocument.authentication;
292+
293+
if (request.body['@context']) {
294+
didDocument['@context'] = Array.isArray(request.body['@context'])
295+
? request.body['@context']
296+
: [request.body['@context']];
297+
}
298+
299+
if (service) {
300+
const services = Array.isArray(service) ? service : [service];
301+
didDocument.service = services.map((s) => ({
302+
id: `${didDocument.id}#${s.idFragment}`,
303+
type: s.type,
304+
serviceEndpoint: s.serviceEndpoint,
305+
recipientKeys: s.recipientKeys,
306+
routingKeys: s.routingKeys,
307+
priority: s.priority,
308+
accept: s.accept,
309+
}));
310+
}
311+
} else {
312+
return response.status(StatusCodes.BAD_REQUEST).json({
313+
error: 'Provide a DID Document or the VerificationMethodType to create a DID',
314+
} satisfies UnsuccessfulCreateDidResponseBody);
315+
}
316+
did = await identityServiceStrategySetup.agent.createDid(
320317
network || didDocument.id.split(':')[2],
321318
didDocument,
322319
response.locals.customer
@@ -861,4 +858,104 @@ export class DIDController {
861858
} satisfies UnsuccessfulResolveDidResponseBody);
862859
}
863860
}
861+
862+
/**
863+
* @openapi
864+
*
865+
* /did/export/{did}:
866+
* post:
867+
* tags: [ Decentralized Identifiers (DIDs) ]
868+
* summary: Export a DID Document.
869+
* description: This endpoint exports a decentralized identifier associated with the user's account with the custodied keys.
870+
* parameters:
871+
* - in: path
872+
* name: did
873+
* description: DID identifier to resolve.
874+
* schema:
875+
* type: string
876+
* required: true
877+
* requestBody:
878+
* content:
879+
* application/x-www-form-urlencoded:
880+
* schema:
881+
* type: object
882+
* properties:
883+
* password:
884+
* type: string
885+
* required: false
886+
* providerId:
887+
* type: string
888+
* required: false
889+
* application/json:
890+
* schema:
891+
* type: object
892+
* properties:
893+
* password:
894+
* type: string
895+
* required: false
896+
* providerId:
897+
* type: string
898+
* required: false
899+
* responses:
900+
* 200:
901+
* description: The request was successful.
902+
* content:
903+
* application/json:
904+
* schema:
905+
* $ref: '#/components/schemas/DidResult'
906+
* 400:
907+
* description: A problem with the input fields has occurred. Additional state information plus metadata may be available in the response body.
908+
* content:
909+
* application/json:
910+
* schema:
911+
* $ref: '#/components/schemas/InvalidRequest'
912+
* example:
913+
* error: InvalidRequest
914+
* 401:
915+
* $ref: '#/components/schemas/UnauthorizedError'
916+
* 500:
917+
* description: An internal error has occurred. Additional state information plus metadata may be available in the response body.
918+
* content:
919+
* application/json:
920+
* schema:
921+
* $ref: '#/components/schemas/InvalidRequest'
922+
* example:
923+
* error: Internal Error
924+
*/
925+
@validate
926+
public async exportDid(request: Request, response: Response) {
927+
try {
928+
// Get the params from body
929+
const { did } = request.params as ResolveDIDRequestParams;
930+
931+
const { providerId, password } = request.body;
932+
let result: ExportDidResponse;
933+
switch (providerId) {
934+
case 'dock':
935+
result = await new DockIdentityService().exportDid(did, password || '', response.locals.customer);
936+
break;
937+
case 'studio':
938+
default:
939+
result = await new IdentityServiceStrategySetup(
940+
response.locals.customer.customerId
941+
).agent.exportDid(did, '', response.locals.customer);
942+
}
943+
// Track the operation
944+
eventTracker.emit('track', {
945+
category: OperationCategoryNameEnum.DID,
946+
name: OperationNameEnum.DID_EXPORT,
947+
data: {
948+
did: did,
949+
} satisfies IDIDTrack,
950+
customer: response.locals.customer,
951+
user: response.locals.user,
952+
} satisfies ITrackOperation);
953+
954+
return response.status(StatusCodes.OK).json(result);
955+
} catch (error) {
956+
return response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
957+
error: `Internal error: ${(error as Error)?.message || error}`,
958+
});
959+
}
960+
}
864961
}

src/controllers/validator/service-create-request.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class CreateDIDDocumentServiceValidator implements IValidator {
3232
error: `service.serviceEndpoint is required in object ${service}`,
3333
};
3434
}
35-
if (!Array.isArray(service.serviceEndpoint) || typeof service.serviceEndpoint === 'string') {
35+
if (!Array.isArray(service.serviceEndpoint) && typeof service.serviceEndpoint !== 'string') {
3636
return {
3737
valid: false,
3838
error: `service.serviceEndpoint should be an array or a string in object ${service}`,
@@ -67,7 +67,7 @@ export class CreateDIDDocumentServiceValidator implements IValidator {
6767
}
6868

6969
validate(services: Validatable): IValidationResult {
70-
services = services as CreateDIDService[];
70+
services = (Array.isArray(services) ? services : [services]) as CreateDIDService[];
7171
if (!Array.isArray(services)) {
7272
return {
7373
valid: false,

src/controllers/validator/service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class ServiceValidator implements IValidator {
3737
error: 'service.serviceEndpoint is required',
3838
};
3939
}
40-
if (!Array.isArray(service.serviceEndpoint) || typeof service.serviceEndpoint === 'string') {
40+
if (!Array.isArray(service.serviceEndpoint) && typeof service.serviceEndpoint !== 'string') {
4141
return {
4242
valid: false,
4343
error: 'service.serviceEndpoint should be an array or a string',

src/middleware/auth/routes/api/did-auth.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export class DidAuthRuleProvider extends AuthRuleProvider {
1313
this.registerRule('/did/deactivate', 'POST', 'deactivate:did:mainnet');
1414
this.registerRule('/did/import', 'POST', 'import:did:testnet');
1515
this.registerRule('/did/import', 'POST', 'import:did:mainnet');
16+
this.registerRule('/did/export', 'POST', 'export:did:testnet');
17+
this.registerRule('/did/export', 'POST', 'export:did:mainnet');
1618
// Unauthorized routes
1719
this.registerRule('/did/search/(.*)', 'GET', '', { allowUnauthorized: true, skipNamespace: true });
1820
}

src/services/api/identifier.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ export class IdentifierService {
2525
return await this.identifierRepository.save(existing);
2626
}
2727

28-
public async get(did?: string, relations?: FindOptionsRelations<IdentifierEntity>) {
28+
public async get(did?: string, customer?: CustomerEntity, relations?: FindOptionsRelations<IdentifierEntity>) {
2929
return await this.identifierRepository.findOne({
30-
where: { did },
30+
where: { did, customer },
3131
relations,
3232
});
3333
}

0 commit comments

Comments
 (0)