diff --git a/package-lock.json b/package-lock.json
index 7a155132..6c55f7ef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,16 @@
{
"name": "@cheqd/credential-service",
- "version": "2.9.4-develop.1",
+ "version": "2.9.4-develop.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@cheqd/credential-service",
- "version": "2.9.4-develop.1",
+ "version": "2.9.4-develop.2",
"license": "Apache-2.0",
"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",
@@ -2625,11 +2625,11 @@
}
},
"node_modules/@cheqd/sdk": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/@cheqd/sdk/-/sdk-3.7.0.tgz",
- "integrity": "sha512-/ik+sPc63Nc3VOeeu2isk3+5IQAoMlWZwVS6kmyj+tUoNv5y3pghZ0StftAktNwU7Hh1NAq2s6tri2uBOIQ1lg==",
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/@cheqd/sdk/-/sdk-3.7.1.tgz",
+ "integrity": "sha512-cVek7qerGnhPrujncybThuNi/DzKwkbHY87pGBsHKswNdQVwJWOWf5gRwQMg0FC3NaKeH2moSZpTrTvGGCK+Uw==",
"dependencies": {
- "@cheqd/ts-proto": "^3.3.1",
+ "@cheqd/ts-proto": "^3.3.2",
"@cosmjs/amino": "^0.31.1",
"@cosmjs/crypto": "^0.31.1",
"@cosmjs/encoding": "^0.31.1",
@@ -2641,7 +2641,7 @@
"@stablelib/ed25519": "^1.0.3",
"@types/secp256k1": "^4.0.3",
"cosmjs-types": "^0.8.0",
- "did-jwt": "^7.2.7",
+ "did-jwt": "^7.2.8",
"did-resolver": "^4.1.0",
"file-type": "^18.5.0",
"multiformats": "^12.1.1",
@@ -2653,12 +2653,15 @@
}
},
"node_modules/@cheqd/ts-proto": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@cheqd/ts-proto/-/ts-proto-3.3.1.tgz",
- "integrity": "sha512-/Xebb3fPGujw8Fwbu+wneVh5bP7CWEwX9iE01N2wh3cmSyHpbLBlEIcaJQ0Fzq6pSCQIomS4z4+V3cOw2Bkd+g==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/@cheqd/ts-proto/-/ts-proto-3.3.2.tgz",
+ "integrity": "sha512-CpA7d4MPquWFPuAS7Ex5WtErLKhdblyYex4j87RGxMAdyCm4RrAK2dJC5gZqpHVX4aMtlG1/qfeoaTNuczZYrg==",
"dependencies": {
"long": "^5.2.3",
- "protobufjs": "^7.2.4"
+ "protobufjs": "^7.2.5"
+ },
+ "engines": {
+ "node": ">=18.0.0"
}
},
"node_modules/@colors/colors": {
diff --git a/package.json b/package.json
index 22cb32e2..ce8a69df 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/controllers/issuer.ts b/src/controllers/issuer.ts
index 8d1ec915..571b8496 100644
--- a/src/controllers/issuer.ts
+++ b/src/controllers/issuer.ts
@@ -1,15 +1,16 @@
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 = [
@@ -17,7 +18,7 @@ export class IssuerController {
.optional()
.isObject()
.custom((value) => {
- const { valid } = validateSpecCompliantPayload(value);
+ const { valid } = validateDidCreatePayload(value);
return valid;
})
.withMessage('Invalid didDocument'),
@@ -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()
@@ -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.
@@ -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',
});
}
@@ -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;
@@ -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) {
diff --git a/src/helpers/helpers.ts b/src/helpers/helpers.ts
index 0e5c489e..c357580f 100644
--- a/src/helpers/helpers.ts
+++ b/src/helpers/helpers.ts
@@ -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' };
@@ -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;
}
diff --git a/src/static/swagger.json b/src/static/swagger.json
index 0af095b0..28114bb5 100644
--- a/src/static/swagger.json
+++ b/src/static/swagger.json
@@ -581,12 +581,12 @@
"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"
}
}
}
@@ -2476,7 +2476,84 @@
]
}
},
- "DidCreateRequest": {
+ "DidDocumentWithoutVerificationMethod": {
+ "type": "object",
+ "properties": {
+ "@context": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "id": {
+ "type": "string"
+ },
+ "controllers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "service": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Service"
+ }
+ },
+ "authentication": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "assertionMethod": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "capabilityInvocation": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "capabilityDelegation": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "keyAgreement": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "example": {
+ "@context": [
+ "https://www.w3.org/ns/did/v1"
+ ],
+ "id": "did:cheqd:testnet:7bf81a20-633c-4cc7-bc4a-5a45801005e0",
+ "controller": [
+ "did:cheqd:testnet:7bf81a20-633c-4cc7-bc4a-5a45801005e0"
+ ],
+ "authentication": [
+ "did:cheqd:testnet:7bf81a20-633c-4cc7-bc4a-5a45801005e0#key-1"
+ ],
+ "service": [
+ {
+ "id": "did:cheqd:testnet:7bf81a20-633c-4cc7-bc4a-5a45801005e0#service-1",
+ "type": "LinkedDomains",
+ "serviceEndpoint": [
+ "https://example.com"
+ ]
+ }
+ ]
+ }
+ },
+ "DidCreateRequestFormBased": {
"type": "object",
"properties": {
"network": {
@@ -2487,7 +2564,7 @@
"mainnet"
]
},
- "methodSpecificIdAlgo": {
+ "identifierFormatType": {
"description": "Algorithm to use for generating the method-specific ID. The two styles supported are UUIDs and Indy-style Base58. See cheqd DID method documentation for more details.",
"type": "string",
"enum": [
@@ -2504,13 +2581,89 @@
"Ed25519VerificationKey2020"
]
},
+ "service": {
+ "description": "Communicating or interacting with the DID subject or associated entities via one or more service endpoints. See DID Core specification for more details.",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "idFragment": {
+ "type": "string",
+ "example": "service-1"
+ },
+ "type": {
+ "type": "string",
+ "example": "LinkedDomains"
+ },
+ "serviceEndpoint": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "example": "https://example.com"
+ }
+ }
+ }
+ }
+ },
+ "key": {
+ "description": "The unique identifier in hexadecimal public key format used in the verification method to create the DID.",
+ "type": "string"
+ },
+ "@context": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "example": [
+ "https://www.w3.org/ns/did/v1"
+ ]
+ }
+ }
+ },
+ "DidCreateRequestJson": {
+ "type": "object",
+ "properties": {
+ "network": {
+ "description": "Network to create the DID on (testnet or mainnet)",
+ "type": "string",
+ "enum": [
+ "testnet",
+ "mainnet"
+ ]
+ },
+ "identifierFormatType": {
+ "description": "Algorithm to use for generating the method-specific ID. The two styles supported are UUIDs and Indy-style Base58. See cheqd DID method documentation for more details.",
+ "type": "string",
+ "enum": [
+ "uuid",
+ "base58btc"
+ ]
+ },
"assertionMethod": {
"description": "Usually a reference to a Verification Method. An Assertion Method is required to issue JSON-LD credentials. See DID Core specification for more details.",
"type": "boolean",
"default": true
},
+ "options": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string",
+ "example": "8255ddadd75695e01f3d98fcec8ccc7861a030b317d4326b0e48a4d579ddc43a"
+ },
+ "verificationMethodType": {
+ "description": "Type of verification method to use for the DID. See DID Core specification for more details. Only the types listed below are supported.",
+ "type": "string",
+ "enum": [
+ "Ed25519VerificationKey2018",
+ "JsonWebKey2020",
+ "Ed25519VerificationKey2020"
+ ]
+ }
+ }
+ },
"didDocument": {
- "$ref": "#/components/schemas/DidDocument"
+ "$ref": "#/components/schemas/DidDocumentWithoutVerificationMethod"
}
}
},
@@ -2567,6 +2720,7 @@
}
},
"Service": {
+ "description": "Communicating or interacting with the DID subject or associated entities via one or more service endpoints. See DID Core specification for more details.",
"type": "object",
"properties": {
"id": {
diff --git a/src/types/shared.ts b/src/types/shared.ts
index 1cfdeb1d..97ce9791 100644
--- a/src/types/shared.ts
+++ b/src/types/shared.ts
@@ -29,8 +29,9 @@ import type { ICredentialIssuerLD } from '@veramo/credential-ld';
import type { AbstractIdentifierProvider } from '@veramo/did-manager';
import type { AbstractKeyManagementSystem } from '@veramo/key-manager';
import type { DataSource } from 'typeorm';
-import { CheqdNetwork } from '@cheqd/sdk';
+import { CheqdNetwork, MethodSpecificIdAlgo, Service, VerificationMethods } from '@cheqd/sdk';
import type { AlternativeUri } from '@cheqd/ts-proto/cheqd/resource/v2';
+import type { DIDDocument } from 'did-resolver';
const DefaultUuidPattern = '([a-zA-Z0-9-]{36})';
const DefaultMethodSpecificIdPattern = `(?:[a-zA-Z0-9]{21,22}|${DefaultUuidPattern})`;
@@ -61,6 +62,20 @@ export type MinimalPaymentCondition = {
feePaymentWindow: number; // in minutes, strictly integer, e.g. 5 minutes, 10 minutes
};
+export type CreateDidRequestBody = {
+ didDocument?: DIDDocument
+ identifierFormatType: MethodSpecificIdAlgo;
+ network: CheqdNetwork;
+ verificationMethodType?: VerificationMethods;
+ service?: Service | Service[];
+ '@context'?: string | string[];
+ key?: string;
+ options?: {
+ verificationMethodType: VerificationMethods;
+ key: string;
+ };
+};
+
export type CreateUnencryptedStatusListRequestBody = {
did: string;
statusListName: string;
diff --git a/src/types/swagger-types.ts b/src/types/swagger-types.ts
index 9edaf6c8..4869e061 100644
--- a/src/types/swagger-types.ts
+++ b/src/types/swagger-types.ts
@@ -726,7 +726,57 @@
* type: LinkedDomains
* serviceEndpoint:
* - https://example.com
- * DidCreateRequest:
+ * DidDocumentWithoutVerificationMethod:
+ * type: object
+ * properties:
+ * '@context':
+ * type: array
+ * items:
+ * type: string
+ * id:
+ * type: string
+ * controllers:
+ * type: array
+ * items:
+ * type: string
+ * service:
+ * type: array
+ * items:
+ * $ref: '#/components/schemas/Service'
+ * authentication:
+ * type: array
+ * items:
+ * type: string
+ * assertionMethod:
+ * type: array
+ * items:
+ * type: string
+ * capabilityInvocation:
+ * type: array
+ * items:
+ * type: string
+ * capabilityDelegation:
+ * type: array
+ * items:
+ * type: string
+ * keyAgreement:
+ * type: array
+ * items:
+ * type: string
+ * example:
+ * '@context':
+ * - https://www.w3.org/ns/did/v1
+ * id: did:cheqd:testnet:7bf81a20-633c-4cc7-bc4a-5a45801005e0
+ * controller:
+ * - did:cheqd:testnet:7bf81a20-633c-4cc7-bc4a-5a45801005e0
+ * authentication:
+ * - did:cheqd:testnet:7bf81a20-633c-4cc7-bc4a-5a45801005e0#key-1
+ * service:
+ * - id: did:cheqd:testnet:7bf81a20-633c-4cc7-bc4a-5a45801005e0#service-1
+ * type: LinkedDomains
+ * serviceEndpoint:
+ * - https://example.com
+ * DidCreateRequestFormBased:
* type: object
* properties:
* network:
@@ -735,7 +785,7 @@
* enum:
* - testnet
* - mainnet
- * methodSpecificIdAlgo:
+ * identifierFormatType:
* description: Algorithm to use for generating the method-specific ID. The two styles supported are UUIDs and Indy-style Base58. See cheqd DID method documentation for more details.
* type: string
* enum:
@@ -748,12 +798,67 @@
* - Ed25519VerificationKey2018
* - JsonWebKey2020
* - Ed25519VerificationKey2020
+ * service:
+ * description: Communicating or interacting with the DID subject or associated entities via one or more service endpoints. See DID Core specification for more details.
+ * type: array
+ * items:
+ * type: object
+ * properties:
+ * idFragment:
+ * type: string
+ * example: service-1
+ * type:
+ * type: string
+ * example: LinkedDomains
+ * serviceEndpoint:
+ * type: array
+ * items:
+ * type: string
+ * example: https://example.com
+ * key:
+ * description: The unique identifier in hexadecimal public key format used in the verification method to create the DID.
+ * type: string
+ * '@context':
+ * type: array
+ * items:
+ * type: string
+ * example:
+ * - https://www.w3.org/ns/did/v1
+ *
+ * DidCreateRequestJson:
+ * type: object
+ * properties:
+ * network:
+ * description: Network to create the DID on (testnet or mainnet)
+ * type: string
+ * enum:
+ * - testnet
+ * - mainnet
+ * identifierFormatType:
+ * description: Algorithm to use for generating the method-specific ID. The two styles supported are UUIDs and Indy-style Base58. See cheqd DID method documentation for more details.
+ * type: string
+ * enum:
+ * - uuid
+ * - base58btc
* assertionMethod:
* description: Usually a reference to a Verification Method. An Assertion Method is required to issue JSON-LD credentials. See DID Core specification for more details.
* type: boolean
* default: true
+ * options:
+ * type: object
+ * properties:
+ * key:
+ * type: string
+ * example: 8255ddadd75695e01f3d98fcec8ccc7861a030b317d4326b0e48a4d579ddc43a
+ * verificationMethodType:
+ * description: Type of verification method to use for the DID. See DID Core specification for more details. Only the types listed below are supported.
+ * type: string
+ * enum:
+ * - Ed25519VerificationKey2018
+ * - JsonWebKey2020
+ * - Ed25519VerificationKey2020
* didDocument:
- * $ref: '#/components/schemas/DidDocument'
+ * $ref: '#/components/schemas/DidDocumentWithoutVerificationMethod'
* DidResult:
* type: object
* properties:
@@ -790,6 +895,7 @@
* publicKeyBase58: BTJiso1S4iSiReP6wGksSneGfiKHxz9SYcm2KknpqBJt
* type: Ed25519VerificationKey2018
* Service:
+ * description: Communicating or interacting with the DID subject or associated entities via one or more service endpoints. See DID Core specification for more details.
* type: object
* properties:
* id:
diff --git a/tests/constants.ts b/tests/constants.ts
index 74863f99..b5debb99 100644
--- a/tests/constants.ts
+++ b/tests/constants.ts
@@ -13,3 +13,27 @@ export const DEFAULT_SUBJECT_DID = 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLG
// Messages
export const DEFAULT_DOES_NOT_HAVE_PERMISSIONS =
'Unauthorized error: Your account is not authorized to carry out this action.';
+
+export const DEFAULT_CONTEXT="https://www.w3.org/ns/did/v1"
+
+export const NOT_EXISTENT_KEY = "88888888888895e01f3d98fcec8ccc7861a030b317d4326b0e48a88888888888"
+export const NOT_SUPPORTED_VERIFICATION_METHOD_TYPE = "not_supported_vm_type"
+
+export const INVALID_ID = "invalid_id";
+export const INVALID_DID = "invalid_did";
+
+export enum NETWORK {
+ MAINNET = "mainnet",
+ TESTNET = "testnet"
+};
+
+export enum ID_TYPE {
+ UUID = "uuid",
+ BASE58BTC = "base58btc"
+};
+
+export enum VERIFICATION_METHOD_TYPES {
+ Ed25519VerificationKey2018 = "Ed25519VerificationKey2018",
+ Ed25519VerificationKey2020 = "Ed25519VerificationKey2020",
+ JsonWebKey2020 = "JsonWebKey2020"
+};
diff --git a/tests/did/create.negative.spec.ts b/tests/did/create.negative.spec.ts
new file mode 100644
index 00000000..77dc913c
--- /dev/null
+++ b/tests/did/create.negative.spec.ts
@@ -0,0 +1,186 @@
+import {
+ NETWORK,
+ ID_TYPE,
+ INVALID_ID,
+ INVALID_DID,
+ NOT_EXISTENT_KEY,
+ VERIFICATION_METHOD_TYPES,
+ DEFAULT_DOES_NOT_HAVE_PERMISSIONS,
+ NOT_SUPPORTED_VERIFICATION_METHOD_TYPE
+} from '../constants';
+import * as fs from 'fs';
+import { v4 } from 'uuid';
+import { test, expect } from '@playwright/test';
+import { StatusCodes } from 'http-status-codes';
+
+const PAYLOADS_BASE_PATH = './tests/payloads/did';
+
+test.use({ storageState: 'playwright/.auth/user.json' });
+
+test('[Negative] It cannot create DID with missed verificationMethodType field in request body (Form based)', async ({ request }) => {
+ const response = await request.post(`/did/create`, {
+ data: `network=${NETWORK.TESTNET}&identifierFormatType=${ID_TYPE.BASE58BTC}`,
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
+ });
+ expect(response.status()).toBe(StatusCodes.BAD_REQUEST);
+ expect(await response.text()).toEqual(expect.stringContaining("Provide a DID Document or the VerificationMethodType to create a DID"));
+});
+
+test('[Negative] It cannot create DID with not existent key in request body (Form based)', async ({ request }) => {
+ const response = await request.post(`/did/create`, {
+ data: `network=${NETWORK.TESTNET}&identifierFormatType=${ID_TYPE.BASE58BTC}&` +
+ `verificationMethodType=${VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2020}&` +
+ `key=${NOT_EXISTENT_KEY}`,
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
+ });
+ expect(response.status()).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
+ expect(await response.text()).toEqual(expect.stringContaining("Key not found"));
+});
+
+test('[Negative] It cannot create DID with not existent key in request body (JSON based)', async ({ request }) => {
+ const did = `did:cheqd:testnet:${v4()}`;
+ const response = await request.post('/did/create', {
+ data: {
+ network: NETWORK.TESTNET,
+ identifierFormatType: ID_TYPE.UUID,
+ options: {
+ verificationMethodType: VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2020,
+ key: NOT_EXISTENT_KEY
+ },
+ didDocument: {
+ id: did,
+ controller: [
+ did
+ ],
+ authentication: [
+ `${did}#key-1`
+ ]
+ }
+ },
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response.status()).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
+ expect(await response.text()).toEqual(expect.stringContaining("Key not found"));
+});
+
+test('[Negative] It cannot create DID with an invalid VerificationMethodType in request body (JSON based)', async ({ request }) => {
+ const did = `did:cheqd:testnet:${v4()}`;
+ const response = await request.post('/did/create', {
+ data: {
+ network: NETWORK.TESTNET,
+ identifierFormatType: ID_TYPE.UUID,
+ options: {
+ verificationMethodType: NOT_SUPPORTED_VERIFICATION_METHOD_TYPE
+ },
+ didDocument: {
+ id: did,
+ controller: [
+ did
+ ],
+ authentication: [
+ `${did}#key-1`
+ ]
+ }
+ },
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response.status()).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
+ expect(await response.text()).toEqual(expect.stringContaining("Unsupported verificationMethod type"));
+});
+
+test('[Negative] It cannot create DID with an invalid length of id in DIDDocument in request body (JSON based)', async ({ request }) => {
+ const invalidDidLength = `did:cheqd:testnet:${INVALID_ID}`;
+ const response = await request.post('/did/create', {
+ data: {
+ network: NETWORK.TESTNET,
+ identifierFormatType: ID_TYPE.UUID,
+ options: {
+ verificationMethodType: VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2018
+ },
+ didDocument: {
+ id: invalidDidLength,
+ controller: [
+ invalidDidLength
+ ],
+ authentication: [
+ `${invalidDidLength}#key-1`
+ ]
+ }
+ },
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response.status()).toBe(StatusCodes.INTERNAL_SERVER_ERROR);
+ expect(await response.text()).toEqual(expect.stringContaining("unique id should be one of: 16 bytes of decoded base58 string or UUID"));
+});
+
+test('[Negative] It cannot create DID with an invalid id format in DIDDocument in request body (JSON based)', async ({ request }) => {
+ const response = await request.post('/did/create', {
+ data: {
+ network: NETWORK.TESTNET,
+ identifierFormatType: ID_TYPE.UUID,
+ options: {
+ verificationMethodType: VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2018
+ },
+ didDocument: {
+ id: INVALID_DID,
+ controller: [
+ INVALID_DID
+ ],
+ authentication: [
+ `${INVALID_DID}#key-1`
+ ]
+ }
+ },
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response.status()).toBe(StatusCodes.BAD_REQUEST);
+ expect(await response.text()).toEqual(expect.stringContaining("Invalid didDocument"));
+});
+
+test('[Negative] It cannot create DID without VerificationMethodType in request body (JSON based)', async ({ request }) => {
+ const response = await request.post('/did/create', {
+ data: {
+ network: NETWORK.TESTNET,
+ identifierFormatType: ID_TYPE.UUID,
+ didDocument: {
+ id: INVALID_DID,
+ controller: [
+ INVALID_DID
+ ],
+ authentication: [
+ `${INVALID_DID}#key-1`
+ ]
+ }
+ },
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response.status()).toBe(StatusCodes.BAD_REQUEST);
+ expect(await response.text()).toEqual(expect.stringContaining("Invalid didDocument"));
+});
+
+test('[Negative] It cannot create DID without DidDocument in request body (JSON based)', async ({ request }) => {
+ const response = await request.post('/did/create', {
+ data: {
+ network: NETWORK.TESTNET,
+ identifierFormatType: ID_TYPE.UUID,
+ options: {
+ verificationMethodType: VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2020
+ },
+ },
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response.status()).toBe(StatusCodes.BAD_REQUEST);
+ expect(await response.text()).toEqual(expect.stringContaining("Provide a DID Document or the VerificationMethodType to create a DID"));
+});
+
+test('[Negative] It cannot create DID in mainnet network for user with testnet role', async ({ request }) => {
+ const response = await request.post(`/did/create`, {
+ data: JSON.parse(fs.readFileSync(`${PAYLOADS_BASE_PATH}/did-create-without-permissions.json`, 'utf-8')),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ expect(response).not.toBeOK();
+ expect(response.status()).toBe(StatusCodes.FORBIDDEN);
+ expect(await response.text()).toEqual(expect.stringContaining(DEFAULT_DOES_NOT_HAVE_PERMISSIONS));
+});
diff --git a/tests/did/create.positive.spec.ts b/tests/did/create.positive.spec.ts
new file mode 100644
index 00000000..43c94a6f
--- /dev/null
+++ b/tests/did/create.positive.spec.ts
@@ -0,0 +1,185 @@
+import {
+ ID_TYPE,
+ NETWORK,
+ DEFAULT_CONTEXT,
+ VERIFICATION_METHOD_TYPES
+} from '../constants';
+import { v4 } from 'uuid';
+import { buildSimpleService } from 'helpers';
+import { test, expect } from '@playwright/test';
+import { StatusCodes } from 'http-status-codes';
+import { MethodSpecificIdAlgo, createVerificationKeys } from '@cheqd/sdk';
+
+test.use({ storageState: 'playwright/.auth/user.json' });
+
+test('[Positive] It can create DID with mandatory properties (Form based + Indy style)', async ({ request }) => {
+ // send request to create DID
+ let response = await request.post(`/did/create`, {
+ data: `network=${NETWORK.TESTNET}&identifierFormatType=${ID_TYPE.BASE58BTC}&` +
+ `verificationMethodType=${VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2020}`,
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ // resolve a created DID
+ response = await request.get(`/did/search/${(await response.json()).did}`, {
+ headers: { 'Content-Type': 'application/json' }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ const didDocument = (await response.json()).didDocument;
+
+ // Check mandatory properties
+ expect(didDocument.id.split(":")[2]).toBe(NETWORK.TESTNET);
+ expect(didDocument.verificationMethod[0].type).toBe(VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2020);
+});
+
+test('[Positive] It can create DID with mandatory and optional properties (Form based + UUID style)', async ({ request }) => {
+ // send request to create key
+ let response = await request.post('/key/create', {
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ const kid = (await response.json()).kid;
+
+ // send request to create DID
+ response = await request.post(`/did/create`, {
+ data: `network=${NETWORK.TESTNET}&identifierFormatType=${ID_TYPE.BASE58BTC}&` +
+ `verificationMethodType=${VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2020}&` +
+ `service=${JSON.stringify(buildSimpleService())}&key=${kid}&@context=${DEFAULT_CONTEXT}`,
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ const body = await response.json();
+
+ // Check mandatory properties
+ expect(body.did.split(":")[2]).toBe(NETWORK.TESTNET);
+ expect(body.controllerKeyId).toBe(kid);
+
+ // resolve a created DID
+ response = await request.get(`/did/search/${body.did}`, {
+ headers: { 'Content-Type': 'application/json' }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ const didDocument = (await response.json()).didDocument;
+
+ // check optional properties
+ expect(didDocument.verificationMethod[0].type).toBe(VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2020);
+ expect(didDocument.service[0].id).toBe(`${body.did}#service-1`);
+ expect(didDocument.service[0].type).toBe("LinkedDomains");
+ expect(didDocument.service[0].serviceEndpoint[0]).toBe("https://example.com");
+});
+
+test('[Positive] It can create DID with mandatory properties (JSON based + Indy style)', async ({ request }) => {
+ // send request to create key
+ let response = await request.post('/key/create', {
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ const did = createVerificationKeys((await response.json()).kid, MethodSpecificIdAlgo.Base58, "key-1").didUrl;
+
+ // send request to create DID
+ response = await request.post('/did/create', {
+ data: {
+ options: {
+ verificationMethodType: VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2020
+ },
+ didDocument: {
+ id: did,
+ controller: [
+ did
+ ],
+ authentication: [
+ `${did}#key-1`
+ ]
+ }
+ },
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ // resolve a created DID
+ response = await request.get(`/did/search/${(await response.json()).did}`, {
+ headers: { 'Content-Type': 'application/json' }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ const didDocument = (await response.json()).didDocument;
+
+ // Check mandatory properties
+ expect(didDocument.id.split(":")[2]).toBe(NETWORK.TESTNET);
+ expect(didDocument.verificationMethod[0].type).toBe(VERIFICATION_METHOD_TYPES.Ed25519VerificationKey2020);
+});
+
+test('[Positive] It can create DID with mandatory and optional properties (JSON based + UUID style)', async ({ request }) => {
+ // send request to create key
+ let response = await request.post('/key/create', {
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ const kid = (await response.json()).kid;
+
+ const did = `did:cheqd:testnet:${v4()}`;
+ response = await request.post('/did/create', {
+ data: {
+ network: NETWORK.TESTNET,
+ identifierFormatType: ID_TYPE.UUID,
+ assertionMethod: true,
+ options: {
+ verificationMethodType: VERIFICATION_METHOD_TYPES.JsonWebKey2020,
+ key: kid
+ },
+ didDocument: {
+ "@context": [
+ "https://www.w3.org/ns/did/v1"
+ ],
+ id: did,
+ controller: [
+ did
+ ],
+ authentication: [
+ `${did}#key-1`
+ ],
+ service: [{
+ id: `${did}#service-1`,
+ type: "LinkedDomains",
+ serviceEndpoint: [
+ "https://example.com"
+ ]
+ }]
+ }
+ },
+ headers: { "Content-Type": "application/json" }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ // resolve a created DID
+ response = await request.get(`/did/search/${(did)}`, {
+ headers: { 'Content-Type': 'application/json' }
+ });
+ expect(response).toBeOK();
+ expect(response.status()).toBe(StatusCodes.OK);
+
+ const didDocument = (await response.json()).didDocument;
+
+ // check optional properties
+ expect(didDocument.verificationMethod[0].type).toBe(VERIFICATION_METHOD_TYPES.JsonWebKey2020);
+ expect(didDocument.service[0].id).toBe(`${did}#service-1`);
+ expect(didDocument.service[0].type).toBe("LinkedDomains");
+ expect(didDocument.service[0].serviceEndpoint[0]).toBe("https://example.com");
+});
diff --git a/tests/did/deactivate.negative.spec.ts b/tests/did/deactivate.negative.spec.ts
new file mode 100644
index 00000000..11ab2240
--- /dev/null
+++ b/tests/did/deactivate.negative.spec.ts
@@ -0,0 +1,12 @@
+import { test, expect } from '@playwright/test';
+import { StatusCodes } from 'http-status-codes';
+import { DEFAULT_DOES_NOT_HAVE_PERMISSIONS, DEFAULT_MAINNET_DID } from '../constants';
+
+test.use({ storageState: 'playwright/.auth/user.json' });
+
+test('[Negative] It cannot deactivated DID in mainnet network for user with testnet role', async ({ request }) => {
+ const response = await request.post(`/did/deactivate/${DEFAULT_MAINNET_DID}`, {});
+ expect(response).not.toBeOK();
+ expect(response.status()).toBe(StatusCodes.FORBIDDEN);
+ expect(await response.text()).toEqual(expect.stringContaining(DEFAULT_DOES_NOT_HAVE_PERMISSIONS));
+});
diff --git a/tests/did/did.spec.ts b/tests/did/did.spec.ts
deleted file mode 100644
index 3d3e1cec..00000000
--- a/tests/did/did.spec.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { test, expect } from '@playwright/test';
-import { StatusCodes } from 'http-status-codes';
-import { DEFAULT_DOES_NOT_HAVE_PERMISSIONS, DEFAULT_MAINNET_DID } from '../constants';
-import * as fs from 'fs';
-
-test.use({ storageState: 'playwright/.auth/user.json' });
-
-const PAYLOADS_BASE_PATH = './tests/payloads/did';
-
-// Negative tests. All of this tests should return 403 Forbidden
-// cause here the user tries to make mainnet operations with testnet role
-test('Create DID for user with testnet role but network is mainnet', async ({ request }) => {
- const response = await request.post(`/did/create`, {
- data: JSON.parse(fs.readFileSync(`${PAYLOADS_BASE_PATH}/did-create-without-permissions.json`, 'utf-8')),
- headers: {
- 'Content-Type': 'application/json',
- },
- });
- expect(response).not.toBeOK();
- expect(response.status()).toBe(StatusCodes.FORBIDDEN);
- expect(await response.text()).toEqual(expect.stringContaining(DEFAULT_DOES_NOT_HAVE_PERMISSIONS));
-});
-
-test('Update DID for user with testnet role but network is mainnet', async ({ request }) => {
- const response = await request.post(`/did/update`, {
- data: JSON.parse(fs.readFileSync(`${PAYLOADS_BASE_PATH}/did-update-without-permissions.json`, 'utf-8')),
- headers: {
- 'Content-Type': 'application/json',
- },
- });
- expect(response).not.toBeOK();
- expect(response.status()).toBe(StatusCodes.FORBIDDEN);
- expect(await response.text()).toEqual(expect.stringContaining(DEFAULT_DOES_NOT_HAVE_PERMISSIONS));
-});
-
-test('Deactivate DID for user with testnet role but network is mainnet', async ({ request }) => {
- const response = await request.post(`/did/deactivate/${DEFAULT_MAINNET_DID}`, {});
- expect(response).not.toBeOK();
- expect(response.status()).toBe(StatusCodes.FORBIDDEN);
- expect(await response.text()).toEqual(expect.stringContaining(DEFAULT_DOES_NOT_HAVE_PERMISSIONS));
-});
diff --git a/tests/did/update.negative.spec.ts b/tests/did/update.negative.spec.ts
new file mode 100644
index 00000000..9e7f650a
--- /dev/null
+++ b/tests/did/update.negative.spec.ts
@@ -0,0 +1,20 @@
+import * as fs from 'fs';
+import { test, expect } from '@playwright/test';
+import { StatusCodes } from 'http-status-codes';
+import { DEFAULT_DOES_NOT_HAVE_PERMISSIONS } from '../constants';
+
+const PAYLOADS_BASE_PATH = './tests/payloads/did';
+
+test.use({ storageState: 'playwright/.auth/user.json' });
+
+test('[Negative] It cannot update DID in mainnet network for user with testnet role', async ({ request }) => {
+ const response = await request.post(`/did/update`, {
+ data: JSON.parse(fs.readFileSync(`${PAYLOADS_BASE_PATH}/did-update-without-permissions.json`, 'utf-8')),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ expect(response).not.toBeOK();
+ expect(response.status()).toBe(StatusCodes.FORBIDDEN);
+ expect(await response.text()).toEqual(expect.stringContaining(DEFAULT_DOES_NOT_HAVE_PERMISSIONS));
+});
diff --git a/tests/helpers.ts b/tests/helpers.ts
index 3daa5952..7e96f6e3 100644
--- a/tests/helpers.ts
+++ b/tests/helpers.ts
@@ -8,7 +8,7 @@ import {
export const buildSimpleCreateDID = (network = 'testnet') => {
return {
- methodSpecificIdAlgo: 'uuid',
+ identifierFormatType: 'uuid',
verificationMethodType: 'Ed25519VerificationKey2018',
assertionMethod: true,
network: network,
@@ -137,3 +137,16 @@ export const buildSimpleIssueCredentialRequest = (
},
};
};
+
+export const buildSimpleService = (
+ idFragment = "service-1",
+ type = "LinkedDomains",
+ serviceEndpoint = ["https://example.com"]
+) => {
+ return {
+ idFragment: idFragment,
+ type: type,
+ serviceEndpoint: serviceEndpoint
+ };
+};
+
diff --git a/tests/payloads/did/did-create-without-permissions.json b/tests/payloads/did/did-create-without-permissions.json
index f6e1ecd8..6cb224bc 100644
--- a/tests/payloads/did/did-create-without-permissions.json
+++ b/tests/payloads/did/did-create-without-permissions.json
@@ -1,5 +1,5 @@
{
- "methodSpecificIdAlgo": "uuid",
+ "identifierFormatType": "uuid",
"verificationMethodType": "Ed25519VerificationKey2018",
"assertionMethod": true,
"network": "mainnet"