diff --git a/packages/browser-wallet/CHANGELOG.md b/packages/browser-wallet/CHANGELOG.md index 702209d8..fb56793f 100644 --- a/packages/browser-wallet/CHANGELOG.md +++ b/packages/browser-wallet/CHANGELOG.md @@ -6,6 +6,7 @@ - Changed 'Zero Knowledge' to 'Zero-knowledge' in display texts. - An issue with images in verifiable credentials for lower resolutions. +- Schema validation of verifiable credentials with attributes that have `{ type: "integer" }` no longer rejects BigInt values. ## 1.1.6 diff --git a/packages/browser-wallet/src/background/web3Id.ts b/packages/browser-wallet/src/background/web3Id.ts index 086b5439..aea526b4 100644 --- a/packages/browser-wallet/src/background/web3Id.ts +++ b/packages/browser-wallet/src/background/web3Id.ts @@ -12,6 +12,8 @@ import { IdStatement, StatementTypes, AttributeType, + isTimestampAttribute, + TimestampAttribute, } from '@concordium/web-sdk'; import { sessionVerifiableCredentials, @@ -126,12 +128,21 @@ function rejectRequest(message: string): { run: false; response: MessageStatusWr }; } +// TODO Expose function from SDK and re-use. +function timestampToDate(attribute: TimestampAttribute): Date { + return new Date(Date.parse(attribute.timestamp)); +} + function validateTimestampAttribute(attributeTag: string, attributeValue: AttributeType) { - if ( - attributeValue instanceof Date && - (attributeValue.getTime() < MIN_DATE_TIMESTAMP || attributeValue.getTime() > MAX_DATE_TIMESTAMP) - ) { - return `The attribute [${attributeValue}] for key [${attributeTag}] is out of bounds for a Date. The Date must be between ${MIN_DATE_ISO} and ${MAX_DATE_ISO}`; + if (isTimestampAttribute(attributeValue)) { + const timestamp = timestampToDate(attributeValue).getTime(); + if (Number.isNaN(timestamp)) { + return `The attribute [${attributeValue.timestamp}] for key [${attributeTag}] cannot be parsed as a Date.`; + } + + if (timestamp < MIN_DATE_TIMESTAMP || timestamp > MAX_DATE_TIMESTAMP) { + return `The attribute [${attributeValue.timestamp}] for key [${attributeTag}] is out of bounds for a Date. The Date must be between ${MIN_DATE_ISO} and ${MAX_DATE_ISO}`; + } } return undefined; } @@ -154,6 +165,14 @@ function validateAttributeBounds( attributeTag: string, attributeValue: AttributeType ): { error: false } | { error: true; message: string } { + if ( + typeof attributeValue !== 'string' && + typeof attributeValue !== 'bigint' && + !isTimestampAttribute(attributeValue) + ) { + return { error: true, message: 'Unsupported attribute type' }; + } + const stringError = validateStringAttribute(attributeTag, attributeValue); if (stringError) { return { error: true, message: stringError }; diff --git a/packages/browser-wallet/src/popup/pages/AddWeb3IdCredential/AddWeb3IdCredential.tsx b/packages/browser-wallet/src/popup/pages/AddWeb3IdCredential/AddWeb3IdCredential.tsx index 23679e08..7797fbc3 100644 --- a/packages/browser-wallet/src/popup/pages/AddWeb3IdCredential/AddWeb3IdCredential.tsx +++ b/packages/browser-wallet/src/popup/pages/AddWeb3IdCredential/AddWeb3IdCredential.tsx @@ -17,6 +17,7 @@ import { useAsyncMemo } from 'wallet-common-helpers'; import { useHdWallet } from '@popup/shared/utils/account-helpers'; import { displayUrl } from '@popup/shared/utils/string-helpers'; import { + coerceBigIntIntegerToNumber, createCredentialId, createPublicKeyIdentifier, fetchCredentialMetadata, @@ -123,7 +124,8 @@ export default function AddWeb3IdCredential({ onAllow, onReject }: Props) { const schemaWithNoId = withIdRemovedFromSchema(schema); const validationResult = validator.validate( { credentialSubject: credential.credentialSubject }, - schemaWithNoId as unknown as Schema + schemaWithNoId as unknown as Schema, + { preValidateProperty: coerceBigIntIntegerToNumber } ); if (!validationResult.valid) { setError(t('error.schemaValidation', { errors: validationResult.errors.toString() })); diff --git a/packages/browser-wallet/src/shared/utils/verifiable-credential-helpers.ts b/packages/browser-wallet/src/shared/utils/verifiable-credential-helpers.ts index 571129b2..0a64f294 100644 --- a/packages/browser-wallet/src/shared/utils/verifiable-credential-helpers.ts +++ b/packages/browser-wallet/src/shared/utils/verifiable-credential-helpers.ts @@ -15,7 +15,7 @@ import { VerifiableCredentialStatus, } from '@shared/storage/types'; import { Buffer } from 'buffer/'; -import jsonschema from 'jsonschema'; +import jsonschema, { Schema } from 'jsonschema'; import { applyExecutionNRGBuffer, getContractName } from './contract-helpers'; import { getNet } from './network-helpers'; import { logError } from './log-helpers'; @@ -1236,3 +1236,21 @@ export function withIdRemovedFromSchema(schema: VerifiableCredentialSchema) { return { ...schema, properties: schemaOuterPropertiesWithNoId }; } + +/** + * BigInts do not validate against a json schema as being an integer. This pre validate function + * coerces any bigint values where { type: "integer" } into number, as that will ensure that + * they validate against the schema. + */ +export function coerceBigIntIntegerToNumber( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + instance: any, + key: string, + schema: Schema +) { + const value = instance[key]; + if (schema.type && schema.type === 'integer' && typeof value === 'bigint') { + // eslint-disable-next-line no-param-reassign + instance[key] = Number(value); + } +} diff --git a/packages/browser-wallet/test/verifiable-credential-helpers.test.ts b/packages/browser-wallet/test/verifiable-credential-helpers.test.ts index 5a8cc243..7e0bd9ec 100644 --- a/packages/browser-wallet/test/verifiable-credential-helpers.test.ts +++ b/packages/browser-wallet/test/verifiable-credential-helpers.test.ts @@ -1,3 +1,4 @@ +import { Schema, Validator } from 'jsonschema'; import { RevocationDataHolder, RevokeCredentialHolderParam, @@ -13,6 +14,7 @@ import { getCredentialIdFromSubjectDID, getContractAddressFromIssuerDID, getVerifiableCredentialPublicKeyfromSubjectDID, + coerceBigIntIntegerToNumber, } from '../src/shared/utils/verifiable-credential-helpers'; import { mainnet, testnet } from '../src/shared/constants/networkConfiguration'; @@ -184,3 +186,21 @@ test('getCredentialIdFromSubjectDID extracts credId without network', () => { 'aad98095db73b5b22f7f64823a495c6c57413947353646313dc453fa4604715d2f93b2c1f8cb4c9625edd6330e1d27fa' ); }); + +test('bigint with type integer validates against schema when coercing in pre validate', () => { + const validator = new Validator(); + const schema: Schema = { + type: 'object', + properties: { + age: { type: 'integer' }, + }, + }; + + const p = { + age: BigInt(97), + }; + + const validationResult = validator.validate(p, schema, { preValidateProperty: coerceBigIntIntegerToNumber }); + + expect(validationResult.valid).toBeTruthy(); +});