diff --git a/package-lock.json b/package-lock.json index b680742..3f80ffa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@tradetrust-tt/token-registry-v4": "npm:@tradetrust-tt/token-registry@^4.16.0", "@tradetrust-tt/token-registry-v5": "npm:@tradetrust-tt/token-registry@^5.3.0", "@tradetrust-tt/tradetrust": "^6.10.1", - "@tradetrust-tt/tradetrust-utils": "^2.3.1", + "@tradetrust-tt/tradetrust-utils": "^2.3.2", "@tradetrust-tt/tt-verify": "^9.4.0", "@trustvc/w3c-context": "^1.2.13", "@trustvc/w3c-credential-status": "^1.2.12", @@ -5188,9 +5188,9 @@ } }, "node_modules/@tradetrust-tt/tradetrust-utils": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@tradetrust-tt/tradetrust-utils/-/tradetrust-utils-2.3.1.tgz", - "integrity": "sha512-ts0nyqEN62qpUXXSZ59nYBqajcKzMYKOi5cBpwlTYbOS4cRi6PcVZmM273xLmf2XLwNb8BA6agLpAx3ll5IWZw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@tradetrust-tt/tradetrust-utils/-/tradetrust-utils-2.3.2.tgz", + "integrity": "sha512-T3rikZ3v0qqB3xzmq+KM30OEziyesHgN0CRQrhEvLlQw5IULtmcuwLZGHLE8KyaI8Zd7mqbuubJlfyEMNXJZuA==", "dependencies": { "@tradetrust-tt/tt-verify": "^9.4.0", "dotenv": "^16.4.5", diff --git a/package.json b/package.json index 29b336a..fba04a7 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "@tradetrust-tt/token-registry-v4": "npm:@tradetrust-tt/token-registry@^4.16.0", "@tradetrust-tt/token-registry-v5": "npm:@tradetrust-tt/token-registry@^5.3.0", "@tradetrust-tt/tradetrust": "^6.10.1", - "@tradetrust-tt/tradetrust-utils": "^2.3.1", + "@tradetrust-tt/tradetrust-utils": "^2.3.2", "@tradetrust-tt/tt-verify": "^9.4.0", "@trustvc/w3c-context": "^1.2.13", "@trustvc/w3c-credential-status": "^1.2.12", diff --git a/src/__tests__/core/verify.test.ts b/src/__tests__/core/verify.test.ts index 88cda4c..6fb349b 100644 --- a/src/__tests__/core/verify.test.ts +++ b/src/__tests__/core/verify.test.ts @@ -8,6 +8,7 @@ import { WRAPPED_DOCUMENT_DID_TOKEN_REGISTRY_V3, WRAPPED_DOCUMENT_DNS_TXT_V2, } from '../fixtures/fixtures'; +import { W3CCredentialStatusCode } from 'src/verify/fragments/document-status/w3cCredentialStatus'; const providerUrl = 'https://rpc-amoy.polygon.technology'; @@ -98,7 +99,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'W3CSignatureIntegrity', reason: { - code: 0, + code: W3CCredentialStatusCode.SKIPPED, codeString: 'SKIPPED', message: "Document either has no proof or proof.type is not 'BbsBlsSignature2020'.", }, @@ -120,7 +121,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'W3CIssuerIdentity', reason: { - code: 0, + code: W3CCredentialStatusCode.SKIPPED, codeString: 'SKIPPED', message: 'Document has no issuer field.', }, @@ -188,7 +189,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'W3CCredentialStatus', reason: { - code: 0, + code: W3CCredentialStatusCode.SKIPPED, codeString: 'SKIPPED', message: 'Document does not have a valid credentialStatus or type.', }, @@ -237,12 +238,13 @@ describe.concurrent('W3C verify', () => { statusListIndex: '131072', }, }; - expect(await verifyDocument(tampered)).toEqual( expect.arrayContaining([ expect.objectContaining({ name: 'W3CCredentialStatus', reason: { + code: W3CCredentialStatusCode.UNEXPECTED_ERROR, + codeString: 'ERROR', message: 'Invalid statusListIndex: Index out of range: min=0, max=131071', }, status: 'ERROR', @@ -328,7 +330,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'TransferableRecords', reason: { - code: 9, + code: W3CCredentialStatusCode.UNRECOGNIZED_DOCUMENT, codeString: 'UNRECOGNIZED_DOCUMENT', message: "Document's credentialStatus does not have tokenRegistry", }, @@ -358,7 +360,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'TransferableRecords', reason: { - code: 9, + code: W3CCredentialStatusCode.UNRECOGNIZED_DOCUMENT, codeString: 'UNRECOGNIZED_DOCUMENT', message: "Document's credentialStatus does not have tokenNetwork.chainId", }, @@ -384,7 +386,7 @@ describe.concurrent('W3C verify', () => { expect.objectContaining({ name: 'TransferableRecords', reason: { - code: 1, + code: W3CCredentialStatusCode.DOCUMENT_NOT_ISSUED, codeString: 'DOCUMENT_NOT_MINTED', message: 'Document has not been issued under token registry', }, diff --git a/src/utils/fragment/index.ts b/src/utils/fragment/index.ts index ed30ae7..0d972bb 100644 --- a/src/utils/fragment/index.ts +++ b/src/utils/fragment/index.ts @@ -1 +1,54 @@ -export { errorMessageHandling, interpretFragments } from '@tradetrust-tt/tradetrust-utils'; +import { + errorMessageHandling as OAErrorMessageHandling, + CONSTANTS, + interpretFragments, +} from '@tradetrust-tt/tradetrust-utils'; +import { InvalidVerificationFragment, utils, VerificationFragment } from '@tradetrust-tt/tt-verify'; +import { W3CCredentialStatusCode } from '../../verify/fragments/document-status/w3cCredentialStatus'; +import { CredentialStatusResult } from '@trustvc/w3c-vc'; + +const getW3CCredentialStatusFragment = + utils.getFragmentByName>( + 'W3CCredentialStatus', + ); + +const w3cCredentialStatusRevoked = (fragments: VerificationFragment[]): boolean => { + const issuedFragment = getW3CCredentialStatusFragment(fragments); + return ( + issuedFragment?.reason?.code === W3CCredentialStatusCode.DOCUMENT_REVOKED || + issuedFragment?.reason?.code === W3CCredentialStatusCode.DOCUMENT_REVOKED_AND_SUSPENDED + ); +}; + +const w3cCredentialStatusSuspended = (fragments: VerificationFragment[]): boolean => { + const issuedFragment = getW3CCredentialStatusFragment(fragments); + //checking for both revoked and suspended + return ( + issuedFragment?.reason?.code === W3CCredentialStatusCode.DOCUMENT_SUSPENDED || + issuedFragment?.reason?.code === W3CCredentialStatusCode.DOCUMENT_REVOKED_AND_SUSPENDED + ); +}; + +const errorMessageHandling = (fragments: VerificationFragment[]): string[] => { + const errors = []; + const isW3cFragments = fragments.some( + (f) => f.name.startsWith('W3C') || f.name === 'TransferableRecords', + ); + if (isW3cFragments) { + if (w3cCredentialStatusRevoked(fragments)) { + errors.push(CONSTANTS.TYPES.REVOKED); + } + if (w3cCredentialStatusSuspended(fragments)) { + errors.push(CONSTANTS.TYPES.SUSPENDED); + } + + return errors; + } else return OAErrorMessageHandling(fragments); +}; + +export { + interpretFragments, + errorMessageHandling, + w3cCredentialStatusRevoked, + w3cCredentialStatusSuspended, +}; diff --git a/src/verify/fragments/document-status/w3cCredentialStatus.ts b/src/verify/fragments/document-status/w3cCredentialStatus.ts index 7fc72eb..69eace2 100644 --- a/src/verify/fragments/document-status/w3cCredentialStatus.ts +++ b/src/verify/fragments/document-status/w3cCredentialStatus.ts @@ -5,14 +5,28 @@ import { } from '@trustvc/w3c-credential-status'; import { CredentialStatus, isSignedDocument, verifyCredentialStatus } from '@trustvc/w3c-vc'; import { SignedVerifiableCredential } from '../../..'; - +//w3cCredentialStatus enums +export enum W3CCredentialStatusCode { + SKIPPED = 0, + DOCUMENT_NOT_ISSUED = 1, + DOCUMENT_REVOKED = 11, + DOCUMENT_SUSPENDED = 12, + DOCUMENT_REVOKED_AND_SUSPENDED = 101, + STATUS_LIST_NOT_FOUND = 404, + INVALID_VALIDATION_METHOD = 8, + UNRECOGNIZED_DOCUMENT = 9, + SERVER_ERROR = 500, + UNEXPECTED_ERROR = 4, + INVALID_ARGUMENT = 6, + INVALID_ISSUERS = 7, +} export const w3cCredentialStatus: Verifier = { skip: async () => { return { type: 'DOCUMENT_STATUS', name: 'W3CCredentialStatus', reason: { - code: 0, + code: W3CCredentialStatusCode.SKIPPED, codeString: 'SKIPPED', message: `Document does not have a valid credentialStatus or type.`, }, @@ -47,12 +61,18 @@ export const w3cCredentialStatus: Verifier = { verifyCredentialStatus(cs, cs?.type as CredentialStatusType, verifierOptions), ), ); + const purposes = verificationResult.map((item) => item.purpose); + const hasRevocation = purposes.includes('revocation'); + const hasSuspension = purposes.includes('suspension'); + const hasRevocationAndSuspension = hasRevocation && hasSuspension; if (verificationResult.some((r) => r.error)) { return { type: 'DOCUMENT_STATUS', name: 'W3CCredentialStatus', reason: { + code: W3CCredentialStatusCode.UNEXPECTED_ERROR, + codeString: 'ERROR', message: verificationResult.map((r) => r.error).join(', '), }, data: verificationResult, @@ -71,6 +91,23 @@ export const w3cCredentialStatus: Verifier = { name: 'W3CCredentialStatus', data: verificationResult, status: 'INVALID', + reason: { + code: hasRevocationAndSuspension + ? W3CCredentialStatusCode.DOCUMENT_REVOKED_AND_SUSPENDED + : hasRevocation + ? W3CCredentialStatusCode.DOCUMENT_REVOKED + : W3CCredentialStatusCode.DOCUMENT_SUSPENDED, + codeString: hasRevocationAndSuspension + ? 'REVOKED_AND_SUSPENDED' + : hasRevocation + ? 'REVOKED' + : 'SUSPENDED', + message: hasRevocationAndSuspension + ? 'Document has been revoked and suspended.' + : hasRevocation + ? 'Document has been revoked.' + : 'Document has been suspended.', + }, }; } },