From 1f8f0282a38853d3bc6fbbce3e02a028f68749ff Mon Sep 17 00:00:00 2001 From: Jakub Sydor Date: Mon, 13 Jun 2022 20:03:04 +0200 Subject: [PATCH] feat: add credential revocation --- docs/api/README.md | 79 ++++----- .../modules_cache_client.CacheClient.md | 63 ++++++++ ...ntials.VerifiableCredentialsServiceBase.md | 97 ++++++++++- ...tials.CredentialRevocationDetailsResult.md | 22 +++ ...entials.StatusList2021CredentialSubject.md | 40 +++++ ...ntials.StatusList2021UnsignedCredential.md | 59 +++++++ .../modules/modules_verifiable_credentials.md | 29 ++++ .../cache-client/cache-client.service.ts | 72 ++++++++- src/modules/claims/claims.service.ts | 2 +- src/modules/signer/signer.service.ts | 7 +- .../verifiable-credentials/types/index.ts | 1 + .../types/status-list.types.ts | 60 +++++++ .../verifiable-credentials-base.service.ts | 150 ++++++++++++++++-- .../verifiable-credentials.const.ts | 7 + 14 files changed, 619 insertions(+), 69 deletions(-) create mode 100644 docs/api/interfaces/modules_verifiable_credentials.CredentialRevocationDetailsResult.md create mode 100644 docs/api/interfaces/modules_verifiable_credentials.StatusList2021CredentialSubject.md create mode 100644 docs/api/interfaces/modules_verifiable_credentials.StatusList2021UnsignedCredential.md create mode 100644 src/modules/verifiable-credentials/types/status-list.types.ts create mode 100644 src/modules/verifiable-credentials/verifiable-credentials.const.ts diff --git a/docs/api/README.md b/docs/api/README.md index 7dde4779..6505558d 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -1,6 +1,6 @@

-

+

