Skip to content

Commit

Permalink
Merge pull request #343 from Concordium/display-issuer-metadata
Browse files Browse the repository at this point in the history
Display issuer metadata in credential details page
  • Loading branch information
orhoj authored Aug 18, 2023
2 parents 70fc7a3 + 407a301 commit ff5deee
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
}
}

.issuer-logo {
width: rem(28px);
height: rem(28px);
}

.verifiable-credential {
color: $color-white;
border-radius: rem(16px);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,62 @@ import {
import { fetchContractName } from '@shared/utils/token-helpers';
import { TimeStampUnit, dateFromTimestamp, ClassName } from 'wallet-common-helpers';
import { withDateAndTime } from '@shared/utils/time-helpers';
import Img from '@popup/shared/Img';
import { accountRoutes } from '../Account/routes';
import { ConfirmGenericTransferState } from '../Account/ConfirmGenericTransfer';
import RevokeIcon from '../../../assets/svg/revoke.svg';
import { useCredentialEntry } from './VerifiableCredentialHooks';
import { useCredentialEntry, useIssuerMetadata } from './VerifiableCredentialHooks';
import { DisplayAttribute, VerifiableCredentialCard, VerifiableCredentialCardHeader } from './VerifiableCredentialCard';

/**
* Component for displaying issuer metadata, if any is available.
* All the fields in the issuer metadata are optional, and if none of
* them are provided, then the component returns null.
* @param issuer the did for the issuer
*/
function DisplayIssuerMetadata({ issuer }: { issuer: string }) {
const { t } = useTranslation('verifiableCredential');
const issuerMetadata = useIssuerMetadata(issuer);

if (
issuerMetadata === undefined ||
(issuerMetadata.description === undefined &&
issuerMetadata.icon === undefined &&
issuerMetadata.name === undefined &&
issuerMetadata.url === undefined)
) {
return null;
}

return (
<div className="verifiable-credential__body-attributes">
<h3>{t('details.issuer.title')}</h3>
{issuerMetadata.icon && <Img className="issuer-logo" src={issuerMetadata.icon.url} withDefaults />}
{issuerMetadata.name && (
<DisplayAttribute
attributeKey="issuerName"
attributeTitle={t('details.issuer.name')}
attributeValue={issuerMetadata.name}
/>
)}
{issuerMetadata.description && (
<DisplayAttribute
attributeKey="issuerName"
attributeTitle={t('details.issuer.description')}
attributeValue={issuerMetadata.description}
/>
)}
{issuerMetadata.url && (
<DisplayAttribute
attributeKey="issuerName"
attributeTitle={t('details.issuer.url')}
attributeValue={issuerMetadata.url}
/>
)}
</div>
);
}

/**
* Component for displaying the extra details about a verifiable credential, i.e. the
* credential holder id, when it is valid from and, if available, when it is valid until.
Expand All @@ -35,10 +85,12 @@ function VerifiableCredentialExtraDetails({
status,
metadata,
className,
issuer,
}: {
credentialEntry: CredentialQueryResponse;
status: VerifiableCredentialStatus;
metadata: VerifiableCredentialMetadata;
issuer: string;
} & ClassName) {
const { t } = useTranslation('verifiableCredential');

Expand Down Expand Up @@ -72,6 +124,7 @@ function VerifiableCredentialExtraDetails({
/>
)}
</div>
<DisplayIssuerMetadata issuer={issuer} />
</div>
</div>
);
Expand Down Expand Up @@ -194,6 +247,7 @@ export default function VerifiableCredentialDetails({
credentialEntry={credentialEntry}
status={status}
metadata={metadata}
issuer={credential.issuer}
/>
)}
{!showExtraDetails && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { grpcClientAtom } from '@popup/store/settings';
import { VerifiableCredential, VerifiableCredentialStatus, VerifiableCredentialSchema } from '@shared/storage/types';
import {
CredentialQueryResponse,
IssuerMetadata,
VerifiableCredentialMetadata,
fetchIssuerMetadata,
getCredentialHolderId,
getCredentialRegistryContractAddress,
getCredentialRegistryMetadata,
getVerifiableCredentialEntry,
getVerifiableCredentialStatus,
} from '@shared/utils/verifiable-credential-helpers';
Expand Down Expand Up @@ -108,6 +111,28 @@ export function useCredentialMetadata(credential?: VerifiableCredential) {
return metadata;
}

/**
* Retrieves the issuer metadata JSON file. This is done by getting the credential
* registry metadata from the credential registry contract, and then fetching the
* issuer metadata JSON file at the extracted URL.
* @param issuer the issuer did
* @returns the issuer metadata for the provided issuer did
*/
export function useIssuerMetadata(issuer: string): IssuerMetadata | undefined {
const [issuerMetadata, setIssuerMetadata] = useState<IssuerMetadata>();
const client = useAtomValue(grpcClientAtom);

useEffect(() => {
const registryContractAddress = getCredentialRegistryContractAddress(issuer);
getCredentialRegistryMetadata(client, registryContractAddress).then((res) => {
const abortController = new AbortController();
fetchIssuerMetadata(res.issuerMetadata, abortController).then(setIssuerMetadata);
});
}, [client, issuer]);

return issuerMetadata;
}

/**
* Retrieves data and uses the provided data setter to update chrome.storage with the changes found.
* The dataFetcher is responsible for delivering the exact updated picture that should be set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const t: typeof en = {
id: 'Legitimationholders ID',
validFrom: 'Gyldig fra',
validUntil: 'Gyldig indtil',
issuer: {
title: 'Udstedt af',
name: 'Navn',
description: 'Beskrivelse',
url: 'Hjemmeside',
},
},
status: {
Active: 'Aktiv',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ const t = {
id: 'Credential holder ID',
validFrom: 'Valid from',
validUntil: 'Valid until',
issuer: {
title: 'Issued by',
name: 'Name',
description: 'Description',
url: 'Website',
},
},
status: {
Active: 'Active',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ function deserializeRegistryMetadata(serializedRegistryMetadata: string): Metada
export async function getCredentialRegistryMetadata(client: ConcordiumGRPCClient, contractAddress: ContractAddress) {
const instanceInfo = await client.getInstanceInfo(contractAddress);
if (instanceInfo === undefined) {
return undefined;
throw new Error('Given contract address was not a created instance');
}

const result = await client.invokeContract({
Expand Down Expand Up @@ -491,6 +491,47 @@ const verifiableCredentialMetadataSchema = {
},
};

const issuerMetadataSchema = {
$ref: '#/definitions/IssuerMetadata',
$schema: 'http://json-schema.org/draft-07/schema#',
definitions: {
HexString: {
type: 'string',
},
IssuerMetadata: {
additionalProperties: false,
properties: {
description: {
type: 'string',
},
icon: {
$ref: '#/definitions/MetadataUrl',
},
name: {
type: 'string',
},
url: {
type: 'string',
},
},
type: 'object',
},
MetadataUrl: {
additionalProperties: false,
properties: {
hash: {
$ref: '#/definitions/HexString',
},
url: {
type: 'string',
},
},
required: ['url'],
type: 'object',
},
},
};

export interface VerifiableCredentialMetadata {
title: string;
logo: MetadataUrl;
Expand Down Expand Up @@ -606,7 +647,10 @@ export async function getVerifiableCredentialEntry(
async function fetchDataFromUrl<T>(
{ url, hash }: MetadataUrl,
abortController: AbortController,
jsonSchema: typeof verifiableCredentialMetadataSchema | typeof verifiableCredentialSchemaSchema
jsonSchema:
| typeof verifiableCredentialMetadataSchema
| typeof verifiableCredentialSchemaSchema
| typeof issuerMetadataSchema
): Promise<T> {
const response = await fetch(url, {
headers: new Headers({ 'Access-Control-Allow-Origin': '*' }),
Expand All @@ -615,7 +659,7 @@ async function fetchDataFromUrl<T>(
});

if (!response.ok) {
throw new Error(`Failed to fetch the schema at: ${url}`);
throw new Error(`Failed to fetch the data at: ${url}`);
}

const body = Buffer.from(await response.arrayBuffer());
Expand Down Expand Up @@ -663,6 +707,23 @@ export async function fetchCredentialMetadata(
return fetchDataFromUrl(metadata, abortController, verifiableCredentialMetadataSchema);
}

export interface IssuerMetadata {
name?: string;
icon?: MetadataUrl;
description?: string;
url?: string;
}

/**
* Retrieves registry issuer metadata from the specified URL.
*/
export async function fetchIssuerMetadata(
metadata: MetadataUrl,
abortController: AbortController
): Promise<IssuerMetadata> {
return fetchDataFromUrl(metadata, abortController, issuerMetadataSchema);
}

/**
* Retrieves credential schemas for each of the provided credentials. The method ensures
* that duplicate schemas are not fetched multiple times, by only fetching once per
Expand Down

0 comments on commit ff5deee

Please sign in to comment.