[Injimob 812] openID4VP sharing flow UI implementation (#1628)

* [INJIMOB-1629] add an api to fetch the trusted verifiers list for vp sharing flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add screens for showing vcs matching openId4vp authorization request and selecting VCs

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add states in scan machine to show loader screen when vp sharing is started

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] send events to parent machine from openId4vp machine to update UI when performing vp sharing

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] change the position of check box in vc container in vp sharing flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add styles to send vp screen elements to match wireframe and add context variable to store vp sharing purpose

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] show error screen if no credential in wallet matches with authorization request

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] show confirmation pop up when user decline the consent for sharing vp

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] show error screen if the verifier authentication is failed after scanning vp sharing qr code

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] move error and overlay details assigning logic to send vp screen controller

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add translations for vp sharing flow texts in all languages

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] make changes in card skeleton component to show vc card loader in vp sharing flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] fix issues with reject button in sharing vp screen

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add support for sharing vp with out selfie from kebab menu

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add support for sharing vp with selfie from kebab menu

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add missing translations for error and overlay screen texts of vp sharing flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add logic in scan screen to show error screens in vp sharing from kebab menu flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add logic to filter the VCs based on the type of the VC in vp sharing flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812]: generate proof for vp token and send vp

Signed-off-by: adityankannan-tw <adityan410pm@gmail.com>

* [INJIMOB-1629] show generic error message if any error occured while validating vp qr code

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add logic to allow user retry vp sharing 3 times if any technical error occurred

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] add react method in native module to send the generated vp response metadata and make changes to show any errors occured after sharing vp

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-1629] show success screen if vp is shared successfully to the verifier

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812]: refactor proof generation and integrate remote openid4vp package

Signed-off-by: adityankannan-tw <adityan410pm@gmail.com>

* [INJIMOB-812]: refactor proof generation and integrate remote openid4vp package

Signed-off-by: adityankannan-tw <adityan410pm@gmail.com>

* [INJIMOB-812] make changes in the code to fetch the VCs properly if scope is present in the authorization code

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] fix the logic of checking if order field value in issuer wellknown is null and empty or not

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] move qr login and openid4vp red id's to scan actions

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] remove unnecessary logs

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] rename openid4vp actions to make them more meaningful and remove unnecessary events in send vp screen controller

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] show error screen if none of the selected VC has image but user chosen share with selfie option

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] add guard logic for checking if any of the selected VC has image or not

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812]: update package resolved with openid4vp library

Signed-off-by: adityankannan-tw <adityan410pm@gmail.com>

* [INJIMOB-812] fix the logic of retry button in technical error screen and perform the vp sharing again when user click on retry button

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] show specific error screen if required info is missing in qr code

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812]: refactor native module and wrapper with updated library name

Signed-off-by: adityankannan-tw <adityan410pm@gmail.com>

* [INJIMOB-1709]: Integrate VC verifier aar for Vc verification of Mosip VC (#1624)

* [INJIMOB-1709]: integrate VC verifier aar for Vc verification of Mosip VC

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>

* [INJIMOB-1709]: Handling Response from Vc Verifier Library

Signed-off-by: BalachandarG <balachandar.g@thoughtworks.com>

* [INJIMOB-1709]: Removed mavenLocal from build.gradle

Signed-off-by: BalachandarG <balachandar.g@thoughtworks.com>

* [INJIMOB-1709]: Updated build.gradle

Signed-off-by: BalachandarG <balachandar.g@thoughtworks.com>

* [INJIMOB-1709]: Removed Certify from isMosipVC Condition.

Signed-off-by: BalachandarG <balachandar.g@thoughtworks.com>

* [INJIMOB-1709]: Adding isAndroid check and verifying using digital bazaar for iOs.

Signed-off-by: BalachandarG <balachandar.g@thoughtworks.com>

---------

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>
Signed-off-by: BalachandarG <balachandar.g@thoughtworks.com>
Co-authored-by: Alka Prasad <prasadalka1998@gmail.com>

* [INJIMOB-1629] rename openId4VP to openID4VP in all files and address pr comments

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] fetch trusted verifiers list from mimoto end point

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] rename openId4VP to openID4VP in all files

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] make trusted verifier api call cache preferred

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] rename openId4VP to openID4VP in all places

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812]: refactor and update openid4vp swift library

Signed-off-by: adityankannan-tw <adityan410pm@gmail.com>

* [INJIMOB-812] remove code related to scope in openid4vp flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] remove code related to type in openid4vp actions

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] rename response_uri to response_uris in openid4vp native wrappers

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] rename typegen file in openid4vp machine

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] change target state of checkFaceAuthConsent to getTrustedVerifiersList

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812]: update openid4vp swift package

Signed-off-by: adityankannan-tw <adityan410pm@gmail.com>

* [INJIMOB-2097]: bump up app version to 0.15.0 (#1631)

Signed-off-by: adityankannan-tw <adityan410pm@gmail.com>
Co-authored-by: adityankannan-tw <adityan410pm@gmail.com>

* [INJIMOB-1884] remove SetupKeySelectionScreen (#1632)

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-812] change the conditional logic for checking if downloading error is generic or not in issuer guards and selectors

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] fetch the trusted verifiers list properly from api response

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] send the list of selected vcs images to face scanner machine only if face is available

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] fix home button navigation issue in error screen of vp sharing flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] move openid4vp machine to showError state if there is any occurred in vp sharing flow and reset error when user navigates to home screen

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] fix some flows in vp sharing flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

* [INJIMOB-812] fix the logic for showing the error screen as part of scan screen in vp sharing flow

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>

---------

Signed-off-by: PuBHARGAVI <46226958+PuBHARGAVI@users.noreply.github.com>
Signed-off-by: adityankannan-tw <adityan410pm@gmail.com>
Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>
Signed-off-by: BalachandarG <balachandar.g@thoughtworks.com>
Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>
Co-authored-by: adityankannan-tw <adityan410pm@gmail.com>
Co-authored-by: balachandarg-tw <115633327+balachandarg-tw@users.noreply.github.com>
Co-authored-by: Alka Prasad <prasadalka1998@gmail.com>
Co-authored-by: adityankannan-tw <109274996+adityankannan-tw@users.noreply.github.com>
Co-authored-by: abhip2565 <74866247+abhip2565@users.noreply.github.com>
This commit is contained in:
PuBHARGAVI
2024-10-04 11:21:16 +05:30
committed by GitHub
parent 2521a93991
commit fe59309e88
63 changed files with 3020 additions and 139 deletions

View File

@@ -2,7 +2,7 @@ fileignoreconfig:
- filename: package.json
checksum: 5b4fcb5ddc7cc96cc2d1733b544d56ea66e88cdab995a1052fbf9ac0e9c2dc21
- filename: package-lock.json
checksum: 98f4ef19f06521bac3ea3033d82810203214bf55b0469790a1d8acc20933c581
checksum: 84234f4ae94673b929b5355259c4fa0e497d4ec327985f497120b6fb66bb8c63
- filename: lib/jsonld-signatures/suites/ed255192018/ed25519.ts
checksum: 493b6e31144116cb612c24d98b97d8adcad5609c0a52c865a6847ced0a0ddc3a
- filename: components/PasscodeVerify.tsx
@@ -38,9 +38,9 @@ fileignoreconfig:
- filename: screens/Home/MyVcs/GetIdInputModal.tsx
checksum: 5c736ed79a372d0ffa7c02eb33d0dc06edbbb08d120978ff287f5f06cd6c7746
- filename: shared/openId4VCI/Utils.ts
checksum: ee4db1768be8d51fac0eb876a7b16fd2ab1806abcc711f01056f672003d11f31
checksum: 78473ce25cd52c8d07da9ba98683bdb64ae83a9de8fa907c62f2ebeca7dd21dc
- filename: shared/cryptoutil/cryptoUtil.ts
checksum: a8edd1047e33bfc9e37b73945b8edcd294b8e29baf380f86cb0f647b355c8e5a
checksum: 281f061ae6eb722c75c2caf2bdfb5b1bf72f292b471318d972c898c5a972c65f
- filename: shared/telemetry/TelemetryConstants.js
checksum: fd8dc3a69cdef68855dc5f0531d8e634bfa2621bb4dc22f85b8247512a349c4c
- filename: shared/telemetry/TelemetryUtils.js
@@ -66,11 +66,11 @@ fileignoreconfig:
- filename: screens/MainLayout.tsx
checksum: 53ead79279c9609e42a8993db1a66bdb4649e1ae3b909d462b45b00c507c416e
- filename: android/app/build.gradle
checksum: 46a4054440361b25d13ecd75811bf239a6abb4830ce7f79b2b15ccd878758760
checksum: 251ac12b49cf226d40931a02eb1b362a1d29e74cfe8f7a60ed128d66b0fea838
- filename: .github/workflows/push-triggers.yml
checksum: abc19ea38c8d7b79f15695d015709cc88a34a995181aaf12bc8344f940f3cbc4
- filename: android/fastlane/Fastfile
checksum: a25f155bcbbae7ab09563637c23771f7349738f12a6ddc8ae71c29c61ed535af
checksum: a5e816489a80b0a7498f35b7a2919f2287d4307b660687c2a9c51412aa8eceb7
- filename: .github/workflows/internal-build.yml
checksum: e9b85cf0405d777faee9345269f6f9eb861ed205728dca63cf27a5db79c876a7
- filename: assets/Issuer_search_clearing_button.svg
@@ -82,7 +82,7 @@ fileignoreconfig:
- filename: screens/Settings/BackupViaPassword.tsx
checksum: d2a355356bcaf8f7ef3b53ba93710cec15fefd0fdf31efd779eebd2bfab61c19
- filename: shared/api.ts
checksum: 1c5d43058e8733a403e02d0b3fa5f56e11519efa4564e48c92b4491f4bd60508
checksum: 05165e469008816a441126f8eed69d54c137d39c3c66e695f8710e8d33a9b038
- filename: machines/backupWithEncryption.ts
checksum: 038c12d30b2312fcbd9230a1c6ddb494d2e561fe0d09741335fa80ab67e2c550
- filename: machines/backupWithEncryption.typegen.ts
@@ -154,9 +154,9 @@ fileignoreconfig:
- filename: injitest/README.md
checksum: 82974a6b9363512472272245e9b433f92e63377e58ba306980876b745181a09c
- filename: shared/VCMetadata.ts
checksum: e93f988415bf91064e2cf5fbc09ff6c7226798baa5da721fa0715d5d0d6afddf
checksum: 4c0f2acc58894e5a427e1317b38d04daff91f64d1e61d6ee2f246ee516ef97ca
- filename: ios/Podfile.lock
checksum: b8c97d58a88207bae811db83074388cff249a83055a1f92ea7dee2f59b7a32c9
checksum: 43bd4742f2ba13357d8b9c44430bfa3cca0bf9bf8341984fd81174a929c85955
- filename: components/BackupAndRestoreBannerNotification.tsx
checksum: e465a9947727687d784d0cb9d8db1e28f765b0659bf4a3aa6d75643aa7b14102
- filename: components/ActivityLogEvent.ts
@@ -205,6 +205,10 @@ fileignoreconfig:
checksum: 4b08ee05c9c6fe9154ed3cdc9d23324e4d9cfac8a7028686f2b22903424d4cef
- filename: machines/VCItemMachine/VCItemGaurds.ts
checksum: 4f32814fc26a0edaa54a42dbc9f9e1d899144eb059ac8da211d1738887871829
- filename: machines/VerifiableCredential/VCItemMachine/VCItemGaurds.ts
checksum: 797069975e6402527d506db8f4644586957b8437d9cf867eaed4b0000683c4f8
- filename: ios/RNSecureKeystoreModule.m
checksum: d3f70ec17eacdfc3ed64936be1fc14b83fd1004c9f861fb0827da8df28ac3bf9
- filename: machines/VCItemMachine/VCItemServices.ts
checksum: 51b4872a64abd76b124000358068c0b213d50fb131d735c122cd9a177cd8390c
- filename: machines/VCItemMachine/VCItemActions.ts
@@ -238,7 +242,7 @@ fileignoreconfig:
- filename: machines/backupAndRestore/backupRestore.typegen.ts
checksum: 64a8e42712083e0cbf0d6ce6b1139c62b59a14af17e4132d14f903d4d3bbe6b2
- filename: machines/app.typegen.ts
checksum: c310de13b855b22fe68e8fa954dc3a8a4728284c5904da99986a1f4eac8ea0be
checksum: 6e194b5eb5f2ae91627bb1db832e0c9ab959f376593498df839db964fecf4258
- filename: screens/Home/MyVcsTabMachine.typegen.ts
checksum: 948efb4d61551e4f3cd9eb9913b927158daa5c3d16f49ad297e7cb63190bc023
- filename: machines/IssuersMachine.typegen.ts
@@ -246,7 +250,7 @@ fileignoreconfig:
- filename: screens/Home/MyVcsTab.tsx
checksum: ccf9ca41165b35628e026e7557f6fa7771f29dae8b09324d9af93c2c9b0eca6f
- filename: machines/store.typegen.ts
checksum: 46f3a7c2d15ed03fc70e27ecae5a12c128011c49913b35cdb8edba12b1a999db
checksum: 077a1906c908daf79544f604632df36f3359075a7bc912e51ab5707cb178bf48
- filename: machines/VerifiableCredential/VCMetaMachine/VCMetaMachine.ts
checksum: 36244323200d1e965adb48205d359fba807a5a153f2fc3ce75fe34e8b1bdb01a
- filename: machines/Issuers/IssuersMachine.ts
@@ -256,18 +260,17 @@ fileignoreconfig:
- filename: screens/Issuers/CredentialTypeSelectionScreen.tsx
checksum: 144bbf59e86a89bf580ac7931645ca3eaed69a9409de36f6ce9f88a14091a9d3
- filename: components/QrCodeOverlay.tsx
checksum: 68758cbddafe575c7f53294327963112a0780d30fd23cd0b6bf82d7dcfd856fe
checksum: 47220a4ebd8af702afe622a29b689f651eb23387bac1c623f241839beb413d25
checksum: dacbce1d6cd7e702400897981f44b65288541f4a41ee970f1e6ac1146af150bb
- filename: machines/Issuers/IssuersEvents.ts
checksum: fd8c30e0cf43a784be883c9d79a3bff0d2bcd9075e937d225939040542998b10
- filename: machines/Issuers/IssuersGuards.ts
checksum: d87b6f4277c4be68f1884efa5c73e1b1d02a1afaefb276417b95a312f599578a
checksum: 21783a057207ad04facdb4c71884f49b0230490def04158419d730e0cc60eb83
- filename: machines/Issuers/IssuersActions.ts
checksum: e865a33dcecfe185eff5ac06208f0f2e8ff6574f778e31da74d6ba74c67e285d
checksum: 4414aa10588d2305293b1902982c5969895c858355e4b91d01dfaa8601c2dd62
- filename: injitest/automation_trigger.sh
checksum: f2f34839c99cb1b871dde17aed8508a071345d22738796e005ff709d2dab8644
- filename: machines/Issuers/IssuersService.ts
checksum: 1b54b0249488fc496e3582588c644034865fd50386757789baaaf1c3821464d5
checksum: e3832dff27687abc28609d2b281e570b4b0017995b7cfb56627a6b96949c469a
- filename: screens/Home/ViewVcModal.tsx
checksum: cfb25d562185488432b76287c4ef93359c1c64d8e29f5755d4c0a726c1485442
- filename: injitest/src/main/resources/TestData.json
@@ -293,7 +296,7 @@ fileignoreconfig:
- filename: machines/QrLogin/QrLoginMachine.ts
checksum: f4234549baea9a7f69be98f52c30a04f2f6138f9b1f2b60a7b40f15f7e03345b
- filename: machines/QrLogin/QrLoginServices.ts
checksum: b20d0caa6d23078b4010ea5185f01270356422dd216edd7834b069cdedd3383d
checksum: 0eee865344c9e15722bac2307b5dd6025fd809233b71456472609d578c9a365a
- filename: machines/bleShare/scan/scanActions.ts
checksum: e32e1be4a02f9247844771293e5fc285bd6e1bfd3f0451a6bbc5bd171931b6f2
- filename: injitest/src/test/java/iosTestCases/ActivateVcTest.java
@@ -303,7 +306,7 @@ fileignoreconfig:
- filename: injitest/src/test/java/iosTestCases/VerifyHistoryTest.java
checksum: 8a00278af4c8744c713c57328991bbca438eb5d5d89b492a7f5234c47362f44b
- filename: machines/store.ts
checksum: 93ffa32067d698ecc9b7c1eec3b58ce1b3bee44d53c0e7daded0467393e3f0d6
checksum: 3fd2db0c41f8bd5f30ef922b856549cb5423997b2123c5364e643e47e5efd3cf
- filename: components/BannerNotificationContainer.tsx
checksum: 9e5b4a61b87e86666f0bee550d410df2b8576dfe5ec374de0ab139a468a234f7
- filename: injitest/src/test/java/iosTestCases/PinVcTest.java
@@ -323,32 +326,30 @@ fileignoreconfig:
- filename: injitest/src/test/java/iosTestCases/VcDownloadAndVerifyUsingUinTest.java
checksum: a6feabb768e2d97dfb0a1693f09d839686ce6be686523cf273b2d3ce614b34fd
- filename: machines/Issuers/IssuersMachine.typegen.ts
checksum: 08fd5e4eff836c219a0f16f6d4178a3511ec2581507076d3f9d32dcadbc01351
checksum: 581e73b10471735aa6415590b0f01f6c9b503a196d3fca450a4c2a9e647487d3
- filename: injitest/src/test/java/iosTestCases/DeletingVcTest.java
checksum: 0816d4b9440da5384fd867d13555986d223124b9609280350226510a06ae96fa
- filename: machines/VerifiableCredential/VCItemMachine/VCItemMachine.ts
checksum: fdc0c23a7107eb713d20f60fda675f9e9fe8ef29c981d798d90e581dfee340c8
checksum: 8ac74d2e5c6de179e460b86899eb048ad4c5bd67abc3d28c015e92335b8afe24
- filename: machines/VerifiableCredential/VCItemMachine/VCItemServices.ts
checksum: 1ce38602f148388940eec172a5c9be83de7a600adcae0ba9e8ac27e5ebc44641
checksum: 9ba47d5bda1f3ebec6b5f49b7f77ea6f22a62899c9c742964495926885c53766
- filename: ios/RNPixelpassModule.m
checksum: c91348eceec5edbffa03ba03f3f52a8e90ff7f942816c9609080d1647052fd66
- filename: android/app/src/main/java/io/mosip/residentapp/InjiVciClientModule.java
checksum: 17f55840bab193bc353034445ba4fce53e1ce466e95f616c15a1351f8d2f23bc
- filename: ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved
checksum: b168940c6b487dc96fd22f564f2e187dae46f4fa5e4a64cf81c4d810b1c1ae78
checksum: 2d0a5899777bff2ff8412e4931e0b1087e44f63047a2e3525e82eda0dfe8791c
- filename: injitest/src/main/resources/Vids.json
checksum: 8bcffed7a6dd565ae695e1b29de0655e10bd5c5420af2718defd593a687b8817
- filename: injitest/src/main/java/inji/utils/UpdateNetworkSettings.java
checksum: e249ce3e6b7f47abc183fe5a3637bb39ccb06900ef75b9b2f08426d1535e22aa
- filename: App.tsx
checksum: d16d4a40b246abe25a5d2da7ec65163b5756fe8ba9390608a7fc7f8e721b2ed1
- filename: machines/VerifiableCredential/VCItemMachine/VCItemServices.ts
checksum: 46f5b7ad6e6dcd9de9f9872c79d2c07addcd228324a43cca18525f6b1f4ff7cb
- filename: injitest/src/test/java/iosTestCases/ShareVcTest.java
checksum: 1cf9b61d3fcea9b63b2b9f7dffe9b5a1848e196c39f77790b6c9d83f201c6197
- filename: android/app/src/main/java/io/mosip/residentapp/RNSecureKeystoreModule.java
checksum: f307f8273f72ec70b991baf799ae71f93c785c76e3e15847004f567558340e32
checksum: db9d36d21607f247e2791d0ade02f2868700c432333636b0ae5a542649b69f3a
- filename: injitest/src/test/java/androidTestCases/ShareVcTest.java
checksum: a7e3e579b6ac05f95932638b61272142774d0690c13717c890e87374782ea509
- filename: ios/RNPixelpassModule.m
@@ -364,7 +365,7 @@ fileignoreconfig:
- filename: ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved
checksum: b168940c6b487dc96fd22f564f2e187dae46f4fa5e4a64cf81c4d810b1c1ae78
- filename: ios/Inji.xcodeproj/project.pbxproj
checksum: 4359976ed4d1ac3206d76b87d3458d070027199c8569ba123436c4b5343aba74
checksum: 6e83472f832f71f75aa82ed06eb677d865195755074144e4bf832d6adb30e959
- filename: screens/Settings/ReceivedCardsModal.tsx
checksum: 6dee9153a61009b0252d294154c88d5e1b241a517c76e930b391a39d7bc52392
- filename: components/FaceScanner/FaceCompare.tsx
@@ -372,5 +373,21 @@ fileignoreconfig:
- filename: components/FaceScanner/LivenessDetection.tsx
checksum: d4140a42ee9ca0f7c90e490f762d181a723fd9dd20db891cbbe53bfbd8f81632
- filename: machines/VerifiableCredential/VCItemMachine/VCItemActions.ts
checksum: 9b68ccc45681459d164197f73a1875e6f8bdf473acede18c811f4a784fca00e0
checksum: 037ddde01479c24f954e87be5088dd0f449f0379bd8bb0b34605ca40be0f3f6c
- filename: machines/app.ts
checksum: 5da59bb384d04e29c7745d773108903fa144275c57edc1aca1898fcae7baea84
- filename: shared/cryptoutil/signFormatConverter.ts
checksum: 3a0a03f4e719194858a61810d12180cdc407c53f04ce1455360d5d535556b7ac
- filename: shared/CloudBackupAndRestoreUtils.ts
checksum: dd98da030f7b4decb62ab2466604a2a28e6c5a1866eae9a5634b090b742497f6
- filename: ios/RNSecureKeystoreModule.swift
checksum: 98784857098e15aca851c6eb79e48016887408ff84bb82afafdc03b38ab2e338
- filename: machines/Issuers/IssuersModel.ts
checksum: fe4084d1f5dbf89e71cc3fbe9f845d3f2c9bb32901bd5b95a8addcb13257b414
- filename: android/app/src/main/java/io/mosip/residentapp/InjiOpenId4VPModule.java
checksum: 6b315164dca5de95c11e0dc8cbb480207b19c312b1c9135adc39ef74a1ff7e35
- filename: screens/Scan/SendVPScreenController.ts
checksum: f898ac7f1ecfa1df17e33b327d675f57debf2d5bd56052fc047dd03577354590
- filename: machines/openID4VP/openID4VPMachine.typegen.ts
checksum: 986c2743bba3554068c1cf52e6f3e7b1762699e62dab6f971a6f796e2ea3eb81
version: ""

