Skip to content

Commit

Permalink
feat: Allow DID controller switching [DEV-3587] (#481)
Browse files Browse the repository at this point in the history
* Add ability to pass public key to update, deactivate DIDs and resource creation

* Add flow for testing DID controller switching

* Add new test and checks

* Make changes due to did-provider-cheqd

* npm run update

* format

* Freeze swagger-ui-dist package

* changes due to review comments

* npm run format

* Resolve deadlink issue with md

---------

Co-authored-by: Tasos Derisiotis <50984242+Eengineer1@users.noreply.github.com>
  • Loading branch information
Andrew Nikitin and Eengineer1 committed Feb 6, 2024
1 parent 697538d commit 2ef5b09
Show file tree
Hide file tree
Showing 20 changed files with 2,315 additions and 2,201 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ By default, `ENABLE_AUTHENTICATION` is set to off/`false`. To enable external Ve
1. `LOGTO_M2M_APP_ID`: Application ID for machine-to-machine application in LogTo. This is used for elevated
management APIs within LogTo.
2. `LOGTO_M2M_APP_SECRET`: Application secret
4. **Default role update using [LogTo webhooks](https://docs.logto.io/next/docs/recipes/webhooks/)**: LogTo supports
4. **Default role update using [LogTo webhooks](https://docs.logto.io/docs/recipes/webhooks/)**: LogTo supports
webhooks to fire of requests to an API when it detects certain actions/changes. If you want to automatically assign a
role to users, a webhook is recommended to be setup for firing off whenever there's a new account created, or a new
sign-in.
Expand Down
3,635 changes: 1,504 additions & 2,131 deletions package-lock.json

Large diffs are not rendered by default.

52 changes: 27 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,55 +50,57 @@
"README.md"
],
"dependencies": {
"@cheqd/did-provider-cheqd": "^3.6.16",
"@cheqd/did-provider-cheqd": "^3.7.0",
"@cheqd/sdk": "^3.7.9",
"@cheqd/ts-proto": "^3.4.0",
"@cosmjs/amino": "^0.32.2",
"@cosmjs/encoding": "^0.32.2",
"@logto/express": "^2.2.0",
"@logto/express": "^2.3.0",
"@stablelib/ed25519": "^1.0.3",
"@veramo/core": "^5.5.3",
"@veramo/credential-ld": "^5.5.3",
"@veramo/credential-w3c": "^5.5.3",
"@veramo/data-store": "^5.5.3",
"@veramo/did-manager": "^5.5.3",
"@veramo/did-resolver": "^5.5.3",
"@veramo/key-manager": "^5.5.3",
"@veramo/kms-local": "^5.5.3",
"@veramo/utils": "^5.5.3",
"@verida/account-node": "^3.0.0",
"@veramo/core": "^5.6.0",
"@veramo/credential-ld": "^5.6.0",
"@veramo/credential-w3c": "^5.6.0",
"@veramo/data-store": "^5.6.0",
"@veramo/did-manager": "^5.6.0",
"@veramo/did-provider-key": "^5.6.0",
"@veramo/did-resolver": "^5.6.0",
"@veramo/key-manager": "^5.6.0",
"@veramo/kms-local": "^5.6.0",
"@veramo/utils": "^5.6.0",
"@verida/account-node": "^3.0.1",
"@verida/client-ts": "^3.0.2",
"@verida/encryption-utils": "^3.0.0",
"@verida/types": "^3.0.0",
"@verida/vda-did-resolver": "^3.0.0",
"@verida/vda-did-resolver": "^3.0.1",
"bs58": "^5.0.0",
"cookie-parser": "^1.4.6",
"copyfiles": "^2.4.1",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"did-resolver": "^4.1.0",
"dotenv": "^16.3.1",
"dotenv": "^16.4.1",
"express": "^4.18.2",
"express-session": "^1.17.3",
"express-session": "^1.18.0",
"express-validator": "^7.0.1",
"helmet": "^7.1.0",
"http-status-codes": "^2.3.0",
"json-stringify-safe": "^5.0.1",
"jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0",
"multiformats": "^13.0.0",
"multiformats": "^13.0.1",
"node-cache": "^5.1.2",
"pg": "^8.11.3",
"pg-connection-string": "^2.6.2",
"secp256k1": "^5.0.0",
"sqlite3": "^5.1.7",
"swagger-ui-express": "^5.0.0",
"typeorm": "^0.3.19",
"uint8arrays": "^5.0.1",
"swagger-ui-dist": "5.10.5",
"typeorm": "^0.3.20",
"uint8arrays": "^4.0.10",
"uri-js": "^4.4.1"
},
"devDependencies": {
"@playwright/test": "^1.40.1",
"@playwright/test": "^1.41.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^11.1.0",
"@semantic-release/git": "^10.0.1",
Expand All @@ -114,12 +116,12 @@
"@types/helmet": "^4.0.0",
"@types/json-stringify-safe": "^5.0.3",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.10.6",
"@types/node": "^20.11.15",
"@types/secp256k1": "^4.0.6",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.6",
"@types/uuid": "^9.0.7",
"@types/validator": "^13.11.7",
"@types/uuid": "^9.0.8",
"@types/validator": "^13.11.8",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"buffer": "6.0.3",
Expand All @@ -128,10 +130,10 @@
"eslint-config-prettier": "^9.1.0",
"eslint-config-typescript": "^3.0.0",
"jest": "^29.7.0",
"prettier": "^3.2.2",
"semantic-release": "^22.0.12",
"prettier": "^3.2.4",
"semantic-release": "^23.0.0",
"swagger-jsdoc": "^6.2.8",
"ts-jest": "^29.1.1",
"ts-jest": "^29.1.2",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typescript": "^5.3.3",
Expand Down
58 changes: 53 additions & 5 deletions src/controllers/did.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ import type {
DeactivateDIDRequestParams,
GetDIDRequestParams,
ResolveDIDRequestParams,
DeactivateDIDRequestBody,
} from '../types/did.js';
import { check, validationResult, param } from './validator/index.js';
import type { IKey, RequireOnly } from '@veramo/core';
import { extractPublicKeyHex } from '@veramo/utils';
import type { ValidationErrorResponseBody } from '../types/shared.js';
import type { KeyImport } from '../types/key.js';
import { arePublicKeyHexsInWallet } from '../services/helpers.js';
import { CheqdProviderErrorCodes } from '@cheqd/did-provider-cheqd';
import type { CheqdProviderError } from '@cheqd/did-provider-cheqd';

export class DIDController {
public static createDIDValidator = [
Expand Down Expand Up @@ -93,6 +97,7 @@ export class DIDController {
.bail()
.isDIDArray()
.bail(),
check('publicKeyHexs').optional().isArray().withMessage('publicKeyHexs should be an array of strings').bail(),
];

public static deactivateDIDValidator = [param('did').exists().isString().isDID().bail()];
Expand Down Expand Up @@ -293,7 +298,7 @@ export class DIDController {
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/DidResult'
* $ref: '#/components/schemas/DidUpdateResponse'
* 400:
* $ref: '#/components/schemas/InvalidRequest'
* 401:
Expand All @@ -313,12 +318,23 @@ export class DIDController {
}

// handle request params
const { did, service, verificationMethod, authentication } = request.body as UpdateDidRequestBody;
const { did, service, verificationMethod, authentication, publicKeyHexs } =
request.body as UpdateDidRequestBody;
// Get the didDocument from the request if it's placed there
let updatedDocument: DIDDocument
let updatedDocument: DIDDocument;
// Get strategy e.g. postgres or local
const identityServiceStrategySetup = new IdentityServiceStrategySetup(response.locals.customer.customerId);

// If list of publicKeyHexs is placed - check that publicKeyHexs are owned by the customer
if (publicKeyHexs) {
const areOwned = await arePublicKeyHexsInWallet(publicKeyHexs, response.locals.customer);
if (!areOwned.status) {
return response.status(StatusCodes.BAD_REQUEST).json({
error: areOwned.error as string,
} satisfies UnsuccessfulUpdateDidResponseBody);
}
}

try {
if (request.body.didDocument) {
// Just pass the didDocument from the user as is
Expand Down Expand Up @@ -355,10 +371,23 @@ export class DIDController {

const result = await identityServiceStrategySetup.agent.updateDid(
updatedDocument,
response.locals.customer
response.locals.customer,
publicKeyHexs
);
return response.status(StatusCodes.OK).json(result satisfies UpdateDidResponseBody);
} catch (error) {
const errorCode = (error as CheqdProviderError).errorCode;
// Handle specific cases when DID is deactivated or verificationMethod is empty
if (
errorCode &&
(errorCode === CheqdProviderErrorCodes.DeactivatedController ||
errorCode === CheqdProviderErrorCodes.EmptyVerificationMethod)
) {
return response.status(StatusCodes.BAD_REQUEST).json({
error: `updateDID: error: ${(error as CheqdProviderError).message}`,
} satisfies UnsuccessfulUpdateDidResponseBody);
}

return response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
error: `Internal error: ${(error as Error)?.message || error}`,
} satisfies UnsuccessfulUpdateDidResponseBody);
Expand Down Expand Up @@ -495,6 +524,14 @@ export class DIDController {
* schema:
* type: string
* required: true
* requestBody:
* content:
* application/x-www-form-urlencoded:
* schema:
* $ref: '#/components/schemas/DidDeactivateRequest'
* application/json:
* schema:
* $ref: '#/components/schemas/DidDeactivateRequest'
* responses:
* 200:
* description: The request was successful.
Expand Down Expand Up @@ -522,13 +559,24 @@ export class DIDController {

// Extract did from request params
const { did } = request.params as DeactivateDIDRequestParams;
const { publicKeyHexs } = request.body as DeactivateDIDRequestBody;

// If list of publicKeyHexs is placed - check that publicKeyHexs are owned by the customer
if (publicKeyHexs) {
const areOwned = await arePublicKeyHexsInWallet(publicKeyHexs, response.locals.customer);
if (!areOwned.status) {
return response.status(StatusCodes.BAD_REQUEST).json({
error: areOwned.error as string,
} satisfies UnsuccessfulDeactivateDidResponseBody);
}
}

// Get strategy e.g. postgres or local
const identityServiceStrategySetup = new IdentityServiceStrategySetup(response.locals.customer.customerId);

try {
// Deactivate DID
await identityServiceStrategySetup.agent.deactivateDid(did, response.locals.customer);
await identityServiceStrategySetup.agent.deactivateDid(did, response.locals.customer, publicKeyHexs);
// Send the deactivated DID as result
const result = await identityServiceStrategySetup.agent.resolveDid(request.params.did);

Expand Down
16 changes: 14 additions & 2 deletions src/controllers/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
UnsuccessfulCreateResourceResponseBody,
UnsuccessfulQueryResourceResponseBody,
} from '../types/resource.js';
import { arePublicKeyHexsInWallet } from '../services/helpers.js';

export class ResourceController {
public static createResourceValidator = [
Expand Down Expand Up @@ -153,7 +154,17 @@ export class ResourceController {
// Extract the did from the request
const { did } = request.params;
// Extract the resource parameters from the request
const { data, encoding, name, type, alsoKnownAs, version, network } = request.body as CreateResourceRequestBody;
const { data, encoding, name, type, alsoKnownAs, version, network, publicKeyHexs } =
request.body as CreateResourceRequestBody;
// If list of publicKeyHexs is placed - check that publicKeyHexs are owned by the customer
if (publicKeyHexs) {
const areOwned = await arePublicKeyHexsInWallet(publicKeyHexs, response.locals.customer);
if (!areOwned.status) {
return response.status(StatusCodes.BAD_REQUEST).json({
error: areOwned.error as string,
} satisfies UnsuccessfulCreateResourceResponseBody);
}
}
// Get strategy e.g. postgres or local
const identityServiceStrategySetup = new IdentityServiceStrategySetup(response.locals.customer.customerId);

Expand All @@ -179,7 +190,8 @@ export class ResourceController {
const result = await identityServiceStrategySetup.agent.createResource(
network || did.split(':')[2],
resourcePayload,
response.locals.customer
response.locals.customer,
publicKeyHexs
);

if (result) {
Expand Down
34 changes: 34 additions & 0 deletions src/services/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { TPublicKeyEd25519 } from '@cheqd/did-provider-cheqd';
import type { CustomerEntity } from '../database/entities/customer.entity.js';
import type { IBooleanResponse } from '../types/shared.js';
import { IdentityServiceStrategySetup } from './identity/index.js';
import { KeyService } from './key.js';
import type { CheqdW3CVerifiableCredential } from './w3c-credential.js';
import type { CheqdW3CVerifiablePresentation } from './w3c-presentation.js';

Expand All @@ -21,3 +25,33 @@ export async function isIssuerDidDeactivated(presentation: CheqdW3CVerifiablePre
}
return false;
}

export async function arePublicKeyHexsInWallet(
publicKeyHexs: string[],
customer: CustomerEntity
): Promise<IBooleanResponse> {
const ownedKeys = await KeyService.instance.find({ customer: customer });
if (ownedKeys.length === 0) {
return {
status: false,
error: `Customer has no keys in wallet`,
} satisfies IBooleanResponse;
}
const ownedPublicKeysHexs = ownedKeys.map((key) => key.publicKeyHex);
const notOwnedPublicKeysHexs = publicKeyHexs.filter((key) => !ownedPublicKeysHexs.includes(key));
if (notOwnedPublicKeysHexs.length > 0) {
return {
status: false,
error: `Public keys with hexs: ${notOwnedPublicKeysHexs.join(', ')} are not owned by the customer`,
} satisfies IBooleanResponse;
}
return { status: true } satisfies IBooleanResponse;
}

export function toTPublicKeyEd25519(publicKeyHex: string): TPublicKeyEd25519 {
return {
type: 'Ed25519',
publicKeyHex: publicKeyHex,
kid: publicKeyHex,
} satisfies TPublicKeyEd25519;
}
11 changes: 8 additions & 3 deletions src/services/identity/abstract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ export abstract class AbstractIdentityService implements IIdentityService {
throw new Error(`Not supported`);
}

updateDid(didDocument: DIDDocument, customer: CustomerEntity): Promise<IIdentifier> {
updateDid(didDocument: DIDDocument, customer: CustomerEntity, publicKeyHexs?: string[]): Promise<IIdentifier> {
throw new Error(`Not supported`);
}

deactivateDid(did: string, customer: CustomerEntity): Promise<boolean> {
deactivateDid(did: string, customer: CustomerEntity, publicKeyHexs?: string[]): Promise<boolean> {
throw new Error(`Not supported`);
}

Expand All @@ -85,7 +85,12 @@ export abstract class AbstractIdentityService implements IIdentityService {
throw new Error(`Not supported`);
}

createResource(network: string, payload: ResourcePayload, customer: CustomerEntity): Promise<any> {
createResource(
network: string,
payload: ResourcePayload,
customer: CustomerEntity,
publicKeyHexs?: string[]
): Promise<any> {
throw new Error(`Not supported`);
}

Expand Down

0 comments on commit 2ef5b09

Please sign in to comment.