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

View File

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

View File

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

View File

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

View File

@@ -69,7 +69,10 @@ final class LdpStatusChecker {
var results: [String: CredentialStatusResult] = [:]
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 {
let result = try await checkStatusEntry(entry: entry, purpose: purpose)
results[purpose] = result
@@ -175,7 +178,7 @@ final class LdpStatusChecker {
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,31 @@
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 = {
isValid: boolean;
error?: ErrorResult;
statusListVC?: string; // Available only in iOS
statusListVC?: Record<string, any>; // Available only in iOS
};
export type ErrorResult = {
code: string;
message: string;
}
};
export type VerificationSummaryResult = {
verificationStatus: boolean;
@@ -39,8 +55,8 @@ class VCVerifier {
): Promise<Record<string, CredentialStatusResult>> {
try {
return await this.vcVerifier.getCredentialStatus(
JSON.stringify(credential),
format,
JSON.stringify(credential),
format,
);
} catch (error) {
throw new Error(`Failed to get credential status: ${error}`);
@@ -53,9 +69,9 @@ class VCVerifier {
): Promise<VerificationSummaryResult> {
try {
return await this.vcVerifier.getVerificationSummary(
credentialString,
credentialFormat,
[],
credentialString,
credentialFormat,
[],
);
} catch (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 {AssertionProofPurpose} from '../../lib/jsonld-signatures/purposes/AssertionProofPurpose';
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 {TelemetryConstants} from '../telemetry/TelemetryConstants';
import {getMosipIdentifier} from '../commonUtil';
import {NativeModules} from 'react-native';
import {isAndroid, isIOS} from '../constants';
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.
// Ed25519Signature2018 proof type check is not tested with its real credential
@@ -59,10 +67,11 @@ async function verifyCredentialForAndroid(
typeof verifiableCredential === 'string'
? verifiableCredential
: JSON.stringify(verifiableCredential);
const vcVerifierResult = await VCVerifier.getInstance().getVerificationSummary(
credentialString,
credentialFormat,
);
const vcVerifierResult =
await VCVerifier.getInstance().getVerificationSummary(
credentialString,
credentialFormat,
);
return handleVcVerifierResponse(vcVerifierResult, verifiableCredential);
}
@@ -81,12 +90,13 @@ async function verifyCredentialForIos(
Since Digital Bazaar library is not able to verify ProofType: "Ed25519Signature2020",
defaulting it to return true until VcVerifier is implemented for iOS.
*/
let verificationResponse: VerificationResult;
let verificationResponse: VerificationResult;
if (verifiableCredential.proof.type === ProofType.ED25519_2020) {
verificationResponse = createSuccessfulVerificationResult();
}
else{
const purpose = getPurposeFromProof(verifiableCredential.proof.proofPurpose);
} else {
const purpose = getPurposeFromProof(
verifiableCredential.proof.proofPurpose,
);
const suite = selectVerificationSuite(verifiableCredential.proof);
const vcjsOptions = {
purpose,
@@ -94,12 +104,11 @@ async function verifyCredentialForIos(
credential: verifiableCredential,
documentLoader: jsonld.documentLoaders.xhr(),
};
const result = await vcjs.verifyCredential(vcjsOptions);
verificationResponse = handleResponse(result, verifiableCredential);
}
if (verificationResponse.isVerified) {
const statusArray = await VCVerifier.getInstance().getCredentialStatus(
verifiableCredential,
@@ -188,7 +197,9 @@ async function handleVcVerifierResponse(
verifiableCredential,
);
}
const isRevoked = await checkIsStatusRevoked(verificationResult.credentialStatus)
const isRevoked = await checkIsStatusRevoked(
verificationResult.credentialStatus,
);
return {
isVerified: verificationResult.verificationStatus,
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);
if (!isValid) {
throw new Error(
`StatusListVC verification failed for ${type} entry ${status.error}`,
`StatusListVC verification failed for ${type} entry ${status.error}`,
);
}
}
};
export async function checkIsStatusRevoked(
vcStatus: Record<string, CredentialStatusResult>,
): Promise<boolean> {
if (!Object.keys(vcStatus).length) return false;
vcStatus: Record<string, CredentialStatusResult>,
): Promise<RevocationStatusType> {
if (!vcStatus || !Object.keys(vcStatus).length) return RevocationStatus.FALSE;
const revocationStatus = vcStatus["revocation"] as CredentialStatusResult;
if (!revocationStatus) return false;
const revocationStatus = vcStatus['revocation'] as CredentialStatusResult;
if (!revocationStatus) return RevocationStatus.FALSE;
const {isValid, error} = revocationStatus;
if (isValid) {
// Validate the valid statuses statusList VC for iOS
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 (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
// Validate the valid statuses statusList VC for iOS
if (isIOS()) {
handleStatusListVCVerification(revocationStatus, "revoked");
handleStatusListVCVerification(revocationStatus, 'revoked');
}
console.error(`Credential is revoked`);
// If revocation status is invalid, the credential is revoked
return true
return RevocationStatus.TRUE;
}
function createSuccessfulVerificationResult(): VerificationResult {
@@ -298,12 +315,12 @@ export interface VerificationResult {
isVerified: boolean;
verificationMessage: string;
verificationErrorCode: string;
isRevoked?: boolean;
isRevoked?: RevocationStatusType;
}
//TODO: Implement status list VC verification for iOS.
//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;
}