Skip to content

Commit

Permalink
feat: Improve /did/create API [DEV-3197] (#388)
Browse files Browse the repository at this point in the history
* Update package-lock.json.

* Remove assertionMethod from form based request
body in /did/create API.

* Remove didDocument from form based request body in
/did/create API.

* Rename /did/create API request body section name
from "methodSpecificIdAlgo" to "identifierFormatType".

* Update DidCreateRequest type.

* Remove an unused assertionMethod variable for a
form based case.

* Add service section for form based request body
in /did/create API.

* Update DidCreateRequestFormBased request type.

* Add key section for form based request body in
/did/create API.

* Update swagger (OpenAPI).

* Bump '@cheqd/sdk' version.

* Add options section for json based request body in
/create/did API.

* Remove verificationMethod from json based request
body in /did/create API.

* Add example for key field in json based request
body  in /did/create API.

* Remove an unused expression:
- This always evaluates to truthy.

* Remove an incorrect description from
DidDocumentWithoutVerificationMethod type.

* Add '@context' section for form based request body
in /did/create API.

* Use an exist createDidVerificationMethod function
instead of helper functions.

* Create CreateDidRequestBody type for asserting
request body in /did/create API.

* Add integration tests for testing form-based request body in
/did/create API.

* Add integration tests for testing JSON based
request body in /did/create API.

* Fix problem with handling multiple services
in did/create API.

* Fix problem with handling multiple "context"
 in /did/create API.

* Remove indirect imports in issuer.ts.

* Remove an unused cast.

* Add negative integration tests for testing
/did/create API.

* Re-arch integration tests for did/create API.

* Refactor positive integration tests for
/did/create API.

* Update positive integration tests.

* Re-arch integration tests for /did/create API.

* Update negative integration tests.

* Update integration tests.

* Update integration tests.

* Refactor err msgs in negative integration tests.

* Update err messages in integration tests.

* Rename from DidCreateRequest to DidCreateRequestJson.

* Add check to checking statusCodes is in 200...299
family.

* Update validation for DID.

* Revert validation for DID.

* Fix validation for DidCreatePayload

* Update err messages in integration tests.

---------

Co-authored-by: Andrew Nikitin <[email protected]>
  • Loading branch information
abdulla-ashurov and Andrew Nikitin committed Oct 16, 2023
1 parent 3aced2c commit 9791dc6
Show file tree
Hide file tree
Showing 15 changed files with 828 additions and 102 deletions.
27 changes: 15 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
],
"dependencies": {
"@cheqd/did-provider-cheqd": "^3.6.8",
"@cheqd/sdk": "^3.7.0",
"@cheqd/sdk": "^3.7.1",
"@cheqd/ts-proto": "^3.3.1",
"@cosmjs/amino": "^0.31.1",
"@cosmjs/encoding": "^0.31.1",
Expand Down
109 changes: 74 additions & 35 deletions src/controllers/issuer.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import type { Request, Response } from 'express';
import { check, param, validationResult } from 'express-validator';
import { fromString } from 'uint8arrays';
import type { DIDDocument, Service, VerificationMethod } from 'did-resolver';
import { fromString, toString } from 'uint8arrays';
import { v4 } from 'uuid';
import { MethodSpecificIdAlgo, VerificationMethods, CheqdNetwork } from '@cheqd/sdk';
import { CheqdNetwork, DIDDocument, MethodSpecificIdAlgo, Service, VerificationMethod, VerificationMethods, createDidVerificationMethod } from '@cheqd/sdk';
import type { MsgCreateResourcePayload } from '@cheqd/ts-proto/cheqd/resource/v2/index.js';
import { StatusCodes } from 'http-status-codes';

import { IdentityServiceStrategySetup } from '../services/identity/index.js';
import { generateDidDoc, getQueryParams, validateSpecCompliantPayload } from '../helpers/helpers.js';
import { generateDidDoc, getQueryParams, validateDidCreatePayload, validateSpecCompliantPayload } from '../helpers/helpers.js';
import { DIDMetadataDereferencingResult, DefaultResolverUrl } from '@cheqd/did-provider-cheqd';
import { bases } from "multiformats/basics";
import { base64ToBytes } from "did-jwt";
import type { CreateDidRequestBody } from '../types/shared.js';

export class IssuerController {
public static createValidator = [
check('didDocument')
.optional()
.isObject()
.custom((value) => {
const { valid } = validateSpecCompliantPayload(value);
const { valid } = validateDidCreatePayload(value);
return valid;
})
.withMessage('Invalid didDocument'),
Expand All @@ -26,11 +27,11 @@ export class IssuerController {
.isString()
.isIn([VerificationMethods.Ed255192020, VerificationMethods.Ed255192018, VerificationMethods.JWK])
.withMessage('Invalid verificationMethod'),
check('methodSpecificIdAlgo')
check('identifierFormatType')
.optional()
.isString()
.isIn([MethodSpecificIdAlgo.Base58, MethodSpecificIdAlgo.Uuid])
.withMessage('Invalid methodSpecificIdAlgo'),
.withMessage('Invalid identifierFormatType'),
check('network')
.optional()
.isString()
Expand Down Expand Up @@ -187,10 +188,10 @@ export class IssuerController {
* content:
* application/x-www-form-urlencoded:
* schema:
* $ref: '#/components/schemas/DidCreateRequest'
* $ref: '#/components/schemas/DidCreateRequestFormBased'
* application/json:
* schema:
* $ref: '#/components/schemas/DidCreateRequest'
* $ref: '#/components/schemas/DidCreateRequestJson'
* responses:
* 200:
* description: The request was successful.
Expand Down Expand Up @@ -226,45 +227,83 @@ export class IssuerController {
}

const {
methodSpecificIdAlgo,
identifierFormatType,
network,
verificationMethodType,
assertionMethod = true,
serviceEndpoint,
} = request.body;
service,
key,
options,
} = request.body satisfies CreateDidRequestBody;
let didDocument: DIDDocument;
try {
if (request.body.didDocument) {
didDocument = request.body.didDocument;
if (options) {
const publicKeyHex = options.key || (await new IdentityServiceStrategySetup(response.locals.customerId).agent.createKey(
'Ed25519',
response.locals.customerId
)).publicKeyHex;
const pkBase64 = publicKeyHex.length == 43 ? publicKeyHex : toString(fromString(publicKeyHex, 'hex'), 'base64');

didDocument.verificationMethod = createDidVerificationMethod([options.verificationMethodType], [{
methodSpecificId: bases['base58btc'].encode(base64ToBytes(pkBase64)),
didUrl: didDocument.id,
keyId: `${didDocument.id}#key-1`,
publicKey: pkBase64
}]);
} else {
return response.status(StatusCodes.BAD_REQUEST).json({
error: 'Provide options section to create a DID',
});
}
} else if (verificationMethodType) {
const key = await new IdentityServiceStrategySetup(response.locals.customerId).agent.createKey(
const publicKeyHex = key || (await new IdentityServiceStrategySetup(response.locals.customerId).agent.createKey(
'Ed25519',
response.locals.customerId
);
)).publicKeyHex;
didDocument = generateDidDoc({
verificationMethod: verificationMethodType || VerificationMethods.Ed255192018,
verificationMethod: verificationMethodType,
verificationMethodId: 'key-1',
methodSpecificIdAlgo: (methodSpecificIdAlgo as MethodSpecificIdAlgo) || MethodSpecificIdAlgo.Uuid,
methodSpecificIdAlgo: identifierFormatType || MethodSpecificIdAlgo.Uuid,
network,
publicKey: key.publicKeyHex,
publicKey: publicKeyHex,
});

if (assertionMethod) {
didDocument.assertionMethod = didDocument.authentication;
if (Array.isArray(request.body['@context'])) {
didDocument['@context'] = request.body['@context'];
}
if (typeof request.body['@context'] === 'string') {
didDocument['@context'] = [request.body['@context']];
}

if (serviceEndpoint) {
didDocument.service = [
{
id: `${didDocument.id}#service-1`,
type: 'service-1',
serviceEndpoint: [serviceEndpoint],
},
];
if (service) {
if (Array.isArray(service)) {
try {
const services = JSON.parse(`[${service.toString()}]`);
didDocument.service = [];
for (const service of services) {
didDocument.service.push({
id: `${didDocument.id}#${service.idFragment}`,
type: service.type,
serviceEndpoint: service.serviceEndpoint,
})
}
} catch (e) {
return response.status(StatusCodes.BAD_REQUEST).json({
error: 'Provide the correct service section to create a DID',
});
};
} else {
didDocument.service = [{
id: `${didDocument.id}#${service.idFragment}`,
type: service.type,
serviceEndpoint: service.serviceEndpoint,
}];
}
}
} else {
return response.status(StatusCodes.BAD_REQUEST).json({
error: 'Provide a DID Document or the network type to create a DID',
error: 'Provide a DID Document or the VerificationMethodType to create a DID',
});
}

Expand Down Expand Up @@ -507,7 +546,7 @@ export class IssuerController {
if (result) {
const url = new URL(
`${process.env.RESOLVER_URL || DefaultResolverUrl}${did}?` +
`resourceId=${resourcePayload.id}&resourceMetadata=true`
`resourceId=${resourcePayload.id}&resourceMetadata=true`
);
const didDereferencing = (await (await fetch(url)).json()) as DIDMetadataDereferencingResult;

Expand Down Expand Up @@ -651,11 +690,11 @@ export class IssuerController {
try {
const did = request.params.did
? await new IdentityServiceStrategySetup(response.locals.customerId).agent.resolveDid(
request.params.did
)
request.params.did
)
: await new IdentityServiceStrategySetup(response.locals.customerId).agent.listDids(
response.locals.customerId
);
response.locals.customerId
);

return response.status(StatusCodes.OK).json(did);
} catch (error) {
Expand Down
14 changes: 12 additions & 2 deletions src/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ export function toDefaultDkg(did: string): DkgOptions {
}
}

export function validateDidCreatePayload(didDocument: DIDDocument): SpecValidationResult {
if (!didDocument) return { valid: true };

// id is required, validated on both compile and runtime
if (!didDocument.id || !didDocument.id.startsWith('did:cheqd:')) return { valid: false, error: 'id is required' };

if (!isValidService(didDocument)) return { valid: false, error: 'Service is Invalid' };
return { valid: true } as SpecValidationResult;
}

export function validateSpecCompliantPayload(didDocument: DIDDocument): SpecValidationResult {
// id is required, validated on both compile and runtime
if (!didDocument.id && !didDocument.id.startsWith('did:cheqd:')) return { valid: false, error: 'id is required' };
Expand All @@ -92,8 +102,8 @@ export function validateSpecCompliantPayload(didDocument: DIDDocument): SpecVali
export function isValidService(didDocument: DIDDocument): boolean {
return didDocument.service
? didDocument?.service?.every((s) => {
return s?.serviceEndpoint && s?.id && s?.type;
})
return s?.serviceEndpoint && s?.id && s?.type;
})
: true;
}

Expand Down
Loading

0 comments on commit 9791dc6

Please sign in to comment.