mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-10 05:58:01 -05:00
[INJIMOB-3389]: Implement singleton pattern for OVP and VCI client Modules (#2005)
* [INJIMOB-3389]: Implement Singleton pattern for Openid4vp Module Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-3389]: Implement singleton pattern for VCI client module Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-3389]: Update constructor for Openid4Vp singleton Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> --------- Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com>
This commit is contained in:
@@ -399,3 +399,6 @@ fileignoreconfig:
|
||||
- filename: machines/openID4VP/openID4VPModel.ts
|
||||
checksum: 5d1ed430f84852d6c85bc439c47641cfb5b19cbd1a03faf8918429685db51e07
|
||||
version: ""
|
||||
- filename: shared/openID4VP/OpenID4VPHelper.ts
|
||||
checksum: 2ab5f935ea3d1ec4d109d8614c2246f40e284594288566338f185611470e6928
|
||||
version: "1.0"
|
||||
|
||||
@@ -13,11 +13,11 @@ import {
|
||||
updateCredentialInformation,
|
||||
verifyCredentialData,
|
||||
} from '../../shared/openId4VCI/Utils';
|
||||
import {VciClient} from '../../shared/vciClient/VciClient';
|
||||
import VciClient from '../../shared/vciClient/VciClient';
|
||||
import {issuerType} from './IssuersMachine';
|
||||
import {setItem} from '../store';
|
||||
import {API_CACHED_STORAGE_KEYS} from '../../shared/constants';
|
||||
import { createCacheObject } from '../../shared/Utils';
|
||||
import {createCacheObject} from '../../shared/Utils';
|
||||
|
||||
export const IssuersService = () => {
|
||||
return {
|
||||
@@ -77,31 +77,32 @@ export const IssuersService = () => {
|
||||
cNonce: cNonce,
|
||||
});
|
||||
};
|
||||
const credential = await VciClient.requestCredentialFromTrustedIssuer(
|
||||
constructIssuerMetaData(
|
||||
context.selectedIssuer,
|
||||
context.selectedCredentialType,
|
||||
context.selectedCredentialType.scope,
|
||||
),
|
||||
{
|
||||
clientId: context.selectedIssuer.client_id,
|
||||
redirectUri: context.selectedIssuer.redirect_uri,
|
||||
},
|
||||
getProofJwt,
|
||||
navigateToAuthView,
|
||||
);
|
||||
const credential =
|
||||
await VciClient.getInstance().requestCredentialFromTrustedIssuer(
|
||||
constructIssuerMetaData(
|
||||
context.selectedIssuer,
|
||||
context.selectedCredentialType,
|
||||
context.selectedCredentialType.scope,
|
||||
),
|
||||
{
|
||||
clientId: context.selectedIssuer.client_id,
|
||||
redirectUri: context.selectedIssuer.redirect_uri,
|
||||
},
|
||||
getProofJwt,
|
||||
navigateToAuthView,
|
||||
);
|
||||
return updateCredentialInformation(context, credential);
|
||||
},
|
||||
sendTxCode: async (context: any) => {
|
||||
await VciClient.client.sendTxCodeFromJS(context.txCode);
|
||||
await VciClient.getInstance().sendTxCode(context.txCode);
|
||||
},
|
||||
|
||||
sendConsentGiven: async () => {
|
||||
await VciClient.client.sendIssuerTrustResponseFromJS(true);
|
||||
await VciClient.getInstance().sendIssuerConsent(true);
|
||||
},
|
||||
|
||||
sendConsentNotGiven: async () => {
|
||||
await VciClient.client.sendIssuerTrustResponseFromJS(false);
|
||||
await VciClient.getInstance().sendIssuerConsent(false);
|
||||
},
|
||||
|
||||
checkIssuerIdInStoredTrustedIssuers: async (context: any) => {
|
||||
@@ -144,7 +145,7 @@ export const IssuersService = () => {
|
||||
) => {
|
||||
let issuer = issuerMetadata as issuerType;
|
||||
issuer.issuer_id = issuer.credential_issuer;
|
||||
const wellknownCacheObject= createCacheObject(issuer)
|
||||
const wellknownCacheObject = createCacheObject(issuer);
|
||||
await setItem(
|
||||
API_CACHED_STORAGE_KEYS.fetchIssuerWellknownConfig(issuer.issuer_id),
|
||||
wellknownCacheObject,
|
||||
@@ -194,7 +195,7 @@ export const IssuersService = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const credential = await VciClient.requestCredentialByOffer(
|
||||
const credential = await VciClient.getInstance().requestCredentialByOffer(
|
||||
context.qrData,
|
||||
getTxCode,
|
||||
getSignedProofJwt,
|
||||
@@ -222,7 +223,7 @@ export const IssuersService = () => {
|
||||
true,
|
||||
context.cNonce,
|
||||
);
|
||||
await VciClient.client.sendProofFromJS(proofJWT);
|
||||
await VciClient.getInstance().sendProof(proofJWT);
|
||||
return proofJWT;
|
||||
},
|
||||
constructProofForTrustedIssuers: async (context: any) => {
|
||||
@@ -237,7 +238,7 @@ export const IssuersService = () => {
|
||||
false,
|
||||
context.cNonce,
|
||||
);
|
||||
await VciClient.client.sendProofFromJS(proofJWT);
|
||||
await VciClient.getInstance().sendProof(proofJWT);
|
||||
return proofJWT;
|
||||
},
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {JSONPath} from 'jsonpath-plus';
|
||||
import {VCShareFlowType} from '../../shared/Utils';
|
||||
import {ActivityLogEvents} from '../activityLog';
|
||||
import {VPShareActivityLog} from '../../components/VPShareActivityLogEvent';
|
||||
import {OpenID4VP} from '../../shared/openID4VP/OpenID4VP';
|
||||
import OpenID4VP from '../../shared/openID4VP/OpenID4VP';
|
||||
import {VCFormat} from '../../shared/VCFormat';
|
||||
import {
|
||||
getIssuerAuthenticationAlorithmForMdocVC,
|
||||
@@ -218,7 +218,7 @@ export const openID4VPActions = (model: any) => {
|
||||
),
|
||||
|
||||
shareDeclineStatus: () => {
|
||||
OpenID4VP.sendErrorToVerifier(
|
||||
OpenID4VP.getInstance().sendErrorToVerifier(
|
||||
OVP_ERROR_MESSAGES.DECLINED,
|
||||
OVP_ERROR_CODE.DECLINED,
|
||||
);
|
||||
@@ -290,7 +290,7 @@ function getVcsMatchingAuthRequest(context, event) {
|
||||
}
|
||||
|
||||
if (Object.keys(matchingVCs).length === 0) {
|
||||
OpenID4VP.sendErrorToVerifier(
|
||||
OpenID4VP.getInstance().sendErrorToVerifier(
|
||||
OVP_ERROR_MESSAGES.NO_MATCHING_VCS,
|
||||
OVP_ERROR_CODE.NO_MATCHING_VCS,
|
||||
);
|
||||
|
||||
@@ -5,16 +5,16 @@ import {
|
||||
} from '../../shared/cryptoutil/cryptoUtil';
|
||||
import {getJWK, hasKeyPair} from '../../shared/openId4VCI/Utils';
|
||||
import base64url from 'base64url';
|
||||
import {
|
||||
constructDetachedJWT,
|
||||
isClientValidationRequired,
|
||||
OpenID4VP,
|
||||
} from '../../shared/openID4VP/OpenID4VP';
|
||||
import OpenID4VP from '../../shared/openID4VP/OpenID4VP';
|
||||
import {VCFormat} from '../../shared/VCFormat';
|
||||
import {KeyTypes} from '../../shared/cryptoutil/KeyTypes';
|
||||
import {getMdocAuthenticationAlorithm} from '../../components/VC/common/VCUtils';
|
||||
import {isIOS} from '../../shared/constants';
|
||||
import {canonicalize} from '../../shared/Utils';
|
||||
import {
|
||||
constructDetachedJWT,
|
||||
isClientValidationRequired,
|
||||
} from '../../shared/openID4VP/OpenID4VPHelper';
|
||||
|
||||
const signatureSuite = 'JsonWebSignature2020';
|
||||
|
||||
@@ -29,8 +29,7 @@ export const openID4VPServices = () => {
|
||||
},
|
||||
|
||||
getAuthenticationResponse: (context: any) => async () => {
|
||||
OpenID4VP.initialize();
|
||||
const serviceRes = await OpenID4VP.authenticateVerifier(
|
||||
const serviceRes = await OpenID4VP.getInstance().authenticateVerifier(
|
||||
context.urlEncodedAuthorizationRequest,
|
||||
context.trustedVerifiers,
|
||||
);
|
||||
@@ -48,10 +47,12 @@ export const openID4VPServices = () => {
|
||||
},
|
||||
|
||||
sendVP: (context: any) => async () => {
|
||||
const openid = OpenID4VP.getInstance();
|
||||
|
||||
const jwk = await getJWK(context.publicKey, context.keyType);
|
||||
const holderId = 'did:jwk:' + base64url(JSON.stringify(jwk)) + '#0';
|
||||
|
||||
const unSignedVpTokens = await OpenID4VP.constructUnsignedVPToken(
|
||||
const unSignedVpTokens = await openid.constructUnsignedVPToken(
|
||||
context.selectedVCs,
|
||||
holderId,
|
||||
signatureSuite,
|
||||
@@ -136,9 +137,7 @@ export const openID4VPServices = () => {
|
||||
vpTokenSigningResultMap[formatType] = signedData;
|
||||
}
|
||||
}
|
||||
return await OpenID4VP.shareVerifiablePresentation(
|
||||
vpTokenSigningResultMap,
|
||||
);
|
||||
return await openid.shareVerifiablePresentation(vpTokenSigningResultMap);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,133 +1,142 @@
|
||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import React, {useEffect, useRef, useState, useCallback} from 'react';
|
||||
import psl from 'psl';
|
||||
import {
|
||||
View,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
TouchableOpacity,
|
||||
Text, BackHandler
|
||||
View,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
BackHandler,
|
||||
} from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { VciClient } from '../shared/vciClient/VciClient';
|
||||
import { Theme } from '../components/ui/styleUtils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {WebView} from 'react-native-webview';
|
||||
import {Ionicons} from '@expo/vector-icons';
|
||||
import VciClient from '../shared/vciClient/VciClient';
|
||||
import {Theme} from '../components/ui/styleUtils';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
|
||||
const AuthWebViewScreen: React.FC<any> = ({ route, navigation }) => {
|
||||
const { authorizationURL, clientId, redirectUri, controller } = route.params;
|
||||
const webViewRef = useRef<WebView>(null);
|
||||
const [showWebView, setShowWebView] = useState(false);
|
||||
const { t } = useTranslation('authWebView');
|
||||
const AuthWebViewScreen: React.FC<any> = ({route, navigation}) => {
|
||||
const {authorizationURL, clientId, redirectUri, controller} = route.params;
|
||||
const webViewRef = useRef<WebView>(null);
|
||||
const [showWebView, setShowWebView] = useState(false);
|
||||
const {t} = useTranslation('authWebView');
|
||||
|
||||
const hostName = new URL(authorizationURL).hostname; // example.mosip.net
|
||||
const parsed = psl.parse(hostName);
|
||||
const rootDomain = parsed.domain || hostName;
|
||||
const ALERT_TITLE = t('title', {
|
||||
wallet: "Inji Wallet",
|
||||
domain: rootDomain || 'mosip.net'
|
||||
});
|
||||
const ALERT_MESSAGE = t('message');
|
||||
const hostName = new URL(authorizationURL).hostname; // example.mosip.net
|
||||
const parsed = psl.parse(hostName);
|
||||
const rootDomain = parsed.domain || hostName;
|
||||
const ALERT_TITLE = t('title', {
|
||||
wallet: 'Inji Wallet',
|
||||
domain: rootDomain || 'mosip.net',
|
||||
});
|
||||
const ALERT_MESSAGE = t('message');
|
||||
|
||||
const handleBackPress = useCallback(() => {
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const handleBackPress = useCallback(() => {
|
||||
return true;
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!authorizationURL || !clientId || !redirectUri) {
|
||||
console.error('Missing required parameters for authentication');
|
||||
navigation.goBack();
|
||||
return;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!authorizationURL || !clientId || !redirectUri) {
|
||||
console.error('Missing required parameters for authentication');
|
||||
navigation.goBack();
|
||||
return;
|
||||
}
|
||||
navigation.setOptions({gestureEnabled: false});
|
||||
|
||||
navigation.setOptions({ gestureEnabled: false });
|
||||
|
||||
const backHandler = BackHandler.addEventListener('hardwareBackPress', handleBackPress);
|
||||
|
||||
Alert.alert(ALERT_TITLE, ALERT_MESSAGE, [
|
||||
{
|
||||
text: t('cancel'),
|
||||
style: 'cancel',
|
||||
onPress: () => {
|
||||
controller.CANCEL();
|
||||
navigation.goBack();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: t('continue'),
|
||||
style: 'default',
|
||||
onPress: () => setShowWebView(true),
|
||||
},
|
||||
]);
|
||||
|
||||
return () => backHandler.remove();
|
||||
}, [authorizationURL, clientId, redirectUri, navigation, controller, handleBackPress]);
|
||||
|
||||
|
||||
const handleNavigationRequest = (request: any) => {
|
||||
const { url } = request;
|
||||
if (url.startsWith(redirectUri)) {
|
||||
try {
|
||||
const uri = new URL(url);
|
||||
const code = uri.searchParams.get('code');
|
||||
|
||||
if (!code) {
|
||||
controller.CANCEL();
|
||||
navigation.goBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
VciClient.client.sendAuthCodeFromJS(code);
|
||||
navigation.goBack();
|
||||
return false;
|
||||
} catch (err: any) {
|
||||
console.error('Error parsing redirect URL:', err);
|
||||
controller.CANCEL();
|
||||
navigation.goBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
const Header = () => (
|
||||
<View style={Theme.AuthWebViewScreenStyle.header}>
|
||||
<TouchableOpacity onPress={() => {
|
||||
controller.CANCEL();
|
||||
navigation.goBack();
|
||||
}}>
|
||||
<Ionicons name="arrow-back" size={24} color="black" />
|
||||
</TouchableOpacity>
|
||||
<Text style={Theme.AuthWebViewScreenStyle.headerText}>Authenticate</Text>
|
||||
<View style={{ width: 24 }} />
|
||||
</View>
|
||||
const backHandler = BackHandler.addEventListener(
|
||||
'hardwareBackPress',
|
||||
handleBackPress,
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Header />
|
||||
{showWebView && (
|
||||
<WebView
|
||||
ref={webViewRef}
|
||||
originWhitelist={['*']}
|
||||
source={{ uri: authorizationURL }}
|
||||
onShouldStartLoadWithRequest={handleNavigationRequest}
|
||||
startInLoadingState
|
||||
renderLoading={() => (
|
||||
<View style={Theme.AuthWebViewScreenStyle.loader}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
)}
|
||||
javaScriptEnabled
|
||||
incognito
|
||||
sharedCookiesEnabled={false}
|
||||
thirdPartyCookiesEnabled={false}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
Alert.alert(ALERT_TITLE, ALERT_MESSAGE, [
|
||||
{
|
||||
text: t('cancel'),
|
||||
style: 'cancel',
|
||||
onPress: () => {
|
||||
controller.CANCEL();
|
||||
navigation.goBack();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: t('continue'),
|
||||
style: 'default',
|
||||
onPress: () => setShowWebView(true),
|
||||
},
|
||||
]);
|
||||
|
||||
return () => backHandler.remove();
|
||||
}, [
|
||||
authorizationURL,
|
||||
clientId,
|
||||
redirectUri,
|
||||
navigation,
|
||||
controller,
|
||||
handleBackPress,
|
||||
]);
|
||||
|
||||
const handleNavigationRequest = (request: any) => {
|
||||
const {url} = request;
|
||||
if (url.startsWith(redirectUri)) {
|
||||
try {
|
||||
const uri = new URL(url);
|
||||
const code = uri.searchParams.get('code');
|
||||
|
||||
if (!code) {
|
||||
controller.CANCEL();
|
||||
navigation.goBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
VciClient.getInstance().sendAuthCode(code);
|
||||
navigation.goBack();
|
||||
return false;
|
||||
} catch (err: any) {
|
||||
console.error('Error parsing redirect URL:', err);
|
||||
controller.CANCEL();
|
||||
navigation.goBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const Header = () => (
|
||||
<View style={Theme.AuthWebViewScreenStyle.header}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
controller.CANCEL();
|
||||
navigation.goBack();
|
||||
}}>
|
||||
<Ionicons name="arrow-back" size={24} color="black" />
|
||||
</TouchableOpacity>
|
||||
<Text style={Theme.AuthWebViewScreenStyle.headerText}>Authenticate</Text>
|
||||
<View style={{width: 24}} />
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Header />
|
||||
{showWebView && (
|
||||
<WebView
|
||||
ref={webViewRef}
|
||||
originWhitelist={['*']}
|
||||
source={{uri: authorizationURL}}
|
||||
onShouldStartLoadWithRequest={handleNavigationRequest}
|
||||
startInLoadingState
|
||||
renderLoading={() => (
|
||||
<View style={Theme.AuthWebViewScreenStyle.loader}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
)}
|
||||
javaScriptEnabled
|
||||
incognito
|
||||
sharedCookiesEnabled={false}
|
||||
thirdPartyCookiesEnabled={false}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthWebViewScreen;
|
||||
|
||||
@@ -28,7 +28,7 @@ import {SvgImage} from '../../components/ui/svg';
|
||||
import {Loader} from '../../components/ui/Loader';
|
||||
import {Icon} from 'react-native-elements';
|
||||
import {ScanLayoutProps} from '../../routes/routeTypes';
|
||||
import {OpenID4VP} from '../../shared/openID4VP/OpenID4VP';
|
||||
import OpenID4VP from '../../shared/openID4VP/OpenID4VP';
|
||||
import {GlobalContext} from '../../shared/GlobalContext';
|
||||
import {APP_EVENTS} from '../../machines/app';
|
||||
import {useScanScreen} from './ScanScreenController';
|
||||
@@ -47,7 +47,7 @@ export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
|
||||
if (controller.errorModal.show && controller.isOVPViaDeepLink) {
|
||||
const timeout = setTimeout(
|
||||
() => {
|
||||
OpenID4VP.sendErrorToVerifier(
|
||||
OpenID4VP.getInstance().sendErrorToVerifier(
|
||||
OVP_ERROR_MESSAGES.NO_MATCHING_VCS,
|
||||
OVP_ERROR_CODE.NO_MATCHING_VCS,
|
||||
);
|
||||
@@ -107,7 +107,7 @@ export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
|
||||
});
|
||||
|
||||
const handleDismiss = () => {
|
||||
OpenID4VP.sendErrorToVerifier(
|
||||
OpenID4VP.getInstance().sendErrorToVerifier(
|
||||
OVP_ERROR_MESSAGES.DECLINED,
|
||||
OVP_ERROR_CODE.DECLINED,
|
||||
);
|
||||
@@ -120,7 +120,7 @@ export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
|
||||
};
|
||||
|
||||
const handleRejectButtonEvent = () => {
|
||||
OpenID4VP.sendErrorToVerifier(
|
||||
OpenID4VP.getInstance().sendErrorToVerifier(
|
||||
OVP_ERROR_MESSAGES.DECLINED,
|
||||
OVP_ERROR_CODE.DECLINED,
|
||||
);
|
||||
@@ -209,7 +209,7 @@ export const SendVPScreen: React.FC<ScanLayoutProps> = props => {
|
||||
const getPrimaryButtonEvent = () => {
|
||||
if (controller.showConfirmationPopup && controller.isOVPViaDeepLink) {
|
||||
return () => {
|
||||
OpenID4VP.sendErrorToVerifier(
|
||||
OpenID4VP.getInstance().sendErrorToVerifier(
|
||||
OVP_ERROR_MESSAGES.DECLINED,
|
||||
OVP_ERROR_CODE.DECLINED,
|
||||
);
|
||||
|
||||
@@ -4,21 +4,28 @@ import {
|
||||
SelectedCredentialsForVPSharing,
|
||||
VC,
|
||||
} from '../../machines/VerifiableCredential/VCMetaMachine/vc';
|
||||
import {createSignatureED, encodeB64} from '../cryptoutil/cryptoUtil';
|
||||
import getAllConfigurations from '../api';
|
||||
import {base64ToByteArray, parseJSON} from '../Utils';
|
||||
import {walletMetadata} from './walletMetadata';
|
||||
import {getWalletMetadata, isClientValidationRequired} from './OpenID4VPHelper';
|
||||
import {parseJSON} from '../Utils';
|
||||
|
||||
export const OpenID4VP_Proof_Sign_Algo = 'EdDSA';
|
||||
|
||||
export class OpenID4VP {
|
||||
static InjiOpenID4VP = NativeModules.InjiOpenID4VP;
|
||||
class OpenID4VP {
|
||||
private static instance: OpenID4VP;
|
||||
private InjiOpenID4VP = NativeModules.InjiOpenID4VP;
|
||||
|
||||
static initialize() {
|
||||
OpenID4VP.InjiOpenID4VP.init(__AppId.getValue());
|
||||
private constructor() {
|
||||
this.InjiOpenID4VP.init(__AppId.getValue());
|
||||
}
|
||||
|
||||
static async authenticateVerifier(
|
||||
public static getInstance(): OpenID4VP {
|
||||
if (!OpenID4VP.instance) {
|
||||
OpenID4VP.instance = new OpenID4VP();
|
||||
}
|
||||
return OpenID4VP.instance;
|
||||
}
|
||||
|
||||
async authenticateVerifier(
|
||||
urlEncodedAuthorizationRequest: string,
|
||||
trustedVerifiersList: any,
|
||||
) {
|
||||
@@ -26,7 +33,7 @@ export class OpenID4VP {
|
||||
const metadata = (await getWalletMetadata()) || walletMetadata;
|
||||
|
||||
const authenticationResponse =
|
||||
await OpenID4VP.InjiOpenID4VP.authenticateVerifier(
|
||||
await this.InjiOpenID4VP.authenticateVerifier(
|
||||
urlEncodedAuthorizationRequest,
|
||||
trustedVerifiersList,
|
||||
metadata,
|
||||
@@ -35,83 +42,49 @@ export class OpenID4VP {
|
||||
return JSON.parse(authenticationResponse);
|
||||
}
|
||||
|
||||
static async constructUnsignedVPToken(
|
||||
async constructUnsignedVPToken(
|
||||
selectedVCs: Record<string, VC[]>,
|
||||
holderId,
|
||||
signatureAlgorithm,
|
||||
holderId: string,
|
||||
signatureAlgorithm: string,
|
||||
) {
|
||||
let updatedSelectedVCs = this.processSelectedVCs(selectedVCs);
|
||||
|
||||
const unSignedVpTokens =
|
||||
await OpenID4VP.InjiOpenID4VP.constructUnsignedVPToken(
|
||||
updatedSelectedVCs,
|
||||
holderId,
|
||||
signatureAlgorithm,
|
||||
);
|
||||
const updatedSelectedVCs = this.processSelectedVCs(selectedVCs);
|
||||
const unSignedVpTokens = await this.InjiOpenID4VP.constructUnsignedVPToken(
|
||||
updatedSelectedVCs,
|
||||
holderId,
|
||||
signatureAlgorithm,
|
||||
);
|
||||
return parseJSON(unSignedVpTokens);
|
||||
}
|
||||
|
||||
static async shareVerifiablePresentation(
|
||||
async shareVerifiablePresentation(
|
||||
vpTokenSigningResultMap: Record<string, any>,
|
||||
) {
|
||||
return await OpenID4VP.InjiOpenID4VP.shareVerifiablePresentation(
|
||||
return await this.InjiOpenID4VP.shareVerifiablePresentation(
|
||||
vpTokenSigningResultMap,
|
||||
);
|
||||
}
|
||||
|
||||
static sendErrorToVerifier(errorMessage: string, errorCode: string) {
|
||||
OpenID4VP.InjiOpenID4VP.sendErrorToVerifier(errorMessage, errorCode);
|
||||
sendErrorToVerifier(errorMessage: string, errorCode: string) {
|
||||
this.InjiOpenID4VP.sendErrorToVerifier(errorMessage, errorCode);
|
||||
}
|
||||
|
||||
private static processSelectedVCs(selectedVCs: Record<string, VC[]>) {
|
||||
private processSelectedVCs(selectedVCs: Record<string, VC[]>) {
|
||||
const selectedVcsData: SelectedCredentialsForVPSharing = {};
|
||||
Object.entries(selectedVCs).forEach(([inputDescriptorId, vcsArray]) => {
|
||||
vcsArray.forEach(vcData => {
|
||||
const credentialFormat = vcData.vcMetadata.format;
|
||||
vcData = vcData.verifiableCredential.credential;
|
||||
const credential = vcData.verifiableCredential.credential;
|
||||
if (!selectedVcsData[inputDescriptorId]) {
|
||||
selectedVcsData[inputDescriptorId] = {};
|
||||
}
|
||||
if (!selectedVcsData[inputDescriptorId][credentialFormat]) {
|
||||
selectedVcsData[inputDescriptorId][credentialFormat] = [];
|
||||
}
|
||||
selectedVcsData[inputDescriptorId][credentialFormat].push(vcData);
|
||||
selectedVcsData[inputDescriptorId][credentialFormat].push(credential);
|
||||
});
|
||||
});
|
||||
return selectedVcsData;
|
||||
}
|
||||
}
|
||||
|
||||
export async function constructDetachedJWT(
|
||||
privateKey: any,
|
||||
vpToken: string,
|
||||
keyType: string,
|
||||
): Promise<string> {
|
||||
const jwtHeader = {
|
||||
alg: OpenID4VP_Proof_Sign_Algo,
|
||||
crit: ['b64'],
|
||||
b64: false,
|
||||
};
|
||||
const header64 = encodeB64(JSON.stringify(jwtHeader));
|
||||
const headerBytes = new TextEncoder().encode(header64);
|
||||
const vpTokenBytes = base64ToByteArray(vpToken);
|
||||
const payloadBytes = new Uint8Array([...headerBytes, 46, ...vpTokenBytes]);
|
||||
|
||||
const signature = await createSignatureED(privateKey, payloadBytes);
|
||||
|
||||
return header64 + '..' + signature;
|
||||
}
|
||||
|
||||
export async function isClientValidationRequired() {
|
||||
const config = await getAllConfigurations();
|
||||
return config.openid4vpClientValidation === 'true';
|
||||
}
|
||||
|
||||
export async function getWalletMetadata() {
|
||||
const config = await getAllConfigurations();
|
||||
if (!config.walletMetadata) {
|
||||
return null;
|
||||
}
|
||||
const walletMetadata = JSON.parse(config.walletMetadata);
|
||||
return walletMetadata;
|
||||
}
|
||||
export default OpenID4VP;
|
||||
|
||||
38
shared/openID4VP/OpenID4VPHelper.ts
Normal file
38
shared/openID4VP/OpenID4VPHelper.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import {createSignatureED, encodeB64} from '../cryptoutil/cryptoUtil';
|
||||
import {base64ToByteArray} from '../Utils';
|
||||
import getAllConfigurations from '../api';
|
||||
import {OpenID4VP_Proof_Sign_Algo} from './OpenID4VP';
|
||||
|
||||
export async function constructDetachedJWT(
|
||||
privateKey: any,
|
||||
vpToken: string,
|
||||
keyType: string,
|
||||
): Promise<string> {
|
||||
const jwtHeader = {
|
||||
alg: OpenID4VP_Proof_Sign_Algo,
|
||||
crit: ['b64'],
|
||||
b64: false,
|
||||
};
|
||||
const header64 = encodeB64(JSON.stringify(jwtHeader));
|
||||
const headerBytes = new TextEncoder().encode(header64);
|
||||
const vpTokenBytes = base64ToByteArray(vpToken);
|
||||
const payloadBytes = new Uint8Array([...headerBytes, 46, ...vpTokenBytes]);
|
||||
|
||||
const signature = await createSignatureED(privateKey, payloadBytes);
|
||||
|
||||
return header64 + '..' + signature;
|
||||
}
|
||||
|
||||
export async function isClientValidationRequired() {
|
||||
const config = await getAllConfigurations();
|
||||
return config.openid4vpClientValidation === 'true';
|
||||
}
|
||||
|
||||
export async function getWalletMetadata() {
|
||||
const config = await getAllConfigurations();
|
||||
if (!config.walletMetadata) {
|
||||
return null;
|
||||
}
|
||||
const walletMetadata = JSON.parse(config.walletMetadata);
|
||||
return walletMetadata;
|
||||
}
|
||||
@@ -4,14 +4,38 @@ import {VerifiableCredential} from '../../machines/VerifiableCredential/VCMetaMa
|
||||
|
||||
const emitter = new NativeEventEmitter(NativeModules.InjiVciClient);
|
||||
|
||||
export class VciClient {
|
||||
static get client() {
|
||||
const nativeClient = NativeModules.InjiVciClient;
|
||||
nativeClient.init(__AppId.getValue());
|
||||
return nativeClient;
|
||||
class VciClient {
|
||||
private static instance: VciClient;
|
||||
private InjiVciClient = NativeModules.InjiVciClient;
|
||||
|
||||
private constructor() {
|
||||
this.InjiVciClient.init(__AppId.getValue());
|
||||
}
|
||||
|
||||
static async requestCredentialByOffer(
|
||||
static getInstance(): VciClient {
|
||||
if (!VciClient.instance) {
|
||||
VciClient.instance = new VciClient();
|
||||
}
|
||||
return VciClient.instance;
|
||||
}
|
||||
|
||||
async sendProof(jwt: string) {
|
||||
this.InjiVciClient.sendProofFromJS(jwt);
|
||||
}
|
||||
|
||||
async sendAuthCode(authCode: string) {
|
||||
this.InjiVciClient.sendAuthCodeFromJS(authCode);
|
||||
}
|
||||
|
||||
async sendTxCode(code: string) {
|
||||
this.InjiVciClient.sendTxCodeFromJS(code);
|
||||
}
|
||||
|
||||
async sendIssuerConsent(consent: boolean) {
|
||||
this.InjiVciClient.sendIssuerTrustResponseFromJS(consent);
|
||||
}
|
||||
|
||||
async requestCredentialByOffer(
|
||||
credentialOffer: string,
|
||||
getTxCode: (
|
||||
inputMode: string | undefined,
|
||||
@@ -19,22 +43,17 @@ export class VciClient {
|
||||
length: number | undefined,
|
||||
) => void,
|
||||
getProofJwt: (
|
||||
accesToken: string,
|
||||
accessToken: string,
|
||||
cNonce: string | null,
|
||||
issuerMetadata: object,
|
||||
credentialConfigurationId: string,
|
||||
) => void,
|
||||
navigateToAuthView: (authorizationEndpoint: string) => void,
|
||||
requestTrustIssuerConsent: (issuerMetadata: object) => void,
|
||||
) {
|
||||
): Promise<VerifiableCredential> {
|
||||
const proofListener = emitter.addListener(
|
||||
'onRequestProof',
|
||||
async ({
|
||||
accessToken,
|
||||
cNonce,
|
||||
issuerMetadata,
|
||||
credentialConfigurationId,
|
||||
}) => {
|
||||
({accessToken, cNonce, issuerMetadata, credentialConfigurationId}) => {
|
||||
getProofJwt(
|
||||
accessToken,
|
||||
cNonce,
|
||||
@@ -46,27 +65,28 @@ export class VciClient {
|
||||
|
||||
const authListener = emitter.addListener(
|
||||
'onRequestAuthCode',
|
||||
async ({authorizationEndpoint}) => {
|
||||
({authorizationEndpoint}) => {
|
||||
navigateToAuthView(authorizationEndpoint);
|
||||
},
|
||||
);
|
||||
|
||||
const txCodeListener = emitter.addListener(
|
||||
'onRequestTxCode',
|
||||
async ({inputMode, description, length}) => {
|
||||
({inputMode, description, length}) => {
|
||||
getTxCode(inputMode, description, length);
|
||||
},
|
||||
);
|
||||
|
||||
const trustIssuerListener = emitter.addListener(
|
||||
'onCheckIssuerTrust',
|
||||
async ({issuerMetadata}) => {
|
||||
const issuerMetadataObject = JSON.parse(issuerMetadata);
|
||||
requestTrustIssuerConsent(issuerMetadataObject);
|
||||
({issuerMetadata}) => {
|
||||
requestTrustIssuerConsent(JSON.parse(issuerMetadata));
|
||||
},
|
||||
);
|
||||
|
||||
let response = '';
|
||||
try {
|
||||
response = await VciClient.client.requestCredentialByOffer(
|
||||
response = await this.InjiVciClient.requestCredentialByOffer(
|
||||
credentialOffer,
|
||||
JSON.stringify({
|
||||
clientId: 'wallet',
|
||||
@@ -76,24 +96,25 @@ export class VciClient {
|
||||
} catch (error) {
|
||||
console.error('Error requesting credential by offer:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
proofListener.remove();
|
||||
authListener.remove();
|
||||
txCodeListener.remove();
|
||||
trustIssuerListener.remove();
|
||||
}
|
||||
|
||||
proofListener.remove();
|
||||
authListener.remove();
|
||||
txCodeListener.remove();
|
||||
trustIssuerListener.remove();
|
||||
return JSON.parse(response) as VerifiableCredential;
|
||||
}
|
||||
|
||||
static async requestCredentialFromTrustedIssuer(
|
||||
resolvedIssuerMetaData: Object,
|
||||
clientMetadata: Object,
|
||||
getProofJwt: (accessToken: string, cNonce: string) => Promise<void>,
|
||||
async requestCredentialFromTrustedIssuer(
|
||||
resolvedIssuerMetaData: object,
|
||||
clientMetadata: object,
|
||||
getProofJwt: (accessToken: string, cNonce: string) => void,
|
||||
navigateToAuthView: (authorizationEndpoint: string) => void,
|
||||
) {
|
||||
): Promise<VerifiableCredential> {
|
||||
const proofListener = emitter.addListener(
|
||||
'onRequestProof',
|
||||
async ({accessToken, cNonce}) => {
|
||||
({accessToken, cNonce}) => {
|
||||
getProofJwt(accessToken, cNonce);
|
||||
proofListener.remove();
|
||||
},
|
||||
@@ -105,20 +126,23 @@ export class VciClient {
|
||||
navigateToAuthView(authorizationEndpoint);
|
||||
},
|
||||
);
|
||||
|
||||
let response = '';
|
||||
try {
|
||||
response = await VciClient.client.requestCredentialFromTrustedIssuer(
|
||||
response = await this.InjiVciClient.requestCredentialFromTrustedIssuer(
|
||||
JSON.stringify(resolvedIssuerMetaData),
|
||||
JSON.stringify(clientMetadata),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error requesting credential from trusted issuer:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
proofListener.remove();
|
||||
authListener.remove();
|
||||
}
|
||||
|
||||
proofListener.remove();
|
||||
authListener.remove();
|
||||
|
||||
return JSON.parse(response);
|
||||
return JSON.parse(response) as VerifiableCredential;
|
||||
}
|
||||
}
|
||||
|
||||
export default VciClient;
|
||||
|
||||
Reference in New Issue
Block a user