View File

@@ -262,12 +262,14 @@ android {
}
dependencies {
implementation("io.mosip:inji-openID4VP:0.1.0-SNAPSHOT")
implementation("com.facebook.react:react-android")
implementation 'com.facebook.soloader:soloader:0.10.1+'
implementation("io.mosip:pixelpass:0.2.1")
implementation("io.mosip:secure-keystore:0.3.0-SNAPSHOT")
implementation("io.mosip:tuvali:0.5.1")
implementation("io.mosip:inji-vci-client:0.2.0-SNAPSHOT")
implementation("com.google.code.gson:gson:2.10.1")
implementation("io.mosip:vcverifier-aar:1.2.0-SNAPSHOT")

View File

@@ -0,0 +1,122 @@
package io.mosip.residentapp;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import io.mosip.openID4VP.OpenID4VP;
import io.mosip.openID4VP.dto.VPResponseMetadata;
import io.mosip.openID4VP.dto.Verifier;
public class InjiOpenID4VPModule extends ReactContextBaseJavaModule {
private OpenID4VP openID4VP;
private Gson gson;
InjiOpenID4VPModule(@Nullable ReactApplicationContext reactContext) {
super(reactContext);
}
@NonNull
@Override
public String getName() {
return "InjiOpenID4VP";
}
@ReactMethod
public void init(String appId) {
Log.d("InjiOpenID4VPModule", "Initializing InjiOpenID4VPModule with " + appId);
openID4VP = new OpenID4VP(appId);
gson = new GsonBuilder()
.disableHtmlEscaping()
.create();
}
@ReactMethod
public void authenticateVerifier(String encodedAuthorizationRequest, ReadableArray trustedVerifiers,
Promise promise) {
try {
Map<String, String> authenticationResponse = openID4VP.authenticateVerifier(encodedAuthorizationRequest,
convertReadableArrayToVerifierArray(trustedVerifiers));
String authenticationResponseAsJson = gson.toJson(authenticationResponse, Map.class);
promise.resolve(authenticationResponseAsJson);
} catch (Exception exception) {
promise.reject(exception);
}
}
@ReactMethod
public void constructVerifiablePresentationToken(ReadableMap selectedVCs, Promise promise) {
try {
Map<String, List<String>> selectedVCsMap = new HashMap<>();
ReadableMapKeySetIterator iterator = selectedVCs.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
ReadableArray valueArray = selectedVCs.getArray(key);
List<String> valueList = new ArrayList<>();
for (int i = 0; i < valueArray.size(); i++) {
valueList.add(valueArray.getString(i));
}
selectedVCsMap.put(key, valueList);
}
String vpToken = openID4VP.constructVerifiablePresentationToken(selectedVCsMap);
promise.resolve(vpToken);
} catch (Exception exception) {
promise.reject(exception);
}
}
@ReactMethod
public void shareVerifiablePresentation(ReadableMap vpResponseMetadata, Promise promise) {
try {
VPResponseMetadata vpResMetadata = getVPResponseMetadata(vpResponseMetadata);
String response = openID4VP.shareVerifiablePresentation(vpResMetadata);
promise.resolve(response);
} catch (Exception exception) {
promise.reject(exception);
}
}
private VPResponseMetadata getVPResponseMetadata(ReadableMap vpResponseMetadata) throws IllegalArgumentException {
String jws = vpResponseMetadata.getString("jws");
String signatureAlgorithm = vpResponseMetadata.getString("signatureAlgorithm");
String publicKey = vpResponseMetadata.getString("publicKey");
String domain = vpResponseMetadata.getString("domain");
return new VPResponseMetadata(jws, signatureAlgorithm, publicKey, domain);
}
public List<Verifier> convertReadableArrayToVerifierArray(ReadableArray readableArray) {
List<Verifier> trustedVerifiersList = new ArrayList<>();
for (int i = 0; i < readableArray.size(); i++) {
ReadableMap verifierMap = readableArray.getMap(i);
String clientId = verifierMap.getString("client_id");
ReadableArray responseUris = verifierMap.getArray("response_uris");
List<String> responseUriList = new ArrayList<>();
for (int j = 0; j < responseUris.size(); j++) {
responseUriList.add(responseUris.getString(j));
}
trustedVerifiersList.add(new Verifier(clientId, responseUriList));
}
return trustedVerifiersList;
}
}

View File

@@ -26,6 +26,7 @@ public class InjiPackage implements ReactPackage {
modules.add(new RNWalletModule(new RNEventEmitter(reactApplicationContext), new Wallet(reactApplicationContext), reactApplicationContext));
modules.add(new RNVerifierModule(new RNEventEmitter(reactApplicationContext), new Verifier(reactApplicationContext), reactApplicationContext));
modules.add(new RNQrLoginIntentModule(reactApplicationContext));
modules.add(new InjiOpenID4VPModule(reactApplicationContext));
modules.add(new RNVCVerifierModule(reactApplicationContext));
return modules;
}

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<g id="Group_58148" data-name="Group 58148" transform="translate(-0.114 -0.21)">
<g id="Group_57140" data-name="Group 57140" transform="translate(-0.095)">
<g id="Rectangle_7173" data-name="Rectangle 7173" transform="translate(0.209 0.21)" fill="#f2801d" stroke="#f2801d" stroke-width="2">
<rect width="18" height="18" rx="4" stroke="none"/>
<rect x="1" y="1" width="16" height="16" rx="3" fill="none"/>
</g>
<path id="check_circle_FILL0_wght400_GRAD0_opsz48" d="M9.739,14.057,15.4,8.414l-.921-.9L9.739,12.242l-2.4-2.393-.9.9Z" transform="translate(-1.615 -1.732)" fill="#fff" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 804 B

View File

