Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor registry client, remove issuerAuth. #537

Merged
merged 3 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/BackupItemModal/BackupItemModal.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export type BackupItemModalProps = {
onRequestClose: () => void;
open?: boolean;
onBackup: (password: string | undefined) => Promise<void>;
onBackup: () => Promise<void>;
backupItemName: string;
backupModalText: string;
};
2 changes: 1 addition & 1 deletion app/components/BackupItemModal/BackupItemModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function BackupItemModal({ onRequestClose, open, onBackup, backup
const [password, setPassword] = useState<string>();

const createBackup = useAsyncCallback(
() => onBackup(enablePassword ? password : undefined),
() => onBackup(),
{ onSuccess: onRequestClose, onError: onRequestClose }
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { useAsyncCallback } from 'react-async-hook';
import { Text, View } from 'react-native';
import AnimatedEllipsis from 'rn-animated-ellipsis';
Expand All @@ -11,17 +11,21 @@ import { makeSelectDidFromProfile, selectWithFactory } from '../../store/selecto
import dynamicStyleSheet from './CredentialRequestHandler.styles';
import { Credential } from '../../types/credential';
import { stageCredentials } from '../../store/slices/credentialFoyer';
import { DidRegistryContext } from '../../init/registries';

type CredentialRequestHandlerProps = {
credentialRequestParams: Record<string, unknown> | undefined;
rawProfileRecord: ProfileRecordRaw;
onFailed: () => void;
}

export default function CredentialRequestHandler({ credentialRequestParams, rawProfileRecord, onFailed }: CredentialRequestHandlerProps): JSX.Element {
export default function CredentialRequestHandler({
credentialRequestParams, rawProfileRecord, onFailed
}: CredentialRequestHandlerProps): JSX.Element {
const { styles } = useDynamicStyles(dynamicStyleSheet);
const dispatch = useAppDispatch();
const [modalIsOpen, setModalIsOpen] = useState(false);
const registries = useContext(DidRegistryContext);

const credentialRequest = useAsyncCallback(requestCredential, { onSuccess: onFinish });
const errorMessage = credentialRequest.error?.message;
Expand All @@ -42,7 +46,7 @@ export default function CredentialRequestHandler({ credentialRequestParams, rawP
if (isCredentialRequestParams(credentialRequestParams)) {
setModalIsOpen(true);
const rawDidRecord = selectWithFactory(makeSelectDidFromProfile, { rawProfileRecord });
credentialRequest.execute(credentialRequestParams, rawDidRecord);
credentialRequest.execute(credentialRequestParams, rawDidRecord, registries);
}
}, [credentialRequestParams, rawProfileRecord]);

Expand All @@ -57,7 +61,7 @@ export default function CredentialRequestHandler({ credentialRequestParams, rawP
confirmText="Close"
>
{credentialRequest.loading ? (
<View style={styles.loadingContainer}>
<View style={styles.loadingContainer}>
<AnimatedEllipsis style={styles.loadingDots} minOpacity={0.4} animationDelay={200}/>
</View>
) : (
Expand Down
9 changes: 4 additions & 5 deletions app/components/ProfileItem/ProfileItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default function ProfileItem({ rawProfileRecord }: ProfileItemProps): JSX
const modalContent = useMemo(() => {
if (activeModal === null) return null;

const actionProps = {
const actionProps = {
rawProfileRecord,
onRequestClose: () => setActiveModal(null)
};
Expand All @@ -45,7 +45,7 @@ export default function ProfileItem({ rawProfileRecord }: ProfileItemProps): JSX
[ActiveModal.Rename]: <RenameModal {...actionProps} />,
[ActiveModal.Backup]: <BackupModal {...actionProps} />,
[ActiveModal.Delete]: <DeleteModal {...actionProps} />,

}[activeModal];
}, [activeModal]);

Expand Down Expand Up @@ -110,12 +110,11 @@ function RenameModal({ rawProfileRecord, onRequestClose }: ActionModalProps): JS
}

function BackupModal({ rawProfileRecord, onRequestClose }: ActionModalProps): JSX.Element {
const backupProfile = (password: string | undefined) =>
exportProfile(rawProfileRecord, password);
const backupProfile = () => exportProfile(rawProfileRecord);

return (
<BackupItemModal
onRequestClose={onRequestClose}
onRequestClose={onRequestClose}
onBackup={backupProfile}
backupItemName="Profile"
backupModalText="This will backup your profile and its contents into a file for you to download."
Expand Down
3 changes: 2 additions & 1 deletion app/components/VerificationCard/VerificationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import moment from 'moment';
import { View, Text, TouchableOpacity } from 'react-native';
import { MaterialCommunityIcons, MaterialIcons } from '@expo/vector-icons';

import { useDynamicStyles, useVerifyCredential, VerifyPayload } from '../../hooks';
import { useDynamicStyles, useVerifyCredential } from '../../hooks';
import { VerifyPayload } from '../../lib/verifiableObject';
import dynamicStyleSheet from './VerificationCard.styles';
import { navigationRef } from '../../navigation';
import { CredentialRecordRaw } from '../../model';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { VerifyPayload } from '../../hooks';
import type { VerifyPayload } from '../../lib/verifiableObject';
import { Credential } from '../../types/credential';

export type VerificationStatusCardProps = {
Expand Down
12 changes: 7 additions & 5 deletions app/components/VerificationStatusCard/VerificationStatusCard.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useMemo } from 'react';
import React, { useContext } from 'react';
import { View, Text } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import moment from 'moment';
import { registryCollections } from '@digitalcredentials/issuer-registry-client';

import { VerificationStatusCardProps, StatusItemProps } from './VerificationStatusCard.d';
import dynamicStyleSheet from './VerificationStatusCard.styles';
import { useDynamicStyles } from '../../hooks';
import { BulletList } from '../../components';
import { DidRegistryContext } from '../../init/registries';
import { issuerInRegistries } from '../../lib/verifiableObject';

const DATE_FORMAT = 'MMM D, YYYY';

Expand All @@ -20,10 +21,11 @@ enum LogId {

export default function VerificationStatusCard({ credential, verifyPayload }: VerificationStatusCardProps): JSX.Element {
const { styles } = useDynamicStyles(dynamicStyleSheet);
const registries = useContext(DidRegistryContext);

const { expirationDate, issuer } = credential;

const issuerId = typeof issuer === 'string' ? null : issuer?.id;
const registryList = useMemo(() => issuerId ? registryCollections.issuerDid.registriesFor(issuerId).map(({ name }) => name) : null, [issuerId]);
const registryNames = issuerInRegistries({ issuer, registries });

const details = verifyPayload.result.log?.reduce<Record<string, boolean>>((acc, log) => {
acc[log.id] = log.valid;
Expand Down Expand Up @@ -60,7 +62,7 @@ export default function VerificationStatusCard({ credential, verifyPayload }: Ve
negativeText="Is not verified in a registered institution"
verified={details[LogId.IssuerDIDResolves]}
>
{registryList && <BulletList items={registryList} />}
{registryNames && <BulletList items={registryNames} />}
</StatusItem>
</View>
<View style={styles.container}>
Expand Down
18 changes: 18 additions & 0 deletions app/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const KnownDidRegistries = [
{
'name': 'DCC Pilot Registry',
'url': 'https://digitalcredentials.github.io/issuer-registry/registry.json'
},
{
'name': 'DCC Sandbox Registry',
'url': 'https://digitalcredentials.github.io/sandbox-registry/registry.json'
},
{
'name': 'DCC Community Registry',
'url': 'https://digitalcredentials.github.io/community-registry/registry.json'
},
{
'name': 'DCC Registry',
'url': 'https://digitalcredentials.github.io/dcc-registry/registry.json'
}
];
27 changes: 10 additions & 17 deletions app/hooks/useAppLoading.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useState, useContext } from 'react';
import { useSelector } from 'react-redux';
import {
useFonts,
Rubik_400Regular,
Rubik_500Medium,
Rubik_700Bold,
useFonts
} from '@expo-google-fonts/rubik';
import { RobotoMono_400Regular } from '@expo-google-fonts/roboto-mono';
import { loadRegistryCollections } from '@digitalcredentials/issuer-registry-client';
import { FileLogger, LogLevel, logLevelNames } from 'react-native-file-logger';

import { DidRegistryContext, loadKnownDidRegistries } from '../init/registries';
import {
pollWalletState,
lock,
pollWalletState,
selectWalletState,
} from '../store/slices/wallet';
import { getAllRecords } from '../store';
import { useAppDispatch } from './useAppDispatch';
import { initializeLogger } from '../init/logger';

export function useAppLoading(): boolean {
const [loading, setLoading] = useState(true);

const didRegistries = useContext(DidRegistryContext);

const primaryTasks = [
useFontsLoaded(),
useWalletStateInitialized(),
];

const primaryTasksFinished = useMemo(() => primaryTasks.every(t => t), primaryTasks);

useEffect(() => {
useEffect(() => {
if (primaryTasksFinished) runSecondaryTasks();
}, [primaryTasksFinished]);

async function runSecondaryTasks() {
await Promise.all([
loadRegistryCollections(),
initializeLogger(),
loadKnownDidRegistries({ client: didRegistries })
]);

setLoading(false);
Expand Down Expand Up @@ -66,7 +68,7 @@ function useWalletStateInitialized() {
dispatch(pollWalletState());
} else {
/**
* SecureStore items aren't removed when the app is deleted, so if the
* SecureStore items aren't removed when the app is deleted, so if the
* database status is unlocked but not initialized, we need to update the
* status to locked.
*/
Expand All @@ -80,12 +82,3 @@ function useWalletStateInitialized() {

return walletStateInitialized;
}

async function initializeLogger() {
function formatter(level: LogLevel, msg: string) {
const levelName = logLevelNames[level];
return `> ${new Date().toISOString()} [${levelName}] ${msg}`;
}

await FileLogger.configure({ formatter, maximumNumberOfFiles: 1 });
}
60 changes: 11 additions & 49 deletions app/hooks/useVerifyCredential.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import { useState, useCallback } from 'react';
import { ResultLog, verifyCredential } from '../lib/validate';
import { useCallback, useContext, useState } from 'react';
import { CredentialError } from '../types/credential';
import { CredentialRecordRaw } from '../model';
import { useFocusEffect } from '@react-navigation/native';
import { LruCache } from '@digitalcredentials/lru-memoize';
import { DidRegistryContext } from '../init/registries';
import {
VerifyPayload,
verificationResultFor
} from '../lib/verifiableObject';

/* Verification expiration = 30 days */
const VERIFICATION_EXPIRATION = 1000 * 30;
const DEFAULT_ERROR_MESSAGE = 'An error was encountered while verifying this credential.';
const lruCache = new LruCache({ maxAge: VERIFICATION_EXPIRATION });

export type VerificationResult = {
timestamp: number | null;
log: ResultLog[];
verified: boolean | null;
error?: Error;
}

export type VerifyPayload = {
loading: boolean;
error: string | null;
result: VerificationResult;
}

const initialResult = { timestamp: null, log: [], verified: null };

Expand All @@ -31,12 +18,15 @@ export function useVerifyCredential(rawCredentialRecord?: CredentialRecordRaw, f
const [result, setResult] = useState<VerifyPayload['result']>(initialResult);
const [error, setError] = useState<VerifyPayload['error']>(null);

const registries = useContext(DidRegistryContext);

if (rawCredentialRecord === undefined) {
return null;
}

const verify = useCallback(async () => {
const verificationResult = await verificationResultFor(rawCredentialRecord, forceFresh);
const verificationResult = await verificationResultFor({
rawCredentialRecord, forceFresh, registries});
setResult(verificationResult);

if (verificationResult.error) {
Expand All @@ -46,7 +36,7 @@ export function useVerifyCredential(rawCredentialRecord?: CredentialRecordRaw, f

setError(errorMessage);
}

setLoading(false);
}, []);

Expand All @@ -56,31 +46,3 @@ export function useVerifyCredential(rawCredentialRecord?: CredentialRecordRaw, f

return { loading, error, result };
}

export async function verificationResultFor(rawCredentialRecord: CredentialRecordRaw, forceFresh = false): Promise<VerificationResult> {
const cachedRecordId = String(rawCredentialRecord._id);

if (!forceFresh) {
const cachedResult = await lruCache.memoize({
key: cachedRecordId,
fn: () => { return verifyCredential(rawCredentialRecord.credential); }
}) as VerificationResult;
return cachedResult;
}

let response, error;
try {
response = await verifyCredential(rawCredentialRecord.credential);
} catch (err) {
error = err as Error;
}

const result: VerificationResult = {
verified: response?.verified ?? false,
log: response?.results ? response.results[0].log : [],
timestamp: Date.now(),
error,
};

return result;
}
10 changes: 10 additions & 0 deletions app/init/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { FileLogger, LogLevel, logLevelNames } from 'react-native-file-logger';

export async function initializeLogger() {
function formatter(level: LogLevel, msg: string) {
const levelName = logLevelNames[level];
return `> ${new Date().toISOString()} [${levelName}] ${msg}`;
}

await FileLogger.configure({formatter, maximumNumberOfFiles: 1});
}
15 changes: 15 additions & 0 deletions app/init/registries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createContext } from 'react';
import { RegistryClient } from '@digitalcredentials/issuer-registry-client';
import { KnownDidRegistries } from '../config';

const registries = new RegistryClient();

export const DidRegistryContext = createContext(registries);

/**
* Loads remote Known Issuer / Known Verifier DID registries from config.
*/
export async function loadKnownDidRegistries ({ client }: { client: RegistryClient }) {
await client.load({ config: KnownDidRegistries });
// now available for usage through useContext(DidRegistryContext)
}
Loading
Loading