mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
add UserInfo screen
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { NativeEventEmitter, NativeModules, Linking, Modal, Platform, Pressable, TouchableOpacity, ScrollView } from 'react-native';
|
||||
import { YStack, XStack, Text, Button, Tabs, Sheet, Label, Fieldset, Input, Switch, H2, Image, useWindowDimensions, H4, H3, View, Separator } from 'tamagui'
|
||||
import { HelpCircle, IterationCw, VenetianMask, Cog, CheckCircle2, ChevronLeft, Share, Eraser, ArrowRight, UserPlus, CalendarSearch, X, ShieldCheck } from '@tamagui/lucide-icons';
|
||||
import { HelpCircle, IterationCw, VenetianMask, Cog, CheckCircle2, ChevronLeft, Share, Eraser, ArrowRight, UserPlus, CalendarSearch, X, ShieldCheck, Info } from '@tamagui/lucide-icons';
|
||||
import Telegram from '../images/telegram.png'
|
||||
import Github from '../images/github.png'
|
||||
import Internet from "../images/internet.png"
|
||||
@@ -39,6 +39,7 @@ import OPENPASSPORT_LOGO from '../images/openpassport.png'
|
||||
import { countryCodes } from '../../../common/src/constants/constants';
|
||||
import getCountryISO2 from "country-iso-3-to-2";
|
||||
import { flag } from 'country-emoji';
|
||||
import UserInfo from './UserInfo';
|
||||
|
||||
const emitter = (Platform.OS === 'android')
|
||||
? new NativeEventEmitter(NativeModules.nativeModule)
|
||||
@@ -203,6 +204,9 @@ const MainScreen: React.FC = () => {
|
||||
else if (selectedTab === "valid") {
|
||||
setSelectedTab("app");
|
||||
}
|
||||
else if (selectedTab === "userInfo") {
|
||||
setSelectedTab("app");
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -289,7 +293,7 @@ const MainScreen: React.FC = () => {
|
||||
</Sheet>
|
||||
|
||||
|
||||
<Sheet open={HelpIsOpen} onOpenChange={setHelpIsOpen} dismissOnSnapToBottom modal animation="medium" snapPoints={[76]}>
|
||||
<Sheet open={HelpIsOpen} onOpenChange={setHelpIsOpen} dismissOnSnapToBottom modal animation="medium" snapPoints={[82]}>
|
||||
<Sheet.Overlay />
|
||||
<Sheet.Frame bg={bgWhite} borderTopLeftRadius="$9" borderTopRightRadius="$9" pt="$2" pb="$3">
|
||||
<YStack p="$4" f={1} gap="$3">
|
||||
@@ -330,9 +334,19 @@ const MainScreen: React.FC = () => {
|
||||
<Dialog.Button onPress={() => handleContribute()} label="Contribute" />
|
||||
</Dialog.Container>
|
||||
<Separator mt="$5" borderColor={separatorColor} w="80%" alignSelf='center' />
|
||||
<Fieldset mt="$4" gap="$4" horizontal alignSelf="center">
|
||||
<Label color={textBlack} width={200} justifyContent="flex-end" htmlFor="restart">
|
||||
View passport infos
|
||||
</Label>
|
||||
<Button bg="white" jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={() => {
|
||||
setHelpIsOpen(false);
|
||||
setSelectedTab("userInfo");
|
||||
}}>
|
||||
<Info color={textBlack} />
|
||||
</Button>
|
||||
</Fieldset>
|
||||
|
||||
|
||||
<Fieldset horizontal mt="$3" alignSelf='center'>
|
||||
<Fieldset horizontal mt="$2" alignSelf='center'>
|
||||
<Label color={textBlack} width={225} justifyContent="flex-end" htmlFor="restart" >
|
||||
Display other options
|
||||
</Label>
|
||||
@@ -344,7 +358,7 @@ const MainScreen: React.FC = () => {
|
||||
|
||||
|
||||
{displayOtherOptions && (
|
||||
<YStack gap="$2" mt="$3" ai="center">
|
||||
<YStack gap="$2" mt="$2" ai="center">
|
||||
<Fieldset gap="$4" horizontal>
|
||||
<Label color={textBlack} width={200} justifyContent="flex-end" htmlFor="restart">
|
||||
Rescan passport
|
||||
@@ -801,6 +815,9 @@ const MainScreen: React.FC = () => {
|
||||
<Tabs.Content value="wrong" f={1}>
|
||||
<WrongProofScreen />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="userInfo" f={1}>
|
||||
<UserInfo />
|
||||
</Tabs.Content>
|
||||
</Tabs>
|
||||
<XStack mt="$2.5" justifyContent='center' alignItems='center' gap="$1.5">
|
||||
<ShieldCheck color={textBlack} size={12} />
|
||||
|
||||
89
app/src/screens/UserInfo.tsx
Normal file
89
app/src/screens/UserInfo.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import { YStack, Text, XStack, Separator } from 'tamagui';
|
||||
import useUserStore from '../stores/userStore';
|
||||
import { textBlack, separatorColor } from '../utils/colors';
|
||||
import { findSubarrayIndex } from '../../../common/src/utils/utils';
|
||||
import { PassportData } from '../../../common/src/utils/types';
|
||||
import { hash } from '../../../common/src/utils/utils';
|
||||
import { parseCertificate } from '../../../common/src/utils/certificates/handleCertificate';
|
||||
|
||||
const UserInfo: React.FC = () => {
|
||||
const { passportData } = useUserStore();
|
||||
const { eContent, signedAttr, dg1Hash, dgPresents } = passportData as PassportData;
|
||||
const dg1HashOffset = dg1Hash ? findSubarrayIndex(eContent, dg1Hash.map(byte => byte > 127 ? byte - 256 : byte)) : undefined;
|
||||
|
||||
const InfoRow = ({ label, value }: { label: string; value: string | number }) => (
|
||||
<XStack py="$2" justifyContent="space-between">
|
||||
<Text color={textBlack} fontSize="$5">{label}</Text>
|
||||
<Text color={textBlack} fontSize="$5">{value}</Text>
|
||||
</XStack>
|
||||
);
|
||||
|
||||
function findHashSizeOfEContent(eContent: number[], signedAttr: number[]) {
|
||||
const allHashes = ['sha512', 'sha384', 'sha256', 'sha1'];
|
||||
for (const hashFunction of allHashes) {
|
||||
const hashValue = hash(hashFunction, eContent);
|
||||
const hashOffset = findSubarrayIndex(signedAttr, hashValue);
|
||||
if (hashOffset !== -1) {
|
||||
return { hashFunction, offset: hashOffset };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { hashFunction: eContentHashFunction, offset: eContentHashOffset } = findHashSizeOfEContent(eContent, signedAttr) || { hashFunction: '', offset: 0 };
|
||||
const dscHashFunction = parseCertificate(passportData?.dsc || '').hashFunction;
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<YStack f={1} p="$0" gap="$2" jc="flex-start" mt="$10">
|
||||
<Text fontSize="$8" color={textBlack} mb="$4">Passport Data Info</Text>
|
||||
<Separator borderColor={separatorColor} />
|
||||
|
||||
<InfoRow
|
||||
label="Data Groups"
|
||||
value={passportData?.dgPresents?.toString().split(',').map(item => item.replace('DG', '')).join(',') || 'None'}
|
||||
/>
|
||||
<Separator borderColor={separatorColor} />
|
||||
|
||||
<InfoRow
|
||||
label="DG1 Hash Size"
|
||||
value={`${passportData?.dg1Hash?.length || 0} ${passportData?.dg1Hash?.length === 32 ? '(sha256)' : passportData?.dg1Hash?.length === 20 ? '(sha1)' : passportData?.dg1Hash?.length === 48 ? '(sha384)' : passportData?.dg1Hash?.length === 64 ? '(sha512)' : ''}`}
|
||||
/>
|
||||
<Separator borderColor={separatorColor} />
|
||||
<InfoRow
|
||||
label="DG1 Hash Offset"
|
||||
value={dg1HashOffset || 0}
|
||||
/>
|
||||
<Separator borderColor={separatorColor} />
|
||||
|
||||
<InfoRow
|
||||
label="eContent Size"
|
||||
value={passportData?.eContent?.length || 0}
|
||||
/>
|
||||
<Separator borderColor={separatorColor} />
|
||||
<InfoRow
|
||||
label="eContent Hash Function"
|
||||
value={eContentHashFunction}
|
||||
/>
|
||||
<Separator borderColor={separatorColor} />
|
||||
<InfoRow
|
||||
label="eContent Hash Offset"
|
||||
value={eContentHashOffset}
|
||||
/>
|
||||
<Separator borderColor={separatorColor} />
|
||||
|
||||
<InfoRow
|
||||
label="Signed Attributes Size"
|
||||
value={passportData?.signedAttr?.length || 0}
|
||||
/>
|
||||
<Separator borderColor={separatorColor} />
|
||||
<InfoRow
|
||||
label="Signed Attributes Hash Function"
|
||||
value={dscHashFunction}
|
||||
/>
|
||||
</YStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserInfo;
|
||||
@@ -148,6 +148,8 @@ const handleResponseIOS = async (
|
||||
const parsed = JSON.parse(response);
|
||||
|
||||
const dgHashesObj = JSON.parse(parsed?.dataGroupHashes)
|
||||
const dg1HashString = dgHashesObj?.DG1?.sodHash
|
||||
const dg1Hash = Array.from(Buffer.from(dg1HashString, 'hex'))
|
||||
const dg2HashString = dgHashesObj?.DG2?.sodHash
|
||||
const dg2Hash = Array.from(Buffer.from(dg2HashString, 'hex'))
|
||||
|
||||
@@ -190,7 +192,9 @@ const handleResponseIOS = async (
|
||||
const passportData = {
|
||||
mrz,
|
||||
dsc: pem,
|
||||
dg2Hash,
|
||||
dg2Hash: dg2Hash,
|
||||
dg1Hash: dg1Hash,
|
||||
dgPresents: parsed?.dataGroupsPresent,
|
||||
eContent: concatenatedDataHashesArraySigned,
|
||||
signedAttr: signedEContentArray,
|
||||
encryptedDigest: encryptedDigestArray,
|
||||
@@ -235,12 +239,22 @@ const handleResponseAndroid = async (
|
||||
} = response;
|
||||
|
||||
const dgHashesObj = JSON.parse(dataGroupHashes);
|
||||
const dg2Hash = dgHashesObj["2"]; // This will give you the DG2 hash
|
||||
const dg1HashString = dgHashesObj["1"];
|
||||
const dg1Hash = Array.from(Buffer.from(dg1HashString, 'hex'));
|
||||
const dg2Hash = dgHashesObj["2"];
|
||||
const pem = "-----BEGIN CERTIFICATE-----" + documentSigningCertificate + "-----END CERTIFICATE-----"
|
||||
|
||||
const dgPresents = Object.keys(dgHashesObj)
|
||||
.map(key => parseInt(key))
|
||||
.filter(num => !isNaN(num))
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
const passportData: PassportData = {
|
||||
mrz: mrz.replace(/\n/g, ''),
|
||||
dsc: pem,
|
||||
dg2Hash,
|
||||
dg1Hash,
|
||||
dgPresents,
|
||||
eContent: JSON.parse(encapContent),
|
||||
signedAttr: JSON.parse(eContent),
|
||||
encryptedDigest: JSON.parse(encryptedDigest),
|
||||
|
||||
Reference in New Issue
Block a user