@@ -61,10 +61,7 @@ export class ActivityLog {
export function getActionText(activity: ActivityLog, t, wellknown: Object) {
if (!!activity.credentialConfigurationId) {
const cardType = getIdType(
wellknown,
activity.credentialConfigurationId,
);
const cardType = getIdType(wellknown, activity.credentialConfigurationId);
return `${t(activity.type, {idType: cardType, id: activity.id})}`;
}
return `${t(activity.type, {idType: '', id: activity.id})}`;

View File

@@ -41,7 +41,7 @@ export const FaceScanner: React.FC<FaceScannerProps> = props => {
const {appService} = useContext(GlobalContext);
const isActive = useSelector(appService, selectIsActive);
const machine = useRef(createFaceScannerMachine(props.vcImage));
const machine = useRef(createFaceScannerMachine(props.vcImages));
const service = useInterpret(machine.current);
const [cameraType, setCameraType] = useState(Camera.Constants.Type.front);
@@ -118,7 +118,7 @@ export const FaceScanner: React.FC<FaceScannerProps> = props => {
const result = await cropEyeAreaFromFace(
picArray,
props.vcImage,
props.vcImages[0],
faceToCompare,
);
return result ? props.onValid() : props.onInvalid();
@@ -209,7 +209,7 @@ export const FaceScanner: React.FC<FaceScannerProps> = props => {
}
};
interface FaceScannerProps {
vcImage: string;
vcImages: string[];
onValid: () => void;
onInvalid: () => void;
isLiveness: boolean;

View File

@@ -36,14 +36,18 @@ export const VCCardView: React.FC<VCItemProps> = props => {
const [wellknown, setWellknown] = useState(null);
useEffect(() => {
const {issuer, wellKnown, credentialConfigurationId, vcMetadata: {format}} =
verifiableCredentialData;
const {
issuer,
wellKnown,
credentialConfigurationId,
vcMetadata: {format},
} = verifiableCredentialData;
if (wellKnown) {
getCredentialIssuersWellKnownConfig(
issuer,
CARD_VIEW_DEFAULT_FIELDS,
credentialConfigurationId,
format
format,
)
.then(response => {
setWellknown(response.matchingCredentialIssuerMetadata);
@@ -59,7 +63,7 @@ export const VCCardView: React.FC<VCItemProps> = props => {
}, [verifiableCredentialData?.wellKnown]);
if (!isVCLoaded(controller.credential, fields)) {
return <VCCardSkeleton />;
return <VCCardSkeleton flow={props.flow} />;
}
const CardViewContent = props => (

View File

@@ -27,21 +27,37 @@ import {useCopilot} from 'react-native-copilot';
import {useTranslation} from 'react-i18next';
export const VCCardViewContent: React.FC<VCItemContentProps> = props => {
const isVCSelectable = props.selectable && (
<CheckBox
checked={props.selected}
checkedIcon={
<Icon name="check-circle" type="material" color={Theme.Colors.Icon} />
}
uncheckedIcon={
<Icon
name="radio-button-unchecked"
color={Theme.Colors.uncheckedIcon}
/>
}
onPress={() => props.onPress()}
/>
);
const checkBoxForVCSharing = props.selectable &&
props.flow !== VCItemContainerFlowType.OPENID4VP && (
<CheckBox
checked={props.selected}
checkedIcon={
<Icon name="check-circle" type="material" color={Theme.Colors.Icon} />
}
uncheckedIcon={
<Icon
name="radio-button-unchecked"
color={Theme.Colors.uncheckedIcon}
/>
}
onPress={() => props.onPress()}
/>
);
const checkBoxForVPSharing = props.selectable &&
props.flow === VCItemContainerFlowType.OPENID4VP && (
<CheckBox
checked={props.selected}
checkedIcon={SvgImage.selectedCheckBox()}
uncheckedIcon={
<Icon
name="check-box-outline-blank"
color={Theme.Colors.uncheckedIcon}
size={22}
/>
}
onPress={() => props.onPress()}
/>
);
const issuerLogo = props.verifiableCredentialData.issuerLogo;
const faceImage = props.verifiableCredentialData.face;
const {start} = useCopilot();
@@ -63,6 +79,7 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = props => {
: undefined
}>
<Row crossAlign="center" padding="3 0 0 3">
{checkBoxForVPSharing}
{VcItemContainerProfileImage(props)}
<Column fill align={'space-around'} margin="0 10 0 10">
<VCItemFieldValue
@@ -115,7 +132,7 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = props => {
</Pressable>
</>
)}
{isVCSelectable}
{checkBoxForVCSharing}
</Row>
<WalletBinding service={props.service} vcMetadata={props.vcMetadata} />

View File

@@ -4,9 +4,52 @@ import {ImageBackground, View} from 'react-native';
import React from 'react';
import LinearGradient from 'react-native-linear-gradient';
import ShimmerPlaceholder from 'react-native-shimmer-placeholder';
import {VCItemContainerFlowType, VCShareFlowType} from '../../../shared/Utils';
export const VCCardSkeleton = () => {
return (
export const VCCardSkeleton: React.FC<VCCardSkeletonProps> = props => {
return props.flow === VCItemContainerFlowType.OPENID4VP ? (
<View style={Theme.Styles.closeCardBgContainer}>
<ImageBackground
source={Theme.CloseCard}
resizeMode="stretch"
style={Theme.Styles.vertloadingContainer}>
<Column>
<Row crossAlign="center">
<ShimmerPlaceholder
LinearGradient={LinearGradient}
width={22}
height={22}
style={{borderRadius: 5, marginRight: 20, marginLeft: 15}}
/>
<ShimmerPlaceholder
LinearGradient={LinearGradient}
width={40}
height={53}
style={{borderRadius: 5}}
/>
<Column fill align={'space-around'} margin={'0 10 0 10'}>
<ShimmerPlaceholder
LinearGradient={LinearGradient}
width={100}
style={{borderRadius: 5, marginTop: 5}}
/>
<ShimmerPlaceholder
LinearGradient={LinearGradient}
width={50}
style={{borderRadius: 5, marginTop: 5}}
/>
</Column>
<ShimmerPlaceholder
LinearGradient={LinearGradient}
width={35}
height={35}
style={{borderRadius: 5}}
/>
</Row>
</Column>
</ImageBackground>
</View>
) : (
<View style={Theme.Styles.closeCardBgContainer}>
<ImageBackground
source={Theme.CloseCard}
@@ -50,3 +93,7 @@ export const VCCardSkeleton = () => {
</View>
);
};
export interface VCCardSkeletonProps {
flow?: string;
}

View File

@@ -53,6 +53,7 @@ import ColoredInfo from '../../assets/Colored_Info.svg';
import Info from '../../assets/Info.svg';
import Search from '../../assets/Search.svg';
import CloudUploadDoneIcon from '../../assets/Cloud_Upload_Done_Icon.svg';
import SelectedCheckBox from '../../assets/Selected_Check_Box.svg';
export class SvgImage {
static MosipLogo(props: LogoProps) {
@@ -64,6 +65,10 @@ export class SvgImage {
return <KebabIcon {...testIDProps(testId)} />;
}
static selectedCheckBox() {
return <SelectedCheckBox />;
}
static walletActivatedIcon() {
return (
<WalletActivatedIcon

View File

@@ -114,6 +114,7 @@ export const DefaultTheme = {
GrayText: Colors.GrayText,
errorGrayText: Colors.mediumDarkGrey,
gradientBtn: ['#F59B4B', '#E86E04'],
selectIDTextGradient: ['#F5F5F5', '#FFFFFF'],
dotColor: Colors.dorColor,
plainText: Colors.plainText,
IconBackground: Colors.LightOrange,
@@ -1592,6 +1593,29 @@ export const DefaultTheme = {
fontWeight: 'bold',
},
}),
VPSharingStyles: StyleSheet.create({
purposeContainer: {
backgroundColor: Colors.TimeoutHintBoxColor,
borderColor: Colors.TimeoutHintBoxBorder,
borderWidth: 1,
borderRadius: 5,
},
purposeText: {
fontSize: 13,
position: 'relative',
fontFamily: 'Inter_500Medium',
},
cardsSelectedText: {
fontFamily: 'Inter_500Medium',
color: '#000000',
fontSize: 14,
},
selectIDText: {
position: 'relative',
fontFamily: 'Inter_600SemiBold',
fontSize: 16,
},
}),
CameraDisabledStyles: StyleSheet.create({
container: {
position: 'absolute',

View File

@@ -117,6 +117,7 @@ export const PurpleTheme = {
DefaultToggle: Colors.LightPurple,
GrayText: Colors.GrayText,
gradientBtn: Colors.GradientColors,
selectIDTextGradient: ['#F5F5F5', '#FFFFFF'],
dotColor: Colors.dorColor,
plainText: Colors.plainText,
IconBackground: Colors.LightPurple,
@@ -1593,6 +1594,29 @@ export const PurpleTheme = {
fontWeight: 'bold',
},
}),
VPSharingStyles: StyleSheet.create({
purposeContainer: {
backgroundColor: Colors.TimeoutHintBoxColor,
borderColor: Colors.TimeoutHintBoxBorder,
borderWidth: 1,
borderRadius: 5,
},
purposeText: {
fontSize: 13,
position: 'relative',
fontFamily: 'Inter_500Medium',
},
cardsSelectedText: {
fontFamily: 'Inter_500Medium',
color: '#000000',
fontSize: 14,
},
selectIDText: {
position: 'relative',
fontFamily: 'Inter_600SemiBold',
fontSize: 16,
},
}),
CameraDisabledStyles: StyleSheet.create({
container: {
position: 'absolute',

View File

@@ -3,13 +3,16 @@
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
1E6875E92CA554E80086D870 /* OpenID4VP in Frameworks */ = {isa = PBXBuildFile; productRef = 1E6875E82CA554E80086D870 /* OpenID4VP */; };
1E6875EB2CA554FD0086D870 /* RNOpenID4VPModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E6875EA2CA554FD0086D870 /* RNOpenID4VPModule.m */; };
1E6875ED2CA5550F0086D870 /* RNOpenID4VPModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E6875EC2CA5550F0086D870 /* RNOpenID4VPModule.swift */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
73295844242A4AD3AA52D0BE /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = D98B96A488E54CBDB286B26F /* noop-file.swift */; };
96905EF65AED1B983A6B3ABC /* libPods-Inji.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Inji.a */; };
@@ -61,6 +64,8 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Inji/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Inji/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Inji/main.m; sourceTree = "<group>"; };
1E6875EA2CA554FD0086D870 /* RNOpenID4VPModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNOpenID4VPModule.m; sourceTree = "<group>"; };
1E6875EC2CA5550F0086D870 /* RNOpenID4VPModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNOpenID4VPModule.swift; sourceTree = "<group>"; };
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Inji.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Inji.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6C2E3173556A471DD304B334 /* Pods-Inji.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Inji.debug.xcconfig"; path = "Target Support Files/Pods-Inji/Pods-Inji.debug.xcconfig"; sourceTree = "<group>"; };
7A4D352CD337FB3A3BF06240 /* Pods-Inji.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Inji.release.xcconfig"; path = "Target Support Files/Pods-Inji/Pods-Inji.release.xcconfig"; sourceTree = "<group>"; };
@@ -110,6 +115,7 @@
files = (
E8AF2B9D2C0D93E800E775F6 /* VCIClient in Frameworks */,
9C4850432C3E5873002ECBD5 /* ios-tuvali-library in Frameworks */,
1E6875E92CA554E80086D870 /* OpenID4VP in Frameworks */,
9CE34B1F2BFE03E4001AF414 /* pixelpass in Frameworks */,
9C7CDF492C89802C00243A9A /* securekeystore in Frameworks */,
96905EF65AED1B983A6B3ABC /* libPods-Inji.a in Frameworks */,
@@ -144,6 +150,8 @@
D98B96A488E54CBDB286B26F /* noop-file.swift */,
E86208142C0335C5007C3E24 /* RNVCIClientModule.swift */,
E86208162C0335EC007C3E24 /* RNVCIClientModule.m */,
1E6875EA2CA554FD0086D870 /* RNOpenID4VPModule.m */,
1E6875EC2CA5550F0086D870 /* RNOpenID4VPModule.swift */,
);
name = Inji;
sourceTree = "<group>";
@@ -276,6 +284,7 @@
E8AF2B9C2C0D93E800E775F6 /* VCIClient */,
9C4850422C3E5873002ECBD5 /* ios-tuvali-library */,
9C7CDF482C89802C00243A9A /* securekeystore */,
1E6875E82CA554E80086D870 /* OpenID4VP */,
);
productName = Inji;
productReference = 13B07F961A680F5B00A75B9A /* Inji.app */;
@@ -308,6 +317,7 @@
E86208242C039895007C3E24 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */,
9C4850412C3E5873002ECBD5 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */,
9C7CDF472C89802C00243A9A /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */,
1E6875E72CA554E80086D870 /* XCRemoteSwiftPackageReference "inji-openid4vp-ios-swift" */,
);
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
@@ -486,11 +496,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1E6875ED2CA5550F0086D870 /* RNOpenID4VPModule.swift in Sources */,
9C48504F2C3E59B5002ECBD5 /* RNVersionModule.swift in Sources */,
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
9C0E86BB2BEE36C300E9F9F6 /* RNPixelpassModule.m in Sources */,
E86208152C0335C5007C3E24 /* RNVCIClientModule.swift in Sources */,
9C0E86B52BEE357A00E9F9F6 /* RNPixelpassModule.swift in Sources */,
1E6875EB2CA554FD0086D870 /* RNOpenID4VPModule.m in Sources */,
9C4850532C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
9C4850502C3E59B5002ECBD5 /* RNEventMapper.swift in Sources */,
@@ -732,6 +744,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
1E6875E72CA554E80086D870 /* XCRemoteSwiftPackageReference "inji-openid4vp-ios-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mosip/inji-openid4vp-ios-swift";
requirement = {
branch = develop;
kind = branch;
};
};
9C4850412C3E5873002ECBD5 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mosip/tuvali-ios-swift/";
@@ -767,6 +787,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
1E6875E82CA554E80086D870 /* OpenID4VP */ = {
isa = XCSwiftPackageProductDependency;
package = 1E6875E72CA554E80086D870 /* XCRemoteSwiftPackageReference "inji-openid4vp-ios-swift" */;
productName = OpenID4VP;
};
9C4850422C3E5873002ECBD5 /* ios-tuvali-library */ = {
isa = XCSwiftPackageProductDependency;
package = 9C4850412C3E5873002ECBD5 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */;

View File

@@ -28,6 +28,15 @@
"version" : "5.2.0"
}
},
{
"identity" : "inji-openid4vp-ios-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mosip/inji-openid4vp-ios-swift",
"state" : {
"branch" : "develop",
"revision" : "c7bc1e7f6c98c8d857c88062aabe55453482b82c"
}
},
{
"identity" : "inji-vci-client-ios-swift",
"kind" : "remoteSourceControl",

23
ios/RNOpenID4VPModule.m Normal file
View File

@@ -0,0 +1,23 @@
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(InjiOpenID4VP, NSObject)
RCT_EXTERN_METHOD(init:(NSString *)appId)
RCT_EXTERN_METHOD(authenticateVerifier:(NSString *)encodedAuthorizationRequest
trustedVerifierJSON:(id)trustedVerifierJSON
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(constructVerifiablePresentationToken:(id)credentialsMap
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(shareVerifiablePresentation:(id)vpResponseMetadata
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(requiresMainQueueSetup:(BOOL))
@end

113
ios/RNOpenID4VPModule.swift Normal file
View File

@@ -0,0 +1,113 @@
import Foundation
import OpenID4VP
import React
@objc(InjiOpenID4VP)
class RNOpenId4VpModule: NSObject, RCTBridgeModule {
private var openID4VP: OpenID4VP?
static func moduleName() -> String {
return "InjiOpenID4VP"
}
@objc
func `init`(_ appId: String) {
openID4VP = OpenID4VP(traceabilityId: appId)
}
@objc
func authenticateVerifier(_ encodedAuthorizationRequest: String,
trustedVerifierJSON: AnyObject,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
Task {
do {
guard let verifierMeta = trustedVerifierJSON as? [[String:Any]] else {
reject("OPENID4VP", "Invalid verifier meta format", nil)
return
}
let trustedVerifiersList: [Verifier] = try verifierMeta.map { verifierDict in
guard let clientId = verifierDict["client_id"] as? String,
let responseUris = verifierDict["response_uris"] as? [String] else {
throw NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid Verifier data"])
}
return Verifier(clientId: clientId, responseUris: responseUris)
}
let authenticationResponse: AuthenticationResponse = try await openID4VP!.authenticateVerifier(encodedAuthorizationRequest: encodedAuthorizationRequest, trustedVerifierJSON: trustedVerifiersList)
let response = try toJsonString(authenticationResponse.response)
resolve(response)
} catch {
reject("OPENID4VP", "Unable to authenticate the Verifier", error)
}
}
}
@objc
func constructVerifiablePresentationToken(_ credentialsMap: AnyObject, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
Task {
do {
guard let credentialsMap = credentialsMap as? [String:[String]] else {
reject("OPENID4VP", "Invalid credentials map format", nil)
return
}
let response = try await openID4VP?.constructVerifiablePresentationToken(credentialsMap: credentialsMap)
resolve(response)
} catch {
reject("OPENID4VP","Failed to construct verifiable presentation",error)
}
}
}
@objc
func shareVerifiablePresentation(_ vpResponseMetadata: AnyObject, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
Task {
do {
guard let vpResponse = vpResponseMetadata as? [String:String] else {
reject("OPENID4VP", "Invalid vp response meta format", nil)
return
}
guard let jws = vpResponse["jws"] as String?,
let signatureAlgorithm = vpResponse["signatureAlgorithm"] as String?,
let publicKey = vpResponse["publicKey"] as String?,
let domain = vpResponse["domain"] as String?
else {
reject("OPENID4VP", "Invalid vp response metat", nil)
return
}
let vpResponseMeta = VPResponseMetadata(jws: jws, signatureAlgorithm: signatureAlgorithm, publicKey: publicKey, domain: domain)
let response = try await openID4VP?.shareVerifiablePresentation(vpResponseMetadata: vpResponseMeta)
resolve(response)
} catch {
reject("OPENID4VP","Failed to send verifiable presentation",error)
}
}
}
func toJsonString(_ jsonObject: Any) throws -> String {
guard let jsonDict = jsonObject as? [String: String] else {
throw NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON object type"])
}
let jsonData = try JSONSerialization.data(withJSONObject: jsonDict, options: [])
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
throw NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to convert data to string"])
}
return jsonString
}
@objc
static func requiresMainQueueSetup() -> Bool {
return true
}
}

View File

@@ -787,6 +787,51 @@
}
}
},
"SendVPScreen": {
"requester": "الطالب",
"cardsSelected": "البطاقات المختارة",
"cardSelected": "card selected",
"unCheck": "البطاقة مختارة",
"checkAll": "تحقق من الكل",
"consentDialog": {
"title": "الموافقة مطلوبة",
"message": "نحن نطلب موافقتك على مشاركة بيانات الاعتماد الخاصة بك التي يمكن التحقق منها. وهذا سيمكننا من التحقق من هويتك وتلبية طلبات الخدمة الخاصة بك. اختر \"نعم، متابعة\" للموافقة أو \"رفض\" إذا كنت لا ترغب في المشاركة.",
"confirmButton": "نعم، تابع",
"cancelButton": "انخفاض"
},
"confirmationDialog": {
"title": "هل أنت متأكد؟",
"message": "سيؤدي رفض الموافقة إلى منعك من مشاركة بيانات اعتمادك التي يمكن التحقق منها.",
"confirmButton": "نعم، تابع",
"cancelButton": "عُد"
},
"errors": {
"noMatchingCredentials": {
"title": "لم يتم العثور على بيانات اعتماد مطابقة!",
"message": "أعد محاولة المشاركة بعد تنزيل بيانات الاعتماد."
},
"invalidVerifier": {
"title": "حدث خطأ!",
"message": "لم يتم التعرف على المدقق. يرجى الحصول على رمز QR صالح من أداة التحقق."
},
"credentialsMismatch": {
"title": "حدث خطأ!",
"message": "تم اكتشاف عدم تطابق في بيانات الاعتماد. تأكد من أنك قمت باختيار الخيار الصحيح ثم حاول مرة أخرى."
},
"genericError": {
"title": "أُووبس! حدث خطأ.",
"message": "بسبب خطأ فني، لا يمكننا مشاركة البطاقة. انقر فوق إعادة المحاولة لإعادة المشاركة أو انقر فوق الصفحة الرئيسية للخروج من عملية المشاركة."
},
"invalidQrCode": {
"title": "رمز الاستجابة السريعة غير صالح",
"message": "رمز الاستجابة السريعة غير صالح لأن بعض المعلومات المطلوبة مفقودة. يرجى مطالبة المدقق بتقديم رمز QR صالح لمشاركة بيانات الاعتماد الخاصة بك."
},
"noImage": {
"title": "حدث خطأ!",
"message": "يتطلب التحقق من الوجه صورة في بيانات الاعتماد المحددة. الرجاء استخدام خيار المشاركة أو تحديد بيانات اعتماد تتضمن صورة."
}
}
},
"VerifyIdentityOverlay": {
"faceAuth": "التحقق من الوجه",
"status": {

View File

@@ -795,6 +795,51 @@
}
}
},
"SendVPScreen": {
"requester": "Requester",
"cardsSelected": "cards selected",
"cardSelected": "card selected",
"unCheck": "Uncheck",
"checkAll": "Check All",
"consentDialog": {
"title": "Consent Required",
"message": "We require your consent to share your verifiable credentials. This will enable us to verify your identity and fulfil your service requests. Choose \"Yes, Proceed\" to consent or \"Decline\" if you do not wish to share.",
"confirmButton": "Yes, Proceed",
"cancelButton": "Decline"
},
"confirmationDialog": {
"title": "Are you sure?",
"message": "Declining the consent will prevent you from sharing your verifiable credentials.",
"confirmButton": "Yes, Proceed",
"cancelButton": "Go Back"
},
"errors": {
"noMatchingCredentials": {
"title": "No matching credentials found!",
"message": "Retry sharing after downloading the credentials."
},
"invalidVerifier": {
"title": "An Error Occured!",
"message": "The verifier is not recognized. Please obtain a valid QR code from the verifier."
},
"credentialsMismatch": {
"title": "An Error Occured!",
"message": "Credential mismatch detected. Ensure you have selected the right one and try again."
},
"genericError": {
"title": "Oops! An Error Occured.",
"message": "Due to technical error, we are unable to share the card. Click on retry to re-share or click on Home to exit the sharing process."
},
"invalidQrCode": {
"title": "Invalid QR code",
"message": "The QR code is invalid because some required information is missing. Please ask the verifier to provide a valid QR code to share your credentials."
},
"noImage": {
"title": "An Error Occured!",
"message": "Face verification requires a photo in the selected credential(s). Please use the Share option or select a credential that includes an image."
}
}
},
"VerifyIdentityOverlay": {
"faceAuth": "Face Verification",
"status": {
@@ -948,4 +993,4 @@
"keyManagementTitle": "Key Management",
"keyManagementDesc": "Select the key generation method that aligns with your preference, putting you in control of your credential security.\nDrag and arrange the keys, with the top one being your highest priority."
}
}
}

View File

@@ -786,6 +786,51 @@
}
}
},
"SendVPScreen": {
"requester": "Humihiling",
"cardsSelected": "mga card na napili",
"cardSelected": "card pinili",
"unCheck": "Alisin ang check",
"checkAll": "Suriin Lahat",
"consentDialog": {
"title": "Kinakailangan ang Pahintulot",
"message": "Hinihiling namin ang iyong pahintulot na ibahagi ang iyong mga nabe-verify na kredensyal. Ito ay magbibigay-daan sa amin na i-verify ang iyong pagkakakilanlan at matupad ang iyong mga kahilingan sa serbisyo. Piliin ang \"Oo, Magpatuloy\" sa pagsang-ayon o \"Tanggihan\" kung ayaw mong ibahagi.",
"confirmButton": "Oo, Magpatuloy",
"cancelButton": "Tanggihan"
},
"confirmationDialog": {
"title": "Sigurado ka ba?",
"message": "Ang pagtanggi sa pahintulot ay mapipigilan ka sa pagbabahagi ng iyong mga nabe-verify na kredensyal.",
"confirmButton": "Oo, Magpatuloy",
"cancelButton": "Bumalik ka"
},
"errors": {
"noMatchingCredentials": {
"title": "Walang nakitang katugmang mga kredensyal!",
"message": "Subukang muli ang pagbabahagi pagkatapos i-download ang mga kredensyal."
},
"invalidVerifier": {
"title": "Isang Error ang Naganap!",
"message": "Hindi nakikilala ang verifier. Mangyaring kumuha ng wastong QR code mula sa verifier."
},
"credentialsMismatch": {
"title": "Isang Error ang Naganap!",
"message": "May nakitang hindi pagkakatugma ng kredensyal. Tiyaking napili mo ang tama at subukang muli."
},
"genericError": {
"title": "Oops! Isang Error ang Naganap.",
"message": "Dahil sa teknikal na error, hindi namin maibahagi ang card. Mag-click sa subukang muli upang muling ibahagi o mag-click sa Home upang lumabas sa proseso ng pagbabahagi."
},
"invalidQrCode": {
"title": "Di-wastong QR code",
"message": "Di-wasto ang QR code dahil nawawala ang ilang kinakailangang impormasyon. Mangyaring hilingin sa verifier na magbigay ng wastong QR code upang ibahagi ang iyong mga kredensyal."
},
"noImage": {
"title": "Isang Error ang Naganap!",
"message": "Ang pag-verify ng mukha ay nangangailangan ng larawan sa napiling (mga) kredensyal. Pakigamit ang opsyong Ibahagi o pumili ng kredensyal na may kasamang larawan."
}
}
},
"VerifyIdentityOverlay": {
"faceAuth": "Pagpapatunay ng Mukha",
"status": {

View File

@@ -789,6 +789,51 @@
}
}
},
"SendVPScreen": {
"requester": "अनुरोधकर्ता",
"cardsSelected": "कार्ड चयनित",
"cardSelected": "कार्ड चयनित",
"unCheck": "सही का निशान हटाएँ",
"checkAll": "सभी चेक करें",
"consentDialog": {
"title": "सहमति आवश्यक",
"message": "हमें आपकी सत्यापन योग्य साख साझा करने के लिए आपकी सहमति की आवश्यकता है। इससे हमें आपकी पहचान सत्यापित करने और आपके सेवा अनुरोधों को पूरा करने में मदद मिलेगी। यदि आप साझा नहीं करना चाहते हैं तो सहमति के लिए \"हां, आगे बढ़ें\" या \"अस्वीकार करें\" चुनें।",
"confirmButton": "हाँ, आगे बढ़ें",
"cancelButton": "गिरावट"
},
"confirmationDialog": {
"title": "क्या आपको यकीन है?",
"message": "सहमति को अस्वीकार करने से आपको अपनी सत्यापन योग्य साख साझा करने से रोका जा सकेगा।",
"confirmButton": "हाँ, आगे बढ़ें",
"cancelButton": "वापस जाओ"
},
"errors": {
"noMatchingCredentials": {
"title": "कोई मेल खाता क्रेडेंशियल नहीं मिला!",
"message": "क्रेडेंशियल डाउनलोड करने के बाद साझा करने का पुनः प्रयास करें।"
},
"invalidVerifier": {
"title": "एक त्रुटि हुई!",
"message": "सत्यापनकर्ता को पहचाना नहीं गया है. कृपया सत्यापनकर्ता से एक वैध क्यूआर कोड प्राप्त करें।"
},
"credentialsMismatch": {
"title": "एक त्रुटि हुई!",
"message": "क्रेडेंशियल बेमेल का पता चला. सुनिश्चित करें कि आपने सही का चयन किया है और पुनः प्रयास करें।"
},
"genericError": {
"title": "उफ़! एक त्रुटि हुई।",
"message": "तकनीकी त्रुटि के कारण हम कार्ड साझा करने में असमर्थ हैं। पुनः साझा करने के लिए पुनः प्रयास करें पर क्लिक करें या साझाकरण प्रक्रिया से बाहर निकलने के लिए होम पर क्लिक करें।"
},
"invalidQrCode": {
"title": "अमान्य क्यूआर कोड",
"message": "क्यूआर कोड अमान्य है क्योंकि कुछ आवश्यक जानकारी गायब है। कृपया सत्यापनकर्ता से अपने क्रेडेंशियल साझा करने के लिए एक वैध क्यूआर कोड प्रदान करने के लिए कहें।"
},
"noImage": {
"title": "एक त्रुटि हुई!",
"message": "चेहरे के सत्यापन के लिए चयनित क्रेडेंशियल में एक फोटो की आवश्यकता होती है। कृपया शेयर विकल्प का उपयोग करें या एक क्रेडेंशियल चुनें जिसमें एक छवि शामिल हो।"
}
}
},
"VerifyIdentityOverlay": {
"faceAuth": "चेहरा सत्यापन",
"status": {

View File

@@ -787,6 +787,51 @@
}
}
},
"SendVPScreen": {
"requester": "ವಿನಂತಿಸುವವರು",
"cardsSelected": "ಕಾರ್ಡ್‌ಗಳನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ",
"cardSelected": "ಕಾರ್ಡ್ ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ",
"unCheck": "ಅನ್ಚೆಕ್ ಮಾಡಿ",
"checkAll": "ಎಲ್ಲವನ್ನೂ ಪರಿಶೀಲಿಸಿ",
"consentDialog": {
"title": "ಒಪ್ಪಿಗೆ ಅಗತ್ಯವಿದೆ",
"message": "ನಿಮ್ಮ ಪರಿಶೀಲಿಸಬಹುದಾದ ರುಜುವಾತುಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ನಮಗೆ ನಿಮ್ಮ ಒಪ್ಪಿಗೆಯ ಅಗತ್ಯವಿದೆ. ಇದು ನಿಮ್ಮ ಗುರುತನ್ನು ಪರಿಶೀಲಿಸಲು ಮತ್ತು ನಿಮ್ಮ ಸೇವಾ ವಿನಂತಿಗಳನ್ನು ಪೂರೈಸಲು ನಮಗೆ ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ. ಒಪ್ಪಿಗೆ ನೀಡಲು \"ಹೌದು, ಮುಂದುವರೆಯಿರಿ\" ಅಥವಾ ನೀವು ಹಂಚಿಕೊಳ್ಳಲು ಬಯಸದಿದ್ದರೆ \"ನಿರಾಕರಿಸಿ\" ಆಯ್ಕೆಮಾಡಿ.",
"confirmButton": "ಹೌದು, ಮುಂದುವರೆಯಿರಿ",
"cancelButton": "ನಿರಾಕರಿಸು"
},
"confirmationDialog": {
"title": "ನೀವು ಖಚಿತವಾಗಿರುವಿರಾ?",
"message": "ಸಮ್ಮತಿಯನ್ನು ನಿರಾಕರಿಸುವುದರಿಂದ ನಿಮ್ಮ ಪರಿಶೀಲಿಸಬಹುದಾದ ರುಜುವಾತುಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳುವುದರಿಂದ ನಿಮ್ಮನ್ನು ತಡೆಯುತ್ತದೆ.",
"confirmButton": "ಹೌದು, ಮುಂದುವರೆಯಿರಿ",
"cancelButton": "ಹಿಂತಿರುಗಿ"
},
"errors": {
"noMatchingCredentials": {
"title": "ಯಾವುದೇ ಹೊಂದಾಣಿಕೆಯ ರುಜುವಾತುಗಳು ಕಂಡುಬಂದಿಲ್ಲ!",
"message": "ರುಜುವಾತುಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿದ ನಂತರ ಹಂಚಿಕೊಳ್ಳಲು ಮರುಪ್ರಯತ್ನಿಸಿ."
},
"invalidVerifier": {
"title": "ಒಂದು ದೋಷ ಸಂಭವಿಸಿದೆ!",
"message": "ಪರಿಶೀಲಕನನ್ನು ಗುರುತಿಸಲಾಗಿಲ್ಲ. ದಯವಿಟ್ಟು ಪರಿಶೀಲಕರಿಂದ ಮಾನ್ಯವಾದ QR ಕೋಡ್ ಅನ್ನು ಪಡೆದುಕೊಳ್ಳಿ."
},
"credentialsMismatch": {
"title": "ಒಂದು ದೋಷ ಸಂಭವಿಸಿದೆ!",
"message": "ರುಜುವಾತು ಹೊಂದಿಕೆಯಾಗದಿರುವುದು ಪತ್ತೆಯಾಗಿದೆ. ನೀವು ಸರಿಯಾದದನ್ನು ಆರಿಸಿದ್ದೀರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ ಮತ್ತು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."
},
"genericError": {
"title": "ಓಹ್! ಒಂದು ದೋಷ ಸಂಭವಿಸಿದೆ.",
"message": "ತಾಂತ್ರಿಕ ದೋಷದಿಂದಾಗಿ, ಕಾರ್ಡ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ನಮಗೆ ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ಮರು-ಹಂಚಿಕೊಳ್ಳಲು ಮರುಪ್ರಯತ್ನದ ಮೇಲೆ ಕ್ಲಿಕ್ ಮಾಡಿ ಅಥವಾ ಹಂಚಿಕೆ ಪ್ರಕ್ರಿಯೆಯಿಂದ ನಿರ್ಗಮಿಸಲು ಹೋಮ್ ಮೇಲೆ ಕ್ಲಿಕ್ ಮಾಡಿ."
},
"invalidQrCode": {
"title": "ಅಮಾನ್ಯವಾದ QR ಕೋಡ್",
"message": "QR ಕೋಡ್ ಅಮಾನ್ಯವಾಗಿದೆ ಏಕೆಂದರೆ ಕೆಲವು ಅಗತ್ಯ ಮಾಹಿತಿಯು ಕಾಣೆಯಾಗಿದೆ. ದಯವಿಟ್ಟು ನಿಮ್ಮ ರುಜುವಾತುಗಳನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಮಾನ್ಯವಾದ QR ಕೋಡ್ ಒದಗಿಸಲು ಪರಿಶೀಲಕರನ್ನು ಕೇಳಿ."
},
"noImage": {
"title": "ಒಂದು ದೋಷ ಸಂಭವಿಸಿದೆ!",
"message": "ಮುಖ ಪರಿಶೀಲನೆಗೆ ಆಯ್ಕೆಮಾಡಿದ ರುಜುವಾತು(ಗಳಲ್ಲಿ) ಫೋಟೋ ಅಗತ್ಯವಿದೆ. ದಯವಿಟ್ಟು ಹಂಚಿಕೆ ಆಯ್ಕೆಯನ್ನು ಬಳಸಿ ಅಥವಾ ಚಿತ್ರವನ್ನು ಒಳಗೊಂಡಿರುವ ರುಜುವಾತುಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ."
}
}
},
"VerifyIdentityOverlay": {
"faceAuth": "ಮುಖ ಪರಿಶೀಲನೆ",
"status": {

View File

@@ -787,6 +787,51 @@
}
}
},
"SendVPScreen": {
"requester": "கோரிக்கையாளர்",
"cardsSelected": "அட்டைகள் தேர்ந்தெடுக்கப்பட்டன",
"cardSelected": "அட்டை தேர்ந்தெடுக்கப்பட்டது",
"unCheck": "தேர்வுநீக்கவும்",
"checkAll": "அனைத்தையும் சரிபார்க்கவும்",
"consentDialog": {
"title": "ஒப்புதல் தேவை",
"message": "உங்களின் சரிபார்க்கக்கூடிய நற்சான்றிதழ்களைப் பகிர உங்கள் ஒப்புதல் தேவை. இது உங்கள் அடையாளத்தைச் சரிபார்க்கவும் உங்கள் சேவை கோரிக்கைகளை நிறைவேற்றவும் எங்களுக்கு உதவும். ஒப்புக்கொள்ள \"ஆம், தொடரவும்\" அல்லது நீங்கள் பகிர விரும்பவில்லை என்றால் \"நிராகரி\" என்பதைத் தேர்ந்தெடுக்கவும்.",
"confirmButton": "ஆம், தொடரவும்",
"cancelButton": "நிராகரி"
},
"confirmationDialog": {
"title": "நீங்கள் உறுதியாக இருக்கிறீர்களா?",
"message": "ஒப்புதலை நிராகரிப்பது உங்கள் சரிபார்க்கக்கூடிய நற்சான்றிதழ்களைப் பகிர்வதைத் தடுக்கும்.",
"confirmButton": "ஆம், தொடரவும்",
"cancelButton": "திரும்பி செல்"
},
"errors": {
"noMatchingCredentials": {
"title": "பொருந்தக்கூடிய சான்றுகள் எதுவும் கிடைக்கவில்லை!",
"message": "நற்சான்றிதழ்களைப் பதிவிறக்கிய பிறகு மீண்டும் பகிர முயற்சிக்கவும்."
},
"invalidVerifier": {
"title": "ஒரு பிழை ஏற்பட்டது!",
"message": "சரிபார்ப்பவர் அங்கீகரிக்கப்படவில்லை. சரிபார்ப்பாளரிடமிருந்து சரியான QR குறியீட்டைப் பெறவும்."
},
"credentialsMismatch": {
"title": "ஒரு பிழை ஏற்பட்டது!",
"message": "நற்சான்றிதழ் பொருத்தமின்மை கண்டறியப்பட்டது. நீங்கள் சரியானதைத் தேர்ந்தெடுத்துள்ளீர்கள் என்பதை உறுதிசெய்து, மீண்டும் முயற்சிக்கவும்."
},
"genericError": {
"title": "அச்சச்சோ! ஒரு பிழை ஏற்பட்டது.",
"message": "தொழில்நுட்ப பிழை காரணமாக, கார்டை எங்களால் பகிர முடியவில்லை. மறுபகிர்வதற்கு மீண்டும் முயற்சி என்பதைக் கிளிக் செய்யவும் அல்லது பகிர்தல் செயல்முறையிலிருந்து வெளியேற முகப்பு என்பதைக் கிளிக் செய்யவும்."
},
"invalidQrCode": {
"title": "தவறான QR குறியீடு",
"message": "தேவையான சில தகவல்கள் இல்லாததால், QR குறியீடு தவறானது. உங்கள் நற்சான்றிதழ்களைப் பகிர சரியான QR குறியீட்டை வழங்குமாறு சரிபார்ப்பாளரிடம் கேட்கவும்."
},
"noImage": {
"title": "ஒரு பிழை ஏற்பட்டது!",
"message": "முகம் சரிபார்ப்புக்கு தேர்ந்தெடுக்கப்பட்ட நற்சான்றிதழில்(களில்) ஒரு புகைப்படம் தேவை. பகிர் விருப்பத்தைப் பயன்படுத்தவும் அல்லது படத்தை உள்ளடக்கிய நற்சான்றிதழைத் தேர்ந்தெடுக்கவும்."
}
}
},
"VerifyIdentityOverlay": {
"faceAuth": "முக சரிபார்ப்பு",
"status": {

View File

@@ -49,7 +49,7 @@ export const IssuersGuards = () => {
event.data instanceof BiometricCancellationError,
isGenericError: (_: any, event: any) => {
const errorMessage = event.data.message;
return !errorMessage.includes(NETWORK_REQUEST_FAILED);
return errorMessage === ErrorMessage.GENERIC;
},
};
};

View File

@@ -32,7 +32,10 @@ export function selectIsBiometricCancelled(state: State) {
}
export function selectIsNonGenericError(state: State) {
return state.context.errorMessage !== ErrorMessage.GENERIC;
return (
state.context.errorMessage !== ErrorMessage.GENERIC &&
state.context.errorMessage !== ''
);
}
export function selectIsDone(state: State) {

View File

@@ -2,10 +2,7 @@ import {assign, send} from 'xstate';
import {CommunicationDetails} from '../../../shared/Utils';
import {StoreEvents} from '../../store';
import {VCMetadata} from '../../../shared/VCMetadata';
import {
MIMOTO_BASE_URL,
MY_VCS_STORE_KEY,
} from '../../../shared/constants';
import {MIMOTO_BASE_URL, MY_VCS_STORE_KEY} from '../../../shared/constants';
import i18n from '../../../i18n';
import {getHomeMachineService} from '../../../screens/Home/HomeScreenController';
import {DownloadProps} from '../../../shared/api';
@@ -459,7 +456,8 @@ export const VCItemActions = model => {
type: 'VC_DOWNLOADED',
id: context.vcMetadata.displayId,
issuer: context.vcMetadata.issuer!!,
credentialConfigurationId: context.verifiableCredential.credentialConfigurationId,
credentialConfigurationId:
context.verifiableCredential.credentialConfigurationId,
timestamp: Date.now(),
deviceName: '',
});
@@ -472,7 +470,8 @@ export const VCItemActions = model => {
(context: any, _) => {
const vcMetadata = VCMetadata.fromVC(context.vcMetadata);
return ActivityLogEvents.LOG_ACTIVITY({
credentialConfigurationId: context.verifiableCredential.credentialConfigurationId,
credentialConfigurationId:
context.verifiableCredential.credentialConfigurationId,
issuer: vcMetadata.issuer!!,
id: vcMetadata.displayId,
_vcKey: vcMetadata.getVcKey(),
@@ -491,7 +490,8 @@ export const VCItemActions = model => {
return ActivityLogEvents.LOG_ACTIVITY({
_vcKey: vcMetadata.getVcKey(),
type: 'WALLET_BINDING_SUCCESSFULL',
credentialConfigurationId: context.verifiableCredential.credentialConfigurationId,
credentialConfigurationId:
context.verifiableCredential.credentialConfigurationId,
issuer: vcMetadata.issuer!!,
id: vcMetadata.displayId,
timestamp: Date.now(),
@@ -510,7 +510,8 @@ export const VCItemActions = model => {
_vcKey: vcMetadata.getVcKey(),
type: 'WALLET_BINDING_FAILURE',
id: vcMetadata.displayId,
credentialConfigurationId: context.verifiableCredential.credentialConfigurationId,
credentialConfigurationId:
context.verifiableCredential.credentialConfigurationId,
issuer: vcMetadata.issuer!!,
timestamp: Date.now(),
deviceName: '',

View File

@@ -1,7 +1,14 @@
import {NativeModules} from 'react-native';
import Cloud from '../../../shared/CloudBackupAndRestoreUtils';
import getAllConfigurations, {API_URLS, CACHED_API, DownloadProps,} from '../../../shared/api';
import {fetchKeyPair, generateKeyPair,} from '../../../shared/cryptoutil/cryptoUtil';
import getAllConfigurations, {
API_URLS,
CACHED_API,
DownloadProps,
} from '../../../shared/api';
import {
fetchKeyPair,
generateKeyPair,
} from '../../../shared/cryptoutil/cryptoUtil';
import {CredentialDownloadResponse, request} from '../../../shared/request';
import {WalletBindingResponse} from '../VCMetaMachine/vc';
import {verifyCredential} from '../../../shared/vcjs/verifyCredential';
@@ -106,8 +113,8 @@ export const VCItemServices = model => {
);
try {
return getMatchingCredentialIssuerMetadata(
wellknownResponse,
context.verifiableCredential.credentialConfigurationId,
wellknownResponse,
context.verifiableCredential.credentialConfigurationId,
);
} catch (error) {
return {};

View File

@@ -1,6 +1,7 @@
import {StateFrom} from 'xstate';
import {VCMetadata} from '../../../shared/VCMetadata';
import {vcMetaMachine} from './VCMetaMachine';
import {VC} from './vc';
type State = StateFrom<typeof vcMetaMachine>;
@@ -19,6 +20,12 @@ export function selectShareableVcsMetadata(state: State): VCMetadata[] {
);
}
export function selectShareableVcs(state: State): VC[] {
return Object.values(state.context.myVcs).filter(
vc => vc?.verifiableCredential != null,
);
}
export function selectReceivedVcsMetadata(state: State): VCMetadata[] {
return state.context.receivedVcsMetadata;
}

View File

@@ -0,0 +1,117 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
'done.invoke.app.init.checkKeyPairs:invocation[0]': {
type: 'done.invoke.app.init.checkKeyPairs:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.app.init.generateKeyPairs:invocation[0]': {
type: 'done.invoke.app.init.generateKeyPairs:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.app.ready.focus.active:invocation[0]': {
type: 'done.invoke.app.ready.focus.active:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform.app.init.checkKeyPairs:invocation[0]': {
type: 'error.platform.app.init.checkKeyPairs:invocation[0]';
data: unknown;
};
'xstate.init': {type: 'xstate.init'};
};
invokeSrcNameMap: {
checkFocusState: 'done.invoke.app.ready.focus:invocation[0]';
checkKeyPairs: 'done.invoke.app.init.checkKeyPairs:invocation[0]';
checkNetworkState: 'done.invoke.app.ready.network:invocation[0]';
generateKeyPairsAndStoreOrder: 'done.invoke.app.init.generateKeyPairs:invocation[0]';
getAppInfo: 'done.invoke.app.init.info:invocation[0]';
isQrLoginByDeepLink: 'done.invoke.app.ready.focus.active:invocation[0]';
resetQRLoginDeepLinkData: 'done.invoke.app.ready.focus.active:invocation[1]';
};
missingImplementations: {
actions: 'forwardToServices';
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
forwardToServices: 'ACTIVE' | 'INACTIVE' | 'OFFLINE' | 'ONLINE';
loadCredentialRegistryHostFromStorage: 'READY';
loadCredentialRegistryInConstants: 'STORE_RESPONSE';
loadEsignetHostFromConstants: 'STORE_RESPONSE';
loadEsignetHostFromStorage: 'READY';
logServiceEvents: 'done.invoke.app.init.checkKeyPairs:invocation[0]';
logStoreEvents:
| 'KEY_INVALIDATE_ERROR'
| 'RESET_KEY_INVALIDATE_ERROR_DISMISS'
| 'xstate.init';
requestDeviceInfo: 'REQUEST_DEVICE_INFO';
resetKeyInvalidateError: 'READY' | 'RESET_KEY_INVALIDATE_ERROR_DISMISS';
resetLinkCode: 'RESET_LINKCODE';
setAppInfo: 'APP_INFO_RECEIVED';
setIsDecryptError: 'DECRYPT_ERROR';
setIsReadError: 'ERROR';
setLinkCode: 'done.invoke.app.ready.focus.active:invocation[0]';
spawnServiceActors: 'done.invoke.app.init.checkKeyPairs:invocation[0]';
spawnStoreActor:
| 'KEY_INVALIDATE_ERROR'
| 'RESET_KEY_INVALIDATE_ERROR_DISMISS'
| 'xstate.init';
unsetIsDecryptError: 'DECRYPT_ERROR_DISMISS' | 'READY';
unsetIsReadError: 'READY';
updateKeyInvalidateError: 'ERROR' | 'KEY_INVALIDATE_ERROR';
};
eventsCausingDelays: {};
eventsCausingGuards: {};
eventsCausingServices: {
checkFocusState: 'APP_INFO_RECEIVED';
checkKeyPairs:
| 'READY'
| 'done.invoke.app.init.generateKeyPairs:invocation[0]';
checkNetworkState: 'APP_INFO_RECEIVED';
generateKeyPairsAndStoreOrder: 'error.platform.app.init.checkKeyPairs:invocation[0]';
getAppInfo: 'STORE_RESPONSE';
isQrLoginByDeepLink: 'ACTIVE';
resetQRLoginDeepLinkData: 'ACTIVE';
};
matchesStates:
| 'init'
| 'init.checkKeyPairs'
| 'init.credentialRegistry'
| 'init.generateKeyPairs'
| 'init.info'
| 'init.services'
| 'init.store'
| 'ready'
| 'ready.focus'
| 'ready.focus.active'
| 'ready.focus.checking'
| 'ready.focus.inactive'
| 'ready.network'
| 'ready.network.checking'
| 'ready.network.offline'
| 'ready.network.online'
| 'waiting'
| {
init?:
| 'checkKeyPairs'
| 'credentialRegistry'
| 'generateKeyPairs'
| 'info'
| 'services'
| 'store';
ready?:
| 'focus'
| 'network'
| {
focus?: 'active' | 'checking' | 'inactive';
network?: 'checking' | 'offline' | 'online';
};
};
tags: never;
}

View File

@@ -618,7 +618,9 @@ export const requestMachine =
_vcKey: vcMetadata.getVcKey(),
type: context.receiveLogType,
id: vcMetadata.displayId,
credentialConfigurationId: context.incomingVc.verifiableCredential.credentialConfigurationId,
credentialConfigurationId:
context.incomingVc.verifiableCredential
.credentialConfigurationId,
issuer: vcMetadata.issuer!!,
timestamp: Date.now(),
deviceName:

View File

@@ -30,7 +30,8 @@ export function selectVerifiableCredentialData(state: State) {
getMosipLogo(),
issuer: vcMetadata.issuer,
wellKnown: state.context.incomingVc?.verifiableCredential?.wellKnown,
credentialConfigurationId: state.context.incomingVc?.verifiableCredential?.credentialConfigurationId,
credentialConfigurationId:
state.context.incomingVc?.verifiableCredential?.credentialConfigurationId,
};
}

View File

@@ -28,13 +28,16 @@ import {ActivityLogEvents} from '../../activityLog';
import {StoreEvents} from '../../store';
import BluetoothStateManager from 'react-native-bluetooth-state-manager';
import {NativeModules} from 'react-native';
import {wallet} from '../../../shared/tuvali';
import {createOpenID4VPMachine} from '../../openID4VP/openID4VPMachine';
export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
const QR_LOGIN_REF_ID = 'QrLogin';
const OPENID4VP_REF_ID = 'OpenID4VP';
export const ScanActions = (model: any) => {
const {RNPixelpassModule} = NativeModules;
return {
setChildRef: assign({
setQrLoginRef: assign({
QrLoginRef: (context: any) => {
const service = spawn(
createQrLoginMachine(context.serviceRefs),
@@ -45,6 +48,17 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
},
}),
setOpenId4VPRef: assign({
OpenId4VPRef: (context: any) => {
const service = spawn(
createOpenID4VPMachine(context.serviceRefs),
OPENID4VP_REF_ID,
);
service.subscribe(logState);
return service;
},
}),
resetLinkCode: model.assign({
linkcode: '',
}),
@@ -85,6 +99,14 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
selectedVc: context.selectedVc,
}),
sendVPScanData: context =>
context.OpenId4VPRef.send({
type: 'AUTHENTICATE',
encodedAuthRequest: context.linkCode,
flowType: context.openID4VPFlowType,
selectedVC: context.selectedVc,
}),
openBluetoothSettings: () => {
isAndroid()
? BluetoothStateManager.openSettings().catch()
@@ -145,10 +167,28 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
flowType: (_context, event) => event.flowType,
}),
setOpenId4VPFlowType: assign({
openID4VPFlowType: (context: any) => {
let flowType = VCShareFlowType.OPENID4VP;
if (context.flowType === VCShareFlowType.MINI_VIEW_SHARE) {
flowType = VCShareFlowType.MINI_VIEW_SHARE_OPENID4VP;
} else if (
context.flowType === VCShareFlowType.MINI_VIEW_SHARE_WITH_SELFIE
) {
flowType = VCShareFlowType.MINI_VIEW_SHARE_WITH_SELFIE_OPENID4VP;
}
return flowType;
},
}),
resetFlowType: assign({
flowType: VCShareFlowType.SIMPLE_SHARE,
}),
resetOpenID4VPFlowType: assign({
openID4VPFlowType: '',
}),
registerLoggers: assign({
loggers: () => {
if (__DEV__) {
@@ -200,7 +240,8 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
? context.shareLogType
: 'VC_SHARED_WITH_VERIFICATION_CONSENT',
id: vcMetadata.displayId,
credentialConfigurationId: context.selectedVc.verifiableCredential.credentialConfigurationId,
credentialConfigurationId:
context.selectedVc.verifiableCredential.credentialConfigurationId,
issuer: vcMetadata.issuer!!,
timestamp: Date.now(),
deviceName:
@@ -217,7 +258,8 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
_vcKey: vcMetadata.getVcKey(),
type: 'PRESENCE_VERIFICATION_FAILED',
timestamp: Date.now(),
credentialConfigurationId: context.selectedVc.verifiableCredential.credentialConfigurationId,
credentialConfigurationId:
context.selectedVc.verifiableCredential.credentialConfigurationId,
id: vcMetadata.displayId,
issuer: vcMetadata.issuer!!,
deviceName:
@@ -228,8 +270,10 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
),
setLinkCode: assign({
linkCode: (_, event) =>
new URL(event.params).searchParams.get('linkCode'),
linkCode: (context: any, event) =>
context.openID4VPFlowType.startsWith('OpenID4VP')
? event.params
: new URL(event.params).searchParams.get('linkCode'),
}),
setLinkCodeFromDeepLink: assign({
@@ -282,7 +326,8 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
_vcKey: '',
id: vcMetadata.displayId,
issuer: vcMetadata.issuer!!,
credentialConfigurationId: selectedVc.verifiableCredential.credentialConfigurationId,
credentialConfigurationId:
selectedVc.verifiableCredential.credentialConfigurationId,
type: 'QRLOGIN_SUCCESFULL',
timestamp: Date.now(),
deviceName: '',

View File

@@ -24,6 +24,10 @@ export const ScanGuards = () => {
}
},
isOnlineSharing: (_, event) => {
return event.params.startsWith('openid4vp://authorize');
},
uptoAndroid11: () => isAndroid() && androidVersion < 31,
isIOS: () => isIOS(),

View File

@@ -1,5 +1,5 @@
/* eslint-disable sonarjs/no-duplicate-string */
import {EventFrom, send, StateFrom} from 'xstate';
import {actions, EventFrom, send, StateFrom} from 'xstate';
import {AppServices} from '../../../shared/GlobalContext';
import {TelemetryConstants} from '../../../shared/telemetry/TelemetryConstants';
import {
@@ -12,9 +12,9 @@ import {ScanActions} from './scanActions';
import {ScanGuards} from './scanGuards';
import {ScanModel} from './scanModel';
import {ScanServices} from './scanServices';
import {openID4VPMachine} from '../../openID4VP/openID4VPMachine';
const model = ScanModel;
const QR_LOGIN_REF_ID = 'QrLogin';
export const ScanEvents = model.events;
export const scanMachine =
@@ -35,6 +35,7 @@ export const scanMachine =
initial: 'inactive',
on: {
SCREEN_BLUR: {
actions: 'resetOpenID4VPFlowType',
target: '#scan.disconnectDevice',
},
SCREEN_FOCUS: {
@@ -75,6 +76,7 @@ export const scanMachine =
},
},
checkStorage: {
entry: 'setOpenId4VPRef',
invoke: {
src: 'checkStorageAvailability',
onDone: [
@@ -319,7 +321,6 @@ export const scanMachine =
'removeLoggers',
'registerLoggers',
'clearUri',
'setChildRef',
'resetFaceCaptureBannerStatus',
],
on: {
@@ -338,7 +339,16 @@ export const scanMachine =
{
target: 'showQrLogin',
cond: 'isQrLogin',
actions: ['sendVcSharingStartEvent', 'setLinkCode'],
actions: [
'setQrLoginRef',
'sendVcSharingStartEvent',
'setLinkCode',
],
},
{
target: 'startVPSharing',
cond: 'isOnlineSharing',
actions: ['setOpenId4VPFlowType', 'setLinkCode'],
},
{
target: 'decodeQuickShareData',
@@ -351,6 +361,89 @@ export const scanMachine =
],
},
},
startVPSharing: {
entry: [
'sendVPScanData',
() =>
sendStartEvent(
getStartEventData(TelemetryConstants.FlowType.vpSharing),
),
],
invoke: {
id: 'OpenId4VP',
src: openID4VPMachine,
onDone: {},
},
on: {
IN_PROGRESS: {
target: '.inProgress',
},
TIMEOUT: {
target: '.timeout',
},
DISMISS: [
{
cond: 'isFlowTypeSimpleShare',
actions: 'resetOpenID4VPFlowType',
target: 'checkStorage',
},
{
target: 'checkStorage',
},
],
SHOW_ERROR: {
target: '.showError',
},
SUCCESS: {
target: '.success',
},
},
states: {
success: {},
showError: {},
inProgress: {
on: {
CANCEL: [
{
cond: 'isFlowTypeSimpleShare',
actions: 'resetOpenID4VPFlowType',
target: '#scan.checkStorage',
},
{
target: '#scan.checkStorage',
},
],
},
},
timeout: {
on: {
STAY_IN_PROGRESS: {
target: 'inProgress',
},
CANCEL: [
{
cond: 'isFlowTypeSimpleShare',
actions: 'resetOpenID4VPFlowType',
target: '#scan.checkStorage',
},
{
target: '#scan.checkStorage',
},
],
RETRY: [
{
cond: 'isFlowTypeSimpleShare',
actions: 'resetOpenID4VPFlowType',
target: '#scan.checkStorage',
},
{
target: '#scan.checkStorage',
},
],
},
},
},
},
decodeQuickShareData: {
entry: 'loadMetaDataToMemory',
on: {
@@ -781,10 +874,8 @@ export const scanMachine =
},
},
{
actions: ScanActions(model, QR_LOGIN_REF_ID),
actions: ScanActions(model),
services: ScanServices(model),
guards: ScanGuards(),
delays: {
DESTROY_TIMEOUT: 500,

View File

@@ -57,12 +57,14 @@ export interface Typegen0 {
| 'resetFaceCaptureBannerStatus'
| 'resetFlowType'
| 'resetLinkCode'
| 'resetOpenID4VPFlowType'
| 'resetSelectedVc'
| 'resetShowQuickShareSuccessBanner'
| 'sendBLEConnectionErrorEvent'
| 'sendScanData'
| 'sendVCShareFlowCancelEndEvent'
| 'sendVCShareFlowTimeoutEndEvent'
| 'sendVPScanData'
| 'sendVcShareSuccessEvent'
| 'sendVcSharingStartEvent'
| 'setBleError'
@@ -70,6 +72,9 @@ export interface Typegen0 {
| 'setFlowType'
| 'setLinkCode'
| 'setLinkCodeFromDeepLink'
| 'setOpenId4VPFlowType'
| 'setOpenId4VPRef'
| 'setQrLoginRef'
| 'setQuickShareData'
| 'setReadyForBluetoothStateCheck'
| 'setReceiverInfo'
@@ -92,6 +97,7 @@ export interface Typegen0 {
| 'isFlowTypeSimpleShare'
| 'isIOS'
| 'isMinimumStorageRequiredForAuditEntryReached'
| 'isOnlineSharing'
| 'isOpenIdQr'
| 'isQrLogin'
| 'isQuickShare'
@@ -156,6 +162,7 @@ export interface Typegen0 {
| 'SCREEN_FOCUS'
| 'SELECT_VC'
| 'xstate.stop';
resetOpenID4VPFlowType: 'CANCEL' | 'DISMISS' | 'RETRY' | 'SCREEN_BLUR';
resetSelectedVc:
| 'DISCONNECT'
| 'DISMISS'
@@ -169,13 +176,23 @@ export interface Typegen0 {
sendScanData: 'QRLOGIN_VIA_DEEP_LINK' | 'SCAN';
sendVCShareFlowCancelEndEvent: 'CANCEL';
sendVCShareFlowTimeoutEndEvent: 'CANCEL' | 'RETRY';
sendVPScanData: 'SCAN';
sendVcShareSuccessEvent: 'VC_ACCEPTED';
sendVcSharingStartEvent: 'SCAN';
setBleError: 'BLE_ERROR';
setChildRef: 'QRLOGIN_VIA_DEEP_LINK' | 'STORE_RESPONSE';
setChildRef: 'QRLOGIN_VIA_DEEP_LINK';
setFlowType: 'SELECT_VC';
setLinkCode: 'SCAN';
setLinkCodeFromDeepLink: 'QRLOGIN_VIA_DEEP_LINK';
setOpenId4VPFlowType: 'SCAN';
setOpenId4VPRef:
| 'CANCEL'
| 'DISMISS'
| 'RESET'
| 'RETRY'
| 'SCREEN_FOCUS'
| 'SELECT_VC';
setQrLoginRef: 'SCAN';
setQuickShareData: 'SCAN';
setReadyForBluetoothStateCheck: 'BLUETOOTH_PERMISSION_ENABLED';
setReceiverInfo: 'CONNECTED';
@@ -200,9 +217,10 @@ export interface Typegen0 {
eventsCausingGuards: {
isFlowTypeMiniViewShare: 'CHECK_FLOW_TYPE';
isFlowTypeMiniViewShareWithSelfie: 'CHECK_FLOW_TYPE' | 'DISMISS';
isFlowTypeSimpleShare: 'CANCEL' | 'CHECK_FLOW_TYPE' | 'DISMISS';
isFlowTypeSimpleShare: 'CANCEL' | 'CHECK_FLOW_TYPE' | 'DISMISS' | 'RETRY';
isIOS: 'BLUETOOTH_STATE_DISABLED' | 'START_PERMISSION_CHECK';
isMinimumStorageRequiredForAuditEntryReached: 'done.invoke.scan.checkStorage:invocation[0]';
isOnlineSharing: 'SCAN';
isOpenIdQr: 'SCAN';
isQrLogin: 'SCAN';
isQuickShare: 'SCAN';
@@ -210,6 +228,7 @@ export interface Typegen0 {
uptoAndroid11: '' | 'START_PERMISSION_CHECK';
};
eventsCausingServices: {
OpenId4VP: 'SCAN';
QrLogin: 'QRLOGIN_VIA_DEEP_LINK' | 'SCAN';
checkBluetoothPermission:
| ''
@@ -220,7 +239,13 @@ export interface Typegen0 {
checkLocationPermission: 'LOCATION_ENABLED';
checkLocationStatus: '' | 'APP_ACTIVE' | 'LOCATION_REQUEST';
checkNearByDevicesPermission: 'APP_ACTIVE' | 'START_PERMISSION_CHECK';
checkStorageAvailability: 'RESET' | 'SCREEN_FOCUS' | 'SELECT_VC';
checkStorageAvailability:
| 'CANCEL'
| 'DISMISS'
| 'RESET'
| 'RETRY'
| 'SCREEN_FOCUS'
| 'SELECT_VC';
disconnect: '' | 'DISMISS' | 'LOCATION_ENABLED' | 'RETRY' | 'SCREEN_BLUR';
monitorConnection: 'DISMISS' | 'SCREEN_BLUR' | 'xstate.init';
requestBluetooth: 'BLUETOOTH_STATE_DISABLED';
@@ -293,6 +318,11 @@ export interface Typegen0 {
| 'showQrLogin.navigatingToHistory'
| 'showQrLogin.storing'
| 'startPermissionCheck'
| 'startVPSharing'
| 'startVPSharing.inProgress'
| 'startVPSharing.showError'
| 'startVPSharing.success'
| 'startVPSharing.timeout'
| {
checkBluetoothPermission?: 'checking' | 'enabled';
checkBluetoothState?: 'checking' | 'enabled' | 'requesting';
@@ -322,6 +352,7 @@ export interface Typegen0 {
| 'verifyingIdentity'
| {sendingVc?: 'inProgress' | 'sent' | 'timeout'};
showQrLogin?: 'idle' | 'navigatingToHistory' | 'storing';
startVPSharing?: 'inProgress' | 'showError' | 'success' | 'timeout';
};
tags: never;
}

View File

@@ -9,6 +9,7 @@ import {qrLoginMachine} from '../../QrLogin/QrLoginMachine';
import {VC} from '../../VerifiableCredential/VCMetaMachine/vc';
import {ActivityLogType} from '../../activityLog';
import {BLEError} from '../types';
import {openID4VPMachine} from '../../openID4VP/openID4VPMachine';
const ScanEvents = {
SELECT_VC: (vc: VC, flowType: string) => ({vc, flowType}),
@@ -55,6 +56,10 @@ const ScanEvents = {
}),
ALLOWED: () => ({}),
DENIED: () => ({}),
SHOW_ERROR: () => ({}),
SUCCESS: () => ({}),
IN_PROGRESS: () => ({}),
TIMEOUT: () => ({}),
QRLOGIN_VIA_DEEP_LINK: (linkCode: string) => ({linkCode}),
};
@@ -68,10 +73,12 @@ export const ScanModel = createModel(
loggers: [] as EmitterSubscription[],
vcName: '',
flowType: VCShareFlowType.SIMPLE_SHARE,
openID4VPFlowType: '',
verificationImage: {} as CameraCapturedPicture,
openId4VpUri: '',
shareLogType: '' as ActivityLogType,
QrLoginRef: {} as ActorRefFrom<typeof qrLoginMachine>,
OpenId4VPRef: {} as ActorRefFrom<typeof openID4VPMachine>,
showQuickShareSuccessBanner: false,
linkCode: '',
quickShareData: {},

View File

@@ -9,6 +9,10 @@ export function selectFlowType(state: State) {
return state.context.flowType;
}
export function selectOpenID4VPFlowType(state: State) {
return state.context.openID4VPFlowType;
}
export function selectReceiverInfo(state: State) {
return state.context.receiverInfo;
}
@@ -18,26 +22,28 @@ export function selectVcName(state: State) {
}
export function selectCredential(state: State) {
return (
return [
state.context.selectedVc?.verifiableCredential?.credential ||
state.context.selectedVc?.verifiableCredential
);
state.context.selectedVc?.verifiableCredential,
];
}
export function selectVerifiableCredentialData(state: State) {
const vcMetadata = new VCMetadata(state.context.selectedVc?.vcMetadata);
return {
vcMetadata: vcMetadata,
issuer: vcMetadata.issuer,
issuerLogo:
state.context.selectedVc?.verifiableCredential?.issuerLogo ||
getMosipLogo(),
face:
state.context.selectedVc?.verifiableCredential?.credential
?.credentialSubject?.face ||
state.context.selectedVc?.credential?.biometrics?.face,
wellKnown: state.context.selectedVc?.verifiableCredential?.wellKnown,
};
return [
{
vcMetadata: vcMetadata,
issuer: vcMetadata.issuer,
issuerLogo:
state.context.selectedVc?.verifiableCredential?.issuerLogo ||
getMosipLogo(),
face:
state.context.selectedVc?.verifiableCredential?.credential
?.credentialSubject?.face ||
state.context.selectedVc?.credential?.biometrics?.face,
wellKnown: state.context.selectedVc?.verifiableCredential?.wellKnown,
},
];
}
export function selectQrLoginRef(state: State) {
@@ -71,6 +77,18 @@ export function selectIsSendingVc(state: State) {
return state.matches('reviewing.sendingVc.inProgress');
}
export function selectIsSendingVP(state: State) {
return state.matches('startVPSharing.inProgress');
}
export function selectIsSendingVPError(state: State) {
return state.matches('startVPSharing.showError');
}
export function selectIsSendingVPSuccess(state: State) {
return state.matches('startVPSharing.success');
}
export function selectIsFaceIdentityVerified(state: State) {
return (
state.matches('reviewing.sendingVc.inProgress') &&
@@ -82,6 +100,10 @@ export function selectIsSendingVcTimeout(state: State) {
return state.matches('reviewing.sendingVc.timeout');
}
export function selectIsSendingVPTimeout(state: State) {
return state.matches('startVPSharing.timeout');
}
export function selectIsSent(state: State) {
return state.matches('reviewing.sendingVc.sent');
}

View File

@@ -30,7 +30,7 @@ const model = createModel(
export const FaceScannerEvents = model.events;
export const createFaceScannerMachine = (vcImage: string) =>
export const createFaceScannerMachine = (vcImages: string[]) =>
/** @xstate-layout N4IgpgJg5mDOIC5QDMCGBjMBldqB2eYATgHQCWeZALiegBZjoDWFUACsQLZmyxkD2eAMQARAKIA5AJJiRiUAAd+fKgLzyQAD0QBaAIwAmAyQMBWAJwBmABzWADHoAslywfOOANCACeu0wHYANhJ-R389SzC3QJjLAF84rzRMHHxCUgpqWgZmVg4ibl41IQBxACUAQQkAFVkNJRU1DW0EHUc7YxtA6z09c2sza3NzQK9fVtdjO0s7fqHrAL1-U0CEpIxsXAJickoaBS4ePkERMEpIIQB5NkkAfSwxauqpCRKseuVqJqQtXW6Q6aGbrWGymIzWMa6AwzEiOCwrZwzbp6QL+NYgZKbNI7TL7Q5FE5nMgXCpsNi3ABilwAwgBVB5yH4NL6CZpQ-zGazhQKmUwdSIGVGQiZGEjTWahIYGOx2azOdGY1LbDJ7EhEMAARwArnBVHh2PjjsJylVaozFJ9VKyfi03I4TJFHI4YqYFkEucL9JY9LC9GDxX7zAYQQqNkr0rssurtbq8obiuJpHUmZbvqAWkt7Y5g45ek7A4EnZ7JmLvQ4ZbKrI5zGjEhiw1sI7ihGUxBURABND6Na3pvzBfwclzWQs80y9CE+RBTexcwWy8eFlyhlKNnawRusEiYin8LV4CBCCCCMC7ABu-CYp8Va9IG7SW53e4PCAoF9wVrwAG07ABdbssuoNq6JY5gkN6gSGCiIJ6HYoTLMKrrGMssH2P4lj+HYzp6CuWLKiQ94EI+Gy7vuh7EEQ-CkAoAA2qBUMgVGcNuDbYnem76ixmCkS+b78B+ag-v+KY9kBfYINKpgkCMrpOg42auIEljCu0PqymEMyyoEBi5qsdY3mxBEcVAQgUgAMlI5LUhUACyYiVABn5sggegLCE-gDCCBiDjWIL+MKM6yhygQLiiiK4eG67GaZFTUmItziLU1Jmo5aa-BMIUkNpMzOOYQUYaMU4SWY0lBiM3qWK6qKOAkdZ4PwEBwBoBn4bi2SMCw+r5IURqpb26VtHYwQYUOSlWEYNieiF1ghGEhg1g47jLvprGtaqBwFEcainOcEB9WJA3mD6fIVQYfozNKLjFsGJCmDMIxLTl47xCtq6GW1G09WoJREPgVCQPtzmDfaGEYQMvJOpYSmehE-jSX6rmQdM7gRNYEW3pGNDRjqsB6gam0EgdzJOcBLkeSE0odIO-go3B-lFTorqWCYc2RA4yxBAY6PvXsgOk20MIjdCY2TJNDPIrN4TBpB0puOY3P4YRlCcU+ZF8+JjMzbYjhcjW7TmHyQTCjYM2yehMojjpKIKxGSvEZgEj8FQPF7SJgFA5l2aonK4Rgk43mmAF2awmVmGVa5zpwjbUUPvq6sDTY9rZVhoH5UbRVOHYpXRNmZgebMXOvXhTacKgMDUqgChUFq6quxaolA+0YoxAjbgjrYRjCuhUlmO0Osophzq1usb3KvHLRtKYIODsLoGi5O4w6Ms9rwYYyxGL0ha1XEQA */
model.createMachine(
{
@@ -198,12 +198,34 @@ export const createFaceScannerMachine = (vcImage: string) =>
});
},
verifyImage: context => {
verifyImage: async context => {
context.cameraRef.pausePreview();
const rxDataURI =
/data:(?<mime>[\w/\-.]+);(?<encoding>\w+),(?<data>.*)/;
const matches = rxDataURI.exec(vcImage).groups;
return faceCompare(context.capturedImage.base64, matches.data);
let isMatchFound = false;
for (const vcImage of vcImages) {
const matches = rxDataURI.exec(vcImage).groups;
try {
isMatchFound = await faceCompare(
context.capturedImage.base64,
matches.data,
);
if (isMatchFound) {
break;
}
} catch (error) {
throw error;
}
}
return isMatchFound;
},
checkNetworkStatus: async () => {
const state = await NetInfo.fetch();
return state.isConnected;
},
},

View File

@@ -32,7 +32,6 @@ export interface Typegen0 {
services: never;
};
eventsCausingActions: {
flipWhichCamera: 'FLIP_CAMERA';
openSettings: 'OPEN_SETTINGS';
setCameraRef: 'READY';
setCaptureError: 'error.platform.faceScanner.capturing:invocation[0]';

View File

@@ -0,0 +1,193 @@
import {assign} from 'xstate';
import {send, sendParent} from 'xstate/lib/actions';
import {SHOW_FACE_AUTH_CONSENT_SHARE_FLOW} from '../../shared/constants';
import {VC} from '../VerifiableCredential/VCMetaMachine/vc';
import {StoreEvents} from '../store';
import {VCShareFlowType} from '../../shared/Utils';
// TODO - get this presentation definition list which are alias for scope param
// from the verifier end point after the endpoint is created and exposed.
export const openID4VPActions = (model: any) => {
return {
setAuthenticationResponse: model.assign({
authenticationResponse: (_, event) => event.data,
}),
setEncodedAuthorizationRequest: model.assign({
encodedAuthorizationRequest: (_, event) => event.encodedAuthRequest,
}),
setFlowType: model.assign({
flowType: (_, event) => event.flowType,
}),
getVcsMatchingAuthRequest: model.assign({
vcsMatchingAuthRequest: (context, event) => {
let vcs = event.vcs;
let matchingVCs = {} as Record<string, [VC]>;
let presentationDefinition;
const response = context.authenticationResponse;
if ('presentation_definition' in response) {
presentationDefinition = JSON.parse(
response['presentation_definition'],
);
}
vcs.forEach(vc => {
presentationDefinition['input_descriptors'].forEach(
inputDescriptor => {
let isMatched = true;
inputDescriptor.constraints.fields?.forEach(field => {
field.path.forEach(path => {
const pathSegments = path.substring(2).split('.');
const pathData = pathSegments.reduce(
(obj, key) => obj?.[key],
vc.verifiableCredential.credential,
);
if (
path === undefined ||
(pathSegments[pathSegments.length - 1] !== 'type' &&
(field.filter?.type !== typeof pathData ||
!pathData.includes(field.filter?.pattern)))
) {
isMatched = false;
return;
}
});
if (!isMatched) {
return;
}
});
if (isMatched) {
matchingVCs[inputDescriptor.id]?.push(vc) ||
(matchingVCs[inputDescriptor.id] = [vc]);
}
},
);
});
return matchingVCs;
},
purpose: context => {
const response = context.authenticationResponse;
if ('presentation_definition' in response) {
const pd = JSON.parse(response['presentation_definition']);
return pd.purpose ?? '';
}
},
}),
setSelectedVCs: model.assign({
selectedVCs: (_, event) => event.selectedVCs,
}),
compareAndStoreSelectedVC: model.assign({
selectedVCs: context => {
const matchingVcs = {};
Object.entries(context.vcsMatchingAuthRequest).map(
([inputDescriptorId, vcs]) =>
(vcs as VC[]).map(vcData => {
if (
vcData.vcMetadata.requestId ===
context.miniViewSelectedVC.vcMetadata.requestId
) {
matchingVcs[inputDescriptorId] = [vcData];
}
}),
);
return matchingVcs;
},
}),
setMiniViewShareSelectedVC: model.assign({
miniViewSelectedVC: (_, event) => event.selectedVC,
}),
setIsShareWithSelfie: model.assign({
isShareWithSelfie: (_, event) =>
event.flowType ===
VCShareFlowType.MINI_VIEW_SHARE_WITH_SELFIE_OPENID4VP,
}),
setShowFaceAuthConsent: model.assign({
showFaceAuthConsent: (_, event) => {
return !event.isDoNotAskAgainChecked;
},
}),
storeShowFaceAuthConsent: send(
(_, event) =>
StoreEvents.SET(
SHOW_FACE_AUTH_CONSENT_SHARE_FLOW,
!event.isDoNotAskAgainChecked,
),
{
to: context => context.serviceRefs.store,
},
),
getFaceAuthConsent: send(
StoreEvents.GET(SHOW_FACE_AUTH_CONSENT_SHARE_FLOW),
{
to: (context: any) => context.serviceRefs.store,
},
),
updateShowFaceAuthConsent: model.assign({
showFaceAuthConsent: (_, event) => {
return event.response || event.response === null;
},
}),
forwardToParent: sendParent('DISMISS'),
setError: model.assign({
error: (_, event) => {
console.error('Error:', event.data.message);
return event.data.message;
},
}),
resetError: model.assign({
error: () => '',
}),
loadKeyPair: assign({
publicKey: (_, event: any) => event.data?.publicKey as string,
privateKey: (context: any, event: any) =>
event.data?.privateKey
? event.data.privateKey
: (context.privateKey as string),
}),
incrementOpenID4VPRetryCount: model.assign({
openID4VPRetryCount: context => context.openID4VPRetryCount + 1,
}),
resetOpenID4VPRetryCount: model.assign({
openID4VPRetryCount: () => 0,
}),
setAuthenticationError: model.assign({
error: (_, event) => {
console.error('Error:', event.data.message);
return 'vc validation - ' + event.data.message;
},
}),
setTrustedVerifiersApiCallError: model.assign({
error: (_, event) => {
console.error('Error:', event.data.message);
return 'api error - ' + event.data.message;
},
}),
setTrustedVerifiers: model.assign({
trustedVerifiers: (_: any, event: any) => event.data.response.verifiers,
}),
};
};

View File

@@ -0,0 +1,33 @@
import {VCShareFlowType} from '../../shared/Utils';
export const openID4VPGuards = () => {
return {
showFaceAuthConsentScreen: (context, event) => {
return context.showFaceAuthConsent && context.isShareWithSelfie;
},
isShareWithSelfie: context => context.isShareWithSelfie,
isSimpleOpenID4VPShare: context =>
context.flowType === VCShareFlowType.OPENID4VP,
isSelectedVCMatchingRequest: context =>
Object.values(context.selectedVCs).length === 1,
isFlowTypeSimpleShare: context =>
context.flowType === VCShareFlowType.SIMPLE_SHARE,
hasKeyPair: (context: any) => {
return !!context.publicKey;
},
isAnyVCHasImage: (context: any) => {
const hasImage = Object.values(context.selectedVCs)
.flatMap(vc => vc)
.some(
vc => vc.verifiableCredential?.credential?.credentialSubject.face,
);
return !!hasImage;
},
};
};

View File

@@ -0,0 +1,335 @@
import {EventFrom} from 'xstate';
import {openID4VPModel} from './openID4VPModel';
import {openID4VPServices} from './openID4VPServices';
import {openID4VPActions} from './openID4VPActions';
import {AppServices} from '../../shared/GlobalContext';
import {openID4VPGuards} from './openID4VPGuards';
import {send, sendParent} from 'xstate/lib/actions';
const model = openID4VPModel;
export const OpenID4VPEvents = model.events;
export const openID4VPMachine = model.createMachine(
{
predictableActionArguments: true,
preserveActionOrder: true,
tsTypes: {} as import('./openID4VPMachine.typegen').Typegen0,
schema: {
context: model.initialContext,
events: {} as EventFrom<typeof model>,
},
id: 'OpenID4VP',
initial: 'waitingForData',
states: {
waitingForData: {
on: {
AUTHENTICATE: {
actions: [
'setEncodedAuthorizationRequest',
'setFlowType',
'setMiniViewShareSelectedVC',
'setIsShareWithSelfie',
],
target: 'checkFaceAuthConsent',
},
},
},
checkFaceAuthConsent: {
entry: 'getFaceAuthConsent',
on: {
STORE_RESPONSE: {
actions: 'updateShowFaceAuthConsent',
target: 'getTrustedVerifiersList',
},
},
},
getTrustedVerifiersList: {
invoke: {
src: 'fetchTrustedVerifiers',
onDone: {
actions: 'setTrustedVerifiers',
target: 'getKeyPairFromKeystore',
},
onError: {
actions: 'setTrustedVerifiersApiCallError',
},
},
},
getKeyPairFromKeystore: {
invoke: {
src: 'getKeyPair',
onDone: {
actions: ['loadKeyPair'],
target: 'checkKeyPair',
},
onError: [
{
actions: 'setError',
},
],
},
},
checkKeyPair: {
description: 'checks whether key pair is generated',
invoke: {
src: 'getSelectedKey',
onDone: {
cond: 'hasKeyPair',
target: 'authenticateVerifier',
},
onError: [
{
actions: 'setError',
},
],
},
},
authenticateVerifier: {
invoke: {
src: 'getAuthenticationResponse',
onDone: {
actions: 'setAuthenticationResponse',
target: 'getVCsSatisfyingAuthRequest',
},
onError: {
actions: 'setAuthenticationError',
target: 'showError',
},
},
},
getVCsSatisfyingAuthRequest: {
on: {
DOWNLOADED_VCS: [
{
cond: 'isSimpleOpenID4VPShare',
actions: 'getVcsMatchingAuthRequest',
target: 'selectingVCs',
},
{
actions: 'getVcsMatchingAuthRequest',
target: 'setSelectedVC',
},
],
},
},
setSelectedVC: {
entry: send('SET_SELECTED_VC'),
on: {
SET_SELECTED_VC: [
{
actions: 'compareAndStoreSelectedVC',
target: 'checkIfMatchingVCsHasSelectedVC',
},
],
},
},
checkIfMatchingVCsHasSelectedVC: {
entry: send('CHECK_SELECTED_VC'),
on: {
CHECK_SELECTED_VC: [
{
cond: 'isSelectedVCMatchingRequest',
target: 'getConsentForVPSharing',
},
{
actions: [
model.assign({
error: () => 'credential mismatch detected',
}),
],
target: 'showError',
},
],
},
},
selectingVCs: {
on: {
VERIFY_AND_ACCEPT_REQUEST: {
actions: [
'setSelectedVCs',
model.assign({isShareWithSelfie: () => true}),
],
target: 'getConsentForVPSharing',
},
ACCEPT_REQUEST: {
target: 'getConsentForVPSharing',
actions: [
'setSelectedVCs',
'setShareLogTypeUnverified',
'resetFaceCaptureBannerStatus',
],
},
CANCEL: {
actions: 'forwardToParent',
target: 'waitingForData',
},
},
},
getConsentForVPSharing: {
on: {
CONFIRM: [
{
cond: 'showFaceAuthConsentScreen',
target: 'faceVerificationConsent',
},
{
cond: 'isShareWithSelfie',
target: 'checkIfAnySelectedVCHasImage',
},
{
target: 'sendingVP',
},
],
CANCEL: {
target: 'showConfirmationPopup',
},
},
},
showConfirmationPopup: {
on: {
CONFIRM: {
actions: sendParent('DISMISS'),
},
GO_BACK: {
target: 'getConsentForVPSharing',
},
},
},
faceVerificationConsent: {
on: {
FACE_VERIFICATION_CONSENT: [
{
cond: 'isSimpleOpenID4VPShare',
actions: ['setShowFaceAuthConsent', 'storeShowFaceAuthConsent'],
target: 'checkIfAnySelectedVCHasImage',
},
{
actions: ['setShowFaceAuthConsent', 'storeShowFaceAuthConsent'],
target: 'verifyingIdentity',
},
],
DISMISS: [
{
cond: 'isSimpleOpenID4VPShare',
target: 'selectingVCs',
},
{
actions: sendParent('DISMISS'),
},
],
},
},
checkIfAnySelectedVCHasImage: {
entry: send('CHECK_FOR_IMAGE'),
on: {
CHECK_FOR_IMAGE: [
{
cond: 'isAnyVCHasImage',
target: 'verifyingIdentity',
},
{
actions: model.assign({
error: () => 'none of the selected VC has image',
}),
target: 'showError',
},
],
},
},
verifyingIdentity: {
on: {
FACE_VALID: [
{
cond: 'hasKeyPair',
target: 'sendingVP',
},
{
target: 'checkKeyPair',
},
],
FACE_INVALID: {
target: 'invalidIdentity',
actions: 'logFailedVerification',
},
CANCEL: [
{
cond: 'isSimpleOpenID4VPShare',
actions: model.assign({isShareWithSelfie: () => false}),
target: 'selectingVCs',
},
{
actions: sendParent('DISMISS'),
},
],
},
},
invalidIdentity: {
on: {
DISMISS: [
{
cond: 'isSimpleOpenID4VPShare',
target: 'selectingVCs',
},
{
actions: sendParent('DISMISS'),
},
],
RETRY_VERIFICATION: {
target: 'verifyingIdentity',
},
},
},
sendingVP: {
entry: sendParent('IN_PROGRESS'),
invoke: {
src: 'sendVP',
onDone: {
actions: sendParent('SUCCESS'),
target: 'success',
},
onError: {
actions: ['setError', sendParent('SHOW_ERROR')],
target: 'showError',
},
},
after: {
SHARING_TIMEOUT: {
actions: sendParent('TIMEOUT'),
},
},
},
showError: {
on: {
RETRY: {
actions: ['incrementOpenID4VPRetryCount'],
target: 'sendingVP',
},
RESET_RETRY_COUNT: {
actions: 'resetOpenID4VPRetryCount',
},
RESET_ERROR: {
actions: 'resetError',
},
},
},
success: {},
},
},
{
actions: openID4VPActions(model),
services: openID4VPServices(),
guards: openID4VPGuards(),
delays: {
SHARING_TIMEOUT: 15 * 1000,
},
},
);
export function createOpenID4VPMachine(serviceRefs: AppServices) {
return openID4VPMachine.withContext({
...openID4VPMachine.context,
serviceRefs,
});
}

View File

@@ -0,0 +1,177 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
'done.invoke.OpenID4VP.authenticateVerifier:invocation[0]': {
type: 'done.invoke.OpenID4VP.authenticateVerifier:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.OpenID4VP.checkKeyPair:invocation[0]': {
type: 'done.invoke.OpenID4VP.checkKeyPair:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.OpenID4VP.getKeyPairFromKeystore:invocation[0]': {
type: 'done.invoke.OpenID4VP.getKeyPairFromKeystore:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.OpenID4VP.getTrustedVerifiersList:invocation[0]': {
type: 'done.invoke.OpenID4VP.getTrustedVerifiersList:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.OpenID4VP.sendingVP:invocation[0]': {
type: 'done.invoke.OpenID4VP.sendingVP:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform.OpenID4VP.authenticateVerifier:invocation[0]': {
type: 'error.platform.OpenID4VP.authenticateVerifier:invocation[0]';
data: unknown;
};
'error.platform.OpenID4VP.checkKeyPair:invocation[0]': {
type: 'error.platform.OpenID4VP.checkKeyPair:invocation[0]';
data: unknown;
};
'error.platform.OpenID4VP.getKeyPairFromKeystore:invocation[0]': {
type: 'error.platform.OpenID4VP.getKeyPairFromKeystore:invocation[0]';
data: unknown;
};
'error.platform.OpenID4VP.getTrustedVerifiersList:invocation[0]': {
type: 'error.platform.OpenID4VP.getTrustedVerifiersList:invocation[0]';
data: unknown;
};
'error.platform.OpenID4VP.sendingVP:invocation[0]': {
type: 'error.platform.OpenID4VP.sendingVP:invocation[0]';
data: unknown;
};
'xstate.init': {type: 'xstate.init'};
};
invokeSrcNameMap: {
fetchTrustedVerifiers: 'done.invoke.OpenID4VP.getTrustedVerifiersList:invocation[0]';
getAuthenticationResponse: 'done.invoke.OpenID4VP.authenticateVerifier:invocation[0]';
getKeyPair: 'done.invoke.OpenID4VP.getKeyPairFromKeystore:invocation[0]';
getSelectedKey: 'done.invoke.OpenID4VP.checkKeyPair:invocation[0]';
sendVP: 'done.invoke.OpenID4VP.sendingVP:invocation[0]';
};
missingImplementations: {
actions:
| 'compareAndStoreSelectedVC'
| 'forwardToParent'
| 'getFaceAuthConsent'
| 'getVcsMatchingAuthRequest'
| 'incrementOpenID4VPRetryCount'
| 'loadKeyPair'
| 'logFailedVerification'
| 'resetError'
| 'resetFaceCaptureBannerStatus'
| 'resetOpenID4VPRetryCount'
| 'setAuthenticationError'
| 'setAuthenticationResponse'
| 'setEncodedAuthorizationRequest'
| 'setError'
| 'setFlowType'
| 'setIsShareWithSelfie'
| 'setMiniViewShareSelectedVC'
| 'setSelectedVCs'
| 'setShareLogTypeUnverified'
| 'setShowFaceAuthConsent'
| 'setTrustedVerifiers'
| 'setTrustedVerifiersApiCallError'
| 'storeShowFaceAuthConsent'
| 'updateShowFaceAuthConsent';
delays: never;
guards:
| 'hasKeyPair'
| 'isAnyVCHasImage'
| 'isSelectedVCMatchingRequest'
| 'isShareWithSelfie'
| 'isSimpleOpenID4VPShare'
| 'showFaceAuthConsentScreen';
services:
| 'fetchTrustedVerifiers'
| 'getAuthenticationResponse'
| 'getKeyPair'
| 'getSelectedKey'
| 'sendVP';
};
eventsCausingActions: {
compareAndStoreSelectedVC: 'SET_SELECTED_VC';
forwardToParent: 'CANCEL';
getFaceAuthConsent: 'AUTHENTICATE';
getVcsMatchingAuthRequest: 'DOWNLOADED_VCS';
incrementOpenID4VPRetryCount: 'RETRY';
loadKeyPair: 'done.invoke.OpenID4VP.getKeyPairFromKeystore:invocation[0]';
logFailedVerification: 'FACE_INVALID';
resetError: 'RESET_ERROR';
resetFaceCaptureBannerStatus: 'ACCEPT_REQUEST';
resetOpenID4VPRetryCount: 'RESET_RETRY_COUNT';
setAuthenticationError: 'error.platform.OpenID4VP.authenticateVerifier:invocation[0]';
setAuthenticationResponse: 'done.invoke.OpenID4VP.authenticateVerifier:invocation[0]';
setEncodedAuthorizationRequest: 'AUTHENTICATE';
setError:
| 'error.platform.OpenID4VP.checkKeyPair:invocation[0]'
| 'error.platform.OpenID4VP.getKeyPairFromKeystore:invocation[0]'
| 'error.platform.OpenID4VP.sendingVP:invocation[0]';
setFlowType: 'AUTHENTICATE';
setIsShareWithSelfie: 'AUTHENTICATE';
setMiniViewShareSelectedVC: 'AUTHENTICATE';
setSelectedVCs: 'ACCEPT_REQUEST' | 'VERIFY_AND_ACCEPT_REQUEST';
setShareLogTypeUnverified: 'ACCEPT_REQUEST';
setShowFaceAuthConsent: 'FACE_VERIFICATION_CONSENT';
setTrustedVerifiers: 'done.invoke.OpenID4VP.getTrustedVerifiersList:invocation[0]';
setTrustedVerifiersApiCallError: 'error.platform.OpenID4VP.getTrustedVerifiersList:invocation[0]';
storeShowFaceAuthConsent: 'FACE_VERIFICATION_CONSENT';
updateShowFaceAuthConsent: 'STORE_RESPONSE';
};
eventsCausingDelays: {
SHARING_TIMEOUT: 'CONFIRM' | 'FACE_VALID' | 'RETRY';
};
eventsCausingGuards: {
hasKeyPair:
| 'FACE_VALID'
| 'done.invoke.OpenID4VP.checkKeyPair:invocation[0]';
isAnyVCHasImage: 'CHECK_FOR_IMAGE';
isSelectedVCMatchingRequest: 'CHECK_SELECTED_VC';
isShareWithSelfie: 'CONFIRM';
isSimpleOpenID4VPShare:
| 'CANCEL'
| 'DISMISS'
| 'DOWNLOADED_VCS'
| 'FACE_VERIFICATION_CONSENT';
showFaceAuthConsentScreen: 'CONFIRM';
};
eventsCausingServices: {
fetchTrustedVerifiers: 'STORE_RESPONSE';
getAuthenticationResponse: 'done.invoke.OpenID4VP.checkKeyPair:invocation[0]';
getKeyPair: 'done.invoke.OpenID4VP.getTrustedVerifiersList:invocation[0]';
getSelectedKey:
| 'FACE_VALID'
| 'done.invoke.OpenID4VP.getKeyPairFromKeystore:invocation[0]';
sendVP: 'CONFIRM' | 'FACE_VALID' | 'RETRY';
};
matchesStates:
| 'authenticateVerifier'
| 'checkFaceAuthConsent'
| 'checkIfAnySelectedVCHasImage'
| 'checkIfMatchingVCsHasSelectedVC'
| 'checkKeyPair'
| 'faceVerificationConsent'
| 'getConsentForVPSharing'
| 'getKeyPairFromKeystore'
| 'getTrustedVerifiersList'
| 'getVCsSatisfyingAuthRequest'
| 'invalidIdentity'
| 'selectingVCs'
| 'sendingVP'
| 'setSelectedVC'
| 'showConfirmationPopup'
| 'showError'
| 'success'
| 'verifyingIdentity'
| 'waitingForData';
tags: never;
}

View File

@@ -0,0 +1,68 @@
import {createModel} from 'xstate/lib/model';
import {AppServices} from '../../shared/GlobalContext';
import {VC} from '../VerifiableCredential/VCMetaMachine/vc';
import {KeyTypes} from '../../shared/cryptoutil/KeyTypes';
const openID4VPEvents = {
AUTHENTICATE: (
encodedAuthRequest: string,
flowType: string,
selectedVC: any,
) => ({encodedAuthRequest, flowType, selectedVC}),
DOWNLOADED_VCS: (vcs: VC[]) => ({vcs}),
SELECT_VC: (vcKey: string, inputDescriptorId: any) => ({
vcKey,
inputDescriptorId,
}),
ACCEPT_REQUEST: (selectedVCs: Record<string, VC[]>) => ({
selectedVCs,
}),
VERIFY_AND_ACCEPT_REQUEST: (selectedVCs: Record<string, VC[]>) => ({
selectedVCs,
}),
CONFIRM: () => ({}),
CANCEL: () => ({}),
FACE_VERIFICATION_CONSENT: (isDoNotAskAgainChecked: boolean) => ({
isDoNotAskAgainChecked,
}),
FACE_VALID: () => ({}),
FACE_INVALID: () => ({}),
DISMISS: () => ({}),
RETRY_VERIFICATION: () => ({}),
STORE_RESPONSE: (response: any) => ({response}),
GO_BACK: () => ({}),
CHECK_SELECTED_VC: () => ({}),
SET_SELECTED_VC: () => ({}),
CHECK_FOR_IMAGE: () => ({}),
RETRY: () => ({}),
RESET_RETRY_COUNT: () => ({}),
RESET_ERROR: () => ({}),
};
export const openID4VPModel = createModel(
{
serviceRefs: {} as AppServices,
encodedAuthorizationRequest: '' as string,
authenticationResponse: {},
vcsMatchingAuthRequest: {} as Record<string, VC[]>,
checkedAll: false as boolean,
selectedVCs: {} as Record<string, VC[]>,
isShareWithSelfie: false as boolean,
showFaceAuthConsent: true as boolean,
purpose: '' as string,
error: '' as string,
publicKey: '',
privateKey: '',
keyType: KeyTypes.RS256,
flowType: '' as string,
miniViewSelectedVC: {} as VC,
openID4VPRetryCount: 0,
trustedVerifiers: [] as VerifierType[],
},
{events: openID4VPEvents},
);
interface VerifierType {
client_id: string;
redirect_uri: string;
response_uri: string;
}

View File

@@ -0,0 +1,96 @@
import {StateFrom} from 'xstate';
import {openID4VPMachine} from './openID4VPMachine';
import {VCMetadata} from '../../shared/VCMetadata';
import {getMosipLogo} from '../../components/VC/common/VCUtils';
import {VerifiableCredentialData} from '../VerifiableCredential/VCMetaMachine/vc';
type State = StateFrom<typeof openID4VPMachine>;
export function selectIsGetVCsSatisfyingAuthRequest(state: State) {
return state.matches('getVCsSatisfyingAuthRequest');
}
export function selectVCsMatchingAuthRequest(state: State) {
return state.context.vcsMatchingAuthRequest;
}
export function selectSelectedVCs(state: State) {
return state.context.selectedVCs;
}
export function selectAreAllVCsChecked(state: State) {
return state.context.checkedAll;
}
export function selectIsGetVPSharingConsent(state: State) {
return state.matches('getConsentForVPSharing');
}
export function selectIsFaceVerificationConsent(state: State) {
return state.matches('faceVerificationConsent');
}
export function selectIsVerifyingIdentity(state: State) {
return state.matches('verifyingIdentity');
}
export function selectIsInvalidIdentity(state: State) {
return state.matches('invalidIdentity');
}
export function selectIsSharingVP(state: State) {
return state.matches('sendingVP');
}
export function selectCredentials(state: State) {
let selectedCredentials: Credential[] = [];
Object.values(state.context.selectedVCs).map(vcs => {
vcs.map(vcData => {
const credential =
vcData?.verifiableCredential?.credential ||
vcData?.verifiableCredential;
selectedCredentials.push(credential);
});
});
return selectedCredentials;
}
export function selectVerifiableCredentialsData(state: State) {
let verifiableCredentialsData: VerifiableCredentialData[] = [];
Object.values(state.context.selectedVCs).map(vcs => {
vcs.map(vcData => {
const vcMetadata = new VCMetadata(vcData.vcMetadata);
verifiableCredentialsData.push({
vcMetadata: vcMetadata,
issuer: vcMetadata.issuer,
issuerLogo: vcData?.verifiableCredential?.issuerLogo || getMosipLogo(),
face:
vcData?.verifiableCredential?.credential?.credentialSubject?.face ||
vcData?.credential?.biometrics?.face,
wellKnown: vcData?.verifiableCredential?.wellKnown,
credentialTypes: vcData?.verifiableCredential?.credentialTypes,
});
});
});
return verifiableCredentialsData;
}
export function selectPurpose(state: State) {
return state.context.purpose;
}
export function selectShowConfirmationPopup(state: State) {
return state.matches('showConfirmationPopup');
}
export function selectIsSelectingVcs(state: State) {
return state.matches('selectingVCs');
}
export function selectIsError(state: State) {
return state.context.error;
}
export function selectOpenID4VPRetryCount(state: State) {
return state.context.openID4VPRetryCount;
}

View File

@@ -0,0 +1,56 @@
import {CACHED_API} from '../../shared/api';
import {fetchKeyPair} from '../../shared/cryptoutil/cryptoUtil';
import {
constructProofJWT,
OpenID4VP,
OpenID4VP_Domain,
OpenID4VP_Proof_Algo_Type,
} from '../../shared/openID4VP/OpenID4VP';
export const openID4VPServices = () => {
return {
fetchTrustedVerifiers: async () => {
return await CACHED_API.fetchTrustedVerifiersList();
},
getAuthenticationResponse: (context: any) => async () => {
OpenID4VP.initialize();
const serviceRes = await OpenID4VP.authenticateVerifier(
context.encodedAuthorizationRequest,
context.trustedVerifiers,
);
return serviceRes;
},
getKeyPair: async (context: any) => {
if (!!(await fetchKeyPair(context.keyType)).publicKey) {
return await fetchKeyPair(context.keyType);
}
},
getSelectedKey: async (context: any) => {
return await fetchKeyPair(context.keyType);
},
sendVP: (context: any) => async () => {
const vpToken = await OpenID4VP.constructVerifiablePresentationToken(
context.selectedVCs,
);
const proofJWT = await constructProofJWT(
context.publicKey,
context.privateKey,
JSON.parse(vpToken),
context.keyType,
);
const vpResponseMetadata = {
jws: proofJWT,
signatureAlgorithm: OpenID4VP_Proof_Algo_Type,
publicKey: context.publicKey,
domain: OpenID4VP_Domain,
};
return await OpenID4VP.shareVerifiablePresentation(vpResponseMetadata);
},
};
};

View File

@@ -10,6 +10,7 @@ export const BOTTOM_TAB_ROUTES = {
export const SCAN_ROUTES = {
ScanScreen: 'ScanScreen' as keyof ScanStackParamList,
SendVcScreen: 'SendVcScreen' as keyof ScanStackParamList,
SendVPScreen: 'SendVPScreen' as keyof ScanStackParamList,
};
export const REQUEST_ROUTES = {
@@ -25,6 +26,7 @@ export const SETTINGS_ROUTES = {
export type ScanStackParamList = {
ScanScreen: undefined;
SendVcScreen: undefined;
SendVPScreen: undefined;
};
export type RequestStackParamList = {

View File

@@ -1,16 +1,16 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { DeviceInfoList } from '../../components/DeviceInfoList';
import { Button, Column, Text } from '../../components/ui';
import { Theme } from '../../components/ui/styleUtils';
import { useReceiveVcScreen } from './ReceiveVcScreenController';
import { MessageOverlay } from '../../components/MessageOverlay';
import { useOverlayVisibleAfterTimeout } from '../../shared/hooks/useOverlayVisibleAfterTimeout';
import { VcDetailsContainer } from '../../components/VC/VcDetailsContainer';
import { SharingStatusModal } from '../Scan/SharingStatusModal';
import { SvgImage } from '../../components/ui/svg';
import { DETAIL_VIEW_DEFAULT_FIELDS } from '../../components/VC/common/VCUtils';
import { getDetailedViewFields } from '../../shared/openId4VCI/Utils';
import React, {useEffect, useState} from 'react';
import {useTranslation} from 'react-i18next';
import {DeviceInfoList} from '../../components/DeviceInfoList';
import {Button, Column, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import {useReceiveVcScreen} from './ReceiveVcScreenController';
import {MessageOverlay} from '../../components/MessageOverlay';
import {useOverlayVisibleAfterTimeout} from '../../shared/hooks/useOverlayVisibleAfterTimeout';
import {VcDetailsContainer} from '../../components/VC/VcDetailsContainer';
import {SharingStatusModal} from '../Scan/SharingStatusModal';
import {SvgImage} from '../../components/ui/svg';
import {DETAIL_VIEW_DEFAULT_FIELDS} from '../../components/VC/common/VCUtils';
import {getDetailedViewFields} from '../../shared/openId4VCI/Utils';
export const ReceiveVcScreen: React.FC = () => {
const {t} = useTranslation('ReceiveVcScreen');

View File

@@ -16,13 +16,13 @@ import {View, I18nManager} from 'react-native';
import {Text} from './../../components/ui';
import {BannerStatusType} from '../../components/BannerNotification';
import {LIVENESS_CHECK} from '../../shared/constants';
import {SendVPScreen} from './SendVPScreen';
const ScanStack = createNativeStackNavigator();
export const ScanLayout: React.FC = () => {
const {t} = useTranslation('ScanScreen');
const controller = useScanLayout();
if (
controller.statusOverlay != null &&
!controller.isAccepted &&
@@ -37,7 +37,8 @@ export const ScanLayout: React.FC = () => {
isHintVisible={
controller.isStayInProgress ||
controller.isBleError ||
controller.isSendingVc
controller.isSendingVc ||
controller.isSendingVP
}
onRetry={controller.statusOverlay?.onRetry}
showBanner={controller.isFaceIdentityVerified}
@@ -113,10 +114,43 @@ export const ScanLayout: React.FC = () => {
),
}}
/>
{controller.openID4VPFlowType === VCShareFlowType.OPENID4VP && (
<ScanStack.Screen
name={SCAN_ROUTES.SendVPScreen}
component={SendVPScreen}
options={{
title: t('SendVPScreen:requester'),
headerTitle: props => (
<View style={Theme.Styles.scanLayoutHeaderContainer}>
<Text style={Theme.Styles.scanLayoutHeaderTitle}>
{props.children}
</Text>
</View>
),
headerBackVisible: false,
headerRight: () =>
!I18nManager.isRTL && (
<Icon
name="close"
color={Theme.Colors.blackIcon}
onPress={controller.DISMISS}
/>
),
headerLeft: () =>
I18nManager.isRTL && (
<Icon
name="close"
color={Theme.Colors.blackIcon}
onPress={controller.DISMISS}
/>
),
}}
/>
)}
</ScanStack.Navigator>
<SharingStatusModal
isVisible={controller.isAccepted}
isVisible={controller.isAccepted || controller.isVPSharingSuccess}
testId={'sharingSuccessModal'}
buttonStatus={'homeAndHistoryIcons'}
title={t('status.accepted.title')}

View File

@@ -24,7 +24,11 @@ import {
selectIsFaceIdentityVerified,
selectCredential,
selectVerifiableCredentialData,
selectIsSendingVPTimeout,
selectIsSendingVP,
selectIsQrLoginViaDeepLink,
selectOpenID4VPFlowType,
selectIsSendingVPSuccess,
} from '../../machines/bleShare/scan/scanSelectors';
import {
selectBleError,
@@ -67,6 +71,7 @@ export function useScanLayout() {
const isBleError = useSelector(scanService, selectIsHandlingBleError);
const isInvalidIdentity = useSelector(scanService, selectIsInvalidIdentity);
const flowType = useSelector(scanService, selectFlowType);
const openID4VPFlowType = useSelector(scanService, selectOpenID4VPFlowType);
const isVerifyingIdentity = useSelector(
scanService,
selectIsVerifyingIdentity,
@@ -133,9 +138,12 @@ export function useScanLayout() {
const isSent = useSelector(scanService, selectIsSent);
const isOffline = useSelector(scanService, selectIsOffline);
const isSendingVc = useSelector(scanService, selectIsSendingVc);
const isSendingVP = useSelector(scanService, selectIsSendingVP);
const isSendingVcTimeout = useSelector(scanService, selectIsSendingVcTimeout);
const isSendingVPTimeout = useSelector(scanService, selectIsSendingVPTimeout);
const isDisconnected = useSelector(scanService, selectIsDisconnected);
const isStayInProgress = isConnectingTimeout || isSendingVcTimeout;
const isStayInProgress =
isConnectingTimeout || isSendingVcTimeout || isSendingVPTimeout;
let isFaceIdentityVerified = useSelector(
scanService,
selectIsFaceIdentityVerified,
@@ -183,7 +191,7 @@ export function useScanLayout() {
onButtonPress: CANCEL,
progress: true,
};
} else if (isSendingVc) {
} else if (isSendingVc || isSendingVP) {
statusOverlay = {
title: t('status.sharing.title'),
hint: t('status.sharing.hint'),
@@ -196,7 +204,7 @@ export function useScanLayout() {
hint: t('status.sharing.hint'),
progress: true,
};
} else if (isSendingVcTimeout) {
} else if (isSendingVcTimeout || isSendingVPTimeout) {
statusOverlay = {
title: t('status.sharing.title'),
hint: t('status.sharing.timeoutHint'),
@@ -282,6 +290,9 @@ export function useScanLayout() {
) {
changeTabBarVisible('none');
navigation.navigate(SCAN_ROUTES.SendVcScreen);
} else if (openID4VPFlowType === VCShareFlowType.OPENID4VP) {
changeTabBarVisible('none');
navigation.navigate(SCAN_ROUTES.SendVPScreen);
} else if (isScanning) {
changeTabBarVisible('flex');
navigation.navigate(SCAN_ROUTES.ScanScreen);
@@ -296,6 +307,7 @@ export function useScanLayout() {
isQrLoginDone,
isBleError,
flowType,
openID4VPFlowType,
isAccepted,
isQrLoginViaDeepLink,
linkCode,
@@ -321,7 +333,10 @@ export function useScanLayout() {
onRetry,
CANCEL,
isSendingVc,
isSendingVP,
isVPSharingSuccess: useSelector(scanService, selectIsSendingVPSuccess),
flowType,
openID4VPFlowType,
isVerifyingIdentity,
isInvalidIdentity,
FACE_INVALID,

View File

@@ -11,19 +11,32 @@ import {QrLogin} from '../QrLogin/QrLogin';
import {useScanScreen} from './ScanScreenController';
import BluetoothStateManager from 'react-native-bluetooth-state-manager';
import {Linking} from 'react-native';
import {isIOS} from '../../shared/constants';
import {isIOS, LIVENESS_CHECK} from '../../shared/constants';
import {BannerNotificationContainer} from '../../components/BannerNotificationContainer';
import {SharingStatusModal} from './SharingStatusModal';
import {SvgImage} from '../../components/ui/svg';
import {LocationPermissionRational} from './LocationPermissionRational';
import {FaceVerificationAlertOverlay} from './FaceVerificationAlertOverlay';
import {useSendVcScreen} from './SendVcScreenController';
import {useSendVPScreen} from './SendVPScreenController';
import {Error} from '../../components/ui/Error';
import {VPShareOverlay} from './VPShareOverlay';
import {VerifyIdentityOverlay} from '../VerifyIdentityOverlay';
import {VCShareFlowType} from '../../shared/Utils';
export const ScanScreen: React.FC = () => {
const {t} = useTranslation('ScanScreen');
const scanScreenController = useScanScreen();
const sendVcScreenController = useSendVcScreen();
const sendVPScreenController = useSendVPScreen();
const [isBluetoothOn, setIsBluetoothOn] = useState(false);
const showErrorModal =
sendVPScreenController.scanScreenError ||
(sendVPScreenController.errorModal.show &&
(sendVPScreenController.flowType ===
VCShareFlowType.MINI_VIEW_SHARE_OPENID4VP ||
sendVPScreenController.flowType ===
VCShareFlowType.MINI_VIEW_SHARE_WITH_SELFIE_OPENID4VP));
useEffect(() => {
(async () => {
@@ -54,6 +67,11 @@ export const ScanScreen: React.FC = () => {
Linking.openSettings();
};
const handleTextButtonEvent = () => {
sendVPScreenController.GO_TO_HOME();
sendVPScreenController.RESET_RETRY_COUNT();
};
function noShareableVcText() {
return (
<Text
@@ -226,14 +244,21 @@ export const ScanScreen: React.FC = () => {
);
}
const faceVerificationController = sendVPScreenController.flowType.startsWith(
'OpenID4VP',
)
? sendVPScreenController
: sendVcScreenController;
return (
<Column fill backgroundColor={Theme.Colors.whiteBackgroundColor}>
<BannerNotificationContainer />
<FaceVerificationAlertOverlay
isVisible={sendVcScreenController.isFaceVerificationConsent}
onConfirm={sendVcScreenController.FACE_VERIFICATION_CONSENT}
close={sendVcScreenController.DISMISS}
isVisible={faceVerificationController.isFaceVerificationConsent}
onConfirm={faceVerificationController.FACE_VERIFICATION_CONSENT}
close={faceVerificationController.DISMISS}
/>
<Centered
padding="24 0"
align="space-evenly"
@@ -252,6 +277,76 @@ export const ScanScreen: React.FC = () => {
/>
</Centered>
{displayStorageLimitReachedError()}
{sendVPScreenController.flowType.startsWith('OpenID4VP') &&
sendVPScreenController.flowType !== VCShareFlowType.OPENID4VP &&
sendVPScreenController.overlayDetails !== null && (
<VPShareOverlay
isVisible={sendVPScreenController.overlayDetails !== null}
title={sendVPScreenController.overlayDetails.title}
titleTestID={sendVPScreenController.overlayDetails.titleTestID}
message={sendVPScreenController.overlayDetails.message}
messageTestID={sendVPScreenController.overlayDetails.messageTestID}
primaryButtonTestID={
sendVPScreenController.overlayDetails.primaryButtonTestID
}
primaryButtonText={
sendVPScreenController.overlayDetails.primaryButtonText
}
primaryButtonEvent={
sendVPScreenController.overlayDetails.primaryButtonEvent
}
secondaryButtonTestID={
sendVPScreenController.overlayDetails.secondaryButtonTestID
}
secondaryButtonText={
sendVPScreenController.overlayDetails.secondaryButtonText
}
secondaryButtonEvent={
sendVPScreenController.overlayDetails.secondaryButtonEvent
}
/>
)}
<>
<Error
isModal
alignActionsOnEnd
showClose={false}
isVisible={showErrorModal}
title={sendVPScreenController.errorModal.title}
message={sendVPScreenController.errorModal.message}
image={SvgImage.PermissionDenied()}
primaryButtonTestID={'retry'}
primaryButtonText={
sendVPScreenController.errorModal.showRetryButton &&
sendVPScreenController.openID4VPRetryCount < 3
? t('ScanScreen:status.retry')
: undefined
}
primaryButtonEvent={sendVPScreenController.RETRY}
textButtonTestID={'home'}
textButtonText={t('ScanScreen:status.accepted.home')}
textButtonEvent={handleTextButtonEvent}
customImageStyles={{paddingBottom: 0, marginBottom: -6}}
customStyles={{marginTop: '30%'}}
testID={'vpShareError'}
/>
<VerifyIdentityOverlay
credential={sendVPScreenController.credentials}
verifiableCredentialData={
sendVPScreenController.verifiableCredentialsData
}
isVerifyingIdentity={sendVPScreenController.isVerifyingIdentity}
onCancel={sendVPScreenController.CANCEL}
onFaceValid={sendVPScreenController.FACE_VALID}
onFaceInvalid={sendVPScreenController.FACE_INVALID}
isInvalidIdentity={sendVPScreenController.isInvalidIdentity}
onNavigateHome={sendVPScreenController.GO_TO_HOME}
onRetryVerification={sendVPScreenController.RETRY_VERIFICATION}
isLivenessEnabled={LIVENESS_CHECK}
/>
</>
</Column>
);
};

View File

@@ -0,0 +1,247 @@
import {useFocusEffect} from '@react-navigation/native';
import React, {useEffect} from 'react';
import {useTranslation} from 'react-i18next';
import {BackHandler, View} from 'react-native';
import {Button, Column, Row, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
import {VcItemContainer} from '../../components/VC/VcItemContainer';
import {LIVENESS_CHECK} from '../../shared/constants';
import {TelemetryConstants} from '../../shared/telemetry/TelemetryConstants';
import {
getImpressionEventData,
sendImpressionEvent,
} from '../../shared/telemetry/TelemetryUtils';
import {isMosipVC, VCItemContainerFlowType} from '../../shared/Utils';
import {VCMetadata} from '../../shared/VCMetadata';
import {VerifyIdentityOverlay} from '../VerifyIdentityOverlay';
import {VPShareOverlay} from './VPShareOverlay';
import {FaceVerificationAlertOverlay} from './FaceVerificationAlertOverlay';
import {useSendVPScreen} from './SendVPScreenController';
import LinearGradient from 'react-native-linear-gradient';
import {Error} from '../../components/ui/Error';
import {SvgImage} from '../../components/ui/svg';
export const SendVPScreen: React.FC = () => {
const {t} = useTranslation('SendVPScreen');
const controller = useSendVPScreen();
const vcsMatchingAuthRequest = controller.vcsMatchingAuthRequest;
useEffect(() => {
sendImpressionEvent(
getImpressionEventData(
TelemetryConstants.FlowType.senderVcShare,
TelemetryConstants.Screens.vcList,
),
);
}, []);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => true;
const disableBackHandler = BackHandler.addEventListener(
'hardwareBackPress',
onBackPress,
);
return () => disableBackHandler.remove();
}, []),
);
const handleTextButtonEvent = () => {
controller.GO_TO_HOME();
controller.RESET_RETRY_COUNT();
};
const getVcKey = vcData => {
return VCMetadata.fromVcMetadataString(vcData.vcMetadata).getVcKey();
};
const noOfCardsSelected = controller.areAllVCsChecked
? Object.values(controller.vcsMatchingAuthRequest).length
: Object.keys(controller.selectedVCKeys).length;
const cardsSelectedText =
noOfCardsSelected === 1
? noOfCardsSelected + ' ' + t('cardSelected')
: noOfCardsSelected + ' ' + t('cardsSelected');
const areAllVcsChecked =
noOfCardsSelected ===
Object.values(controller.vcsMatchingAuthRequest).flatMap(vc => vc).length;
return (
<React.Fragment>
{Object.keys(vcsMatchingAuthRequest).length > 0 && (
<>
{controller.purpose !== '' && (
<View style={{backgroundColor: Theme.Colors.whiteBackgroundColor}}>
<Column
padding="14 12 14 12"
margin="20 20 20 20"
style={Theme.VPSharingStyles.purposeContainer}>
<Text
color={Theme.Colors.TimeoutHintText}
style={Theme.VPSharingStyles.purposeText}>
{controller.purpose}
</Text>
</Column>
</View>
)}
<Column fill backgroundColor={Theme.Colors.lightGreyBackgroundColor}>
<LinearGradient colors={Theme.Colors.selectIDTextGradient}>
<Column>
<Text
margin="15 0 13 24"
color={Theme.Colors.textValue}
style={Theme.VPSharingStyles.selectIDText}>
{t('SendVcScreen:pleaseSelectAnId')}
</Text>
</Column>
</LinearGradient>
<Row
padding="11 24 11 24"
style={{
backgroundColor: '#FAFAFA',
justifyContent: 'space-between',
}}>
<Text style={Theme.VPSharingStyles.cardsSelectedText}>
{cardsSelectedText}
</Text>
<Text
style={{color: '#F2801D', fontFamily: 'Inter_600SemiBold'}}
onPress={
areAllVcsChecked
? controller.UNCHECK_ALL
: controller.CHECK_ALL
}>
{areAllVcsChecked ? t('unCheck') : t('checkAll')}
</Text>
</Row>
<Column scroll backgroundColor={Theme.Colors.whiteBackgroundColor}>
{Object.entries(vcsMatchingAuthRequest).map(
([inputDescriptorId, vcs]) =>
vcs.map(vcData => (
<VcItemContainer
key={getVcKey(vcData)}
vcMetadata={vcData.vcMetadata}
margin="0 2 8 2"
onPress={controller.SELECT_VC_ITEM(
getVcKey(vcData),
inputDescriptorId,
)}
selectable
selected={
controller.areAllVCsChecked ||
Object.keys(controller.selectedVCKeys).includes(
getVcKey(vcData),
)
}
flow={VCItemContainerFlowType.OPENID4VP}
isPinned={vcData.vcMetadata.isPinned}
/>
)),
)}
</Column>
<Column
style={[
Theme.SendVcScreenStyles.shareOptionButtonsContainer,
{position: 'relative'},
]}
backgroundColor={Theme.Colors.whiteBackgroundColor}>
<Button
type="gradient"
styles={{marginTop: 12}}
title={t('SendVcScreen:acceptRequest')}
disabled={Object.keys(controller.selectedVCKeys).length === 0}
onPress={controller.ACCEPT_REQUEST}
/>
{controller.checkIfAnyMatchingVCHasImage() && (
<Button
type="gradient"
title={t('SendVcScreen:acceptRequestAndVerify')}
styles={{marginTop: 12}}
disabled={Object.keys(controller.selectedVCKeys).length === 0}
onPress={controller.VERIFY_AND_ACCEPT_REQUEST}
/>
)}
<Button
type="clear"
loading={controller.isCancelling}
title={t('SendVcScreen:reject')}
onPress={controller.CANCEL}
/>
</Column>
</Column>
<VerifyIdentityOverlay
credential={controller.credentials}
verifiableCredentialData={controller.verifiableCredentialsData}
isVerifyingIdentity={controller.isVerifyingIdentity}
onCancel={controller.CANCEL}
onFaceValid={controller.FACE_VALID}
onFaceInvalid={controller.FACE_INVALID}
isInvalidIdentity={controller.isInvalidIdentity}
onNavigateHome={controller.GO_TO_HOME}
onRetryVerification={controller.RETRY_VERIFICATION}
isLivenessEnabled={LIVENESS_CHECK}
/>
{controller.overlayDetails !== null && (
<VPShareOverlay
isVisible={controller.overlayDetails !== null}
title={controller.overlayDetails.title}
titleTestID={controller.overlayDetails.titleTestID}
message={controller.overlayDetails.message}
messageTestID={controller.overlayDetails.messageTestID}
primaryButtonTestID={
controller.overlayDetails.primaryButtonTestID
}
primaryButtonText={controller.overlayDetails.primaryButtonText}
primaryButtonEvent={controller.overlayDetails.primaryButtonEvent}
secondaryButtonTestID={
controller.overlayDetails.secondaryButtonTestID
}
secondaryButtonText={
controller.overlayDetails.secondaryButtonText
}
secondaryButtonEvent={
controller.overlayDetails.secondaryButtonEvent
}
/>
)}
<FaceVerificationAlertOverlay
isVisible={controller.isFaceVerificationConsent}
onConfirm={controller.FACE_VERIFICATION_CONSENT}
close={controller.DISMISS}
/>
</>
)}
<Error
isModal
alignActionsOnEnd
showClose={false}
isVisible={controller.errorModal.show}
title={controller.errorModal.title}
message={controller.errorModal.message}
image={SvgImage.PermissionDenied()}
primaryButtonTestID={'retry'}
primaryButtonText={
controller.errorModal.showRetryButton &&
controller.openID4VPRetryCount < 3
? t('ScanScreen:status.retry')
: undefined
}
primaryButtonEvent={controller.RETRY}
textButtonTestID={'home'}
textButtonText={t('ScanScreen:status.accepted.home')}
textButtonEvent={handleTextButtonEvent}
customImageStyles={{paddingBottom: 0, marginBottom: -6}}
customStyles={{marginTop: '30%'}}
testID={'vpShareError'}
/>
</React.Fragment>
);
};

View File

@@ -0,0 +1,269 @@
import {NavigationProp, useNavigation} from '@react-navigation/native';
import {useSelector} from '@xstate/react';
import {useContext, useState} from 'react';
import {useTranslation} from 'react-i18next';
import {ActorRefFrom} from 'xstate';
import {Theme} from '../../components/ui/styleUtils';
import {selectIsCancelling} from '../../machines/bleShare/commonSelectors';
import {ScanEvents} from '../../machines/bleShare/scan/scanMachine';
import {
selectFlowType,
selectIsSendingVPError,
} from '../../machines/bleShare/scan/scanSelectors';
import {selectOpenID4VPRetryCount} from '../../machines/openID4VP/openID4VPSelectors';
import {OpenID4VPEvents} from '../../machines/openID4VP/openID4VPMachine';
import {
selectAreAllVCsChecked,
selectCredentials,
selectIsError,
selectIsFaceVerificationConsent,
selectIsGetVCsSatisfyingAuthRequest,
selectIsGetVPSharingConsent,
selectIsInvalidIdentity,
selectIsSelectingVcs,
selectIsSharingVP,
selectIsVerifyingIdentity,
selectPurpose,
selectShowConfirmationPopup,
selectVCsMatchingAuthRequest,
selectVerifiableCredentialsData,
} from '../../machines/openID4VP/openID4VPSelectors';
import {selectMyVcs} from '../../machines/QrLogin/QrLoginSelectors';
import {VCItemMachine} from '../../machines/VerifiableCredential/VCItemMachine/VCItemMachine';
import {selectShareableVcs} from '../../machines/VerifiableCredential/VCMetaMachine/VCMetaSelectors';
import {RootRouteProps} from '../../routes';
import {BOTTOM_TAB_ROUTES} from '../../routes/routesConstants';
import {GlobalContext} from '../../shared/GlobalContext';
import {isMosipVC} from '../../shared/Utils';
import {VCMetadata} from '../../shared/VCMetadata';
import {VPShareOverlayProps} from './VPShareOverlay';
type MyVcsTabNavigation = NavigationProp<RootRouteProps>;
const changeTabBarVisible = (visible: string) => {
Theme.BottomTabBarStyle.tabBarStyle.display = visible;
};
export function getVcsForVPSharing(vcMetadatas: VCMetadata[]) {}
export function useSendVPScreen() {
const {t} = useTranslation('SendVPScreen');
const {appService} = useContext(GlobalContext);
const scanService = appService.children.get('scan')!!;
const vcMetaService = appService.children.get('vcMeta')!!;
const navigation = useNavigation<MyVcsTabNavigation>();
const openID4VPService = scanService.getSnapshot().context.OpenId4VPRef;
const [selectedVCKeys, setSelectedVCKeys] = useState<Record<string, string>>(
{},
);
const shareableVcs = useSelector(vcMetaService, selectShareableVcs);
const myVcs = useSelector(vcMetaService, selectMyVcs);
const isGetVCsSatisfyingAuthRequest = useSelector(
openID4VPService,
selectIsGetVCsSatisfyingAuthRequest,
);
if (isGetVCsSatisfyingAuthRequest) {
openID4VPService.send('DOWNLOADED_VCS', {vcs: shareableVcs});
}
const areAllVCsChecked = useSelector(
openID4VPService,
selectAreAllVCsChecked,
);
const vcsMatchingAuthRequest = useSelector(
openID4VPService,
selectVCsMatchingAuthRequest,
);
const checkIfAnyMatchingVCHasImage = () => {
const hasImage = Object.values(vcsMatchingAuthRequest)
.flatMap(vc => vc)
.some(vc => isMosipVC(vc.vcMetadata.issuer));
return hasImage;
};
const getSelectedVCs = () => {
var selectedVcsData = {};
Object.entries(selectedVCKeys).map(([vcKey, inputDescriptorId]) => {
const vcData = myVcs[vcKey];
selectedVcsData[inputDescriptorId] ??= [];
selectedVcsData[inputDescriptorId].push(vcData);
});
return selectedVcsData;
};
const isSendingVP = useSelector(openID4VPService, selectIsSharingVP);
const showConfirmationPopup = useSelector(
openID4VPService,
selectShowConfirmationPopup,
);
const isSelectingVCs = useSelector(openID4VPService, selectIsSelectingVcs);
const error = useSelector(openID4VPService, selectIsError);
const isVPSharingConsent = useSelector(
openID4VPService,
selectIsGetVPSharingConsent,
);
const CONFIRM = () => openID4VPService.send(OpenID4VPEvents.CONFIRM());
const CANCEL = () => openID4VPService.send(OpenID4VPEvents.CANCEL());
const GO_BACK = () => openID4VPService.send(OpenID4VPEvents.GO_BACK());
const noCredentialsMatchingVPRequest =
isSelectingVCs && Object.keys(vcsMatchingAuthRequest).length === 0;
let errorModal = {
show: error !== '' || noCredentialsMatchingVPRequest,
title: '',
message: '',
showRetryButton: false,
};
if (noCredentialsMatchingVPRequest) {
errorModal.title = t('errors.noMatchingCredentials.title');
errorModal.message = t('errors.noMatchingCredentials.message');
} else if (
error.includes('Verifier authentication was unsuccessful') ||
error.startsWith('api error')
) {
errorModal.title = t('errors.invalidVerifier.title');
errorModal.message = t('errors.invalidVerifier.message');
} else if (error.includes('credential mismatch detected')) {
errorModal.title = t('errors.credentialsMismatch.title');
errorModal.message = t('errors.credentialsMismatch.message');
} else if (error.includes('none of the selected VC has image')) {
errorModal.title = t('errors.noImage.title');
errorModal.message = t('errors.noImage.message');
} else if (error.startsWith('vc validation')) {
errorModal.title = t('errors.invalidQrCode.title');
errorModal.message = t('errors.invalidQrCode.message');
} else if (error !== '') {
errorModal.title = t('errors.genericError.title');
errorModal.message = t('errors.genericError.message');
errorModal.showRetryButton = true;
}
let overlayDetails: Omit<VPShareOverlayProps, 'isVisible'> | null = null;
if (isVPSharingConsent) {
overlayDetails = {
primaryButtonTestID: 'confirm',
primaryButtonText: t('consentDialog.confirmButton'),
primaryButtonEvent: CONFIRM,
secondaryButtonTestID: 'cancel',
secondaryButtonText: t('consentDialog.cancelButton'),
secondaryButtonEvent: CANCEL,
title: t('consentDialog.title'),
titleTestID: 'consentTitle',
message: t('consentDialog.message'),
messageTestID: 'consentMsg',
};
} else if (showConfirmationPopup) {
overlayDetails = {
primaryButtonTestID: 'yesProceed',
primaryButtonText: t('confirmationDialog.confirmButton'),
primaryButtonEvent: CONFIRM,
secondaryButtonTestID: 'goBack',
secondaryButtonText: t('confirmationDialog.cancelButton'),
secondaryButtonEvent: GO_BACK,
title: t('confirmationDialog.title'),
titleTestID: 'confirmationTitle',
message: t('confirmationDialog.message'),
messageTestID: 'confirmationMsg',
};
}
return {
isSendingVP,
flowType: useSelector(openID4VPService, selectFlowType),
showConfirmationPopup,
isSelectingVCs,
checkIfAnyMatchingVCHasImage,
errorModal,
overlayDetails,
scanScreenError: useSelector(scanService, selectIsSendingVPError),
vcsMatchingAuthRequest,
areAllVCsChecked,
selectedVCKeys,
isVerifyingIdentity: useSelector(
openID4VPService,
selectIsVerifyingIdentity,
),
purpose: useSelector(openID4VPService, selectPurpose),
isInvalidIdentity: useSelector(openID4VPService, selectIsInvalidIdentity),
isCancelling: useSelector(scanService, selectIsCancelling),
isFaceVerificationConsent: useSelector(
openID4VPService,
selectIsFaceVerificationConsent,
),
credentials: useSelector(openID4VPService, selectCredentials),
verifiableCredentialsData: useSelector(
openID4VPService,
selectVerifiableCredentialsData,
),
FACE_VERIFICATION_CONSENT: (isDoNotAskAgainChecked: boolean) =>
openID4VPService.send(
OpenID4VPEvents.FACE_VERIFICATION_CONSENT(isDoNotAskAgainChecked),
),
DISMISS: () => scanService.send(ScanEvents.DISMISS()),
RETRY: () => openID4VPService.send(OpenID4VPEvents.RETRY()),
FACE_VALID: () => openID4VPService.send(OpenID4VPEvents.FACE_VALID()),
FACE_INVALID: () => openID4VPService.send(OpenID4VPEvents.FACE_INVALID()),
RETRY_VERIFICATION: () =>
openID4VPService.send(OpenID4VPEvents.RETRY_VERIFICATION()),
GO_TO_HOME: () => {
navigation.navigate(BOTTOM_TAB_ROUTES.home, {screen: 'HomeScreen'});
openID4VPService.send(OpenID4VPEvents.DISMISS());
openID4VPService.send(OpenID4VPEvents.RESET_ERROR());
changeTabBarVisible('flex');
},
SELECT_VC_ITEM:
(vcKey: string, inputDescriptorId: string) =>
(vcRef: ActorRefFrom<typeof VCItemMachine>) => {
var selectedVcs = {...selectedVCKeys};
var isVCSelected = !!!selectedVcs[vcKey];
if (isVCSelected) {
selectedVcs[vcKey] = inputDescriptorId;
} else {
delete selectedVcs[vcKey];
}
setSelectedVCKeys(selectedVcs);
const {serviceRefs, wellknownResponse, ...vcData} =
vcRef.getSnapshot().context;
},
UNCHECK_ALL: () => {
setSelectedVCKeys({});
},
CHECK_ALL: () => {
var updatedVCsList = {};
Object.entries(vcsMatchingAuthRequest).map(([inputDescriptorId, vcs]) => {
vcs.map(vcData => {
const vcKey = VCMetadata.fromVcMetadataString(
vcData.vcMetadata,
).getVcKey();
updatedVCsList[vcKey] = inputDescriptorId;
});
});
setSelectedVCKeys({...updatedVCsList});
},
ACCEPT_REQUEST: () => {
openID4VPService.send(OpenID4VPEvents.ACCEPT_REQUEST(getSelectedVCs()));
},
VERIFY_AND_ACCEPT_REQUEST: () => {
openID4VPService.send(
OpenID4VPEvents.VERIFY_AND_ACCEPT_REQUEST(getSelectedVCs()),
);
},
CANCEL,
openID4VPRetryCount: useSelector(
openID4VPService,
selectOpenID4VPRetryCount,
),
RESET_RETRY_COUNT: () =>
openID4VPService.send(OpenID4VPEvents.RESET_RETRY_COUNT()),
};
}

View File

@@ -102,7 +102,7 @@ export const SendVcScreen: React.FC = () => {
<Column
style={Theme.SendVcScreenStyles.shareOptionButtonsContainer}
backgroundColor={Theme.Colors.whiteBackgroundColor}>
{isMosipVC(controller.verifiableCredentialData.issuer) && (
{isMosipVC(controller.verifiableCredentialData[0].issuer) && (
<Button
type="gradient"
title={t('acceptRequestAndVerify')}

View File

@@ -0,0 +1,74 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {Dimensions} from 'react-native';
import {Overlay} from 'react-native-elements';
import {Button, Column, Text} from '../../components/ui';
import {Theme} from '../../components/ui/styleUtils';
export const VPShareOverlay: React.FC<VPShareOverlayProps> = props => {
const {t} = useTranslation('SendVPScreen');
return (
<Overlay
isVisible={props.isVisible}
// onBackdropPress={props.close}
overlayStyle={Theme.BindingVcWarningOverlay.overlay}>
<Column
align="space-between"
crossAlign="center"
padding={'10'}
width={Dimensions.get('screen').width * 0.8}
height={Dimensions.get('screen').height * 0}>
<Column crossAlign="center" margin="10 0 15 0" padding="0">
<Text
testID={props.titleTestID}
weight="bold"
size="large"
color="#000000"
style={{padding: 3}}>
{props.title}
</Text>
<Text
testID={props.messageTestID}
align="center"
size="mediumSmall"
weight="regular"
margin="10 0 0 0"
color="#5D5D5D">
{props.message}
</Text>
</Column>
<Button
testID={props.primaryButtonTestID}
margin={'10 0 0 0'}
type="gradient"
title={props.primaryButtonText}
onPress={() => props.primaryButtonEvent()}
/>
<Button
testID={props.secondaryButtonTestID}
margin={'10 0 0 0'}
type="clear"
title={props.secondaryButtonText}
onPress={() => props.secondaryButtonEvent()}
/>
</Column>
</Overlay>
);
};
export interface VPShareOverlayProps {
isVisible: boolean;
title: string;
titleTestID: string;
message: string;
messageTestID: string;
primaryButtonTestID: string;
primaryButtonText: string;
primaryButtonEvent: () => void;
secondaryButtonTestID: string;
secondaryButtonText: string;
secondaryButtonEvent: () => void;
}

View File

@@ -13,7 +13,6 @@ export const VerifyIdentityOverlay: React.FC<
> = props => {
const {t} = useTranslation('VerifyIdentityOverlay');
const credential = props.credential;
const vcImage = props.verifiableCredentialData.face;
const modalProps = {
isVisible: props.isVerifyingIdentity,
@@ -42,7 +41,9 @@ export const VerifyIdentityOverlay: React.FC<
align="center">
{credential != null && (
<FaceScanner
vcImage={vcImage}
vcImages={props.verifiableCredentialData
.map(data => data.face)
.filter(face => face !== undefined)}
onValid={props.onFaceValid}
onInvalid={props.onFaceInvalid}
isLiveness={props.isLivenessEnabled}
@@ -75,7 +76,7 @@ export const VerifyIdentityOverlay: React.FC<
};
export interface VerifyIdentityOverlayProps {
credential?: VerifiableCredential | Credential;
credential?: [VerifiableCredential | Credential];
verifiableCredentialData: any;
isVerifyingIdentity: boolean;
onCancel: () => void;

View File

@@ -15,11 +15,15 @@ export enum VCShareFlowType {
MINI_VIEW_SHARE = 'mini view share',
MINI_VIEW_SHARE_WITH_SELFIE = 'mini view share with selfie',
MINI_VIEW_QR_LOGIN = 'mini view qr login',
OPENID4VP = 'OpenID4VP',
MINI_VIEW_SHARE_OPENID4VP = 'OpenID4VP share from mini view',
MINI_VIEW_SHARE_WITH_SELFIE_OPENID4VP = 'OpenID4VP share with selfie from mini view',
}
export enum VCItemContainerFlowType {
QR_LOGIN = 'qr login',
VC_SHARE = 'vc share',
OPENID4VP = 'openID4VP',
}
export interface CommunicationDetails {

View File

@@ -6,7 +6,7 @@ import {
} from '../machines/VerifiableCredential/VCMetaMachine/vc';
import {CredentialIdForMsoMdoc, Protocols} from './openId4VCI/Utils';
import {getMosipIdentifier} from './commonUtil';
import { VCFormat } from './VCFormat';
import {VCFormat} from './VCFormat';
const VC_KEY_PREFIX = 'VC';
const VC_ITEM_STORE_KEY_REGEX = '^VC_[a-zA-Z0-9_-]+$';

View File

@@ -17,6 +17,10 @@ import {
import {TelemetryConstants} from './telemetry/TelemetryConstants';
export const API_URLS: ApiUrls = {
trustedVerifiersList: {
method: 'GET',
buildURL: (): `/${string}` => '/v1/mimoto/verifiers',
},
issuersList: {
method: 'GET',
buildURL: (): `/${string}` => '/v1/mimoto/issuers',
@@ -90,6 +94,14 @@ export const API_URLS: ApiUrls = {
};
export const API = {
fetchTrustedVerifiersList: async () => {
const response = await request(
API_URLS.trustedVerifiersList.method,
API_URLS.trustedVerifiersList.buildURL(),
);
return response;
},
fetchIssuers: async () => {
const response = await request(
API_URLS.issuersList.method,
@@ -129,6 +141,13 @@ export const API = {
};
export const CACHED_API = {
fetchTrustedVerifiersList: (isCachePreferred: boolean = true) =>
generateCacheAPIFunction({
isCachePreferred,
cacheKey: API_CACHED_STORAGE_KEYS.fetchTrustedVerifiers,
fetchCall: API.fetchTrustedVerifiersList,
}),
fetchIssuers: () =>
generateCacheAPIFunction({
cacheKey: API_CACHED_STORAGE_KEYS.fetchIssuers,
@@ -315,6 +334,7 @@ type Api_Params = {
};
type ApiUrls = {
trustedVerifiersList: Api_Params;
issuersList: Api_Params;
issuerConfig: Api_Params;
issuerWellknownConfig: Api_Params;

View File

@@ -69,6 +69,7 @@ export const API_CACHED_STORAGE_KEYS = {
`CACHE_FETCH_ISSUER_CONFIG_${issuerId}`,
fetchIssuerWellknownConfig: (issuerId: string) =>
`CACHE_FETCH_ISSUER_WELLKNOWN_CONFIG_${issuerId}`,
fetchTrustedVerifiers: 'CACHE_FETCH_TRUSTED_VERIFIERS',
};
export function isIOS(): boolean {

View File

@@ -0,0 +1,87 @@
import {NativeModules} from 'react-native';
import {__AppId} from '../GlobalVariables';
import {VC} from '../../machines/VerifiableCredential/VCMetaMachine/vc';
import {getJWT} from '../cryptoutil/cryptoUtil';
import {getJWK} from '../openId4VCI/Utils';
export const OpenID4VP_Key_Ref = 'OpenID4VP_KeyPair';
export const OpenID4VP_Proof_Algo_Type = 'RsaSignature2018';
export const OpenID4VP_Domain = 'OpenID4VP';
export class OpenID4VP {
static InjiOpenID4VP = NativeModules.InjiOpenID4VP;
static initialize() {
OpenID4VP.InjiOpenID4VP.init(__AppId.getValue());
}
static async authenticateVerifier(
encodedAuthorizationRequest: string,
trustedVerifiersList: any,
) {
const authenticationResponse =
await OpenID4VP.InjiOpenID4VP.authenticateVerifier(
encodedAuthorizationRequest,
trustedVerifiersList,
);
return JSON.parse(authenticationResponse);
}
static async constructVerifiablePresentationToken(
selectedVCs: Record<string, VC[]>,
) {
let updatedSelectedVCs = {};
Object.keys(selectedVCs).forEach(inputDescriptorId => {
updatedSelectedVCs[inputDescriptorId] = selectedVCs[
inputDescriptorId
].map(vc => JSON.stringify(vc));
});
const vpToken =
await OpenID4VP.InjiOpenID4VP.constructVerifiablePresentationToken(
updatedSelectedVCs,
);
return vpToken;
}
static async shareVerifiablePresentation(
vpResponseMetadata: Record<string, string>,
) {
return await OpenID4VP.InjiOpenID4VP.shareVerifiablePresentation(
vpResponseMetadata,
);
}
}
export async function constructProofJWT(
publicKey: string,
privateKey: string,
vpToken: Object,
keyType: string,
): Promise<string> {
const jwtHeader = {
alg: keyType,
jwk: await getJWK(publicKey, keyType),
};
const jwtPayload = createJwtPayload(vpToken);
return await getJWT(
jwtHeader,
jwtPayload,
OpenID4VP_Key_Ref,
privateKey,
keyType,
);
}
function createJwtPayload(vpToken: {[key: string]: any}) {
const {'@context': context, type, verifiableCredential, id, holder} = vpToken;
return {
'@context': context,
type,
verifiableCredential,
id,
holder,
};
}

View File

@@ -159,7 +159,10 @@ export const getCredentialIssuersWellKnownConfig = async (
wellknownResponse,
credentialConfigurationId,
);
if (Object.keys(matchingWellknownDetails).includes('order')) {
if (
matchingWellknownDetails.order != null &&
matchingWellknownDetails.order.length > 0
) {
fields = matchingWellknownDetails.order;
} else {
if (format === VCFormat.mso_mdoc) {

View File

@@ -3,6 +3,7 @@ export const TelemetryConstants = {
vcDownload: 'VC Download',
faceModelInit: 'Face SDK initialize',
qrLogin: 'QR Login',
vpSharing: 'VP Sharing',
senderVcShare: 'Sender VC Share',
receiverVcShare: 'Receiver VC Share',
vcActivation: 'VC Activation',