Energy Web Foundation Logo @@ -9,32 +9,28 @@ # Identity and Access Management (IAM) Client Library ## Overview - -IAM Client Library is a [TypeScript](https://www.typescriptlang.org/) library to be used in decentralized applications for authentication and authorization using [Decentralized Identifiers (DIDs)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials (VCs)](https://www.w3.org/TR/vc-data-model/). DIDs and VCs are central components of self-sovereign identity, a paradigm that promotes user custody over their digital identity. +IAM Client Library is a [TypeScript](https://www.typescriptlang.org/) library to be used in decentralized applications for authentication and authorization using [Decentralized Identifiers (DIDs)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials (VCs)](https://www.w3.org/TR/vc-data-model/). DIDs and VCs are central components of self-sovereign identity, a paradigm that promotes user custody over their digital identity. To read more about Decentralized Identifiers and Verifiable Credentials, and how they are used in the Energy Web tech stack, see our documentation [here](https://energy-web-foundation.gitbook.io/energy-web/foundational-concepts/self-sovereign-identity). -`iam-client-lib` is a key dependency of [Switchboard](https://switchboard.energyweb.org/welcome), the identity and access management (IAM) interface for the [Energy Web Decentralized Operating System](#ew-dos). +`iam-client-lib` is a key dependency of [Switchboard](https://switchboard.energyweb.org/welcome), the identity and access management (IAM) interface for the [Energy Web Decentralized Operating System](#ew-dos). -Using `iam-client-lib`, Switchboard allows users to: +Using `iam-client-lib`, Switchboard allows users to: - Create self-sovereign Decentralized Identifiers (DID) for users and assets using a connection with a crypto wallet such as MetaMask. DIDs are anchored in a smart contract on the Energh Web Chain - Define hierarchical, role-based structures for [organizations](./docs/guides/organization.md), [applications](./docs/guides/application.md) and [assets](./docs/guides/asset.md) that participate in grid activities - Request and issue Verifiable Credentials that are required to take on roles within an organization or application that is registered on Switchboard ## Documentation - -For documentation on `iam-client-lib` modules and API: - +For documentation on `iam-client-lib` modules and API: - [ReadTheDocs](https://energy-web-foundation-iam-client-lib.readthedocs-hosted.com/) ## Development - -The following is for installing and building `iam-client-lib` directly. For guidance on how to integrate the library into an application, see [**Getting Started**](#getting-started) below. +The following is for installing and building `iam-client-lib` directly. +For guidance on how to integrate the library into an application, see [**Getting Started**](#getting-started) below. ### Installing Dependencies - -Use `npm` >= 7 to install dependencies. +Use `npm` >= 7 to install dependencies. ```sh npm install ./energyweb-km-utils-v1.0.0.tgz @@ -49,44 +45,44 @@ npm install ``` ### Compile and Build - To generate bundled JS files and types, use `npm run build`. Generated files are located in the dist folder. - + ### Testing - Tests are located in the /e2e directory. For testing, use `npm run test:watch`. ## Getting Started - -The following is for integrating `iam-client-lib` as a dependency in your application. +The following is for integrating `iam-client-lib` as a dependency in your application. ### Demo Source Code - -See source code examples for integrating `iam-client-lib` into client applications here: [iam-client-examples](https://github.com/energywebfoundation/iam-client-examples) +See source code examples for integrating `iam-client-lib` into client applications here: [iam-client-examples](https://github.com/energywebfoundation/iam-client-examples) [![react logo](examples/react-icon.png) React Demo](https://did-auth-demo.energyweb.org/react-example/) / [![angular logo](examples/angular-icon.png) Angular Demo](https://did-auth-demo.energyweb.org/angular-example/) / [![vue logo](examples/vue-icon.png) Vue Demo](https://did-auth-demo.energyweb.org/vue-example/) -### Pre-requisites +### Pre-requisites - `iam-client-lib` is written in TypeScript. Your application must use Node.js >= 10 - `iam-client-lib` has a [WebAssembly](https://webassembly.org/) dependency. Some frameworks/bundlers do not support this out of the box, so additional action is required based on the framework you are using: -**For Angular applications**, add the following to `package.json`: +**For Angular applications**, add the following to `package.json`: -` "browser": { "fs": false, "os": false, "path": false }` +`` +"browser": { + "fs": false, + "os": false, + "path": false + } +`` **For React applications:** you must add a `wasm-loader` to your Webpack configuration. If you do not have direct access to your webpack config, you can use [@craco/craco](https://www.npmjs.com/package/@craco/craco) or [react-app-rewired](https://www.npmjs.com/package/react-app-rewired). ### Install - -To install the latest version of `iam-client-lib`: +To install the latest version of `iam-client-lib`: ```sh npm i iam-client-lib ``` -To install the pre-realse version of `iam-client-lib`: - +To install the pre-realse version of `iam-client-lib`: ```sh npm i iam-client-lib@canary ``` @@ -94,23 +90,20 @@ npm i iam-client-lib@canary Note that some library dependencies require `node.js` built-ins. When `iam-client-lib` is used in browser applications, you must make sure these dependencies are [polyfilled](https://developer.mozilla.org/en-US/docs/Glossary/Polyfill). If your application is bundled with Webpack, most dependencies can be polyfilled with [node-polyfill-webpack-plugin](https://www.npmjs.com/package/node-polyfill-webpack-plugin). ### Initialization - -**Note:** You can see a full implementation of initializing `iam-client-lib` in the Switchboard application [here](https://github.com/energywebfoundation/switchboard-dapp/blob/develop/src/app/shared/services/iam.service.ts). +**Note:** You can see a full implementation of initializing `iam-client-lib` in the Switchboard application [here](https://github.com/energywebfoundation/switchboard-dapp/blob/develop/src/app/shared/services/iam.service.ts). Your application will need to initialize the library's modules. Because the library's modules have internal depenencies, modules must be initialized by the application in the correct order: #### 1. Initialize signer service - -This will initialize staking and messaging services, and allow a connection to the cache server. +This will initialize staking and messaging services, and allow a connection to the cache server. ```js const { signerService, messagingService, connectToCacheServer } = await initWithPrivateKeySigner(privateKey, rpcUrl); ``` -#### 2. Connect to the cache server. - -Depending on the signer type (i.e. MetaMask, WalletConnect), a signature may be requested. +#### 2. Connect to the cache server. +Depending on the signer type (i.e. MetaMask, WalletConnect), a signature may be requested. ```js // IAM has builtin default settings for VOLTA CHAIN, which can overriden @@ -141,18 +134,15 @@ const { ``` #### 3. Connect to the DID registry - ```js const { didRegistry, claimsService } = await connectToDidRegistry(); ``` -## Contributing Guidelines - +## Contributing Guidelines Please read [CONTRIBUTING.md](https://gist.github.com/PurpleBooth/b24679402957c63ec426) for details on our code of conduct, and the process for submitting pull requests to us. ## Questions and Support - -For questions and support please use Energy Web's [Discord channel](https://discord.com/channels/706103009205288990/843970822254362664) +For questions and support please use Energy Web's [Discord channel](https://discord.com/channels/706103009205288990/843970822254362664) Or reach out to our contributing team members: @@ -162,28 +152,25 @@ Or reach out to our contributing team members: - [Ashish Tripathi](https://github.com/nichonien) ## EW-DOS +The Energy Web Decentralized Operating System is a blockchain-based, multi-layer digital infrastructure. -The Energy Web Decentralized Operating System is a blockchain-based, multi-layer digital infrastructure. - -The purpose of EW-DOS is to develop and deploy an open and decentralized digital operating system for the energy sector in support of a low-carbon, customer-centric energy future. +The purpose of EW-DOS is to develop and deploy an open and decentralized digital operating system for the energy sector in support of a low-carbon, customer-centric energy future. We develop blockchain technology, full-stack applications and middleware packages that facilitate participation of Distributed Energy Resources on the grid, and create open market places for transparent and efficient renewable energy trading. -- To learn about more about the EW-DOS tech stack, see our [documentation](https://app.gitbook.com/@energy-web-foundation/s/energy-web/). +- To learn about more about the EW-DOS tech stack, see our [documentation](https://app.gitbook.com/@energy-web-foundation/s/energy-web/). -- For an overview of the energy-sector challenges our use cases address, go [here](https://app.gitbook.com/@energy-web-foundation/s/energy-web/our-mission). +- For an overview of the energy-sector challenges our use cases address, go [here](https://app.gitbook.com/@energy-web-foundation/s/energy-web/our-mission). For a deep-dive into the motivation and methodology behind our technical solutions, we encourage you to read our White Papers: - [Energy Web White Paper on Vision and Purpose](https://www.energyweb.org/reports/EWDOS-Vision-Purpose/) -- [Energy Web White Paper on Technology Detail](https://www.energyweb.org/wp-content/uploads/2020/06/EnergyWeb-EWDOS-PART2-TechnologyDetail-202006-vFinal.pdf) +- [Energy Web White Paper on Technology Detail](https://www.energyweb.org/wp-content/uploads/2020/06/EnergyWeb-EWDOS-PART2-TechnologyDetail-202006-vFinal.pdf) ## Connect with Energy Web - - [Twitter](https://twitter.com/energywebx) - [Discord](https://discord.com/channels/706103009205288990/843970822254362664) - [Telegram](https://t.me/energyweb) ## License - This project is licensed under the GNU General Public License v3.0 or later - see the [LICENSE](LICENSE) file for details diff --git a/docs/api/classes/modules_cache_client.CacheClient.md b/docs/api/classes/modules_cache_client.CacheClient.md index df1d596a..7c699e4b 100644 --- a/docs/api/classes/modules_cache_client.CacheClient.md +++ b/docs/api/classes/modules_cache_client.CacheClient.md @@ -51,12 +51,15 @@ - [getRoleDefinition](modules_cache_client.CacheClient.md#getroledefinition) - [getRolesByOwner](modules_cache_client.CacheClient.md#getrolesbyowner) - [getRolesDefinition](modules_cache_client.CacheClient.md#getrolesdefinition) +- [getStatusListCredential](modules_cache_client.CacheClient.md#getstatuslistcredential) - [getSubOrganizationsByOrganization](modules_cache_client.CacheClient.md#getsuborganizationsbyorganization) - [handleError](modules_cache_client.CacheClient.md#handleerror) - [init](modules_cache_client.CacheClient.md#init) +- [initiateCredentialStatusUpdate](modules_cache_client.CacheClient.md#initiatecredentialstatusupdate) - [isAuthEnabled](modules_cache_client.CacheClient.md#isauthenabled) - [issueClaim](modules_cache_client.CacheClient.md#issueclaim) - [login](modules_cache_client.CacheClient.md#login) +- [persistCredentialStatusUpdate](modules_cache_client.CacheClient.md#persistcredentialstatusupdate) - [rejectClaim](modules_cache_client.CacheClient.md#rejectclaim) - [requestClaim](modules_cache_client.CacheClient.md#requestclaim) @@ -671,6 +674,26 @@ ___ ___ +### getStatusListCredential + +▸ **getStatusListCredential**(`credential`): `Promise`<``null`` \| [`StatusList2021Credential`](../modules/modules_verifiable_credentials.md#statuslist2021credential)\> + +Fetch the StatusList2021Credential object from storage. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `credential` | `VerifiableCredential`<[`RoleCredentialSubject`](../interfaces/modules_verifiable_credentials.RoleCredentialSubject.md)\> | verifiable credential with status list 2021 | + +#### Returns + +`Promise`<``null`` \| [`StatusList2021Credential`](../modules/modules_verifiable_credentials.md#statuslist2021credential)\> + +status list credential if found + +___ + ### getSubOrganizationsByOrganization ▸ **getSubOrganizationsByOrganization**(`namespace`): `Promise`<[`IOrganization`](../interfaces/modules_domains.IOrganization.md)[]\> @@ -721,6 +744,26 @@ ___ ___ +### initiateCredentialStatusUpdate + +▸ **initiateCredentialStatusUpdate**(`verifiableCredential`): `Promise`<[`StatusList2021UnsignedCredential`](../interfaces/modules_verifiable_credentials.StatusList2021UnsignedCredential.md)\> + +Get the StatusList2021Credential object to be signed + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `verifiableCredential` | `VerifiableCredential`<[`RoleCredentialSubject`](../interfaces/modules_verifiable_credentials.RoleCredentialSubject.md)\> | verifiable credential to be revoked | + +#### Returns + +`Promise`<[`StatusList2021UnsignedCredential`](../interfaces/modules_verifiable_credentials.StatusList2021UnsignedCredential.md)\> + +unsigned status list credential + +___ + ### isAuthEnabled ▸ **isAuthEnabled**(): `boolean` @@ -773,6 +816,26 @@ https://energyweb.atlassian.net/wiki/spaces/MYEN/pages/2303295607/ICL-+ICS+Auth+ ___ +### persistCredentialStatusUpdate + +▸ **persistCredentialStatusUpdate**(`statusListCredential`): `Promise`<[`StatusList2021Credential`](../modules/modules_verifiable_credentials.md#statuslist2021credential)\> + +Persist signed StatusList2021Credential object in storage. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `statusListCredential` | [`StatusList2021Credential`](../modules/modules_verifiable_credentials.md#statuslist2021credential) | signed status list | + +#### Returns + +`Promise`<[`StatusList2021Credential`](../modules/modules_verifiable_credentials.md#statuslist2021credential)\> + +status list credential + +___ + ### rejectClaim ▸ **rejectClaim**(`issuer`, `message`): `Promise`<`void`\> diff --git a/docs/api/classes/modules_verifiable_credentials.VerifiableCredentialsServiceBase.md b/docs/api/classes/modules_verifiable_credentials.VerifiableCredentialsServiceBase.md index c94e99cd..99b208ae 100644 --- a/docs/api/classes/modules_verifiable_credentials.VerifiableCredentialsServiceBase.md +++ b/docs/api/classes/modules_verifiable_credentials.VerifiableCredentialsServiceBase.md @@ -25,6 +25,9 @@ verifiableCredentialsService.createRoleVC(...); - [createVerifiablePresentation](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md#createverifiablepresentation) - [getCredentialsByDefinition](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md#getcredentialsbydefinition) - [initiateExchange](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md#initiateexchange) +- [isRevoked](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md#isrevoked) +- [revocationDetails](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md#revocationdetails) +- [revokeCredential](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md#revokecredential) - [verify](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md#verify) - [create](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md#create) @@ -69,11 +72,21 @@ ___ Create a credential with given parameters. +```typescript +await verifiableCredentialsService.createCredential({ + id: 'did:ethr:ewc:0x...00', + namespace: 'root.energyweb.iam.ewc', + version: '1', + issuerFields: [], + expirationDate: new Date(), +}); +``` + #### Parameters | Name | Type | Description | | :------ | :------ | :------ | -| `params` | [`RoleCredentialSubjectParams`](../interfaces/modules_verifiable_credentials.RoleCredentialSubjectParams.md) | verifiable presentation or credential | +| `params` | [`RoleCredentialSubjectParams`](../interfaces/modules_verifiable_credentials.RoleCredentialSubjectParams.md) | verifiable credential parameters | #### Returns @@ -154,7 +167,11 @@ ___ ▸ **getCredentialsByDefinition**(`presentationDefinition`): `Promise`<`SelectResults`\> -Returns issued role verifiable credentials which matches definition +Returns issued role verifiable credentials which matches definition. + +```typescript +await verifiableCredentialsService.getCredentialsByDefinition(presentationDefinition); +``` #### Parameters @@ -197,6 +214,78 @@ credentials query with matching verifiable presentations ___ +### isRevoked + +▸ **isRevoked**(`credential`): `Promise`<`boolean`\> + +Check if given verifiable credential is revoked. + +```typescript +await verifiableCredentialsService.isRevoked(credential); +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `credential` | `VerifiableCredential`<[`RoleCredentialSubject`](../interfaces/modules_verifiable_credentials.RoleCredentialSubject.md)\> | verifiable credential | + +#### Returns + +`Promise`<`boolean`\> + +true if credential is revoked + +___ + +### revocationDetails + +▸ **revocationDetails**(`credential`): `Promise`<``null`` \| [`CredentialRevocationDetailsResult`](../interfaces/modules_verifiable_credentials.CredentialRevocationDetailsResult.md)\> + +Get the credentials revocation details. + +```typescript +await verifiableCredentialsService.revocationDetails(credential); +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `credential` | `VerifiableCredential`<[`RoleCredentialSubject`](../interfaces/modules_verifiable_credentials.RoleCredentialSubject.md)\> | verifiable credential | + +#### Returns + +`Promise`<``null`` \| [`CredentialRevocationDetailsResult`](../interfaces/modules_verifiable_credentials.CredentialRevocationDetailsResult.md)\> + +revoker and revocationTimeStamp for the revocation + +___ + +### revokeCredential + +▸ **revokeCredential**(`credential`): `Promise`<[`StatusList2021Credential`](../modules/modules_verifiable_credentials.md#statuslist2021credential)\> + +Revoke given verifiable credential with StatusList2021. + +```typescript +await verifiableCredentialsService.revokeCredential(credential); +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `credential` | `VerifiableCredential`<[`RoleCredentialSubject`](../interfaces/modules_verifiable_credentials.RoleCredentialSubject.md)\> | verifiable credential | + +#### Returns + +`Promise`<[`StatusList2021Credential`](../modules/modules_verifiable_credentials.md#statuslist2021credential)\> + +StatusList2021Credential + +___ + ### verify ▸ **verify**<`T`\>(`vp`, `options?`): `Promise`<`boolean`\> @@ -231,14 +320,14 @@ ___ ### create -▸ `Static` **create**(`signerService`, `claimsService`): `Promise`<[`VerifiableCredentialsServiceBase`](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md)\> +▸ `Static` **create**(`signerService`, `cacheClient`): `Promise`<[`VerifiableCredentialsServiceBase`](modules_verifiable_credentials.VerifiableCredentialsServiceBase.md)\> #### Parameters | Name | Type | | :------ | :------ | | `signerService` | [`SignerService`](modules_signer.SignerService.md) | -| `claimsService` | [`CacheClient`](modules_cache_client.CacheClient.md) | +| `cacheClient` | [`CacheClient`](modules_cache_client.CacheClient.md) | #### Returns diff --git a/docs/api/interfaces/modules_verifiable_credentials.CredentialRevocationDetailsResult.md b/docs/api/interfaces/modules_verifiable_credentials.CredentialRevocationDetailsResult.md new file mode 100644 index 00000000..256c0a56 --- /dev/null +++ b/docs/api/interfaces/modules_verifiable_credentials.CredentialRevocationDetailsResult.md @@ -0,0 +1,22 @@ +# Interface: CredentialRevocationDetailsResult + +[modules/verifiable-credentials](../modules/modules_verifiable_credentials.md).CredentialRevocationDetailsResult + +## Table of contents + +### Properties + +- [revoker](modules_verifiable_credentials.CredentialRevocationDetailsResult.md#revoker) +- [timestamp](modules_verifiable_credentials.CredentialRevocationDetailsResult.md#timestamp) + +## Properties + +### revoker + +• **revoker**: `string` + +___ + +### timestamp + +• **timestamp**: `number` diff --git a/docs/api/interfaces/modules_verifiable_credentials.StatusList2021CredentialSubject.md b/docs/api/interfaces/modules_verifiable_credentials.StatusList2021CredentialSubject.md new file mode 100644 index 00000000..8c108e51 --- /dev/null +++ b/docs/api/interfaces/modules_verifiable_credentials.StatusList2021CredentialSubject.md @@ -0,0 +1,40 @@ +# Interface: StatusList2021CredentialSubject + +[modules/verifiable-credentials](../modules/modules_verifiable_credentials.md).StatusList2021CredentialSubject + +## Indexable + +▪ [x: `string`]: `unknown` + +## Table of contents + +### Properties + +- [encodedList](modules_verifiable_credentials.StatusList2021CredentialSubject.md#encodedlist) +- [id](modules_verifiable_credentials.StatusList2021CredentialSubject.md#id) +- [statusPurpose](modules_verifiable_credentials.StatusList2021CredentialSubject.md#statuspurpose) +- [type](modules_verifiable_credentials.StatusList2021CredentialSubject.md#type) + +## Properties + +### encodedList + +• **encodedList**: `string` + +___ + +### id + +• **id**: `string` + +___ + +### statusPurpose + +• **statusPurpose**: `string` + +___ + +### type + +• **type**: `string` diff --git a/docs/api/interfaces/modules_verifiable_credentials.StatusList2021UnsignedCredential.md b/docs/api/interfaces/modules_verifiable_credentials.StatusList2021UnsignedCredential.md new file mode 100644 index 00000000..fb6c13e7 --- /dev/null +++ b/docs/api/interfaces/modules_verifiable_credentials.StatusList2021UnsignedCredential.md @@ -0,0 +1,59 @@ +# Interface: StatusList2021UnsignedCredential + +[modules/verifiable-credentials](../modules/modules_verifiable_credentials.md).StatusList2021UnsignedCredential + +## Table of contents + +### Properties + +- [@context](modules_verifiable_credentials.StatusList2021UnsignedCredential.md#@context) +- [credentialSubject](modules_verifiable_credentials.StatusList2021UnsignedCredential.md#credentialsubject) +- [id](modules_verifiable_credentials.StatusList2021UnsignedCredential.md#id) +- [issuanceDate](modules_verifiable_credentials.StatusList2021UnsignedCredential.md#issuancedate) +- [issuer](modules_verifiable_credentials.StatusList2021UnsignedCredential.md#issuer) +- [type](modules_verifiable_credentials.StatusList2021UnsignedCredential.md#type) + +## Properties + +### @context + +• **@context**: (`string` \| `Record`<`string`, `unknown`\>)[] + +___ + +### credentialSubject + +• **credentialSubject**: `Object` + +#### Type declaration + +| Name | Type | +| :------ | :------ | +| `encodedList` | `string` | +| `id` | `string` | +| `statusPurpose` | `string` | +| `type` | `string` | + +___ + +### id + +• **id**: `string` + +___ + +### issuanceDate + +• **issuanceDate**: `string` + +___ + +### issuer + +• **issuer**: `string` + +___ + +### type + +• **type**: `string`[] diff --git a/docs/api/modules/modules_verifiable_credentials.md b/docs/api/modules/modules_verifiable_credentials.md index 75a22f86..6649c052 100644 --- a/docs/api/modules/modules_verifiable_credentials.md +++ b/docs/api/modules/modules_verifiable_credentials.md @@ -9,15 +9,23 @@ ### Interfaces - [CreatePresentationParams](../interfaces/modules_verifiable_credentials.CreatePresentationParams.md) +- [CredentialRevocationDetailsResult](../interfaces/modules_verifiable_credentials.CredentialRevocationDetailsResult.md) - [InitiateExchangeResults](../interfaces/modules_verifiable_credentials.InitiateExchangeResults.md) - [IssuerFields](../interfaces/modules_verifiable_credentials.IssuerFields.md) - [ProofOptions](../interfaces/modules_verifiable_credentials.ProofOptions.md) - [RoleCredentialSubject](../interfaces/modules_verifiable_credentials.RoleCredentialSubject.md) - [RoleCredentialSubjectParams](../interfaces/modules_verifiable_credentials.RoleCredentialSubjectParams.md) +- [StatusList2021CredentialSubject](../interfaces/modules_verifiable_credentials.StatusList2021CredentialSubject.md) +- [StatusList2021UnsignedCredential](../interfaces/modules_verifiable_credentials.StatusList2021UnsignedCredential.md) - [VerifyVerifiableCredentialResults](../interfaces/modules_verifiable_credentials.VerifyVerifiableCredentialResults.md) +### Type aliases + +- [StatusList2021Credential](modules_verifiable_credentials.md#statuslist2021credential) + ### Variables +- [statusList2021CredentialEIP712Types](modules_verifiable_credentials.md#statuslist2021credentialeip712types) - [verifiableCredentialEIP712Types](modules_verifiable_credentials.md#verifiablecredentialeip712types) - [verifiablePresentationEIP712Types](modules_verifiable_credentials.md#verifiablepresentationeip712types) - [verifiablePresentationWithCredentialEIP712Types](modules_verifiable_credentials.md#verifiablepresentationwithcredentialeip712types) @@ -27,8 +35,29 @@ - [getVerifiableCredentialsService](modules_verifiable_credentials.md#getverifiablecredentialsservice) - [isRoleCredential](modules_verifiable_credentials.md#isrolecredential) +## Type aliases + +### StatusList2021Credential + +Ƭ **StatusList2021Credential**: `VerifiableCredential`<[`StatusList2021CredentialSubject`](../interfaces/modules_verifiable_credentials.StatusList2021CredentialSubject.md)\> + ## Variables +### statusList2021CredentialEIP712Types + +• `Const` **statusList2021CredentialEIP712Types**: `Object` + +#### Type declaration + +| Name | Type | +| :------ | :------ | +| `EIP712Domain` | `never`[] | +| `Proof` | { `name`: `string` = '@context'; `type`: `string` = 'string' }[] | +| `StatusList2021` | { `name`: `string` = 'id'; `type`: `string` = 'string' }[] | +| `VerifiableCredential` | { `name`: `string` = '@context'; `type`: `string` = 'string[]' }[] | + +___ + ### verifiableCredentialEIP712Types • `Const` **verifiableCredentialEIP712Types**: `Object` diff --git a/src/modules/cache-client/cache-client.service.ts b/src/modules/cache-client/cache-client.service.ts index 4f7326ec..3de7448b 100644 --- a/src/modules/cache-client/cache-client.service.ts +++ b/src/modules/cache-client/cache-client.service.ts @@ -5,6 +5,7 @@ import { IDIDDocument } from '@ew-did-registry/did-resolver-interface'; import { Credential, StatusList2021Entry, + VerifiableCredential, } from '@ew-did-registry/credentials-interface'; import { IApp, IOrganization, IRole } from '../domains/domains.types'; import { AssetHistory } from '../assets/assets.types'; @@ -31,7 +32,11 @@ import { } from './cache-client.types'; import { SearchType } from '.'; import { getLogger } from '../../config/logger.config'; -import { RoleCredentialSubject } from '../verifiable-credentials'; +import { + RoleCredentialSubject, + StatusList2021Credential, + StatusList2021UnsignedCredential, +} from '../verifiable-credentials'; export class CacheClient implements ICacheClient { public pubKeyAndIdentityToken: IPubKeyAndIdentityToken | undefined; @@ -419,7 +424,7 @@ export class CacheClient implements ICacheClient { * Sets location of the credential status * * @param credential unsigned credential - * @returns credential with reference to status location + * @return credential with reference to status location */ async addStatusToCredential( credential: Credential @@ -432,10 +437,71 @@ export class CacheClient implements ICacheClient { Credential & { credentialStatus: StatusList2021Entry; } - >('/status-list/entries', credential); + >('/status-list/entries', { options: {}, credential }); + return data; + } + + /** + * Get the StatusList2021Credential object to be signed + * + * @param verifiableCredential verifiable credential to be revoked + * @return unsigned status list credential + */ + async initiateCredentialStatusUpdate( + verifiableCredential: VerifiableCredential + ): Promise { + const { data } = + await this._httpClient.post( + '/status-list/credentials/status/initiate', + { + options: {}, + verifiableCredential, + } + ); return data; } + /** + * Persist signed StatusList2021Credential object in storage. + * + * @param statusListCredential signed status list + * @return status list credential + */ + async persistCredentialStatusUpdate( + statusListCredential: StatusList2021Credential + ): Promise { + const { data } = await this._httpClient.post( + '/status-list/credentials/status/finalize', + { + options: {}, + statusListCredential, + } + ); + return data; + } + + /** + * Fetch the StatusList2021Credential object from storage. + * + * @param credential verifiable credential with status list 2021 + * @return status list credential if found + */ + async getStatusListCredential( + credential: VerifiableCredential + ): Promise { + if (!credential.credentialStatus?.statusListCredential) { + throw new Error( + 'Missing statusListCredential property in given credential status' + ); + } + + const response = await axios.get( + credential.credentialStatus?.statusListCredential + ); + + return response.status === 200 ? response.data : null; + } + private async refreshToken(): Promise { getLogger().debug('[CACHE CLIENT] Refreshing token'); if (!this.refresh_token) return undefined; diff --git a/src/modules/claims/claims.service.ts b/src/modules/claims/claims.service.ts index 96477244..2217e071 100644 --- a/src/modules/claims/claims.service.ts +++ b/src/modules/claims/claims.service.ts @@ -626,7 +626,7 @@ export class ClaimsService { if (!service) return v4(); - if (claimData.profile && id) { + if (claimData.profile && service.id) { return service.id; } diff --git a/src/modules/signer/signer.service.ts b/src/modules/signer/signer.service.ts index 13820f9e..2f030e04 100644 --- a/src/modules/signer/signer.service.ts +++ b/src/modules/signer/signer.service.ts @@ -259,10 +259,9 @@ export class SignerService { * @return DID address */ get didHex() { - const chainBn = BigNumber.from(this.chainId); - return `did:${ - Methods.Erc1056 - }:${chainBn.toHexString()}:${this._address.toLowerCase()}`; + return `did:${Methods.Erc1056}:${`0x${this.chainId.toString( + 16 + )}`}:${this._address.toLowerCase()}`; } /** diff --git a/src/modules/verifiable-credentials/types/index.ts b/src/modules/verifiable-credentials/types/index.ts index b9b34788..94e907ca 100644 --- a/src/modules/verifiable-credentials/types/index.ts +++ b/src/modules/verifiable-credentials/types/index.ts @@ -1,3 +1,4 @@ export * from './eip712.types'; export * from './role-credential.types'; export * from './verifiable-credentials.types'; +export * from './status-list.types'; diff --git a/src/modules/verifiable-credentials/types/status-list.types.ts b/src/modules/verifiable-credentials/types/status-list.types.ts new file mode 100644 index 00000000..bfb6b38e --- /dev/null +++ b/src/modules/verifiable-credentials/types/status-list.types.ts @@ -0,0 +1,60 @@ +import { VerifiableCredential } from '@ew-did-registry/credentials-interface'; + +export interface StatusList2021UnsignedCredential { + '@context': Array>; + id: string; + type: string[]; + issuer: string; + issuanceDate: string; + credentialSubject: { + id: string; + type: string; + statusPurpose: string; + encodedList: string; + }; +} + +export interface StatusList2021CredentialSubject { + [x: string]: unknown; + id: string; + type: string; + statusPurpose: string; + encodedList: string; +} + +export type StatusList2021Credential = + VerifiableCredential; + +export interface CredentialRevocationDetailsResult { + /* Revoker of the credential */ + revoker: string; + + /* Timestamp of revocation */ + timestamp: number; +} + +export const statusList2021CredentialEIP712Types = { + EIP712Domain: [], + VerifiableCredential: [ + { name: '@context', type: 'string[]' }, + { name: 'id', type: 'string' }, + { name: 'type', type: 'string[]' }, + { name: 'issuer', type: 'string' }, + { name: 'issuanceDate', type: 'string' }, + { name: 'credentialSubject', type: 'StatusList2021' }, + { name: 'proof', type: 'Proof' }, + ], + StatusList2021: [ + { name: 'id', type: 'string' }, + { name: 'type', type: 'string' }, + { name: 'statusPurpose', type: 'string' }, + { name: 'encodedList', type: 'string' }, + ], + Proof: [ + { name: '@context', type: 'string' }, + { name: 'verificationMethod', type: 'string' }, + { name: 'created', type: 'string' }, + { name: 'proofPurpose', type: 'string' }, + { name: 'type', type: 'string' }, + ], +}; diff --git a/src/modules/verifiable-credentials/verifiable-credentials-base.service.ts b/src/modules/verifiable-credentials/verifiable-credentials-base.service.ts index 2ba701e4..6857cdc5 100644 --- a/src/modules/verifiable-credentials/verifiable-credentials-base.service.ts +++ b/src/modules/verifiable-credentials/verifiable-credentials-base.service.ts @@ -29,9 +29,13 @@ import { CreatePresentationParams, verifiableCredentialEIP712Types, verifiablePresentationWithCredentialEIP712Types, + StatusList2021Credential, + statusList2021CredentialEIP712Types, + CredentialRevocationDetailsResult, } from './types'; import { ERROR_MESSAGES } from '../../errors'; import { CacheClient } from '../cache-client'; +import { KEY_TYPE } from './verifiable-credentials.const'; /** * Service responsible for managing verifiable credentials and presentations. @@ -132,7 +136,8 @@ export abstract class VerifiableCredentialsServiceBase { static async create( // eslint-disable-next-line @typescript-eslint/no-unused-vars signerService: SignerService, - claimsService: CacheClient + // eslint-disable-next-line @typescript-eslint/no-unused-vars + cacheClient: CacheClient ): Promise { throw new Error('Not implemented'); } @@ -271,18 +276,11 @@ export abstract class VerifiableCredentialsServiceBase { }, }; - const keyType = { - kty: 'EC', - crv: 'secp256k1', - alg: 'ES256K-R', - key_ops: ['signTypedData'], - }; - const stringifyCredential = JSON.stringify(credentialObject); const preparedVC = await this.prepareIssueCredential( stringifyCredential, JSON.stringify(proofOptionsObject), - JSON.stringify(keyType) + JSON.stringify(KEY_TYPE) ); const preparation = JSON.parse(preparedVC); @@ -506,7 +504,11 @@ export abstract class VerifiableCredentialsServiceBase { } /** - * Returns issued role verifiable credentials which matches definition + * Returns issued role verifiable credentials which matches definition. + * + * ```typescript + * await verifiableCredentialsService.getCredentialsByDefinition(presentationDefinition); + * ``` * * @param presentationDefinition credential requirements * @returns results of matching each role verifiable credential to definition @@ -543,7 +545,17 @@ export abstract class VerifiableCredentialsServiceBase { /** * Create a credential with given parameters. * - * @param {RoleCredentialSubjectParams} params verifiable presentation or credential + * ```typescript + * await verifiableCredentialsService.createCredential({ + * id: 'did:ethr:ewc:0x...00', + * namespace: 'root.energyweb.iam.ewc', + * version: '1', + * issuerFields: [], + * expirationDate: new Date(), + * }); + * ``` + * + * @param {RoleCredentialSubjectParams} params verifiable credential parameters * @returns Energy Web credential */ public createCredential( @@ -577,6 +589,122 @@ export abstract class VerifiableCredentialsServiceBase { credential.expirationDate = params.expirationDate.toISOString(); } + if (params.credentialStatus) { + credential.credentialStatus = params.credentialStatus; + } + return credential; } + + /** + * Revoke given verifiable credential with StatusList2021. + * + * ```typescript + * await verifiableCredentialsService.revokeCredential(credential); + * ``` + * + * @param {VerifiableCredential} credential verifiable credential + * @return StatusList2021Credential + */ + public async revokeCredential( + credential: VerifiableCredential + ): Promise { + const statusListUnsignedCredential = + await this._cacheClient.initiateCredentialStatusUpdate(credential); + + const proofOptionsObject = { + verificationMethod: `${statusListUnsignedCredential.issuer}#controller`, + proofPurpose: 'assertionMethod', + eip712Domain: { + primaryType: 'VerifiableCredential', + domain: {}, + messageSchema: statusList2021CredentialEIP712Types, + }, + }; + + const stringifyCredential = JSON.stringify(statusListUnsignedCredential); + const preparedVC = await this.prepareIssueCredential( + stringifyCredential, + JSON.stringify(proofOptionsObject), + JSON.stringify(KEY_TYPE) + ); + const preparation = JSON.parse(preparedVC); + + const typedData = preparation.signingInput; + if (!typedData || !typedData.primaryType) { + throw new Error('Expected EIP-712 TypedData'); + } + + delete typedData.types['EIP712Domain']; + + const signature = await this._signerService.signTypedData( + typedData.domain, + typedData.types, + typedData.message + ); + + const signedCredential = await this.completeIssueCredential( + stringifyCredential, + preparedVC, + signature + ); + + const statusList2021Credential = JSON.parse( + signedCredential + ) as StatusList2021Credential; + + return await this._cacheClient.persistCredentialStatusUpdate( + statusList2021Credential + ); + } + + /** + * Check if given verifiable credential is revoked. + * + * ```typescript + * await verifiableCredentialsService.isRevoked(credential); + * ``` + * + * @param {VerifiableCredential} credential verifiable credential + * @return true if credential is revoked + */ + public async isRevoked( + credential: VerifiableCredential + ): Promise { + const revocationDetails = await this.revocationDetails(credential); + return !!revocationDetails; + } + + /** + * Get the credentials revocation details. + * + * ```typescript + * await verifiableCredentialsService.revocationDetails(credential); + * ``` + * + * @param {VerifiableCredential} credential verifiable credential + * @return revoker and revocationTimeStamp for the revocation + */ + public async revocationDetails( + credential: VerifiableCredential + ): Promise { + const statusListCredential = + await this._cacheClient.getStatusListCredential(credential); + + if (!statusListCredential) return null; + + try { + await this.verify(statusListCredential); + + return { + revoker: + typeof statusListCredential.issuer === 'string' + ? statusListCredential.issuer + : statusListCredential.issuer.id, + timestamp: new Date(statusListCredential.issuanceDate).getTime(), + }; + } catch { + return null; + } + } } diff --git a/src/modules/verifiable-credentials/verifiable-credentials.const.ts b/src/modules/verifiable-credentials/verifiable-credentials.const.ts new file mode 100644 index 00000000..9382d626 --- /dev/null +++ b/src/modules/verifiable-credentials/verifiable-credentials.const.ts @@ -0,0 +1,7 @@ +// JSON Web Key (https://datatracker.ietf.org/doc/html/rfc7517) for ethereum keys +export const KEY_TYPE = { + kty: 'EC', + crv: 'secp256k1', + alg: 'ES256K-R', + key_ops: ['signTypedData'], +};