[INJIMOB-3647] refactor: update isRevoked data type (#2149)

* [INJIMOB-3647] refactor: modify data type of isRevoked to EvaluationStatus

Type representing any possible value of EvaluationStatus.

- "TRUE" → Condition was evaluated and is positively true
- "FALSE" → Condition was evaluated and is definitively false
- "UNDETERMINED" → Condition could not be evaluated due to an error

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: modify data type of isRevoked to EvaluationStatus

Type representing any possible value of EvaluationStatus.

- "TRUE" → Condition was evaluated and is positively true
- "FALSE" → Condition was evaluated and is definitively false
- "UNDETERMINED" → Condition could not be evaluated due to an error

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: change statuslistVC type to record from string

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

# Conflicts:
#	shared/vcjs/verifyCredential.ts

* [INJIMOB-3647] refactor: update status revoke check to check for null status

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: VCMetadat constructor isRevoked param

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: rename EvaluationStatus to RevocationStatus

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: modify revocation status logs

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

---------

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>
This commit is contained in:
KiruthikaJeyashankar
2025-12-01 11:28:11 +05:30
committed by GitHub
parent 0e667bd46d
commit 9457ad0d9f
18 changed files with 348 additions and 244 deletions

View File

@@ -64,7 +64,7 @@ fileignoreconfig:
- filename: assets/Finger_Print_Icon.svg - filename: assets/Finger_Print_Icon.svg
checksum: 776d4fe4fc4b54d185ccf97daf0511b9fe2c0e0f7c1a809047020e5e8a100db6 checksum: 776d4fe4fc4b54d185ccf97daf0511b9fe2c0e0f7c1a809047020e5e8a100db6
- filename: screens/MainLayout.tsx - filename: screens/MainLayout.tsx
checksum: dd31361997111c28461239e986112a30ee986e99432ac3016033508863b90ddd checksum: 8909ef957c866221e864c6edaf93081af7f5968857200284bf7047631f525322
- filename: android/app/build.gradle - filename: android/app/build.gradle
checksum: 8d5715e179a398518e6acff82c75b27077c9f893dc90b2972c77f9a09f10be95 checksum: 8d5715e179a398518e6acff82c75b27077c9f893dc90b2972c77f9a09f10be95
- filename: .github/workflows/push-triggers.yml - filename: .github/workflows/push-triggers.yml
@@ -270,7 +270,7 @@ fileignoreconfig:
- filename: machines/Issuers/IssuersService.ts - filename: machines/Issuers/IssuersService.ts
checksum: e3832dff27687abc28609d2b281e570b4b0017995b7cfb56627a6b96949c469a checksum: e3832dff27687abc28609d2b281e570b4b0017995b7cfb56627a6b96949c469a
- filename: screens/Home/ViewVcModal.tsx - filename: screens/Home/ViewVcModal.tsx
checksum: cfb25d562185488432b76287c4ef93359c1c64d8e29f5755d4c0a726c1485442 checksum: 847d45d566b7fc86dd3ebbba74bf587399dd7754466e42ebdecd462a157705e9
- filename: injitest/src/main/resources/TestData.json - filename: injitest/src/main/resources/TestData.json
checksum: 1b5af14c96b456898259b4cb7a5607b006404cf0360274bdc204d7d065698e3c checksum: 1b5af14c96b456898259b4cb7a5607b006404cf0360274bdc204d7d065698e3c
- filename: injitest/src/test/java/androidTestCases/ActivateVcTest.java - filename: injitest/src/test/java/androidTestCases/ActivateVcTest.java
@@ -385,7 +385,7 @@ fileignoreconfig:
- filename: android/app/src/main/java/io/mosip/residentapp/InjiOpenId4VPModule.java - filename: android/app/src/main/java/io/mosip/residentapp/InjiOpenId4VPModule.java
checksum: 6b315164dca5de95c11e0dc8cbb480207b19c312b1c9135adc39ef74a1ff7e35 checksum: 6b315164dca5de95c11e0dc8cbb480207b19c312b1c9135adc39ef74a1ff7e35
- filename: screens/Scan/SendVPScreenController.ts - filename: screens/Scan/SendVPScreenController.ts
checksum: f898ac7f1ecfa1df17e33b327d675f57debf2d5bd56052fc047dd03577354590 checksum: aa228c43a01e653b9da6ee354a39c942bec25848aa9631650611d1b5f85623d7
- filename: screens/Scan/SendVPScreen.tsx - filename: screens/Scan/SendVPScreen.tsx
checksum: de80cb9a932ed99e224438a8c373d117807101a39a440e97977654ef6935af6c checksum: de80cb9a932ed99e224438a8c373d117807101a39a440e97977654ef6935af6c
- filename: machines/openID4VP/openID4VPMachine.typegen.ts - filename: machines/openID4VP/openID4VPMachine.typegen.ts
@@ -416,4 +416,8 @@ fileignoreconfig:
checksum: 669e85d1c8ff526b97fa4ed4b8ed33a100eaba9f2f41bceccd75dc7a85a12103 checksum: 669e85d1c8ff526b97fa4ed4b8ed33a100eaba9f2f41bceccd75dc7a85a12103
- filename: screens/Home/MyVcsTab.tsx - filename: screens/Home/MyVcsTab.tsx
checksum: 68ff83c5d9062fbc077d008956fa654540253c52ce68d7105c175c51562b3dc9 checksum: 68ff83c5d9062fbc077d008956fa654540253c52ce68d7105c175c51562b3dc9
- filename: components/VC/common/VcStatustooTip.tsx
checksum: a48c88da719fadcb3d1aeb730a23735709c0c198351104203abd03445f6cc76f
- filename: screens/Settings/KeyManagementScreen.tsx
checksum: 6871fad16ecb5f019f502b2f7f715bc2a7b646ee08026b321aa1f8ce071dccc1
version: "1.0" version: "1.0"

View File

@@ -59,7 +59,8 @@ const AppLayoutWrapper: React.FC = () => {
const authService = appService.children.get('auth'); const authService = appService.children.get('auth');
const isAppSetupComplete = useSelector(authService, selectAppSetupComplete); const isAppSetupComplete = useSelector(authService, selectAppSetupComplete);
const [isDeepLinkOverlayVisible, setDeepLinkOverlayVisible] = useState(isDeepLinkFlow); const [isDeepLinkOverlayVisible, setDeepLinkOverlayVisible] =
useState(isDeepLinkFlow);
useEffect(() => { useEffect(() => {
if (AppState.currentState === 'active') { if (AppState.currentState === 'active') {

View File

@@ -1,30 +1,28 @@
import React from "react"; import React from 'react';
import { useTranslation } from "react-i18next"; import {useTranslation} from 'react-i18next';
import { View } from "react-native"; import {View} from 'react-native';
import { Column } from "../../ui"; import {Column} from '../../ui';
import { Theme } from "../../ui/styleUtils"; import {Theme} from '../../ui/styleUtils';
import { Text } from "../../ui"; import {Text} from '../../ui';
import { VC_STATUS_KEYS } from "./VCUtils"; import {VC_STATUS_KEYS} from './VCUtils';
export const StatusTooltipContent = () => { export const StatusTooltipContent = () => {
const { t } = useTranslation('ViewVcModal'); const {t} = useTranslation('ViewVcModal');
return ( return (
<Column align="center" style={{marginTop:20}}> <Column align="center" style={{marginTop: 20}}>
{VC_STATUS_KEYS.map(key => ( {VC_STATUS_KEYS.map(key => (
<View key={key} style={{ marginBottom: 20 }}> <View key={key} style={{marginBottom: 20}}>
<Text weight="semibold">{t(`statusToolTipContent.${key}.title`)}</Text> <Text weight="semibold">
<Text {t(`statusToolTipContent.${key}.title`)}
weight="regular" </Text>
style={[ <Text
Theme.Styles.tooltipContentDescription, weight="regular"
{ marginTop: 3 }, style={[Theme.Styles.tooltipContentDescription, {marginTop: 3}]}>
]}> {t(`statusToolTipContent.${key}.description`)}
{t(`statusToolTipContent.${key}.description`)} </Text>
</Text> </View>
</View> ))}
))} </Column>
</Column> );
); };
};

View File

@@ -8,6 +8,7 @@ import {Row, Text} from './ui';
import {Theme} from './ui/styleUtils'; import {Theme} from './ui/styleUtils';
import {useTranslation} from 'react-i18next'; import {useTranslation} from 'react-i18next';
import {VCMetadata} from '../shared/VCMetadata'; import {VCMetadata} from '../shared/VCMetadata';
import {RevocationStatus} from '../shared/vcVerifier/VcVerifier';
export const VCVerification: React.FC<VCVerificationProps> = ({ export const VCVerification: React.FC<VCVerificationProps> = ({
vcMetadata, vcMetadata,
@@ -20,12 +21,15 @@ export const VCVerification: React.FC<VCVerificationProps> = ({
let statusIcon: JSX.Element; let statusIcon: JSX.Element;
if (vcMetadata.isVerified) { if (vcMetadata.isVerified) {
if (vcMetadata.isRevoked) { if (vcMetadata.isRevoked === RevocationStatus.TRUE) {
statusText = t('revoked'); statusText = t('revoked');
statusIcon = <PendingIcon color="brown" />; statusIcon = <PendingIcon color="brown" />;
} else if (vcMetadata.isExpired) { } else if (vcMetadata.isExpired) {
statusText = t('expired'); statusText = t('expired');
statusIcon = <PendingIcon color="red" />; statusIcon = <PendingIcon color="red" />;
} else if (vcMetadata.isRevoked === RevocationStatus.UNDETERMINED) {
statusText = t('pending');
statusIcon = <PendingIcon color="orange" />;
} else { } else {
statusText = t('valid'); statusText = t('valid');
statusIcon = <VerifiedIcon />; statusIcon = <VerifiedIcon />;

View File

@@ -69,7 +69,10 @@ final class LdpStatusChecker {
var results: [String: CredentialStatusResult] = [:] var results: [String: CredentialStatusResult] = [:]
for entry in filteredEntries { for entry in filteredEntries {
let purpose = (entry["statusPurpose"] as? String)?.lowercased() ?? "" guard let purpose = (entry["statusPurpose"] as? String)?.lowercased(), !purpose.isEmpty else {
print("Warning: Skipping entry with missing statusPurpose")
continue
}
do { do {
let result = try await checkStatusEntry(entry: entry, purpose: purpose) let result = try await checkStatusEntry(entry: entry, purpose: purpose)
results[purpose] = result results[purpose] = result
@@ -175,7 +178,7 @@ final class LdpStatusChecker {
throw StatusCheckException(message: "statusMessage count mismatch", errorCode: .statusVerificationError) throw StatusCheckException(message: "statusMessage count mismatch", errorCode: .statusVerificationError)
} }
print("Status message for purpose '\(purpose): $\(statusMessage)") print("Status message for purpose '\(purpose): \(statusMessage)")
} }
let bitSet = try decodeEncodedList(encodedList) let bitSet = try decodeEncodedList(encodedList)

View File

@@ -1,5 +1,9 @@
import {assign, send} from 'xstate'; import {assign, send} from 'xstate';
import {CommunicationDetails, UUID, VerificationStatus} from '../../../shared/Utils'; import {
CommunicationDetails,
UUID,
VerificationStatus,
} from '../../../shared/Utils';
import {StoreEvents} from '../../store'; import {StoreEvents} from '../../store';
import {VCMetadata} from '../../../shared/VCMetadata'; import {VCMetadata} from '../../../shared/VCMetadata';
import { import {
@@ -31,6 +35,7 @@ import {VcMetaEvents} from '../VCMetaMachine/VCMetaMachine';
import {WalletBindingResponse} from '../VCMetaMachine/vc'; import {WalletBindingResponse} from '../VCMetaMachine/vc';
import {BannerStatusType} from '../../../components/BannerNotification'; import {BannerStatusType} from '../../../components/BannerNotification';
import {VCActivityLog} from '../../../components/ActivityLogEvent'; import {VCActivityLog} from '../../../components/ActivityLogEvent';
import {RevocationStatus} from '../../../shared/vcVerifier/VcVerifier';
export const VCItemActions = model => { export const VCItemActions = model => {
return { return {
@@ -58,7 +63,7 @@ export const VCItemActions = model => {
resetIsVerified: assign((context: any) => { resetIsVerified: assign((context: any) => {
const previous = context.vcMetadata; const previous = context.vcMetadata;
const statusChanged = previous.isVerified; const statusChanged = previous.isVerified;
return { return {
...context, ...context,
vcMetadata: new VCMetadata({ vcMetadata: new VCMetadata({
@@ -91,16 +96,21 @@ export const VCItemActions = model => {
sendReverificationSuccessToVcMeta: send( sendReverificationSuccessToVcMeta: send(
(context: any) => ({ (context: any) => ({
type: 'REVERIFY_VC_SUCCESS', type:
statusValue: context.vcMetadata.isRevoked context.vcMetadata.isRevoked === RevocationStatus.UNDETERMINED
? VerificationStatus.REVOKED ? 'REVERIFY_VC_FAILED'
: context.vcMetadata.isExpired : 'REVERIFY_VC_SUCCESS',
? VerificationStatus.EXPIRED statusValue:
: context.vcMetadata.isVerified context.vcMetadata.isRevoked === RevocationStatus.TRUE
? VerificationStatus.VALID ? VerificationStatus.REVOKED
: VerificationStatus.PENDING, : context.vcMetadata.isExpired
? VerificationStatus.EXPIRED
: context.vcMetadata.isVerified &&
context.vcMetadata.isRevoked === RevocationStatus.FALSE
? VerificationStatus.VALID
: VerificationStatus.PENDING,
vcKey: context.vcMetadata.getVcKey(), vcKey: context.vcMetadata.getVcKey(),
vcType: context.vcMetadata.credentialType vcType: context.vcMetadata.credentialType,
}), }),
{ {
to: (context: any) => context.serviceRefs.vcMeta, to: (context: any) => context.serviceRefs.vcMeta,
@@ -110,13 +120,13 @@ export const VCItemActions = model => {
resetStatusChangedFlag: assign({ resetStatusChangedFlag: assign({
statusChangedDuringVerification: () => false, statusChangedDuringVerification: () => false,
}), }),
sendReverificationFailureToVcMeta: send( sendReverificationFailureToVcMeta: send(
(context:any) => ({ (context: any) => ({
type: 'REVERIFY_VC_FAILED', type: 'REVERIFY_VC_FAILED',
statusValue: VerificationStatus.PENDING, statusValue: VerificationStatus.PENDING,
vcKey: context.vcMetadata.getVcKey(), vcKey: context.vcMetadata.getVcKey(),
vcType: context.vcMetadata.credentialType vcType: context.vcMetadata.credentialType,
}), }),
{ {
to: (context: any) => context.serviceRefs.vcMeta, to: (context: any) => context.serviceRefs.vcMeta,

View File

@@ -30,8 +30,8 @@ export const baseRoutes: Screen[] = [
component: KeyManagementScreen, component: KeyManagementScreen,
}, },
{ {
name:'AuthView', name: 'AuthView',
component:AuthWebViewScreen component: AuthWebViewScreen,
}, },
{ {
name: 'Language', name: 'Language',

View File

@@ -10,7 +10,7 @@ import {
import {authRoutes, baseRoutes} from '../routes'; import {authRoutes, baseRoutes} from '../routes';
import {useAppLayout} from './AppLayoutController'; import {useAppLayout} from './AppLayoutController';
import {StatusBar} from 'react-native'; import {StatusBar} from 'react-native';
import {GestureHandlerRootView} from "react-native-gesture-handler"; import {GestureHandlerRootView} from 'react-native-gesture-handler';
const {Navigator, Screen} = createNativeStackNavigator(); const {Navigator, Screen} = createNativeStackNavigator();
export const AppLayout: React.FC = () => { export const AppLayout: React.FC = () => {
@@ -25,17 +25,19 @@ export const AppLayout: React.FC = () => {
}; };
return ( return (
<GestureHandlerRootView> <GestureHandlerRootView>
<NavigationContainer ref={navigationRef}> <NavigationContainer ref={navigationRef}>
<StatusBar animated={true} barStyle="dark-content" /> <StatusBar animated={true} barStyle="dark-content" />
<Navigator initialRouteName={baseRoutes[0].name} screenOptions={options}> <Navigator
{baseRoutes.map(route => ( initialRouteName={baseRoutes[0].name}
<Screen key={route.name} {...route} /> screenOptions={options}>
))} {baseRoutes.map(route => (
{controller.isAuthorized && <Screen key={route.name} {...route} />
authRoutes.map(route => <Screen key={route.name} {...route} />)} ))}
</Navigator> {controller.isAuthorized &&
</NavigationContainer> authRoutes.map(route => <Screen key={route.name} {...route} />)}
</GestureHandlerRootView> </Navigator>
</NavigationContainer>
</GestureHandlerRootView>
); );
}; };

View File

@@ -41,13 +41,13 @@ export const OnboardingOverlay: React.FC<OnboardingProps> = props => {
const renderItem = ({item}) => { const renderItem = ({item}) => {
return ( return (
<View style={Theme.OnboardingOverlayStyles.slide}> <View style={Theme.OnboardingOverlayStyles.slide}>
<ScrollView showsVerticalScrollIndicator={true}> <ScrollView showsVerticalScrollIndicator={true}>
<Text style={Theme.OnboardingOverlayStyles.sliderTitle}> <Text style={Theme.OnboardingOverlayStyles.sliderTitle}>
{item.title} {item.title}
</Text> </Text>
<Text style={Theme.OnboardingOverlayStyles.text}>{item.text}</Text> <Text style={Theme.OnboardingOverlayStyles.text}>{item.text}</Text>
{item.footer} {item.footer}
</ScrollView> </ScrollView>
</View> </View>
); );
}; };

View File

@@ -40,7 +40,11 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
const controller = useViewVcModal(props); const controller = useViewVcModal(props);
const profileImage = controller.verifiableCredentialData.face; const profileImage = controller.verifiableCredentialData.face;
const verificationStatus = controller.verificationStatus; const verificationStatus = controller.verificationStatus;
const verificationStatusMessage = controller.verificationStatus?.isRevoked ? "revoked" : controller.verificationStatus?.isExpired ? "expired" : controller.verificationStatus?.statusType; const verificationStatusMessage = controller.verificationStatus?.isRevoked
? 'revoked'
: controller.verificationStatus?.isExpired
? 'expired'
: controller.verificationStatus?.statusType;
const [verifiableCredential, setVerifiableCredential] = useState(null); const [verifiableCredential, setVerifiableCredential] = useState(null);
const [svgTemplate, setSvgTemplate] = useState<string[] | null>(null); const [svgTemplate, setSvgTemplate] = useState<string[] | null>(null);
const [svgRendererError, setSvgRendererError] = useState<string[] | null>( const [svgRendererError, setSvgRendererError] = useState<string[] | null>(
@@ -69,7 +73,7 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
!controller.verifiableCredentialData.vcMetadata.isVerified && !controller.verifiableCredentialData.vcMetadata.isVerified &&
!controller.isVerificationInProgress !controller.isVerificationInProgress
) { ) {
props.vcItemActor.send({ type: 'VERIFY' }); props.vcItemActor.send({type: 'VERIFY'});
} }
}, [controller.verifiableCredentialData.vcMetadata.isVerified]); }, [controller.verifiableCredentialData.vcMetadata.isVerified]);
@@ -86,11 +90,12 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
setLoadingSvg(true); setLoadingSvg(true);
const vcJsonString = JSON.stringify(controller.credential.credential); const vcJsonString = JSON.stringify(controller.credential.credential);
const result = await VcRenderer.getInstance().generateCredentialDisplayContent( const result =
controller.verifiableCredentialData.format, await VcRenderer.getInstance().generateCredentialDisplayContent(
wellknown ?? null, controller.verifiableCredentialData.format,
vcJsonString, wellknown ?? null,
); vcJsonString,
);
setSvgTemplate(result); setSvgTemplate(result);
setSvgRendererError(null); setSvgRendererError(null);
@@ -181,7 +186,9 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
<BannerNotification <BannerNotification
type={verificationStatus?.statusType as BannerStatus} type={verificationStatus?.statusType as BannerStatus}
message={t(`VcVerificationBanner:${verificationStatusMessage}`, { message={t(`VcVerificationBanner:${verificationStatusMessage}`, {
vcDetails: `${verificationStatus?.vcType} ${verificationStatus?.vcNumber ?? ""}`, vcDetails: `${verificationStatus?.vcType} ${
verificationStatus?.vcNumber ?? ''
}`,
})} })}
onClosePress={controller.RESET_VERIFICATION_STATUS} onClosePress={controller.RESET_VERIFICATION_STATUS}
key={'reVerificationInProgress'} key={'reVerificationInProgress'}
@@ -239,7 +246,9 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
/> />
<MessageOverlay <MessageOverlay
isVisible={controller.isWalletBindingInProgress || controller.isReverifyingVc} isVisible={
controller.isWalletBindingInProgress || controller.isReverifyingVc
}
title={t('inProgress')} title={t('inProgress')}
progress progress
/> />

View File

@@ -1,20 +1,17 @@
import React, { useEffect, useLayoutEffect, useState } from 'react'; import React, {useEffect, useLayoutEffect, useState} from 'react';
import { useTranslation } from 'react-i18next'; import {useTranslation} from 'react-i18next';
import { FlatList, Pressable, View } from 'react-native'; import {FlatList, Pressable, View} from 'react-native';
import { Issuer } from '../../components/openId4VCI/Issuer'; import {Issuer} from '../../components/openId4VCI/Issuer';
import { Error } from '../../components/ui/Error'; import {Error} from '../../components/ui/Error';
import { Header } from '../../components/ui/Header'; import {Header} from '../../components/ui/Header';
import { Button, Column, Row, Text } from '../../components/ui'; import {Button, Column, Row, Text} from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils'; import {Theme} from '../../components/ui/styleUtils';
import { RootRouteProps } from '../../routes'; import {RootRouteProps} from '../../routes';
import { HomeRouteProps } from '../../routes/routeTypes'; import {HomeRouteProps} from '../../routes/routeTypes';
import { useIssuerScreenController } from './IssuerScreenController'; import {useIssuerScreenController} from './IssuerScreenController';
import { Loader } from '../../components/ui/Loader'; import {Loader} from '../../components/ui/Loader';
import ScanIcon from '../../assets/scanIcon.svg'; import ScanIcon from '../../assets/scanIcon.svg';
import { import {isTranslationKeyFound, removeWhiteSpace} from '../../shared/commonUtil';
isTranslationKeyFound,
removeWhiteSpace,
} from '../../shared/commonUtil';
import { import {
ErrorMessage, ErrorMessage,
getDisplayObjectForCurrentLanguage, getDisplayObjectForCurrentLanguage,
@@ -26,31 +23,31 @@ import {
sendInteractEvent, sendInteractEvent,
sendStartEvent, sendStartEvent,
} from '../../shared/telemetry/TelemetryUtils'; } from '../../shared/telemetry/TelemetryUtils';
import { TelemetryConstants } from '../../shared/telemetry/TelemetryConstants'; import {TelemetryConstants} from '../../shared/telemetry/TelemetryConstants';
import { MessageOverlay } from '../../components/MessageOverlay'; import {MessageOverlay} from '../../components/MessageOverlay';
import { SearchBar } from '../../components/ui/SearchBar'; import {SearchBar} from '../../components/ui/SearchBar';
import { SvgImage } from '../../components/ui/svg'; import {SvgImage} from '../../components/ui/svg';
import { Icon } from 'react-native-elements'; import {Icon} from 'react-native-elements';
import { BannerNotificationContainer } from '../../components/BannerNotificationContainer'; import {BannerNotificationContainer} from '../../components/BannerNotificationContainer';
import { CredentialTypeSelectionScreen } from './CredentialTypeSelectionScreen'; import {CredentialTypeSelectionScreen} from './CredentialTypeSelectionScreen';
import { QrScanner } from '../../components/QrScanner'; import {QrScanner} from '../../components/QrScanner';
import { IssuersModel } from '../../machines/Issuers/IssuersModel'; import {IssuersModel} from '../../machines/Issuers/IssuersModel';
import { AUTH_ROUTES } from '../../routes/routesConstants'; import {AUTH_ROUTES} from '../../routes/routesConstants';
import { TransactionCodeModal } from './TransactionCodeScreen'; import {TransactionCodeModal} from './TransactionCodeScreen';
import { TrustModal } from '../../components/TrustModal'; import {TrustModal} from '../../components/TrustModal';
import i18next from 'i18next'; import i18next from 'i18next';
export const IssuersScreen: React.FC< export const IssuersScreen: React.FC<
HomeRouteProps | RootRouteProps HomeRouteProps | RootRouteProps
> = props => { > = props => {
const model = IssuersModel; const model = IssuersModel;
const controller = useIssuerScreenController(props); const controller = useIssuerScreenController(props);
const { i18n, t } = useTranslation('IssuersScreen'); const {i18n, t} = useTranslation('IssuersScreen');
const issuers = controller.issuers; const issuers = controller.issuers;
let [filteredSearchData, setFilteredSearchData] = useState(issuers); let [filteredSearchData, setFilteredSearchData] = useState(issuers);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [tapToSearch, setTapToSearch] = useState(false); const [tapToSearch, setTapToSearch] = useState(false);
const [clearSearchIcon, setClearSearchIcon] = useState(false); const [clearSearchIcon, setClearSearchIcon] = useState(false);
const showFullScreenError = controller.isError const showFullScreenError = controller.isError;
const isVerificationFailed = controller.verificationErrorMessage !== ''; const isVerificationFailed = controller.verificationErrorMessage !== '';
@@ -58,8 +55,7 @@ export const IssuersScreen: React.FC<
const verificationErrorMessage = isTranslationKeyFound(translationKey, t) const verificationErrorMessage = isTranslationKeyFound(translationKey, t)
? t(translationKey) ? t(translationKey)
: t('errors.verificationFailed.ERR_GENERIC'); : t('errors.verificationFailed.ERR_GENERIC');
useLayoutEffect(() => { useLayoutEffect(() => {
if (controller.loadingReason || showFullScreenError) { if (controller.loadingReason || showFullScreenError) {
@@ -72,13 +68,12 @@ export const IssuersScreen: React.FC<
header: props => ( header: props => (
<Header <Header
goBack={props.navigation.goBack} goBack={props.navigation.goBack}
title={ controller.isQrScanning?t('download'):t('title')} title={controller.isQrScanning ? t('download') : t('title')}
testID="issuersScreenHeader" testID="issuersScreenHeader"
/> />
), ),
}); });
} }
}, [ }, [
controller.loadingReason, controller.loadingReason,
controller.errorMessageType, controller.errorMessageType,
@@ -93,8 +88,10 @@ export const IssuersScreen: React.FC<
if (controller.isAuthEndpointToOpen) { if (controller.isAuthEndpointToOpen) {
(props.navigation as any).navigate(AUTH_ROUTES.AuthView, { (props.navigation as any).navigate(AUTH_ROUTES.AuthView, {
authorizationURL: controller.authEndpount, authorizationURL: controller.authEndpount,
clientId: controller.selectedIssuer.client_id ?? "wallet", clientId: controller.selectedIssuer.client_id ?? 'wallet',
redirectUri: controller.selectedIssuer.redirect_uri ?? "io.mosip.residentapp.inji://oauthredirect", redirectUri:
controller.selectedIssuer.redirect_uri ??
'io.mosip.residentapp.inji://oauthredirect',
controller: controller, controller: controller,
}); });
} }
@@ -102,7 +99,7 @@ export const IssuersScreen: React.FC<
const onPressHandler = (id: string, protocol: string) => { const onPressHandler = (id: string, protocol: string) => {
sendStartEvent( sendStartEvent(
getStartEventData(TelemetryConstants.FlowType.vcDownload, { id: id }), getStartEventData(TelemetryConstants.FlowType.vcDownload, {id: id}),
); );
sendInteractEvent( sendInteractEvent(
getInteractEventData( getInteractEventData(
@@ -124,9 +121,9 @@ export const IssuersScreen: React.FC<
return ( return (
controller.errorMessageType === ErrorMessage.TECHNICAL_DIFFICULTIES || controller.errorMessageType === ErrorMessage.TECHNICAL_DIFFICULTIES ||
controller.errorMessageType === controller.errorMessageType ===
ErrorMessage.CREDENTIAL_TYPE_DOWNLOAD_FAILURE || ErrorMessage.CREDENTIAL_TYPE_DOWNLOAD_FAILURE ||
controller.errorMessageType === controller.errorMessageType ===
ErrorMessage.AUTHORIZATION_GRANT_TYPE_NOT_SUPPORTED || ErrorMessage.AUTHORIZATION_GRANT_TYPE_NOT_SUPPORTED ||
controller.errorMessageType === ErrorMessage.NETWORK_REQUEST_FAILED controller.errorMessageType === ErrorMessage.NETWORK_REQUEST_FAILED
); );
} }
@@ -195,7 +192,7 @@ export const IssuersScreen: React.FC<
primaryButtonText="goBack" primaryButtonText="goBack"
primaryButtonEvent={controller.RESET_VERIFY_ERROR} primaryButtonEvent={controller.RESET_VERIFY_ERROR}
primaryButtonTestID="goBack" primaryButtonTestID="goBack"
customStyles={{ marginTop: '30%' }} customStyles={{marginTop: '30%'}}
/> />
); );
} }
@@ -203,14 +200,16 @@ export const IssuersScreen: React.FC<
return issuerTrustConsentComponent(); return issuerTrustConsentComponent();
} }
if (controller.isTxCodeRequested) { if (controller.isTxCodeRequested) {
return <TransactionCodeModal return (
visible={controller.isTxCodeRequested} <TransactionCodeModal
onDismiss={controller.CANCEL} visible={controller.isTxCodeRequested}
onVerify={controller.TX_CODE_RECEIVED} onDismiss={controller.CANCEL}
inputMode= {controller.txCodeDisplayDetails.inputMode} onVerify={controller.TX_CODE_RECEIVED}
description={controller.txCodeDisplayDetails.description} inputMode={controller.txCodeDisplayDetails.inputMode}
length={controller.txCodeDisplayDetails.length} description={controller.txCodeDisplayDetails.description}
/> length={controller.txCodeDisplayDetails.length}
/>
);
} }
if (controller.isBiometricsCancelled) { if (controller.isBiometricsCancelled) {
@@ -253,7 +252,7 @@ export const IssuersScreen: React.FC<
primaryButtonTestID="tryAgain" primaryButtonTestID="tryAgain"
primaryButtonText={ primaryButtonText={
controller.errorMessageType != ErrorMessage.TECHNICAL_DIFFICULTIES && controller.errorMessageType != ErrorMessage.TECHNICAL_DIFFICULTIES &&
controller.errorMessageType != controller.errorMessageType !=
ErrorMessage.AUTHORIZATION_GRANT_TYPE_NOT_SUPPORTED ErrorMessage.AUTHORIZATION_GRANT_TYPE_NOT_SUPPORTED
? 'tryAgain' ? 'tryAgain'
: undefined : undefined
@@ -272,7 +271,6 @@ export const IssuersScreen: React.FC<
/> />
); );
} }
if (controller.isQrScanning) { if (controller.isQrScanning) {
return qrScannerComponent(); return qrScannerComponent();
@@ -280,15 +278,21 @@ export const IssuersScreen: React.FC<
function qrScannerComponent() { function qrScannerComponent() {
return ( return (
<Column crossAlign="center"> <Column crossAlign="center">
<QrScanner <QrScanner onQrFound={controller.QR_CODE_SCANNED} />
onQrFound={controller.QR_CODE_SCANNED}
/>
</Column> </Column>
); );
} }
function issuerTrustConsentComponent() { function issuerTrustConsentComponent() {
return <TrustModal isVisible={true} logo={controller.issuerLogo} name={controller.issuerName} onConfirm={controller.ON_CONSENT_GIVEN} onCancel={controller.CANCEL} /> return (
<TrustModal
isVisible={true}
logo={controller.issuerLogo}
name={controller.issuerName}
onConfirm={controller.ON_CONSENT_GIVEN}
onCancel={controller.CANCEL}
/>
);
} }
return ( return (
@@ -333,19 +337,26 @@ export const IssuersScreen: React.FC<
}}> }}>
{t('description')} {t('description')}
</Text> </Text>
{search === '' && <View style={{ height: 85 }}><Issuer defaultLogo={ScanIcon} displayDetails={{ {search === '' && (
title: t('offerTitle'), <View style={{height: 85}}>
locale: i18n.language, <Issuer
description: t('offerDescription'), defaultLogo={ScanIcon}
}} onPress={ displayDetails={{
controller.SCAN_CREDENTIAL_OFFER_QR_CODE title: t('offerTitle'),
} testID={'credentalOfferButton'} /></View>} locale: i18n.language,
description: t('offerDescription'),
}}
onPress={controller.SCAN_CREDENTIAL_OFFER_QR_CODE}
testID={'credentalOfferButton'}
/>
</View>
)}
<Column scroll style={Theme.IssuersScreenStyles.issuersContainer}> <Column scroll style={Theme.IssuersScreenStyles.issuersContainer}>
{controller.issuers.length > 0 && ( {controller.issuers.length > 0 && (
<FlatList <FlatList
data={filteredSearchData} data={filteredSearchData}
renderItem={({ item }) => ( renderItem={({item}) => (
<Issuer <Issuer
testID={removeWhiteSpace(item.issuer_id)} testID={removeWhiteSpace(item.issuer_id)}
key={item.issuer_id} key={item.issuer_id}
@@ -368,5 +379,3 @@ export const IssuersScreen: React.FC<
</React.Fragment> </React.Fragment>
); );
}; };

View File

@@ -53,11 +53,12 @@ export const ReceiveVcScreen: React.FC = () => {
setLoadingSvg(true); setLoadingSvg(true);
const vcJsonString = JSON.stringify(controller.credential.credential); const vcJsonString = JSON.stringify(controller.credential.credential);
const result = await VcRenderer.getInstance().generateCredentialDisplayContent( const result =
verifiableCredentialData.vcMetadata.format, await VcRenderer.getInstance().generateCredentialDisplayContent(
wellknown ?? null, verifiableCredentialData.vcMetadata.format,
vcJsonString, wellknown ?? null,
); vcJsonString,
);
setSvgTemplate(result); setSvgTemplate(result);
setSvgRendererError(null); setSvgRendererError(null);

View File

@@ -48,7 +48,7 @@ import {VPShareOverlayProps} from './VPShareOverlay';
import {ActivityLogEvents} from '../../machines/activityLog'; import {ActivityLogEvents} from '../../machines/activityLog';
import {VPShareActivityLog} from '../../components/VPShareActivityLogEvent'; import {VPShareActivityLog} from '../../components/VPShareActivityLogEvent';
import {isIOS} from '../../shared/constants'; import {isIOS} from '../../shared/constants';
import { getFaceAttribute } from '../../components/VC/common/VCUtils'; import {getFaceAttribute} from '../../components/VC/common/VCUtils';
type MyVcsTabNavigation = NavigationProp<RootRouteProps>; type MyVcsTabNavigation = NavigationProp<RootRouteProps>;
@@ -65,9 +65,10 @@ export function useSendVPScreen() {
const navigation = useNavigation<MyVcsTabNavigation>(); const navigation = useNavigation<MyVcsTabNavigation>();
const openID4VPService = scanService.getSnapshot().context.OpenId4VPRef; const openID4VPService = scanService.getSnapshot().context.OpenId4VPRef;
// input descriptor id to VCs mapping // input descriptor id to VCs mapping
const [inputDescriptorIdToSelectedVcKeys, setInputDescriptorIdToSelectedVcKeys] = useState<Record<string, [string]>>( const [
{}, inputDescriptorIdToSelectedVcKeys,
); setInputDescriptorIdToSelectedVcKeys,
] = useState<Record<string, [string]>>({});
const hasLoggedErrorRef = useRef(false); const hasLoggedErrorRef = useRef(false);
@@ -97,26 +98,31 @@ export function useSendVPScreen() {
return Object.values(vcs) return Object.values(vcs)
.flatMap(vc => vc) .flatMap(vc => vc)
.some(vc => { .some(vc => {
return getFaceAttribute(vc.verifiableCredential,vc.format) != null; return getFaceAttribute(vc.verifiableCredential, vc.format) != null;
}); });
}; };
const checkIfAllVCsHasImage = vcs => { const checkIfAllVCsHasImage = vcs => {
return Object.values(vcs) return Object.values(vcs)
.flatMap(vc => vc) .flatMap(vc => vc)
.every(vc => getFaceAttribute(vc.verifiableCredential,vc.format) != null); .every(
vc => getFaceAttribute(vc.verifiableCredential, vc.format) != null,
);
}; };
const getSelectedVCs = (): Record<string, any[]> => { const getSelectedVCs = (): Record<string, any[]> => {
let selectedVcsData: Record<string, any[]> = {}; // input_descriptor_id to VC[] let selectedVcsData: Record<string, any[]> = {}; // input_descriptor_id to VC[]
Object.entries(inputDescriptorIdToSelectedVcKeys).forEach(([inputDescriptorId, vcKeys]) => { Object.entries(inputDescriptorIdToSelectedVcKeys).forEach(
vcKeys.forEach((vcKey : string) => { ([inputDescriptorId, vcKeys]) => {
const vcData = myVcs[vcKey]; vcKeys.forEach((vcKey: string) => {
selectedVcsData[inputDescriptorId] = selectedVcsData[inputDescriptorId] || []; const vcData = myVcs[vcKey];
selectedVcsData[inputDescriptorId].push(vcData); selectedVcsData[inputDescriptorId] =
selectedVcsData[inputDescriptorId] || [];
selectedVcsData[inputDescriptorId].push(vcData);
}); });
}); },
return selectedVcsData );
return selectedVcsData;
}; };
const showConfirmationPopup = useSelector( const showConfirmationPopup = useSelector(
@@ -222,9 +228,18 @@ export function useSendVPScreen() {
showLoadingScreen: useSelector(openID4VPService, selectIsShowLoadingScreen), showLoadingScreen: useSelector(openID4VPService, selectIsShowLoadingScreen),
vpVerifierName, vpVerifierName,
flowType: useSelector(openID4VPService, selectFlowType), flowType: useSelector(openID4VPService, selectFlowType),
showTrustConsentModal: useSelector(openID4VPService,selectshowTrustConsentModal), showTrustConsentModal: useSelector(
verifierNameInTrustModal: useSelector(openID4VPService, selectVerifierNameInTrustModal), openID4VPService,
verifierLogoInTrustModal: useSelector(openID4VPService, selectVerifierLogoInTrustModal), selectshowTrustConsentModal,
),
verifierNameInTrustModal: useSelector(
openID4VPService,
selectVerifierNameInTrustModal,
),
verifierLogoInTrustModal: useSelector(
openID4VPService,
selectVerifierLogoInTrustModal,
),
showConfirmationPopup, showConfirmationPopup,
isSelectingVCs, isSelectingVCs,
checkIfAnyVCHasImage, checkIfAnyVCHasImage,
@@ -282,60 +297,80 @@ export function useSendVPScreen() {
(vcRef: ActorRefFrom<typeof VCItemMachine>) => { (vcRef: ActorRefFrom<typeof VCItemMachine>) => {
let descriptorMappingToVCs = {...inputDescriptorIdToSelectedVcKeys}; let descriptorMappingToVCs = {...inputDescriptorIdToSelectedVcKeys};
const isVCSelected = Object.keys(inputDescriptorIdToSelectedVcKeys)?.includes(inputDescriptorId) && inputDescriptorIdToSelectedVcKeys[inputDescriptorId]?.includes(vcKey) ? false : true; const isVCSelected =
Object.keys(inputDescriptorIdToSelectedVcKeys)?.includes(
inputDescriptorId,
) &&
inputDescriptorIdToSelectedVcKeys[inputDescriptorId]?.includes(vcKey)
? false
: true;
if (isVCSelected) { if (isVCSelected) {
if (descriptorMappingToVCs[inputDescriptorId]) { if (descriptorMappingToVCs[inputDescriptorId]) {
if (!descriptorMappingToVCs[inputDescriptorId].includes(vcKey)) { if (!descriptorMappingToVCs[inputDescriptorId].includes(vcKey)) {
descriptorMappingToVCs[inputDescriptorId].push(vcKey); descriptorMappingToVCs[inputDescriptorId].push(vcKey);
}
} else {
descriptorMappingToVCs[inputDescriptorId] = [vcKey];
} }
} else {
descriptorMappingToVCs[inputDescriptorId] = [vcKey];
}
} else { } else {
// remove vc key from the input descriptor mapping // remove vc key from the input descriptor mapping
if (descriptorMappingToVCs[inputDescriptorId]) { if (descriptorMappingToVCs[inputDescriptorId]) {
descriptorMappingToVCs[inputDescriptorId] = descriptorMappingToVCs[ descriptorMappingToVCs[inputDescriptorId] = descriptorMappingToVCs[
inputDescriptorId inputDescriptorId
].filter(key => key !== vcKey); // remove the vcKey from the array ].filter(key => key !== vcKey); // remove the vcKey from the array
if (descriptorMappingToVCs[inputDescriptorId].length === 0) { // if the array is empty, remove the input descriptor id if (descriptorMappingToVCs[inputDescriptorId].length === 0) {
delete descriptorMappingToVCs[inputDescriptorId]; // if the array is empty, remove the input descriptor id
} delete descriptorMappingToVCs[inputDescriptorId];
} }
}
} }
setInputDescriptorIdToSelectedVcKeys(descriptorMappingToVCs) setInputDescriptorIdToSelectedVcKeys(descriptorMappingToVCs);
const {serviceRefs, wellknownResponse, ...vcData} = const {serviceRefs, wellknownResponse, ...vcData} =
vcRef.getSnapshot().context; vcRef.getSnapshot().context;
}, },
UNCHECK_ALL: () => { UNCHECK_ALL: () => {
setInputDescriptorIdToSelectedVcKeys({}) setInputDescriptorIdToSelectedVcKeys({});
}, },
CHECK_ALL: () => { CHECK_ALL: () => {
const updatedInputDescriptorToCredentialsMapping: Record<string, any[]> = {}; const updatedInputDescriptorToCredentialsMapping: Record<string, any[]> =
{};
Object.entries(vcsMatchingAuthRequest).map(([inputDescriptorId, vcs]) => { Object.entries(vcsMatchingAuthRequest).map(([inputDescriptorId, vcs]) => {
updatedInputDescriptorToCredentialsMapping[inputDescriptorId] = []; updatedInputDescriptorToCredentialsMapping[inputDescriptorId] = [];
vcs.map(vcData => { vcs.map(vcData => {
const vcKey = VCMetadata.fromVcMetadataString( const vcKey = VCMetadata.fromVcMetadataString(
vcData.vcMetadata, vcData.vcMetadata,
).getVcKey(); ).getVcKey();
updatedInputDescriptorToCredentialsMapping[inputDescriptorId].push(vcKey); updatedInputDescriptorToCredentialsMapping[inputDescriptorId].push(
vcKey,
);
}); });
}); });
setInputDescriptorIdToSelectedVcKeys({...updatedInputDescriptorToCredentialsMapping}); setInputDescriptorIdToSelectedVcKeys({
...updatedInputDescriptorToCredentialsMapping,
});
}, },
ACCEPT_REQUEST: (selectedDisclosuresByVc) => { ACCEPT_REQUEST: selectedDisclosuresByVc => {
openID4VPService.send(OpenID4VPEvents.ACCEPT_REQUEST(getSelectedVCs(), selectedDisclosuresByVc)); openID4VPService.send(
OpenID4VPEvents.ACCEPT_REQUEST(
getSelectedVCs(),
selectedDisclosuresByVc,
),
);
}, },
VERIFIER_TRUST_CONSENT_GIVEN: () =>{ VERIFIER_TRUST_CONSENT_GIVEN: () => {
openID4VPService.send(OpenID4VPEvents.VERIFIER_TRUST_CONSENT_GIVEN()); openID4VPService.send(OpenID4VPEvents.VERIFIER_TRUST_CONSENT_GIVEN());
}, },
VERIFY_AND_ACCEPT_REQUEST: (selectedDisclosuresByVc) => { VERIFY_AND_ACCEPT_REQUEST: selectedDisclosuresByVc => {
openID4VPService.send( openID4VPService.send(
OpenID4VPEvents.VERIFY_AND_ACCEPT_REQUEST(getSelectedVCs(), selectedDisclosuresByVc), OpenID4VPEvents.VERIFY_AND_ACCEPT_REQUEST(
getSelectedVCs(),
selectedDisclosuresByVc,
),
); );
}, },
CANCEL, CANCEL,

View File

@@ -21,7 +21,7 @@ import {TelemetryConstants} from '../../shared/telemetry/TelemetryConstants';
import {SUPPORTED_KEY_TYPES} from '../../shared/constants'; import {SUPPORTED_KEY_TYPES} from '../../shared/constants';
import {SvgImage} from '../../components/ui/svg'; import {SvgImage} from '../../components/ui/svg';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import { HelpIcon } from '../../components/ui/HelpIcon'; import {HelpIcon} from '../../components/ui/HelpIcon';
const {RNSecureKeystoreModule} = NativeModules; const {RNSecureKeystoreModule} = NativeModules;
@@ -129,10 +129,7 @@ export const KeyManagementScreen: React.FC<KeyManagementScreenProps> = () => {
style={Theme.KeyManagementScreenStyle.heading}> style={Theme.KeyManagementScreenStyle.heading}>
{t('header')} {t('header')}
</Text> </Text>
<HelpScreen <HelpScreen source={'keyManagement'} triggerComponent={HelpIcon()} />
source={'keyManagement'}
triggerComponent={ HelpIcon() }
/>
</View> </View>
<BannerNotificationContainer /> <BannerNotificationContainer />
<View style={Theme.KeyManagementScreenStyle.copilotViewStyle}> <View style={Theme.KeyManagementScreenStyle.copilotViewStyle}>

View File

@@ -13,10 +13,7 @@ import {ProfileInfo} from '../../shared/CloudBackupAndRestoreUtils';
import {useBackupScreen} from './BackupController'; import {useBackupScreen} from './BackupController';
import {BannerNotificationContainer} from '../../components/BannerNotificationContainer'; import {BannerNotificationContainer} from '../../components/BannerNotificationContainer';
import {useBackupRestoreScreen} from '../Settings/BackupRestoreController'; import {useBackupRestoreScreen} from '../Settings/BackupRestoreController';
import { import {getAccountType, getDriveName} from '../../shared/commonUtil';
getAccountType,
getDriveName,
} from '../../shared/commonUtil';
import {HelpScreen} from '../../components/HelpScreen'; import {HelpScreen} from '../../components/HelpScreen';
import {isIOS} from '../../shared/constants'; import {isIOS} from '../../shared/constants';
import {HelpIcon} from '../../components/ui/HelpIcon'; import {HelpIcon} from '../../components/ui/HelpIcon';
@@ -222,11 +219,11 @@ const BackupAndRestoreScreen: React.FC<BackupAndRestoreProps> = props => {
<LoaderAnimation testID="backupAndRestoreScreen" /> <LoaderAnimation testID="backupAndRestoreScreen" />
</Column> </Column>
) : ( ) : (
<ScrollView> <ScrollView>
{LastBackupSection} {LastBackupSection}
{AccountSection} {AccountSection}
{RestoreSection} {RestoreSection}
</ScrollView> </ScrollView>
)} )}
</View> </View>
</Modal> </Modal>

View File

@@ -9,6 +9,7 @@ import {getMosipIdentifier} from './commonUtil';
import {VCFormat} from './VCFormat'; import {VCFormat} from './VCFormat';
import {isMosipVC, UUID} from './Utils'; import {isMosipVC, UUID} from './Utils';
import {getCredentialType} from '../components/VC/common/VCUtils'; import {getCredentialType} from '../components/VC/common/VCUtils';
import {RevocationStatus, RevocationStatusType} from './vcVerifier/VcVerifier';
const VC_KEY_PREFIX = 'VC'; const VC_KEY_PREFIX = 'VC';
const VC_ITEM_STORE_KEY_REGEX = '^VC_[a-zA-Z0-9_-]+$'; const VC_ITEM_STORE_KEY_REGEX = '^VC_[a-zA-Z0-9_-]+$';
@@ -31,7 +32,7 @@ export class VCMetadata {
mosipIndividualId: string = ''; mosipIndividualId: string = '';
format: string = ''; format: string = '';
isExpired: boolean = false; isExpired: boolean = false;
isRevoked: boolean = false; isRevoked: RevocationStatusType = RevocationStatus.FALSE;
downloadKeyType: string = ''; downloadKeyType: string = '';
credentialType: string = ''; credentialType: string = '';
@@ -51,7 +52,7 @@ export class VCMetadata {
format = '', format = '',
downloadKeyType = '', downloadKeyType = '',
isExpired = false, isExpired = false,
isRevoked = false, isRevoked = RevocationStatus.FALSE,
credentialType = '', credentialType = '',
issuerHost = '', issuerHost = '',
lastKnownStatusTimestamp = '', lastKnownStatusTimestamp = '',
@@ -150,7 +151,7 @@ export const getVCMetadata = (context: object, keyType: string) => {
try { try {
const url = new URL(issuerHost); const url = new URL(issuerHost);
return url.hostname.split('.')[0]; return url.hostname.split('.')[0];
}catch (error) { } catch (error) {
// Fallback to issuerHost if URL parsing fails // Fallback to issuerHost if URL parsing fails
return issuerHost; return issuerHost;
} }
@@ -165,7 +166,7 @@ export const getVCMetadata = (context: object, keyType: string) => {
isVerified: context.vcMetadata.isVerified ?? false, isVerified: context.vcMetadata.isVerified ?? false,
isExpired: context.vcMetadata.isExpired ?? false, isExpired: context.vcMetadata.isExpired ?? false,
isRevoked: context.vcMetadata.isRevoked ?? false, isRevoked: context.vcMetadata.isRevoked ?? false,
lastKnownStatusTimestamp:context.vcMetadata.lastKnownStatusTimestamp ?? '', lastKnownStatusTimestamp: context.vcMetadata.lastKnownStatusTimestamp ?? '',
mosipIndividualId: getMosipIndividualId( mosipIndividualId: getMosipIndividualId(
context['verifiableCredential'] as VerifiableCredential, context['verifiableCredential'] as VerifiableCredential,
issuer, issuer,

View File

@@ -1,15 +1,31 @@
import {NativeModules} from 'react-native'; import {NativeModules} from 'react-native';
export const RevocationStatus = Object.freeze({
TRUE: 'TRUE',
FALSE: 'FALSE',
UNDETERMINED: 'UNDETERMINED',
} as const);
/**
* Type representing any possible value of RevocationStatus.
*
* - "TRUE" → Condition was evaluated and is positively true
* - "FALSE" → Condition was evaluated and is definitively false
* - "UNDETERMINED" → Condition could not be evaluated due to an error
*/
export type RevocationStatusType =
(typeof RevocationStatus)[keyof typeof RevocationStatus];
export type CredentialStatusResult = { export type CredentialStatusResult = {
isValid: boolean; isValid: boolean;
error?: ErrorResult; error?: ErrorResult;
statusListVC?: string; // Available only in iOS statusListVC?: Record<string, any>; // Available only in iOS
}; };
export type ErrorResult = { export type ErrorResult = {
code: string; code: string;
message: string; message: string;
} };
export type VerificationSummaryResult = { export type VerificationSummaryResult = {
verificationStatus: boolean; verificationStatus: boolean;
@@ -39,8 +55,8 @@ class VCVerifier {
): Promise<Record<string, CredentialStatusResult>> { ): Promise<Record<string, CredentialStatusResult>> {
try { try {
return await this.vcVerifier.getCredentialStatus( return await this.vcVerifier.getCredentialStatus(
JSON.stringify(credential), JSON.stringify(credential),
format, format,
); );
} catch (error) { } catch (error) {
throw new Error(`Failed to get credential status: ${error}`); throw new Error(`Failed to get credential status: ${error}`);
@@ -53,9 +69,9 @@ class VCVerifier {
): Promise<VerificationSummaryResult> { ): Promise<VerificationSummaryResult> {
try { try {
return await this.vcVerifier.getVerificationSummary( return await this.vcVerifier.getVerificationSummary(
credentialString, credentialString,
credentialFormat, credentialFormat,
[], [],
); );
} catch (error) { } catch (error) {
throw new Error(`Failed to get verification summary: ${error}`); throw new Error(`Failed to get verification summary: ${error}`);

View File

@@ -4,14 +4,22 @@ import {RsaSignature2018} from '../../lib/jsonld-signatures/suites/rsa2018/RsaSi
import {Ed25519Signature2018} from '../../lib/jsonld-signatures/suites/ed255192018/Ed25519Signature2018'; import {Ed25519Signature2018} from '../../lib/jsonld-signatures/suites/ed255192018/Ed25519Signature2018';
import {AssertionProofPurpose} from '../../lib/jsonld-signatures/purposes/AssertionProofPurpose'; import {AssertionProofPurpose} from '../../lib/jsonld-signatures/purposes/AssertionProofPurpose';
import {PublicKeyProofPurpose} from '../../lib/jsonld-signatures/purposes/PublicKeyProofPurpose'; import {PublicKeyProofPurpose} from '../../lib/jsonld-signatures/purposes/PublicKeyProofPurpose';
import {Credential, VerifiableCredential,} from '../../machines/VerifiableCredential/VCMetaMachine/vc'; import {
Credential,
VerifiableCredential,
} from '../../machines/VerifiableCredential/VCMetaMachine/vc';
import {getErrorEventData, sendErrorEvent} from '../telemetry/TelemetryUtils'; import {getErrorEventData, sendErrorEvent} from '../telemetry/TelemetryUtils';
import {TelemetryConstants} from '../telemetry/TelemetryConstants'; import {TelemetryConstants} from '../telemetry/TelemetryConstants';
import {getMosipIdentifier} from '../commonUtil'; import {getMosipIdentifier} from '../commonUtil';
import {NativeModules} from 'react-native'; import {NativeModules} from 'react-native';
import {isAndroid, isIOS} from '../constants'; import {isAndroid, isIOS} from '../constants';
import {VCFormat} from '../VCFormat'; import {VCFormat} from '../VCFormat';
import VCVerifier, {CredentialStatusResult, VerificationSummaryResult} from '../vcVerifier/VcVerifier'; import VCVerifier, {
CredentialStatusResult,
RevocationStatus,
RevocationStatusType,
VerificationSummaryResult,
} from '../vcVerifier/VcVerifier';
// FIXME: Ed25519Signature2018 not fully supported yet. // FIXME: Ed25519Signature2018 not fully supported yet.
// Ed25519Signature2018 proof type check is not tested with its real credential // Ed25519Signature2018 proof type check is not tested with its real credential
@@ -59,10 +67,11 @@ async function verifyCredentialForAndroid(
typeof verifiableCredential === 'string' typeof verifiableCredential === 'string'
? verifiableCredential ? verifiableCredential
: JSON.stringify(verifiableCredential); : JSON.stringify(verifiableCredential);
const vcVerifierResult = await VCVerifier.getInstance().getVerificationSummary( const vcVerifierResult =
credentialString, await VCVerifier.getInstance().getVerificationSummary(
credentialFormat, credentialString,
); credentialFormat,
);
return handleVcVerifierResponse(vcVerifierResult, verifiableCredential); return handleVcVerifierResponse(vcVerifierResult, verifiableCredential);
} }
@@ -81,12 +90,13 @@ async function verifyCredentialForIos(
Since Digital Bazaar library is not able to verify ProofType: "Ed25519Signature2020", Since Digital Bazaar library is not able to verify ProofType: "Ed25519Signature2020",
defaulting it to return true until VcVerifier is implemented for iOS. defaulting it to return true until VcVerifier is implemented for iOS.
*/ */
let verificationResponse: VerificationResult; let verificationResponse: VerificationResult;
if (verifiableCredential.proof.type === ProofType.ED25519_2020) { if (verifiableCredential.proof.type === ProofType.ED25519_2020) {
verificationResponse = createSuccessfulVerificationResult(); verificationResponse = createSuccessfulVerificationResult();
} } else {
else{ const purpose = getPurposeFromProof(
const purpose = getPurposeFromProof(verifiableCredential.proof.proofPurpose); verifiableCredential.proof.proofPurpose,
);
const suite = selectVerificationSuite(verifiableCredential.proof); const suite = selectVerificationSuite(verifiableCredential.proof);
const vcjsOptions = { const vcjsOptions = {
purpose, purpose,
@@ -94,12 +104,11 @@ async function verifyCredentialForIos(
credential: verifiableCredential, credential: verifiableCredential,
documentLoader: jsonld.documentLoaders.xhr(), documentLoader: jsonld.documentLoaders.xhr(),
}; };
const result = await vcjs.verifyCredential(vcjsOptions); const result = await vcjs.verifyCredential(vcjsOptions);
verificationResponse = handleResponse(result, verifiableCredential); verificationResponse = handleResponse(result, verifiableCredential);
} }
if (verificationResponse.isVerified) { if (verificationResponse.isVerified) {
const statusArray = await VCVerifier.getInstance().getCredentialStatus( const statusArray = await VCVerifier.getInstance().getCredentialStatus(
verifiableCredential, verifiableCredential,
@@ -188,7 +197,9 @@ async function handleVcVerifierResponse(
verifiableCredential, verifiableCredential,
); );
} }
const isRevoked = await checkIsStatusRevoked(verificationResult.credentialStatus) const isRevoked = await checkIsStatusRevoked(
verificationResult.credentialStatus,
);
return { return {
isVerified: verificationResult.verificationStatus, isVerified: verificationResult.verificationStatus,
verificationMessage: verificationResult.verificationMessage, verificationMessage: verificationResult.verificationMessage,
@@ -209,45 +220,51 @@ async function handleVcVerifierResponse(
} }
} }
const handleStatusListVCVerification = (status: CredentialStatusResult, type: "revoked" | "valid") => { const handleStatusListVCVerification = (
status: CredentialStatusResult,
type: 'revoked' | 'valid',
) => {
const isValid = verifyStatusListVC(status.statusListVC); const isValid = verifyStatusListVC(status.statusListVC);
if (!isValid) { if (!isValid) {
throw new Error( throw new Error(
`StatusListVC verification failed for ${type} entry ${status.error}`, `StatusListVC verification failed for ${type} entry ${status.error}`,
); );
} }
} };
export async function checkIsStatusRevoked( export async function checkIsStatusRevoked(
vcStatus: Record<string, CredentialStatusResult>, vcStatus: Record<string, CredentialStatusResult>,
): Promise<boolean> { ): Promise<RevocationStatusType> {
if (!Object.keys(vcStatus).length) return false; if (!vcStatus || !Object.keys(vcStatus).length) return RevocationStatus.FALSE;
const revocationStatus = vcStatus["revocation"] as CredentialStatusResult; const revocationStatus = vcStatus['revocation'] as CredentialStatusResult;
if (!revocationStatus) return false; if (!revocationStatus) return RevocationStatus.FALSE;
const {isValid, error} = revocationStatus; const {isValid, error} = revocationStatus;
if (isValid) { if (isValid) {
// Validate the valid statuses statusList VC for iOS // Validate the valid statuses statusList VC for iOS
if (isIOS()) { if (isIOS()) {
handleStatusListVCVerification(revocationStatus, "valid") handleStatusListVCVerification(revocationStatus, 'valid');
} }
return false return RevocationStatus.FALSE;
} }
console.error(`Credential is revoked. Error: ${error?.code}, Message: ${error?.message}`);
// if there is an error fetching revocation status itself, throw error (isValid = true, error = Error) // if there is an error fetching revocation status itself, throw error (isValid = true, error = Error)
if (error) { if (error) {
throw new Error(`Error fetching revocation status. Error: ${error.code}, Message: ${error.message}`); console.error(
`Error fetching revocation status. Error: ${error.code}, Message: ${error.message}`,
);
return RevocationStatus.UNDETERMINED;
} }
// There is no error fetching revocation status, but the status is invalid (isValid = false, error = undefined) - VC is revoked // There is no error fetching revocation status, but the status is invalid (isValid = false, error = undefined) - VC is revoked
// Validate the valid statuses statusList VC for iOS // Validate the valid statuses statusList VC for iOS
if (isIOS()) { if (isIOS()) {
handleStatusListVCVerification(revocationStatus, "revoked"); handleStatusListVCVerification(revocationStatus, 'revoked');
} }
console.error(`Credential is revoked`);
// If revocation status is invalid, the credential is revoked // If revocation status is invalid, the credential is revoked
return true return RevocationStatus.TRUE;
} }
function createSuccessfulVerificationResult(): VerificationResult { function createSuccessfulVerificationResult(): VerificationResult {
@@ -298,12 +315,12 @@ export interface VerificationResult {
isVerified: boolean; isVerified: boolean;
verificationMessage: string; verificationMessage: string;
verificationErrorCode: string; verificationErrorCode: string;
isRevoked?: boolean; isRevoked?: RevocationStatusType;
} }
//TODO: Implement status list VC verification for iOS. //TODO: Implement status list VC verification for iOS.
//Currently Digital Bazaar library does not support VC 2.0 status list VC verification. //Currently Digital Bazaar library does not support VC 2.0 status list VC verification.
function verifyStatusListVC(statusListVC: string | undefined) { function verifyStatusListVC(statusListVC: Record<string, any> | undefined) {
return true; return true;
} }