VC binding initial commit

This commit is contained in:
Monobikash Das
2022-12-09 17:37:22 +05:30
parent 7ed128595a
commit b27f3aef4a
22 changed files with 638 additions and 180 deletions

View File

@@ -3,7 +3,7 @@
buildscript { buildscript {
ext { ext {
buildToolsVersion = "29.0.3" buildToolsVersion = "29.0.3"
minSdkVersion = 21 minSdkVersion = 23
compileSdkVersion = 30 compileSdkVersion = 30
targetSdkVersion = 30 targetSdkVersion = 30
} }

View File

@@ -14,5 +14,9 @@
"id": "Id", "id": "Id",
"nationalCard": "National Card", "nationalCard": "National Card",
"uin": "UIN", "uin": "UIN",
"enableVerification": "Enable Verification",
"profileAuthenticated": "Profile is authenticated!",
"offlineAuthDisabledHeader": "Offline Authentication disabled!",
"offlineAuthDisabledMessage": "Click 'Enable Authentication' to enable this credentials to be used for offline authentication.",
"vid": "VID" "vid": "VID"
} }

View File

@@ -2,10 +2,10 @@ import { formatDistanceToNow } from 'date-fns';
import React from 'react'; import React from 'react';
import * as DateFnsLocale from '../lib/date-fns/locale'; import * as DateFnsLocale from '../lib/date-fns/locale';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Image, ImageBackground } from 'react-native'; import { Image, ImageBackground, View } from 'react-native';
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
import { VC, CredentialSubject, LocalizedField } from '../types/vc'; import { VC, CredentialSubject, LocalizedField } from '../types/vc';
import { Column, Row, Text } from './ui'; import { Button, Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils'; import { Theme } from './ui/styleUtils';
import { TextItem } from './ui/TextItem'; import { TextItem } from './ui/TextItem';
import { VcItemTags } from './VcItemTags'; import { VcItemTags } from './VcItemTags';
@@ -271,12 +271,43 @@ export const VcDetails: React.FC<VcDetailsProps> = (props) => {
text={reason.message} text={reason.message}
/> />
))} ))}
{props.isBindingPending && (
<ImageBackground
borderRadius={10}
style={Theme.Styles.openCardBgContainer}
source={Theme.OpenCard}>
<Column>
<Icon name="lightbulb" color={'#e8a94f'} size={40} />
<Text
style={{ flex: 1 }}
weight="semibold"
size="small"
color={Theme.Colors.Details}
align="left">
{t('offlineAuthDisabledHeader')}
</Text>
<Text
style={{ flex: 1 }}
weight="regular"
size="small"
color={Theme.Colors.Details}
align="left">
{t('offlineAuthDisabledMessage')}
</Text>
<Button title={t('enableVerification')} onPress={props.onBinding} />
</Column>
</ImageBackground>
)}
</Column> </Column>
); );
}; };
interface VcDetailsProps { interface VcDetailsProps {
vc: VC; vc: VC;
isBindingPending: boolean;
onBinding?: () => void;
} }
function getFullAddress(credential: CredentialSubject) { function getFullAddress(credential: CredentialSubject) {

View File

@@ -1,6 +1,12 @@
import React, { useContext, useRef } from 'react'; import React, { useContext, useRef } from 'react';
import { useInterpret, useSelector } from '@xstate/react'; import { useInterpret, useSelector } from '@xstate/react';
import { Pressable, Image, ImageBackground } from 'react-native'; import {
Pressable,
Image,
ImageBackground,
TouchableHighlight,
View,
} from 'react-native';
import { CheckBox, Icon } from 'react-native-elements'; import { CheckBox, Icon } from 'react-native-elements';
import { ActorRefFrom } from 'xstate'; import { ActorRefFrom } from 'xstate';
import { import {
@@ -10,6 +16,8 @@ import {
vcItemMachine, vcItemMachine,
selectContext, selectContext,
selectTag, selectTag,
selectWalletBindingId,
selectEmptyWalletBindingId,
} from '../machines/vcItem'; } from '../machines/vcItem';
import { Column, Row, Text } from './ui'; import { Column, Row, Text } from './ui';
import { Theme } from './ui/styleUtils'; import { Theme } from './ui/styleUtils';
@@ -85,6 +93,28 @@ const getDetails = (arg1, arg2, verifiableCredential) => {
} }
}; };
const WalletVerified: React.FC = () => {
return (
<Icon
name="verified-user"
color={'green'}
size={28}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
const WalletUnverified: React.FC = () => {
return (
<Icon
name="lightbulb"
color={'#e8a94f'}
size={28}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};
export const VcItem: React.FC<VcItemProps> = (props) => { export const VcItem: React.FC<VcItemProps> = (props) => {
const { appService } = useContext(GlobalContext); const { appService } = useContext(GlobalContext);
const { t } = useTranslation('VcDetails'); const { t } = useTranslation('VcDetails');
@@ -98,6 +128,7 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
const service = useInterpret(machine.current, { devTools: __DEV__ }); const service = useInterpret(machine.current, { devTools: __DEV__ });
const context = useSelector(service, selectContext); const context = useSelector(service, selectContext);
const verifiableCredential = useSelector(service, selectVerifiableCredential); const verifiableCredential = useSelector(service, selectVerifiableCredential);
const emptyWalletBindingId = useSelector(service, selectEmptyWalletBindingId);
//Assigning the UIN and VID from the VC details to display the idtype label //Assigning the UIN and VID from the VC details to display the idtype label
const uin = verifiableCredential?.credentialSubject.UIN; const uin = verifiableCredential?.credentialSubject.UIN;
@@ -197,6 +228,41 @@ export const VcItem: React.FC<VcItemProps> = (props) => {
<RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} /> <RotatingIcon name="sync" color={Theme.Colors.rotatingIcon} />
)} )}
</Row> </Row>
<Row>
{emptyWalletBindingId ? (
<Row>
<WalletUnverified />
<Text
numLines={1}
color={Theme.Colors.Details}
weight="bold"
size="smaller"
margin="10 10 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}
children={t('offlineAuthDisabledHeader')}></Text>
</Row>
) : (
<Row>
<WalletVerified />
<Text
numLines={1}
color={Theme.Colors.Details}
weight="bold"
size="smaller"
margin="10 10 10 10"
style={
!verifiableCredential
? Theme.Styles.loadingTitle
: Theme.Styles.subtitle
}
children={t('profileAuthenticated')}></Text>
</Row>
)}
</Row>
<VcItemTags tag={tag} /> <VcItemTags tag={tag} />
</ImageBackground> </ImageBackground>
</Pressable> </Pressable>

View File

@@ -23,7 +23,11 @@
"phoneNumber": "رقم الهاتف", "phoneNumber": "رقم الهاتف",
"email": "البريد الإلكتروني", "email": "البريد الإلكتروني",
"address": "عنوان", "address": "عنوان",
"reasonForSharing": "سبب المشاركة" "reasonForSharing": "سبب المشاركة",
"enableVerification": "تمكين التحقق",
"offlineAuthDisabledHeader": "تم تعطيل المصادقة دون اتصال!",
"offlineAuthDisabledMessage": "انقر فوق تمكين المصادقة لتمكين استخدام بيانات الاعتماد هذه للمصادقة دون اتصال.",
"profileAuthenticated": "تمت مصادقة الملف الشخصي!"
}, },
"AuthScreen": { "AuthScreen": {
"header": "هل ترغب في استخدام المقاييس الحيوية لفتح التطبيق؟", "header": "هل ترغب في استخدام المقاييس الحيوية لفتح التطبيق؟",

View File

@@ -46,6 +46,10 @@
"id": "Id", "id": "Id",
"nationalCard": "National Card", "nationalCard": "National Card",
"uin": "UIN", "uin": "UIN",
"enableVerification": "Enable Verification",
"offlineAuthDisabledHeader": "Offline Authentication disabled!",
"offlineAuthDisabledMessage": "Click 'Enable Authentication' to enable this credentials to be used for offline authentication.",
"profileAuthenticated": "Profile is authenticated!",
"vid": "VID" "vid": "VID"
}, },
"AuthScreen": { "AuthScreen": {

View File

@@ -32,7 +32,11 @@
"reasonForSharing": "Dahilan ng pagbabahagi", "reasonForSharing": "Dahilan ng pagbabahagi",
"idType": "Uri ng ID", "idType": "Uri ng ID",
"nationalCard": "Pambansang Kard", "nationalCard": "Pambansang Kard",
"uin": "UIN" "uin": "UIN",
"enableVerification": "Paganahin ang Pag-verify",
"offlineAuthDisabledHeader": "Na-disable ang Offline Authentication!",
"offlineAuthDisabledMessage": "I-click ang 'Paganahin ang Authentication' upang paganahin ang mga kredensyal na ito na magamit para sa offline na pagpapatotoo.",
"profileAuthenticated": "Na-authenticate ang profile!"
}, },
"AuthScreen": { "AuthScreen": {
"header": "Gusto mo bang gumamit ng biometrics upang i-unlock ang aplikasyon?", "header": "Gusto mo bang gumamit ng biometrics upang i-unlock ang aplikasyon?",

View File

@@ -23,7 +23,11 @@
"phoneNumber": "फ़ोन नंबर", "phoneNumber": "फ़ोन नंबर",
"email": "ईमेल", "email": "ईमेल",
"address": "पता", "address": "पता",
"reasonForSharing": "साझा करने का कारण" "reasonForSharing": "साझा करने का कारण",
"enableVerification": "सत्यापन सक्षम करें",
"offlineAuthDisabledHeader": "ऑफ़लाइन प्रमाणीकरण अक्षम!",
"offlineAuthDisabledMessage": "ऑफ़लाइन प्रमाणीकरण के लिए उपयोग किए जाने वाले इन क्रेडेंशियल्स को सक्षम करने के लिए 'प्रमाणीकरण सक्षम करें' पर क्लिक करें।",
"profileAuthenticated": "प्रोफ़ाइल प्रमाणित है!"
}, },
"AuthScreen": { "AuthScreen": {
"header": "क्या आप एप्लिकेशन को अनलॉक करने के लिए बायोमेट्रिक्स का उपयोग करना चाहेंगे?", "header": "क्या आप एप्लिकेशन को अनलॉक करने के लिए बायोमेट्रिक्स का उपयोग करना चाहेंगे?",

View File

@@ -23,7 +23,11 @@
"phoneNumber": "ಫೋನ್ ಸಂಖ್ಯೆ", "phoneNumber": "ಫೋನ್ ಸಂಖ್ಯೆ",
"email": "ಇಮೇಲ್", "email": "ಇಮೇಲ್",
"address": "ವಿಳಾಸ", "address": "ವಿಳಾಸ",
"reasonForSharing": "ಹಂಚಿಕೆಗೆ ಕಾರಣ" "reasonForSharing": "ಹಂಚಿಕೆಗೆ ಕಾರಣ",
"enableVerification": "ಪರಿಶೀಲನೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ",
"offlineAuthDisabledHeader": "ಆಫ್‌ಲೈನ್ ದೃಢೀಕರಣವನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ!",
"offlineAuthDisabledMessage": "ಆಫ್‌ಲೈನ್ ದೃಢೀಕರಣಕ್ಕಾಗಿ ಬಳಸಲು ಈ ರುಜುವಾತುಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು 'ದೃಢೀಕರಣವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ' ಕ್ಲಿಕ್ ಮಾಡಿ.",
"profileAuthenticated": "ಪ್ರೊಫೈಲ್ ಅನ್ನು ದೃಢೀಕರಿಸಲಾಗಿದೆ!"
}, },
"AuthScreen": { "AuthScreen": {
"header": "ಅಪ್ಲಿಕೇಶನ್ ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಬಯೋಮೆಟ್ರಿಕ್ಸ್ ಬಳಸಲು ನೀವು ಬಯಸುವಿರಾ?", "header": "ಅಪ್ಲಿಕೇಶನ್ ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಬಯೋಮೆಟ್ರಿಕ್ಸ್ ಬಳಸಲು ನೀವು ಬಯಸುವಿರಾ?",

View File

@@ -23,7 +23,11 @@
"phoneNumber": "தொலைபேசி எண்", "phoneNumber": "தொலைபேசி எண்",
"email": "மின்னஞ்சல்", "email": "மின்னஞ்சல்",
"address": "முகவரி", "address": "முகவரி",
"reasonForSharing": "பகிர்வதற்கான காரணம்" "reasonForSharing": "பகிர்வதற்கான காரணம்",
"enableVerification": "சரிபார்ப்பை இயக்கு",
"offlineAuthDisabledHeader": "ஆஃப்லைன் அங்கீகாரம் முடக்கப்பட்டது!",
"offlineAuthDisabledMessage": "இந்த நற்சான்றிதழ்களை ஆஃப்லைன் அங்கீகாரத்திற்காகப் பயன்படுத்துவதற்கு 'அங்கீகாரத்தை இயக்கு' என்பதைக் கிளிக் செய்யவும்.",
"profileAuthenticated": "சுயவிவரம் அங்கீகரிக்கப்பட்டது!"
}, },
"AuthScreen": { "AuthScreen": {
"header": "பயன்பாட்டைத் திறக்க பயோமெட்ரிக்ஸைப் பயன்படுத்த விரும்புகிறீர்களா?", "header": "பயன்பாட்டைத் திறக்க பயோமெட்ரிக்ஸைப் பயன்படுத்த விரும்புகிறீர்களா?",

View File

@@ -13,6 +13,13 @@ import { StoreEvents } from './store';
import { ActivityLogEvents } from './activityLog'; import { ActivityLogEvents } from './activityLog';
import { verifyCredential } from '../shared/vcjs/verifyCredential'; import { verifyCredential } from '../shared/vcjs/verifyCredential';
import { log } from 'xstate/lib/actions'; import { log } from 'xstate/lib/actions';
import { generateKeys } from '../shared/rsakeypair/rsaKeypair';
import { KeyPair } from 'react-native-rsa-native';
import {
getPrivateKey,
savePrivateKey,
} from '../shared/keystore/SecureKeystore';
import { localAssets } from 'expo-updates';
const model = createModel( const model = createModel(
{ {
@@ -32,9 +39,14 @@ const model = createModel(
idError: '', idError: '',
transactionId: '', transactionId: '',
revoked: false, revoked: false,
walletBindingId: '',
walletBindingError: '',
publicKey: '',
}, },
{ {
events: { events: {
KEY_RECEIVED: (key: string) => ({ key }),
KEY_ERROR: (error: Error) => ({ error }),
EDIT_TAG: () => ({}), EDIT_TAG: () => ({}),
SAVE_TAG: (tag: string) => ({ tag }), SAVE_TAG: (tag: string) => ({ tag }),
STORE_READY: () => ({}), STORE_READY: () => ({}),
@@ -49,6 +61,8 @@ const model = createModel(
INPUT_OTP: (otp: string) => ({ otp }), INPUT_OTP: (otp: string) => ({ otp }),
REFRESH: () => ({}), REFRESH: () => ({}),
REVOKE_VC: () => ({}), REVOKE_VC: () => ({}),
ADD_WALLET_BINDING_ID: () => ({}),
BINDING_DONE: () => ({}),
}, },
} }
); );
@@ -56,7 +70,7 @@ const model = createModel(
export const VcItemEvents = model.events; export const VcItemEvents = model.events;
export const vcItemMachine = export const vcItemMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QDcDGBaAlgFzAWwDpUALMVAa0wDsoA1VAYgHEBRAFQH1aBhDgJRYBlAAoB5AHKCWiUAAcA9rByZ5VGSAAeiAIwAWAAwFtANgAcAdl0BmAJwAmOxd0BWADQgAnoivHDpx6aBzubGwc6mAL4R7mhYuIQkZJQ0gtjyAE5gDIJsogL8QmKS0kggCkrYKmqlWgh6hiYWDnba4aFW7l4I5qbOBPohvdoW2jbDUTEYOPhEpBTUUIJg6cjLACIAhtgbs0kLqVsArrAMYgAyZ+rlyqrqtdrm2gRWds521vqmusZ2Nr2dOlGugIAXMb1+dn0NnCExAsWmCTmyUWy1W6U22128xS22wxwYa1EAHVxGdRABBNYFSkATSuihu1VAtQMfSsPlCumhVlGjwBdSsumBdh5Vmc1jGznszlh8PiWORSxW6y2Owg8gA7lQADbyDYQBbcTIQMBUSobbWnUQXekVKp3RA-YWtcz6YzQ4y6OzGfnaIEgwJghz2KEw6JwqbyxLYlHK9GqgjqrW6-WG42m82W7gCNYscRsACS5LOHEJJLJlJYa1tjIdCF0pmMz3Mrucgu+7R9nkQjb6Fh87Z5LcCssjM0wEG1WSrBc4bHJTBrlVuNR0bqeNk9A2Mrv0+mc4X5f3MBF0PRFPP8-kso7i48nWVoLD4BYAYnTStdl0zND3nA0rEbUx930OwQlCflTEAggbHMDkwKdfRtFvBECAnKcGDJbgAGkuG4Jd7VXOo2z6T5fDMHwbCQnpIOg2CfCgkJBjFFD5XQrIAFVSVEHC8IIldmR0axTCMYxRhsXRhmsd47H5QZ+mGHdQNGYxKNYmZIGUGg2A2KACQLQQAFkDMEfif3uYYT09XxGx6dk91k7sEGPU9z1CJD-HZKx1MITTKm03TsnJJ8OHnRdPwZb86z9VpTyosZ3TMAxHK6P1HiML1Xi9YwxIGSJwzlGZYDSdIFh0vScjyFgChECQpDM6LXjsAhTAk-RALguCrHMX1tBFAhLGGXoWj+drdB8gg0UwAAzDw00gDNMAtBh1SoMA0KoZB5HIdbCsIKbZvmk0zSW7UEGoLbUC2KoAG19AAXQaojtH0AwCClcxXihb0TAPfkXEMJDhl5d0eiFcaCrHfblhmuaaCNBaTuW5Z0gyAhZG1LZpoyfaocmmHDvh9MkbOi75Cu787seiK7QE39nNeFrnE9GxAJcSFvX5H5mvsb4+u0WxgNeibo2RWgCcwCmqgOPETiewS6hep4rDdaFglAz6eqc14myvQJ+YGfd2Qmi6LQnBgC3EYQOM4UQ2GEeX6chT1m0eA9wZVqDev-GDKJ3ST3SFfLJjvQhTe1c21gM4zBFMmna2el4bHe0xWm+F5PUA-k2yeAw7Mk8UWnMGwJsyABHQ44H8qBRGwWQVtUdayZ2ya8fLyvioWWvZHOzbyeu1Qqcd+5U6sfoHBykwQjeLWukSmCzxFODHHeCxS7ACuq67uuGBRtGMaxnHW9Dgh263mhu97y6B6oIf46i57U+BN5AlU75nF+3Rs-sAgxLBFWJKWFgnYCaGxUCoDALIau3cCxUFkIcbAFsrY2w4HbB299CIKweFyd6YEP6OHBO1NwTlYLJ3sIKewrR-zeUhifMBECoHb1kLA+BiCo5GRMsPHQ-5gQ-DFLYewcF7CmCPC0OKvwfBSihE4dem9O40DOOTcgDc1obS2i3Pap8N4d2rooigV9+6UwelwuowFk6NmsnuaEF4RFOT6rBd61gXo9FbIKWROiFh6OUXvdI6NMbYGxukXGJ8z7yKgF4gxUtB7GIwXTEeVEBrkNZAhbqX8SHFxgiKQuUppQPAmrqGM9Bsi5HyAIWqxQTGtG6kYcIfUWyhEcNCXqm4akSR+i2cUxtYRUHkCaeApRNGiwWPQExZhf6pIkl6GwYwvQdDsS2Iwe4hQ9GmQYawEMQ6oSGTiDIYATHQhBE6KigQeSTLmXPAY-RTk2WkcBYwIskT7FRCqTE2zFi4mOPsp4jwvjehcMzVSwxfQ8maiKAW0zegDA-sHCMJ83lKjRBiNUmodR6gNETRGmYTHhBEueAWrpPrBDSalFWhgwUclCEAv4Dy9gpGefGbYoyTw-Oyv8nKl5fRujHqCCiIQXAbNhahdilSHgbgPBRdODhLByUCDBOCOV9xfAlDCzRfkyq6UqVCYEm4pSOE+i0P0nLZWeRaDuRwKtzATWKhkdVUBKmQj6KpMwPQYoCy7KlX4hg2xeihMXWCXwZS0NQgdOGUAEbHSxbE8yiB6m-yJb0A8eq2z-Ryu9P0UF8VQh8CAoNUZHk0HFqVaaksb4y0+VGusHxnh6Fqf4RwZriWOndBlFsfUuR-H8CXXN45NpmwgAQeQdcTHOyeE-HoqlFWCmzn1X+fxWh7lTmJIUJte0R37QAIzATtKgEBh1G3epuV6rNMrEK6G0IwDwSLemAi9Ltmy2KronHu34B7fBckFA4U9iAJSztToEKC-DPjuPPjXIdFbnrfDIX8cUDZ-xukbN-ZqXpvgB03MMFooDwGQOgXXFhCDKnFyeJ6cCjw-SvTdHJaZ704NUR+DuJ1wGwleIIwk34TExRu0sF+xW0FrCg1ePwrJ+SlHDNQJUmeLVbCAU+LYAwoxfQEP6E4z4WVPpfB8iY6wA0pJ-PFYC2xc8TwvVen89c3wwRRCiEAA */ /** @xstate-layout N4IgpgJg5mDOIC5QDcDGBaAlgFzAWwGIAlAUQDFSBlACQG0AGAXUVAAcB7WHTdgOxZAAPRABYATABoQAT0QBGMQA4ArADpFATnEB2AGwBmMcrEjFAXzNS0WXHlWoAFmFQBrTLygA1VAQDiJABUAfU8AYSCqAAUAeQA5ShIGZiQQDi5sHn4U4QQROX1VXRFlDTlNXV1tPX0pWQQ5YzFVekU5PW0NXXzFDosrDBx8eydXdy8ff2CwiJJKGPjEuWS2Tm4+ARy8gqKSss7K6tr5EXpdQpFxVoV9RX19ZT6Qa0G7R2c3D0psdgAnMAJKAFoqQZnM4gkkgI0mssqAcopJDJRHINIpVPcxLorroxBoNGJHs9bMN3mMvr9-oDgSRQfMIUsoasMutsvJ6CJtKoOm08WI7hplPQakj6ko1Kd6FpcaVSg9LE8BsS3qNPmAfsg1QARACG2G1JJVUC+uoArrACDEADKWyEpaHM2FCRBiNqqIzGFEqHGYipHBD6UpNOT0TFGfHaejaRQiQmKobKj5GtUan46vUGxPG7Bmgia6IAdViluiAEFNTMywBNW0rdKZDbIjSqYPiModJTsjR+k76M4SjQB0oh0y6WM2eMjTPJrW6-UQdgAd14ABt2NqIGNQn8IGBeBltcuLdFrTXUkz66yEFV6KoLkojDpWgi-QGxJyyspNJKhUotGOXhmZLTqms6qPOS6ruum7bru+6HqEpCaiQsQBAAkiWlpBHmhbFmWJCaqe9oXnCbLKCIzb3IKqINLoAovqiaLaG+ZHaPo4ghnIMbykSQyYBAy7-PhqHBAEJa+IR54siR9Qok2ygeqibEGJGL7cs2ii6EKmh4oYQr-sSfECQQngkEQqFkNWTCMnWUlOggYgOWcPTGCIOIDrRcjdm+ciqMoKI3DcGiSmxcr9OOdiGf8xahAA0iEoQSTZjo5N56J3AFYhCuyyjCnUulNBGlTRnymmVKFCrhaokXECQnjRDFNJhIlMINqKfoKPpvH8f8ZblvmGGWoEQQAEKobEmpjb4QSoQRVl2pJyXOliBRsScwZ4iGLoviiZxdK5rQGPiLqdXYkDcB4ATalAuaoZQACyt2UM1DqtXIsm+QptyufoKkir2Ea3vceKfoKBzlTxp0bhkF1XQCJYmUEoniXNtYtZeb2aB9LqKd9v15YK5EOdU9A7b2A4naosDfD8YyXddVIglE4KJCjZ5Ja9gpokK+RMYKbQKNoL4E26b7KaTtH6BTKaYAAZtI0GQLBmAHgQ868GAVW8Mg7AuBrEOqNLcsKzue7K8uCDuNrqC6pkSTPcRdkOXITnaC5blk55Io9k0WIlNoewGFUUtqrL8seFuiumyrao-L8qisMuuoy78dj64bYdQBHJtwRbWvsNbDp26zRG2SljnqK7Jjux53ZtGiyiacDGj+-o2hcWFAEJmMnghzLmAF5kWY5vbpfyK0TaKF6lTBfJWL0Zx6klPibFRqYBLcXGryTt3vf9zbfBD+atAMvN7Po+P6hTxGA6z4oXk5ecIj6GUwYk50FOWwefEEGNkQAKrBGiAESII9Fr2X9mlO4txMo-WKLlRAP1ey3lRMUPy9AG58g-lrL+EAbr3UeqA1qqV0oZSynAl8AU3SnCjPJAUD5Rwb0qn8AAjiaOA0MoDRGwKwVWfANaWx1nrTeqgWFsKpmMLhrBc5W33rwIuyw2Zo2kiYTkWgOiTxaJiVoNw-RBTRJxC4rEBSGE5hTUR7CJHcIIDHOOCck4pwNsI8x4iPCSOkfnWR8jrJKMdsoVRHIGKaKxJxdqKIgwhgjNGXmtwKbalQKgMArAOGSNQrwVgJpsA-1iP-QBwDCGXnuDeIoXRW5aF7FRXQfolA9EKHyC40Zm64juLE+JiTkncNSekzJE18GUCesXBarU2JFNcjzMpugKlVMqE0EwtwmLsn9sdRhAE4kJKSWMIgYBta606RkrJOSghAJAQMs+yiVDnBKOyFEkoXRvj9K7JsbcVCfjaCcepLS1kcM2dssAuzum3Qen0-JZy1A7CCnkIK+IBYvg5FyIKHF8QaVKBUMxYBWEWI8JafOLheHq01j8xxTC0ViI4Vi1w7iB58C8afHxOQ24FGdrRIo6DIxNKqaULmT4Qp5HbKi9FLioBkpxTYn48dE7YGTj8VOTjiUYsFdiilnimDArsoU84JSOhPwmZKSpIo3xaEKNqzSfiqiGG0BTVchpvAAiBIzWYdIWYKJLmAt6ztbzO00K3b8Qp4H2QHE2Z+koOT0BDD0SWyziTOK+VswRuL+F511oSgCUaNkxt1oqwuyqTm0sQL7LkuIThkWDDiKofoKgBvWi0eS0Yijrw7pG2VArvmxpFWK+xUqk0Nv5dGn5GbbZZqdYMy8eamJaHQXkU4b5BYiijIxUWnF5J+Kfqi7Z3cfAMxpEzBYKqcg-TUIKENeQcStzuByKp8kmjFAqCoDkvY8gWvYFAKAqafl4MBf0wdpy7IRgKJGH6fJkVdD5Oe4wt4G6+1vcy9uFVk2No4cNdwG5XFWLVvGgl+sU0eAQ7wJDnDuF9qpQO7xL10ZwMKFUCFR1qnTrqGEm8-Ya0-Vds7PlJKxjYdw5I6xPxY6irsRKhxGG4PscQ5YqRAjKVyKIzSkj0lnxexuDeDoIbjHaIch8tpImcNif2QAw5eTs2ybssYJsuIygtBuGUcQyg-TGBvE-S5KI+b8g0+srDonkM8J6e+nd8hDBVJDGoO4bQ-E4j8m9cGwj1wQBimAaQkRtSYB+HG-FgjO1DGi7F+LiWfgEak4wXz-oShck0iIcMYbMRaCqTcJoeIEQug0n5DQsSIAxbiwlpL3HeNtoEx2-WmX2s5by9S1GRnd3Fb0J2N8FXaIiCqZxTkTyLhdHkkxZpEaMutbGPmA8AlsAcbGKhXBqHUuJv61tjwO3lx7YOx4I7w3pOjYdvCTKvkcTXsjD6rseqbjkTfsFCzRQWMbbsNF7bu2wD7Y81AI7XXbHisldKyqYPLsQ6h9pu7EAHsFcM89xACIbwYI+z+gMVTfz5tdgYPIOVPUU1gA4Rct2jR6mzOaUa41JpYTiI64jePRRP3dbRE1Qp1E0edE-HyhUsTUMFPJLi8peDsB3PAFIENeejwQOgXsfp0AKHoyGkmLcSboIbhTLuHhvDq7AeIdqI43s3IaDlKMbEzfb0+NTMAVvWqsQKAe04vY2KOTmyKfIsKG6Sh6LKYobRXeklVOqGceoveXjbqpTShQQxYlohtcN9aJxx6TAnkC6ZzfM9NCrp7GuFCIjqGtX9jdOilMabHw0lBgJpjnIuFca5cNZyVgeZP0kcRojYgFZuBwoXdlDc2O8JSG5lFzzBgy3VB+OwuH94tqITD8jaNtdPe1WLXyqDtCmZ0OF01XzkYMZEKLyQjzROif1nfonWlvsrzd0F0+prTK6l+-NKGbAW2fjel7EzyFiFFUAFEqCCgDDHwYTz1Tl7gzj7yjmXD-3qC3y5GuRKE9SqDuS9gchHzKXC1chNxb0TB7hpj7kk0PnQPyDfEvgvRMHxCvWD1r0yiaFaElFYjKEUG4KwWQBwXQIgRsz+j3TAyKhgNcmMEi0qk-mXD4lUHYG4ToJRF8goXxEgIbmU1gNuHfhB3xRwVUAACM4ldYcM6CHIYU-IJCOg8Qhxr9WM5VJF0CbcQ9nZFtqFJ5eDV460l8MtWk3M8NWA-k6C8ZnQegfJfZAw24ItzUDDVlNMPBm0dk0kMlXCa9EAmJyJgD8gSg-Jt8-ChNu0xghV0Cco1AHItAAkAwPVRC6gzMzhA18QDhZkY8DDLUKDUB0D8QmwLMl4ugKhXl2U3VmjMQ8DTB2iECRFhNki01PcZM+dXImg7MUE2hJQyhMirxzl3IQDOIBwAkV0dY11VCxd7ITMJDPwb1n4MQH0n0X1BFyj0Fmhn4lAMRMQn52Rz03Ur0KhxB8pZDYMSj3MMdgj0CmkzhURUR-ZV5xA2CEE2htgF0okWh-Z5JXN4NocXDFiNcxQLlwVrkoV8C6hhYytbghj-Vm5XYWs2tssktLCzi+Q+CqFplspI9l0EiLsoArsbtocjtyiAYD1r9Q1URwj7IAxyIr0yIcYVBF99Z6dGdodaCcSXV2Q0RSt-iNVjBfVCiWTpcyoTd5czAgA */
model.createMachine( model.createMachine(
{ {
tsTypes: {} as import('./vcItem.typegen').Typegen0, tsTypes: {} as import('./vcItem.typegen').Typegen0,
@@ -162,6 +176,9 @@ export const vcItemMachine =
REVOKE_VC: { REVOKE_VC: {
target: 'acceptingRevokeInput', target: 'acceptingRevokeInput',
}, },
ADD_WALLET_BINDING_ID: {
target: 'requestingBindingOtp',
},
}, },
}, },
editingTag: { editingTag: {
@@ -320,7 +337,10 @@ export const vcItemMachine =
], ],
onError: [ onError: [
{ {
actions: [log('OTP error'), 'setOtpError'], actions: [
log((_, event) => (event.data as Error).message),
'setOtpError',
],
target: 'acceptingOtpInput', target: 'acceptingOtpInput',
}, },
], ],
@@ -342,10 +362,115 @@ export const vcItemMachine =
}, },
}, },
}, },
requestingBindingOtp: {
invoke: {
src: 'requestBindingOtp',
onDone: [
{
target: 'acceptingBindingOtp',
actions: log('accepting OTP'),
},
],
onError: [
{
target: '#vc-item.invalid.backend',
actions: log((_, event) => (event.data as Error).message),
},
],
},
},
acceptingBindingOtp: {
entry: ['clearOtp', 'setTransactionId'],
on: {
INPUT_OTP: {
target: 'addKeyPair',
actions: [
log('calling addKeyPair'),
'setTransactionId',
'setOtp',
],
},
DISMISS: {
target: 'idle',
actions: ['clearOtp', 'clearTransactionId'],
},
},
},
addKeyPair: {
invoke: {
src: 'generateKeyPair',
onDone: {
target: 'addingWalletBindingId',
actions: ['setPublicKey'],
},
onError: [
{
target: 'idle',
actions: 'setWalletBindingError',
},
],
},
},
addingWalletBindingId: {
invoke: {
src: 'addWalletBindnigId',
onDone: [
{
target: 'showBindingStatus',
actions: [
'setWalletBindingId',
'storeContext',
log(
(_context, event) =>
'Received walletBindingId : ' + _context.walletBindingId
),
],
},
],
onError: [
{
target: 'idle',
actions: 'setWalletBindingError',
},
],
},
},
// storePrivateKey: {
// invoke: {
// src: 'storePrivateKeyToKeystore',
// onDone: {
// target: 'showBindingStatus',
// },
// onError: {
// actions: 'setWalletBindingError',
// target: 'showBindingStatus',
// },
// }
// },
showBindingStatus: {
on: {
BINDING_DONE: {
target: 'idle',
},
},
},
}, },
}, },
{ {
actions: { actions: {
setWalletBindingError: assign({
walletBindingError: (context, event) => (event.data as Error).message,
}),
setPublicKey: assign({
publicKey: (context, event) => (event.data as KeyPair).public,
}),
setWalletBindingId: assign({
walletBindingId: (context, event) => event.data as string,
}),
updateVc: send( updateVc: send(
(context) => { (context) => {
const { serviceRefs, ...vc } = context; const { serviceRefs, ...vc } = context;
@@ -486,6 +611,49 @@ export const vcItemMachine =
}, },
services: { services: {
addWalletBindnigId: async (context) => {
let response = null;
try {
response = await request('POST', '/binding-credential-request', {
individualId: context.id,
otp: context.otp,
transactionID: context.transactionId,
publicKey: context.publicKey,
});
} catch (error) {
console.error(error);
}
return response.response.id;
},
generateKeyPair: async (context) => {
let keyPair: KeyPair = await generateKeys();
const hasSetPrivateKey: boolean = await savePrivateKey(
context.verifiableCredential.id,
keyPair.private
);
if (!hasSetPrivateKey) {
throw new Error('Could not store private key in keystore.');
}
return keyPair;
},
requestBindingOtp: async (context) => {
try {
return request('POST', '/binding-otp', {
individualId: context.id,
individualIdType: context.idType,
otpChannel: ['EMAIL', 'PHONE'],
transactionID: context.transactionId,
});
} catch (error) {
console.error(error);
}
},
checkStatus: (context) => (callback, onReceive) => { checkStatus: (context) => (callback, onReceive) => {
const pollInterval = setInterval( const pollInterval = setInterval(
() => callback(model.events.POLL()), () => callback(model.events.POLL()),
@@ -541,6 +709,7 @@ export const vcItemMachine =
isVerified: false, isVerified: false,
lastVerifiedOn: null, lastVerifiedOn: null,
locked: context.locked, locked: context.locked,
walletBindingId: context.walletBindingId,
}) })
); );
} }
@@ -703,3 +872,28 @@ export function selectIsAcceptingRevokeInput(state: State) {
export function selectIsRequestingOtp(state: State) { export function selectIsRequestingOtp(state: State) {
return state.matches('requestingOtp'); return state.matches('requestingOtp');
} }
export function selectIsRequestBindingOtp(state: State) {
return state.matches('requestingBindingOtp');
}
export function selectWalletBindingId(state: State) {
return state.context.walletBindingId;
}
export function selectEmptyWalletBindingId(state: State) {
var val = state.context.walletBindingId;
return val === undefined || val == null || val.length <= 0 ? true : false;
}
export function selectWalletBindingError(state: State) {
return state.context.walletBindingError;
}
export function selectAcceptingBindingOtp(state: State) {
return state.matches('acceptingBindingOtp');
}
export function selectShowBindingStatus(state: State) {
return state.matches('showBindingStatus');
}

View File

@@ -1,167 +1,91 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 { // This file was automatically generated. Edits will be overwritten
'@@xstate/typegen': true;
'internalEvents': { export interface Typegen0 {
'': { type: '' }; '@@xstate/typegen': true;
'done.invoke.checkStatus': { internalEvents: {
type: 'done.invoke.checkStatus'; "": { type: "" };
data: unknown; "done.invoke.checkStatus": { type: "done.invoke.checkStatus"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
__tip: 'See the XState TS docs to learn how to strongly type this.'; "done.invoke.downloadCredential": { type: "done.invoke.downloadCredential"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
}; "done.invoke.vc-item.addKeyPair:invocation[0]": { type: "done.invoke.vc-item.addKeyPair:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
'done.invoke.downloadCredential': { "done.invoke.vc-item.addingWalletBindingId:invocation[0]": { type: "done.invoke.vc-item.addingWalletBindingId:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
type: 'done.invoke.downloadCredential'; "done.invoke.vc-item.requestingBindingOtp:invocation[0]": { type: "done.invoke.vc-item.requestingBindingOtp:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
data: unknown; "done.invoke.vc-item.requestingLock:invocation[0]": { type: "done.invoke.vc-item.requestingLock:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
__tip: 'See the XState TS docs to learn how to strongly type this.'; "done.invoke.vc-item.requestingOtp:invocation[0]": { type: "done.invoke.vc-item.requestingOtp:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
}; "done.invoke.vc-item.requestingRevoke:invocation[0]": { type: "done.invoke.vc-item.requestingRevoke:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
'done.invoke.vc-item.requestingLock:invocation[0]': { "done.invoke.vc-item.verifyingCredential:invocation[0]": { type: "done.invoke.vc-item.verifyingCredential:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
type: 'done.invoke.vc-item.requestingLock:invocation[0]'; "error.platform.checkStatus": { type: "error.platform.checkStatus"; data: unknown };
data: unknown; "error.platform.downloadCredential": { type: "error.platform.downloadCredential"; data: unknown };
__tip: 'See the XState TS docs to learn how to strongly type this.'; "error.platform.vc-item.addKeyPair:invocation[0]": { type: "error.platform.vc-item.addKeyPair:invocation[0]"; data: unknown };
}; "error.platform.vc-item.addingWalletBindingId:invocation[0]": { type: "error.platform.vc-item.addingWalletBindingId:invocation[0]"; data: unknown };
'done.invoke.vc-item.requestingOtp:invocation[0]': { "error.platform.vc-item.requestingBindingOtp:invocation[0]": { type: "error.platform.vc-item.requestingBindingOtp:invocation[0]"; data: unknown };
type: 'done.invoke.vc-item.requestingOtp:invocation[0]'; "error.platform.vc-item.requestingLock:invocation[0]": { type: "error.platform.vc-item.requestingLock:invocation[0]"; data: unknown };
data: unknown; "error.platform.vc-item.requestingOtp:invocation[0]": { type: "error.platform.vc-item.requestingOtp:invocation[0]"; data: unknown };
__tip: 'See the XState TS docs to learn how to strongly type this.'; "error.platform.vc-item.requestingRevoke:invocation[0]": { type: "error.platform.vc-item.requestingRevoke:invocation[0]"; data: unknown };
}; "error.platform.vc-item.verifyingCredential:invocation[0]": { type: "error.platform.vc-item.verifyingCredential:invocation[0]"; data: unknown };
'done.invoke.vc-item.requestingRevoke:invocation[0]': { "xstate.init": { type: "xstate.init" };
type: 'done.invoke.vc-item.requestingRevoke:invocation[0]'; };
data: unknown; invokeSrcNameMap: {
__tip: 'See the XState TS docs to learn how to strongly type this.'; "addWalletBindnigId": "done.invoke.vc-item.addingWalletBindingId:invocation[0]";
}; "checkStatus": "done.invoke.checkStatus";
'done.invoke.vc-item.verifyingCredential:invocation[0]': { "downloadCredential": "done.invoke.downloadCredential";
type: 'done.invoke.vc-item.verifyingCredential:invocation[0]'; "generateKeyPair": "done.invoke.vc-item.addKeyPair:invocation[0]";
data: unknown; "requestBindingOtp": "done.invoke.vc-item.requestingBindingOtp:invocation[0]";
__tip: 'See the XState TS docs to learn how to strongly type this.'; "requestLock": "done.invoke.vc-item.requestingLock:invocation[0]";
}; "requestOtp": "done.invoke.vc-item.requestingOtp:invocation[0]";
'error.platform.checkStatus': { "requestRevoke": "done.invoke.vc-item.requestingRevoke:invocation[0]";
type: 'error.platform.checkStatus'; "verifyCredential": "done.invoke.vc-item.verifyingCredential:invocation[0]";
data: unknown; };
}; missingImplementations: {
'error.platform.downloadCredential': { actions: never;
type: 'error.platform.downloadCredential'; delays: never;
data: unknown; guards: never;
}; services: never;
'error.platform.vc-item.requestingLock:invocation[0]': { };
type: 'error.platform.vc-item.requestingLock:invocation[0]'; eventsCausingActions: {
data: unknown; "clearOtp": "" | "BINDING_DONE" | "DISMISS" | "REVOKE_VC" | "STORE_RESPONSE" | "done.invoke.vc-item.requestingBindingOtp:invocation[0]" | "done.invoke.vc-item.requestingOtp:invocation[0]" | "done.invoke.vc-item.verifyingCredential:invocation[0]" | "error.platform.vc-item.addKeyPair:invocation[0]" | "error.platform.vc-item.addingWalletBindingId:invocation[0]" | "error.platform.vc-item.requestingLock:invocation[0]" | "error.platform.vc-item.requestingRevoke:invocation[0]" | "error.platform.vc-item.verifyingCredential:invocation[0]";
}; "clearTransactionId": "" | "BINDING_DONE" | "DISMISS" | "STORE_RESPONSE" | "done.invoke.vc-item.verifyingCredential:invocation[0]" | "error.platform.vc-item.addKeyPair:invocation[0]" | "error.platform.vc-item.addingWalletBindingId:invocation[0]" | "error.platform.vc-item.verifyingCredential:invocation[0]";
'error.platform.vc-item.requestingOtp:invocation[0]': { "logDownloaded": "CREDENTIAL_DOWNLOADED";
type: 'error.platform.vc-item.requestingOtp:invocation[0]'; "logRevoked": "STORE_RESPONSE";
data: unknown; "markVcValid": "done.invoke.vc-item.verifyingCredential:invocation[0]";
}; "requestStoredContext": "GET_VC_RESPONSE" | "REFRESH";
'error.platform.vc-item.requestingRevoke:invocation[0]': { "requestVcContext": "xstate.init";
type: 'error.platform.vc-item.requestingRevoke:invocation[0]'; "revokeVID": "done.invoke.vc-item.requestingRevoke:invocation[0]";
data: unknown; "setCredential": "CREDENTIAL_DOWNLOADED" | "GET_VC_RESPONSE" | "STORE_RESPONSE";
}; "setLock": "done.invoke.vc-item.requestingLock:invocation[0]";
'error.platform.vc-item.verifyingCredential:invocation[0]': { "setOtp": "INPUT_OTP";
type: 'error.platform.vc-item.verifyingCredential:invocation[0]'; "setOtpError": "error.platform.vc-item.requestingLock:invocation[0]" | "error.platform.vc-item.requestingRevoke:invocation[0]";
data: unknown; "setPublicKey": "done.invoke.vc-item.addKeyPair:invocation[0]";
}; "setRevoke": "done.invoke.vc-item.requestingRevoke:invocation[0]";
'xstate.init': { type: 'xstate.init' }; "setTag": "SAVE_TAG";
}; "setTransactionId": "INPUT_OTP" | "REVOKE_VC" | "done.invoke.vc-item.requestingBindingOtp:invocation[0]" | "done.invoke.vc-item.requestingOtp:invocation[0]" | "error.platform.vc-item.requestingLock:invocation[0]" | "error.platform.vc-item.requestingRevoke:invocation[0]";
'invokeSrcNameMap': { "setWalletBindingError": "error.platform.vc-item.addKeyPair:invocation[0]" | "error.platform.vc-item.addingWalletBindingId:invocation[0]";
checkStatus: 'done.invoke.checkStatus'; "setWalletBindingId": "done.invoke.vc-item.addingWalletBindingId:invocation[0]";
downloadCredential: 'done.invoke.downloadCredential'; "storeContext": "CREDENTIAL_DOWNLOADED" | "done.invoke.vc-item.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item.verifyingCredential:invocation[0]";
requestLock: 'done.invoke.vc-item.requestingLock:invocation[0]'; "storeLock": "done.invoke.vc-item.requestingLock:invocation[0]";
requestOtp: 'done.invoke.vc-item.requestingOtp:invocation[0]'; "storeTag": "SAVE_TAG";
requestRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]'; "updateVc": "CREDENTIAL_DOWNLOADED" | "STORE_RESPONSE" | "done.invoke.vc-item.verifyingCredential:invocation[0]";
verifyCredential: 'done.invoke.vc-item.verifyingCredential:invocation[0]'; };
}; eventsCausingDelays: {
'missingImplementations': {
actions: never; };
services: never; eventsCausingGuards: {
guards: never; "hasCredential": "GET_VC_RESPONSE" | "STORE_RESPONSE";
delays: never; "isVcValid": "";
}; };
'eventsCausingActions': { eventsCausingServices: {
clearOtp: "addWalletBindnigId": "done.invoke.vc-item.addKeyPair:invocation[0]";
| '' "checkStatus": "STORE_RESPONSE";
| 'DISMISS' "downloadCredential": "DOWNLOAD_READY";
| 'REVOKE_VC' "generateKeyPair": "INPUT_OTP";
| 'STORE_RESPONSE' "requestBindingOtp": "ADD_WALLET_BINDING_ID";
| 'done.invoke.vc-item.requestingOtp:invocation[0]' "requestLock": "INPUT_OTP";
| 'done.invoke.vc-item.verifyingCredential:invocation[0]' "requestOtp": "LOCK_VC";
| 'error.platform.vc-item.requestingLock:invocation[0]' "requestRevoke": "INPUT_OTP";
| 'error.platform.vc-item.requestingRevoke:invocation[0]' "verifyCredential": "" | "VERIFY";
| 'error.platform.vc-item.verifyingCredential:invocation[0]'; };
clearTransactionId: matchesStates: "acceptingBindingOtp" | "acceptingOtpInput" | "acceptingRevokeInput" | "addKeyPair" | "addingWalletBindingId" | "checkingServerData" | "checkingServerData.checkingStatus" | "checkingServerData.downloadingCredential" | "checkingStore" | "checkingVc" | "checkingVerificationStatus" | "editingTag" | "idle" | "invalid" | "invalid.backend" | "invalid.otp" | "lockingVc" | "loggingRevoke" | "requestingBindingOtp" | "requestingLock" | "requestingOtp" | "requestingRevoke" | "revokingVc" | "showBindingStatus" | "storingTag" | "verifyingCredential" | { "checkingServerData"?: "checkingStatus" | "downloadingCredential";
| '' "invalid"?: "backend" | "otp"; };
| 'DISMISS' tags: never;
| 'STORE_RESPONSE' }
| 'done.invoke.vc-item.verifyingCredential:invocation[0]'
| 'error.platform.vc-item.verifyingCredential:invocation[0]';
logDownloaded: 'CREDENTIAL_DOWNLOADED';
logRevoked: 'STORE_RESPONSE';
markVcValid: 'done.invoke.vc-item.verifyingCredential:invocation[0]';
requestStoredContext: 'GET_VC_RESPONSE' | 'REFRESH';
requestVcContext: 'xstate.init';
revokeVID: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
setCredential:
| 'CREDENTIAL_DOWNLOADED'
| 'GET_VC_RESPONSE'
| 'STORE_RESPONSE';
setLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
setOtp: 'INPUT_OTP';
setOtpError:
| 'error.platform.vc-item.requestingLock:invocation[0]'
| 'error.platform.vc-item.requestingRevoke:invocation[0]';
setRevoke: 'done.invoke.vc-item.requestingRevoke:invocation[0]';
setTag: 'SAVE_TAG';
setTransactionId:
| 'INPUT_OTP'
| 'REVOKE_VC'
| 'done.invoke.vc-item.requestingOtp:invocation[0]'
| 'error.platform.vc-item.requestingLock:invocation[0]'
| 'error.platform.vc-item.requestingRevoke:invocation[0]';
storeContext:
| 'CREDENTIAL_DOWNLOADED'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]';
storeLock: 'done.invoke.vc-item.requestingLock:invocation[0]';
storeTag: 'SAVE_TAG';
updateVc:
| 'CREDENTIAL_DOWNLOADED'
| 'STORE_RESPONSE'
| 'done.invoke.vc-item.verifyingCredential:invocation[0]';
};
'eventsCausingServices': {
checkStatus: 'STORE_RESPONSE';
downloadCredential: 'DOWNLOAD_READY';
requestLock: 'INPUT_OTP';
requestOtp: 'LOCK_VC';
requestRevoke: 'INPUT_OTP';
verifyCredential: '' | 'VERIFY';
};
'eventsCausingGuards': {
hasCredential: 'GET_VC_RESPONSE' | 'STORE_RESPONSE';
isVcValid: '';
};
'eventsCausingDelays': {};
'matchesStates':
| 'acceptingOtpInput'
| 'acceptingRevokeInput'
| 'checkingServerData'
| 'checkingServerData.checkingStatus'
| 'checkingServerData.downloadingCredential'
| 'checkingStore'
| 'checkingVc'
| 'checkingVerificationStatus'
| 'editingTag'
| 'idle'
| 'invalid'
| 'invalid.backend'
| 'invalid.otp'
| 'lockingVc'
| 'loggingRevoke'
| 'requestingLock'
| 'requestingOtp'
| 'requestingRevoke'
| 'revokingVc'
| 'storingTag'
| 'verifyingCredential'
| {
checkingServerData?: 'checkingStatus' | 'downloadingCredential';
invalid?: 'backend' | 'otp';
};
'tags': never;
}

22
package-lock.json generated
View File

@@ -56,8 +56,10 @@
"react-native-popable": "^0.4.3", "react-native-popable": "^0.4.3",
"react-native-qrcode-svg": "^6.1.1", "react-native-qrcode-svg": "^6.1.1",
"react-native-restart": "^0.0.24", "react-native-restart": "^0.0.24",
"react-native-rsa-native": "^2.0.5",
"react-native-safe-area-context": "3.3.2", "react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.10.1", "react-native-screens": "~3.10.1",
"react-native-secure-key-store": "^2.0.10",
"react-native-securerandom": "^1.0.0", "react-native-securerandom": "^1.0.0",
"react-native-simple-markdown": "^1.1.0", "react-native-simple-markdown": "^1.1.0",
"react-native-svg": "12.1.1", "react-native-svg": "12.1.1",
@@ -21206,6 +21208,11 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-rsa-native": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/react-native-rsa-native/-/react-native-rsa-native-2.0.5.tgz",
"integrity": "sha512-gwwvFSwGW5WKrpDyBQ/eTf1UrVABeAvMcT4YWemzPSUo6aHZs1kbBm2rXmwN5okhUzJsry5zjjz/qdx5GXRugQ=="
},
"node_modules/react-native-safe-area-context": { "node_modules/react-native-safe-area-context": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz",
@@ -21228,6 +21235,11 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-secure-key-store": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-native-secure-key-store/-/react-native-secure-key-store-2.0.10.tgz",
"integrity": "sha512-K7aVlIGxyklnjhCidVexVgZF3LsgUD9GIxMy2NB/xkQsS9E2SJWkD/fJ56e25L2I6a9Mp1zuJrKnCtfBs1CvAw=="
},
"node_modules/react-native-securerandom": { "node_modules/react-native-securerandom": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-1.0.1.tgz", "resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-1.0.1.tgz",
@@ -44088,6 +44100,11 @@
"integrity": "sha512-pvJNU3NwQk6bCG2gOWcQpZ4IxhtELB0K9gzmtixfsaTFbW1UXXHkJNjk1kHazcbH5hrD7QbUkR63fsAVh8X4VQ==", "integrity": "sha512-pvJNU3NwQk6bCG2gOWcQpZ4IxhtELB0K9gzmtixfsaTFbW1UXXHkJNjk1kHazcbH5hrD7QbUkR63fsAVh8X4VQ==",
"requires": {} "requires": {}
}, },
"react-native-rsa-native": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/react-native-rsa-native/-/react-native-rsa-native-2.0.5.tgz",
"integrity": "sha512-gwwvFSwGW5WKrpDyBQ/eTf1UrVABeAvMcT4YWemzPSUo6aHZs1kbBm2rXmwN5okhUzJsry5zjjz/qdx5GXRugQ=="
},
"react-native-safe-area-context": { "react-native-safe-area-context": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz",
@@ -44103,6 +44120,11 @@
"warn-once": "^0.1.0" "warn-once": "^0.1.0"
} }
}, },
"react-native-secure-key-store": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-native-secure-key-store/-/react-native-secure-key-store-2.0.10.tgz",
"integrity": "sha512-K7aVlIGxyklnjhCidVexVgZF3LsgUD9GIxMy2NB/xkQsS9E2SJWkD/fJ56e25L2I6a9Mp1zuJrKnCtfBs1CvAw=="
},
"react-native-securerandom": { "react-native-securerandom": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-1.0.1.tgz", "resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-1.0.1.tgz",

View File

@@ -61,8 +61,10 @@
"react-native-popable": "^0.4.3", "react-native-popable": "^0.4.3",
"react-native-qrcode-svg": "^6.1.1", "react-native-qrcode-svg": "^6.1.1",
"react-native-restart": "^0.0.24", "react-native-restart": "^0.0.24",
"react-native-rsa-native": "^2.0.5",
"react-native-safe-area-context": "3.3.2", "react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.10.1", "react-native-screens": "~3.10.1",
"react-native-secure-key-store": "^2.0.10",
"react-native-securerandom": "^1.0.0", "react-native-securerandom": "^1.0.0",
"react-native-simple-markdown": "^1.1.0", "react-native-simple-markdown": "^1.1.0",
"react-native-svg": "12.1.1", "react-native-svg": "12.1.1",

View File

@@ -0,0 +1,69 @@
import { useSelector } from '@xstate/react';
import { useContext } from 'react';
import { ActorRefFrom } from 'xstate';
import {
selectIsRefreshingMyVcs,
selectMyVcs,
VcEvents,
} from '../../../machines/vc';
import { GlobalContext } from '../../../shared/GlobalContext';
import NetInfo from '@react-native-community/netinfo';
import { selectVcLabel } from '../../../machines/settings';
import {
selectAcceptingBindingOtp,
selectIsRequestBindingOtp,
selectOtpError,
selectShowBindingStatus,
selectWalletBindingError,
selectWalletBindingId,
VcItemEvents,
vcItemMachine,
} from '../../../machines/vcItem';
import { ModalProps } from '../../../components/ui/Modal';
export function useBindVcStatus(props: BindVcProps) {
const { appService } = useContext(GlobalContext);
const settingsService = appService.children.get('settings');
const vcService = appService.children.get('vc');
const netInfoFetch = (otp: string) => {
NetInfo.fetch().then((state) => {
if (state.isConnected) {
vcService.send(VcItemEvents.INPUT_OTP(otp));
} else {
vcService.send(VcItemEvents.DISMISS());
showToast('Request network failed');
}
});
};
return {
vcKeys: useSelector(vcService, selectMyVcs),
vcLabel: useSelector(settingsService, selectVcLabel),
isRefreshingVcs: useSelector(vcService, selectIsRefreshingMyVcs),
isBindingOtp: useSelector(vcService, selectIsRequestBindingOtp),
walletAddress: useSelector(vcService, selectWalletBindingId),
isAcceptingBindingOtp: useSelector(vcService, selectAcceptingBindingOtp),
showBindingStatus: useSelector(vcService, selectShowBindingStatus),
walletBindingError: useSelector(vcService, selectWalletBindingError),
inputOtp: (otp: string) => {
netInfoFetch(otp);
},
otpError: useSelector(vcService, selectOtpError),
BINDING_DONE: () => vcService.send(VcItemEvents.BINDING_DONE()),
INPUT_OTP: (otp: string) => vcService.send(VcItemEvents.INPUT_OTP(otp)),
DISMISS: () => vcService.send(VcItemEvents.DISMISS()),
REFRESH: () => vcService.send(VcEvents.REFRESH_MY_VCS()),
};
}
function showToast(arg0: string) {
throw new Error('Function not implemented.');
}
export interface BindVcProps extends ModalProps {
bindingError: string;
onDone: () => void;
}

View File

@@ -0,0 +1,55 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { View } from 'react-native';
import { Button, Icon } from 'react-native-elements';
import { Column, Text } from '../../../components/ui';
import { Theme } from '../../../components/ui/styleUtils';
import { useBindVcStatus, BindVcProps } from './BindVcController';
export const BindStatus: React.FC<BindVcProps> = (props) => {
const controller = useBindVcStatus(props);
var message: string = controller.walletBindingError;
return (
<View style={Theme.OtpVerificationStyles.viewContainer}>
<Column
fill
padding="32"
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<View style={Theme.OtpVerificationStyles.close}>
<Icon name="close" onPress={() => props.onDismiss()} />
</View>
<Column fill align="space-between">
{!controller.walletBindingError && <WalletVerified />}
{controller.walletBindingError ? (
<Text
align="center"
color={Theme.Colors.errorMessage}
margin="16 0 0 0">
{{ message }}
</Text>
) : (
<Text align="center" margin="16 0 0 0">
{'Successfully added to wallet'}
</Text>
)}
<Button title={'GO BACK'} onPress={props.onDone} />
</Column>
<Column fill></Column>
</Column>
</View>
);
};
const WalletVerified: React.FC = () => {
return (
<Icon
name="verified-user"
color={'green'}
size={40}
containerStyle={{ marginStart: 4, bottom: 1 }}
/>
);
};

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { DropdownIcon } from '../../components/DropdownIcon'; import { DropdownIcon } from '../../components/DropdownIcon';
import { TextEditOverlay } from '../../components/TextEditOverlay'; import { TextEditOverlay } from '../../components/TextEditOverlay';
import { Column } from '../../components/ui'; import { Column, Text } from '../../components/ui';
import { Modal } from '../../components/ui/Modal'; import { Modal } from '../../components/ui/Modal';
import { MessageOverlay } from '../../components/MessageOverlay'; import { MessageOverlay } from '../../components/MessageOverlay';
import { ToastItem } from '../../components/ui/ToastItem'; import { ToastItem } from '../../components/ui/ToastItem';
@@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next';
import { VcDetails } from '../../components/VcDetails'; import { VcDetails } from '../../components/VcDetails';
import { OtpVerification } from './MyVcs/OtpVerification'; import { OtpVerification } from './MyVcs/OtpVerification';
import { BindStatus } from './MyVcs/BindVcStatus';
export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => { export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
const { t } = useTranslation('ViewVcModal'); const { t } = useTranslation('ViewVcModal');
@@ -52,7 +53,17 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
}> }>
<Column scroll> <Column scroll>
<Column fill> <Column fill>
<VcDetails vc={controller.vc} /> <VcDetails
vc={controller.vc}
onBinding={() => controller.addtoWallet()}
isBindingPending={controller.isWalletBindingPending}
/>
{controller.walletBindingError !== '' && (
<Text style={{ color: 'red', fontSize: 20 }}>
Error Occured : {controller.walletBindingError}
</Text>
)}
</Column> </Column>
</Column> </Column>
{controller.isEditingTag && ( {controller.isEditingTag && (
@@ -85,6 +96,24 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = (props) => {
/> />
)} )}
{controller.isAcceptingBindingOtp && (
<OtpVerification
isVisible={controller.isAcceptingBindingOtp}
onDismiss={controller.DISMISS}
onInputDone={controller.inputOtp}
error={controller.otpError}
/>
)}
{controller.showBindingStatus && (
<BindStatus
isVisible={controller.showBindingStatus}
bindingError={controller.walletBindingError}
onDismiss={controller.DISMISS}
onDone={controller.BINDING_DONE}
/>
)}
<MessageOverlay <MessageOverlay
isVisible={controller.isRequestingOtp} isVisible={controller.isRequestingOtp}
title={t('requestingOtp')} title={t('requestingOtp')}

View File

@@ -17,6 +17,12 @@ import {
selectVc, selectVc,
VcItemEvents, VcItemEvents,
vcItemMachine, vcItemMachine,
selectWalletBindingId,
selectWalletBindingError,
selectIsRequestBindingOtp,
selectAcceptingBindingOtp,
selectShowBindingStatus,
selectEmptyWalletBindingId,
} from '../../machines/vcItem'; } from '../../machines/vcItem';
import { selectPasscode } from '../../machines/auth'; import { selectPasscode } from '../../machines/auth';
import { biometricsMachine, selectIsSuccess } from '../../machines/biometrics'; import { biometricsMachine, selectIsSuccess } from '../../machines/biometrics';
@@ -125,6 +131,14 @@ export function useViewVcModal({
), ),
isRequestingOtp: useSelector(vcItemActor, selectIsRequestingOtp), isRequestingOtp: useSelector(vcItemActor, selectIsRequestingOtp),
storedPasscode: useSelector(authService, selectPasscode), storedPasscode: useSelector(authService, selectPasscode),
isBindingOtp: useSelector(vcItemActor, selectIsRequestBindingOtp),
isAcceptingBindingOtp: useSelector(vcItemActor, selectAcceptingBindingOtp),
showBindingStatus: useSelector(vcItemActor, selectShowBindingStatus),
walletBindingError: useSelector(vcItemActor, selectWalletBindingError),
isWalletBindingPending: useSelector(
vcItemActor,
selectEmptyWalletBindingId
),
CONFIRM_REVOKE_VC: () => { CONFIRM_REVOKE_VC: () => {
setRevoking(true); setRevoking(true);
@@ -136,6 +150,9 @@ export function useViewVcModal({
setReAuthenticating, setReAuthenticating,
setRevoking, setRevoking,
onError, onError,
addtoWallet: () => {
vcItemActor.send(VcItemEvents.ADD_WALLET_BINDING_ID());
},
lockVc: () => { lockVc: () => {
vcItemActor.send(VcItemEvents.LOCK_VC()); vcItemActor.send(VcItemEvents.LOCK_VC());
}, },
@@ -145,10 +162,12 @@ export function useViewVcModal({
revokeVc: (otp: string) => { revokeVc: (otp: string) => {
netInfoFetch(otp); netInfoFetch(otp);
}, },
ADD_WALLET: () => vcItemActor.send(VcItemEvents.ADD_WALLET_BINDING_ID()),
onSuccess, onSuccess,
EDIT_TAG: () => vcItemActor.send(VcItemEvents.EDIT_TAG()), EDIT_TAG: () => vcItemActor.send(VcItemEvents.EDIT_TAG()),
SAVE_TAG: (tag: string) => vcItemActor.send(VcItemEvents.SAVE_TAG(tag)), SAVE_TAG: (tag: string) => vcItemActor.send(VcItemEvents.SAVE_TAG(tag)),
DISMISS: () => vcItemActor.send(VcItemEvents.DISMISS()), DISMISS: () => vcItemActor.send(VcItemEvents.DISMISS()),
BINDING_DONE: () => vcItemActor.send(VcItemEvents.BINDING_DONE()),
LOCK_VC: () => vcItemActor.send(VcItemEvents.LOCK_VC()), LOCK_VC: () => vcItemActor.send(VcItemEvents.LOCK_VC()),
INPUT_OTP: (otp: string) => vcItemActor.send(VcItemEvents.INPUT_OTP(otp)), INPUT_OTP: (otp: string) => vcItemActor.send(VcItemEvents.INPUT_OTP(otp)),
}; };

View File

@@ -24,7 +24,7 @@ export const ReceiveVcScreen: React.FC = () => {
<Text weight="semibold" margin="24 24 0 24"> <Text weight="semibold" margin="24 24 0 24">
{t('header', { vcLabel: controller.vcLabel.singular })} {t('header', { vcLabel: controller.vcLabel.singular })}
</Text> </Text>
<VcDetails vc={controller.incomingVc} /> <VcDetails vc={controller.incomingVc} isBindingPending={false} />
</Column> </Column>
<Column padding="0 24" margin="32 0 0 0"> <Column padding="0 24" margin="32 0 0 0">
{controller.incomingVc.shouldVerifyPresence ? ( {controller.incomingVc.shouldVerifyPresence ? (

View File

@@ -0,0 +1,13 @@
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
export async function savePrivateKey(id: string, privateKey: string) {
var result = await RNSecureKeyStore.set(id, privateKey, {
accessible: ACCESSIBLE.ALWAYS_THIS_DEVICE_ONLY,
});
return result;
}
export async function getPrivateKey(id: string) {
var result = await RNSecureKeyStore.get(id);
return result;
}

View File

@@ -0,0 +1,5 @@
import { KeyPair, RSA } from 'react-native-rsa-native';
export function generateKeys(): Promise<KeyPair> {
return Promise.resolve(RSA.generateKeys(4096));
}

View File

@@ -12,6 +12,7 @@ export interface VC {
locked: boolean; locked: boolean;
reason?: VCSharingReason[]; reason?: VCSharingReason[];
shouldVerifyPresence?: boolean; shouldVerifyPresence?: boolean;
walletBindingId?: string;
} }
export interface VCSharingReason { export interface VCSharingReason {