mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-19 05:47:43 -04:00
Make RSA decryption for emails work in react native (#771)
This commit is contained in:
@@ -122,6 +122,8 @@ class EncryptionUtility {
|
||||
* Generates a new RSA key pair for asymmetric encryption
|
||||
*/
|
||||
public static async generateRsaKeyPair(): Promise<{ publicKey: string, privateKey: string }> {
|
||||
// TODO: ensure the key pair is generated in the correct format where private key is in expected
|
||||
// JWK format that the WASM app already outputs.
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
|
||||
@@ -1,102 +1,253 @@
|
||||
import { StyleSheet, Image, Platform } from 'react-native';
|
||||
|
||||
import { Collapsible } from '@/components/Collapsible';
|
||||
import { ExternalLink } from '@/components/ExternalLink';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { StyleSheet, View, TouchableOpacity, RefreshControl, ActivityIndicator } from 'react-native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { MailboxEmail } from '@/utils/types/webapi/MailboxEmail';
|
||||
import { useDb } from '@/context/DbContext';
|
||||
import { useWebApi } from '@/context/WebApiContext';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import { TitleContainer } from '@/components/TitleContainer';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import { MailboxBulkRequest, MailboxBulkResponse } from '@/utils/types/webapi/MailboxBulk';
|
||||
import EncryptionUtility from '@/utils/EncryptionUtility';
|
||||
|
||||
export default function TabTwoScreen() {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
|
||||
headerImage={
|
||||
<IconSymbol
|
||||
size={310}
|
||||
color="#808080"
|
||||
name="chevron.left.forwardslash.chevron.right"
|
||||
style={styles.headerImage}
|
||||
/>
|
||||
}>
|
||||
<TitleContainer title="Emails" />
|
||||
<ThemedText>This app includes example code to help you get started.</ThemedText>
|
||||
<Collapsible title="File-based routing">
|
||||
<ThemedText>
|
||||
This app has two screens:{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
|
||||
</ThemedText>
|
||||
<ThemedText>
|
||||
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
|
||||
sets up the tab navigator.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/router/introduction">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Android, iOS, and web support">
|
||||
<ThemedText>
|
||||
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
|
||||
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
|
||||
</ThemedText>
|
||||
</Collapsible>
|
||||
<Collapsible title="Images">
|
||||
<ThemedText>
|
||||
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
|
||||
different screen densities
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://reactnative.dev/docs/images">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Custom fonts">
|
||||
<ThemedText>
|
||||
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
|
||||
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
|
||||
custom fonts such as this one.
|
||||
// Simple hook for minimum duration loading state
|
||||
const useMinDurationLoading = (initialState: boolean, minDuration: number): [boolean, (newState: boolean) => void] => {
|
||||
const [state, setState] = useState(initialState);
|
||||
const [timer, setTimer] = useState<NodeJS.Timeout | null>(null);
|
||||
|
||||
const setStateWithMinDuration = useCallback((newState: boolean) => {
|
||||
if (newState) {
|
||||
setState(true);
|
||||
} else {
|
||||
if (timer) clearTimeout(timer);
|
||||
const newTimer = setTimeout(() => setState(false), minDuration);
|
||||
setTimer(newTimer);
|
||||
}
|
||||
}, [minDuration, timer]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timer) clearTimeout(timer);
|
||||
};
|
||||
}, [timer]);
|
||||
|
||||
return [state, setStateWithMinDuration];
|
||||
};
|
||||
|
||||
export default function EmailsScreen() {
|
||||
const router = useRouter();
|
||||
const dbContext = useDb();
|
||||
const webApi = useWebApi();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [emails, setEmails] = useState<MailboxEmail[]>([]);
|
||||
const [isLoading, setIsLoading] = useMinDurationLoading(true, 100);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
const loadEmails = useCallback(async () : Promise<void> => {
|
||||
try {
|
||||
setError(null);
|
||||
|
||||
if (!dbContext?.sqliteClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get unique email addresses from all credentials
|
||||
const emailAddresses = await dbContext.sqliteClient.getAllEmailAddresses();
|
||||
|
||||
try {
|
||||
// For now we only show the latest 50 emails. No pagination.
|
||||
const data = await webApi.post<MailboxBulkRequest, MailboxBulkResponse>('EmailBox/bulk', {
|
||||
addresses: emailAddresses,
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
});
|
||||
|
||||
// Decrypt emails locally using private key associated with the email address
|
||||
const encryptionKeys = await dbContext.sqliteClient.getAllEncryptionKeys();
|
||||
|
||||
// Decrypt emails locally using public/private key pairs
|
||||
const decryptedEmails = await EncryptionUtility.decryptEmailList(data.mails, encryptionKeys);
|
||||
|
||||
setEmails(decryptedEmails);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error('Failed to load emails');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
}
|
||||
}, [dbContext?.sqliteClient, webApi]);
|
||||
|
||||
useEffect(() => {
|
||||
loadEmails();
|
||||
}, [loadEmails]);
|
||||
|
||||
const onRefresh = useCallback(async () => {
|
||||
setIsRefreshing(true);
|
||||
await loadEmails();
|
||||
setIsRefreshing(false);
|
||||
}, [loadEmails]);
|
||||
|
||||
const formatEmailDate = (dateSystem: string): string => {
|
||||
const now = new Date();
|
||||
const emailDate = new Date(dateSystem);
|
||||
const secondsAgo = Math.floor((now.getTime() - emailDate.getTime()) / 1000);
|
||||
|
||||
if (secondsAgo < 60) {
|
||||
return 'just now';
|
||||
} else if (secondsAgo < 3600) {
|
||||
const minutes = Math.floor(secondsAgo / 60);
|
||||
return `${minutes} ${minutes === 1 ? 'min' : 'mins'} ago`;
|
||||
} else if (secondsAgo < 86400) {
|
||||
const hours = Math.floor(secondsAgo / 3600);
|
||||
return `${hours} ${hours === 1 ? 'hr' : 'hrs'} ago`;
|
||||
} else if (secondsAgo < 172800) {
|
||||
return 'yesterday';
|
||||
} else {
|
||||
return emailDate.toLocaleDateString('en-GB', {
|
||||
day: '2-digit',
|
||||
month: '2-digit'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<ThemedText style={styles.errorText}>Error: {error}</ThemedText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (emails.length === 0) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<ThemedText style={styles.emptyText}>
|
||||
You have not received any emails at your private email addresses yet. When you receive a new email, it will appear here.
|
||||
</ThemedText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return emails.map((email) => (
|
||||
<TouchableOpacity
|
||||
key={email.id}
|
||||
style={styles.emailCard}
|
||||
onPress={() => router.push(`/emails/${email.id}`)}
|
||||
>
|
||||
<View style={styles.emailHeader}>
|
||||
<ThemedText style={styles.emailSubject} numberOfLines={1}>
|
||||
{email.subject}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.emailDate}>
|
||||
{formatEmailDate(email.dateSystem)}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<ThemedText style={styles.emailPreview} numberOfLines={2}>
|
||||
{email.messagePreview}
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Light and dark mode components">
|
||||
<ThemedText>
|
||||
This template has light and dark mode support. The{' '}
|
||||
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
|
||||
what the user's current color scheme is, and so you can adjust UI colors accordingly.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Animations">
|
||||
<ThemedText>
|
||||
This template includes an example of an animated component. The{' '}
|
||||
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
|
||||
the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText>{' '}
|
||||
library to create a waving hand animation.
|
||||
</ThemedText>
|
||||
{Platform.select({
|
||||
ios: (
|
||||
<ThemedText>
|
||||
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
|
||||
component provides a parallax effect for the header image.
|
||||
</ThemedText>
|
||||
),
|
||||
})}
|
||||
</Collapsible>
|
||||
</ParallaxScrollView>
|
||||
</TouchableOpacity>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
|
||||
headerImage={
|
||||
<IconSymbol
|
||||
size={310}
|
||||
color="#808080"
|
||||
name="envelope.fill"
|
||||
style={styles.headerImage}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TitleContainer title="Emails" />
|
||||
{renderContent()}
|
||||
</ParallaxScrollView>
|
||||
{isRefreshing && (
|
||||
<View style={styles.refreshIndicator}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
headerImage: {
|
||||
color: '#808080',
|
||||
bottom: -90,
|
||||
left: -35,
|
||||
position: 'absolute',
|
||||
},
|
||||
centerContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
errorText: {
|
||||
color: '#ef4444',
|
||||
textAlign: 'center',
|
||||
},
|
||||
emptyText: {
|
||||
textAlign: 'center',
|
||||
opacity: 0.7,
|
||||
},
|
||||
emailCard: {
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 12,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 3,
|
||||
elevation: 3,
|
||||
},
|
||||
emailHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 8,
|
||||
},
|
||||
emailSubject: {
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
marginRight: 8,
|
||||
},
|
||||
emailDate: {
|
||||
fontSize: 12,
|
||||
opacity: 0.6,
|
||||
},
|
||||
emailPreview: {
|
||||
fontSize: 14,
|
||||
opacity: 0.8,
|
||||
},
|
||||
refreshIndicator: {
|
||||
position: 'absolute',
|
||||
top: 20,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
290
mobile-app/app/emails/[id].tsx
Normal file
290
mobile-app/app/emails/[id].tsx
Normal file
@@ -0,0 +1,290 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { StyleSheet, View, ScrollView, TouchableOpacity, ActivityIndicator, Alert, Share } from 'react-native';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { Email } from '@/utils/types/webapi/Email';
|
||||
import { useDb } from '@/context/DbContext';
|
||||
import { useWebApi } from '@/context/WebApiContext';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import EncryptionUtility from '@/utils/EncryptionUtility';
|
||||
import WebView from 'react-native-webview';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import { EncryptionKey } from '@/utils/types/EncryptionKey';
|
||||
|
||||
export default function EmailDetailsScreen() {
|
||||
const { id } = useLocalSearchParams();
|
||||
const router = useRouter();
|
||||
const dbContext = useDb();
|
||||
const webApi = useWebApi();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [email, setEmail] = useState<Email | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadEmail();
|
||||
}, [id]);
|
||||
|
||||
const loadEmail = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
if (!dbContext?.sqliteClient || !id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await webApi.get<Email>(`Email/${id}`);
|
||||
|
||||
// Decrypt email locally using public/private key pairs
|
||||
const encryptionKeys = await dbContext.sqliteClient.getAllEncryptionKeys();
|
||||
const decryptedEmail = await EncryptionUtility.decryptEmail(response, encryptionKeys);
|
||||
setEmail(decryptedEmail);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
Alert.alert(
|
||||
'Delete Email',
|
||||
'Are you sure you want to delete this email?',
|
||||
[
|
||||
{
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Delete',
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await webApi.delete(`Email/${id}`);
|
||||
router.back();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to delete email');
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const handleDownloadAttachment = async (attachment: Email['attachments'][0]) => {
|
||||
try {
|
||||
// Get the encrypted attachment bytes from the API
|
||||
const base64EncryptedAttachment = await webApi.downloadBlobAndConvertToBase64(
|
||||
`Email/${id}/attachments/${attachment.id}`
|
||||
);
|
||||
|
||||
if (!dbContext?.sqliteClient || !email) {
|
||||
setError('Database context or email not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get encryption keys for decryption
|
||||
const encryptionKeys = await dbContext.sqliteClient.getAllEncryptionKeys();
|
||||
|
||||
// Decrypt the attachment
|
||||
const decryptedBytes = await EncryptionUtility.decryptAttachment(
|
||||
base64EncryptedAttachment,
|
||||
email,
|
||||
encryptionKeys
|
||||
);
|
||||
|
||||
if (!decryptedBytes) {
|
||||
setError('Failed to decrypt attachment');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save to temporary file
|
||||
const tempFile = `${FileSystem.cacheDirectory}${attachment.filename}`;
|
||||
await FileSystem.writeAsStringAsync(tempFile, decryptedBytes, {
|
||||
encoding: FileSystem.EncodingType.Base64,
|
||||
});
|
||||
|
||||
// Share the file using the native share API
|
||||
await Share.share({
|
||||
url: tempFile,
|
||||
title: attachment.filename,
|
||||
});
|
||||
|
||||
// Clean up temp file
|
||||
await FileSystem.deleteAsync(tempFile);
|
||||
} catch (err) {
|
||||
console.error('handleDownloadAttachment error', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to download attachment');
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<ThemedText style={styles.errorText}>Error: {error}</ThemedText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (!email) {
|
||||
return (
|
||||
<View style={styles.centerContainer}>
|
||||
<ThemedText style={styles.emptyText}>Email not found</ThemedText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<View style={styles.headerTop}>
|
||||
<ThemedText style={styles.subject}>{email.subject}</ThemedText>
|
||||
<TouchableOpacity onPress={handleDelete} style={styles.deleteButton}>
|
||||
<IconSymbol name="trash" size={24} color="#ef4444" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.metadata}>
|
||||
<ThemedText style={styles.metadataText}>
|
||||
From: {email.fromDisplay} ({email.fromLocal}@{email.fromDomain})
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.metadataText}>
|
||||
To: {email.toLocal}@{email.toDomain}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.metadataText}>
|
||||
Date: {new Date(email.dateSystem).toLocaleString()}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Email Body */}
|
||||
<View style={styles.body}>
|
||||
{email.messageHtml ? (
|
||||
<WebView
|
||||
style={styles.webView}
|
||||
source={{ html: email.messageHtml }}
|
||||
scrollEnabled={false}
|
||||
onNavigationStateChange={(event) => {
|
||||
if (event.url !== 'about:blank') {
|
||||
Share.share({
|
||||
url: event.url,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ThemedText style={styles.plainText}>{email.messagePlain}</ThemedText>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Attachments */}
|
||||
{email.attachments && email.attachments.length > 0 && (
|
||||
<View style={styles.attachments}>
|
||||
<ThemedText style={styles.attachmentsTitle}>Attachments</ThemedText>
|
||||
{email.attachments.map((attachment) => (
|
||||
<TouchableOpacity
|
||||
key={attachment.id}
|
||||
style={styles.attachment}
|
||||
onPress={() => handleDownloadAttachment(attachment)}
|
||||
>
|
||||
<IconSymbol name="paperclip" size={20} color="#666" />
|
||||
<ThemedText style={styles.attachmentName}>
|
||||
{attachment.filename} ({Math.ceil(attachment.filesize / 1024)} KB)
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
centerContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
errorText: {
|
||||
color: '#ef4444',
|
||||
textAlign: 'center',
|
||||
},
|
||||
emptyText: {
|
||||
textAlign: 'center',
|
||||
opacity: 0.7,
|
||||
},
|
||||
header: {
|
||||
padding: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#e5e5e5',
|
||||
},
|
||||
headerTop: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 12,
|
||||
},
|
||||
subject: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
flex: 1,
|
||||
marginRight: 16,
|
||||
},
|
||||
deleteButton: {
|
||||
padding: 8,
|
||||
},
|
||||
metadata: {
|
||||
marginTop: 8,
|
||||
},
|
||||
metadataText: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
marginBottom: 4,
|
||||
},
|
||||
body: {
|
||||
padding: 16,
|
||||
},
|
||||
webView: {
|
||||
minHeight: 200,
|
||||
},
|
||||
plainText: {
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
},
|
||||
attachments: {
|
||||
padding: 16,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#e5e5e5',
|
||||
},
|
||||
attachmentsTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 12,
|
||||
},
|
||||
attachment: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 12,
|
||||
backgroundColor: '#f5f5f5',
|
||||
borderRadius: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
attachmentName: {
|
||||
marginLeft: 8,
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
},
|
||||
});
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { View, ActivityIndicator } from 'react-native';
|
||||
import { router } from 'expo-router';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { useDb } from '@/context/DbContext';
|
||||
import { useVaultSync } from '@/hooks/useVaultSync';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import LoadingIndicator from '@/components/LoadingIndicator';
|
||||
import { install } from 'react-native-quick-crypto';
|
||||
|
||||
export default function InitialLoadingScreen() {
|
||||
const { isInitialized: isAuthInitialized, isLoggedIn } = useAuth();
|
||||
@@ -17,6 +17,10 @@ export default function InitialLoadingScreen() {
|
||||
const isFullyInitialized = isAuthInitialized && dbInitialized;
|
||||
const requireLoginOrUnlock = isFullyInitialized && (!isLoggedIn || !dbAvailable);
|
||||
|
||||
// Install the react-native-quick-crypto library which is used by the EncryptionUtility
|
||||
// which acts as a drop-in replacement for the subtle crypto API.
|
||||
install();
|
||||
|
||||
useEffect(() => {
|
||||
async function initialize() {
|
||||
if (hasInitialized.current) {
|
||||
|
||||
@@ -257,6 +257,8 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- ExpoSharing (13.0.1):
|
||||
- ExpoModulesCore
|
||||
- ExpoSplashScreen (0.29.22):
|
||||
- ExpoModulesCore
|
||||
- ExpoSQLite (15.1.4):
|
||||
@@ -277,6 +279,7 @@ PODS:
|
||||
- hermes-engine/Pre-built (= 0.76.9)
|
||||
- hermes-engine/Pre-built (0.76.9)
|
||||
- KeychainAccess (4.2.2)
|
||||
- OpenSSL-Universal (3.3.3001)
|
||||
- RCT-Folly (2024.10.14.00):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -1559,6 +1562,29 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-get-random-values (1.11.0):
|
||||
- React-Core
|
||||
- react-native-quick-crypto (0.7.13):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
- OpenSSL-Universal
|
||||
- RCT-Folly (= 2024.10.14.00)
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-safe-area-context (4.12.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
@@ -2165,6 +2191,7 @@ DEPENDENCIES:
|
||||
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||
- ExpoLinking (from `../node_modules/expo-linking/ios`)
|
||||
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
|
||||
- ExpoSharing (from `../node_modules/expo-sharing/ios`)
|
||||
- ExpoSplashScreen (from `../node_modules/expo-splash-screen/ios`)
|
||||
- ExpoSQLite (from `../node_modules/expo-sqlite/ios`)
|
||||
- ExpoSymbols (from `../node_modules/expo-symbols/ios`)
|
||||
@@ -2210,6 +2237,7 @@ DEPENDENCIES:
|
||||
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
|
||||
- react-native-aes-gcm-crypto (from `../node_modules/react-native-aes-gcm-crypto`)
|
||||
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
|
||||
- react-native-quick-crypto (from `../node_modules/react-native-quick-crypto`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- react-native-webview (from `../node_modules/react-native-webview`)
|
||||
- React-nativeconfig (from `../node_modules/react-native/ReactCommon`)
|
||||
@@ -2252,6 +2280,7 @@ SPEC REPOS:
|
||||
trunk:
|
||||
- CatCrypto
|
||||
- KeychainAccess
|
||||
- OpenSSL-Universal
|
||||
- SocketRocket
|
||||
- SQLite.swift
|
||||
|
||||
@@ -2296,6 +2325,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/expo-linking/ios"
|
||||
ExpoModulesCore:
|
||||
:path: "../node_modules/expo-modules-core"
|
||||
ExpoSharing:
|
||||
:path: "../node_modules/expo-sharing/ios"
|
||||
ExpoSplashScreen:
|
||||
:path: "../node_modules/expo-splash-screen/ios"
|
||||
ExpoSQLite:
|
||||
@@ -2381,6 +2412,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-aes-gcm-crypto"
|
||||
react-native-get-random-values:
|
||||
:path: "../node_modules/react-native-get-random-values"
|
||||
react-native-quick-crypto:
|
||||
:path: "../node_modules/react-native-quick-crypto"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-webview:
|
||||
@@ -2476,6 +2509,7 @@ SPEC CHECKSUMS:
|
||||
ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680
|
||||
ExpoLinking: 8d12bee174ba0cdf31239706578e29e74a417402
|
||||
ExpoModulesCore: c25d77625038b1968ea1afefc719862c0d8dd993
|
||||
ExpoSharing: 849a5ce9985c22598c16ec027e32969be8062e8e
|
||||
ExpoSplashScreen: 0f281e3c2ded4757d2309276c682d023c6299c77
|
||||
ExpoSQLite: 10fceac6748e9e8f010c70733e0e704fa67399ab
|
||||
ExpoSymbols: f3002db15156cd4e505c77b6ea1df5c984db9965
|
||||
@@ -2488,6 +2522,7 @@ SPEC CHECKSUMS:
|
||||
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
|
||||
hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11
|
||||
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
|
||||
OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2
|
||||
RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17
|
||||
RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83
|
||||
RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716
|
||||
@@ -2519,6 +2554,7 @@ SPEC CHECKSUMS:
|
||||
React-microtasksnativemodule: d80ff86c8902872d397d9622f1a97aadcc12cead
|
||||
react-native-aes-gcm-crypto: d572dd7a69f31c539bb8309b3a829bfa3bfad244
|
||||
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
|
||||
react-native-quick-crypto: 361ecda861e23138ce68fea4b820cbc058688fb5
|
||||
react-native-safe-area-context: cd916088cac5300c3266876218377518987b995e
|
||||
react-native-webview: 6b9fc65c1951203a3e958ff3cc0a858d4b6be901
|
||||
React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678
|
||||
|
||||
@@ -449,11 +449,13 @@
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-AliasVault/Pods-AliasVault-frameworks.sh",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ExpoSQLite/crsqlite.framework/crsqlite",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/crsqlite.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
381
mobile-app/package-lock.json
generated
381
mobile-app/package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@react-native-async-storage/async-storage": "^2.1.2",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"@types/jsrsasign": "^10.5.15",
|
||||
"expo": "~52.0.43",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-clipboard": "^7.0.1",
|
||||
@@ -21,6 +22,7 @@
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-linking": "~7.0.5",
|
||||
"expo-router": "~4.0.20",
|
||||
"expo-sharing": "~13.0.1",
|
||||
"expo-splash-screen": "~0.29.22",
|
||||
"expo-sqlite": "~15.1.4",
|
||||
"expo-status-bar": "~2.0.1",
|
||||
@@ -35,6 +37,7 @@
|
||||
"react-native-argon2": "^2.0.1",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-quick-crypto": "^0.7.13",
|
||||
"react-native-reanimated": "~3.16.1",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
@@ -2233,6 +2236,30 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@craftzdog/react-native-buffer": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@craftzdog/react-native-buffer/-/react-native-buffer-6.0.5.tgz",
|
||||
"integrity": "sha512-Av+YqfwA9e7jhgI9GFE/gTpwl/H+dRRLmZyJPOpKTy107j9Oj7oXlm3/YiMNz+C/CEGqcKAOqnXDLs4OL6AAFw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ieee754": "^1.2.1",
|
||||
"react-native-quick-base64": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@egjs/hammerjs": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
|
||||
@@ -4659,6 +4686,12 @@
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/jsrsasign": {
|
||||
"version": "10.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsrsasign/-/jsrsasign-10.5.15.tgz",
|
||||
"integrity": "sha512-3stUTaSRtN09PPzVWR6aySD9gNnuymz+WviNHoTb85dKu+BjaV4uBbWWGykBBJkfwPtcNZVfTn2lbX00U+yhpQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
|
||||
@@ -5267,6 +5300,21 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"possible-typed-array-names": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-core": {
|
||||
"version": "7.0.0-bridge.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
|
||||
@@ -5737,6 +5785,24 @@
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.0",
|
||||
"es-define-property": "^1.0.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@@ -5750,6 +5816,22 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/caller-callsite": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
|
||||
@@ -6587,6 +6669,23 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/define-lazy-prop": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
|
||||
@@ -7122,9 +7221,7 @@
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
@@ -7617,6 +7714,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-sharing": {
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-13.0.1.tgz",
|
||||
"integrity": "sha512-qych3Nw65wlFcnzE/gRrsdtvmdV0uF4U4qVMZBJYPG90vYyWh2QM9rp1gVu0KWOBc7N8CC2dSVYn4/BXqJy6Xw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-splash-screen": {
|
||||
"version": "0.29.22",
|
||||
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.29.22.tgz",
|
||||
@@ -8023,6 +8129,21 @@
|
||||
"integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-callable": "^1.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
@@ -8332,6 +8453,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
@@ -8704,6 +8837,22 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arguments": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
|
||||
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"has-tostringtag": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
@@ -8716,6 +8865,18 @@
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
@@ -8783,6 +8944,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/is-generator-function": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
|
||||
"integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.3",
|
||||
"get-proto": "^1.0.0",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"safe-regex-test": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
@@ -8850,6 +9029,24 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"gopd": "^1.2.0",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
@@ -8859,6 +9056,21 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typed-array": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
|
||||
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"which-typed-array": "^1.1.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
@@ -12091,6 +12303,15 @@
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.49",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
||||
@@ -12172,6 +12393,15 @@
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
@@ -12601,6 +12831,36 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-quick-base64": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-quick-base64/-/react-native-quick-base64-2.1.2.tgz",
|
||||
"integrity": "sha512-xghaXpWdB0ji8OwYyo0fWezRroNxiNFCNFpGUIyE7+qc4gA/IGWnysIG5L0MbdoORv8FkTKUvfd6yCUN5R2VFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-quick-crypto": {
|
||||
"version": "0.7.13",
|
||||
"resolved": "https://registry.npmjs.org/react-native-quick-crypto/-/react-native-quick-crypto-0.7.13.tgz",
|
||||
"integrity": "sha512-9EmXvvBW64gt/hUgeDUGl6KHjDVmtEdC/CbmlyLOTNXxvkOhNI/LhLK9ss6jDqRX3tYTM/DL0uk/+kXSTZQeKA==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
".",
|
||||
"example"
|
||||
],
|
||||
"dependencies": {
|
||||
"@craftzdog/react-native-buffer": "^6.0.5",
|
||||
"events": "^3.3.0",
|
||||
"readable-stream": "^4.5.2",
|
||||
"string_decoder": "^1.3.0",
|
||||
"util": "^0.12.5"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-reanimated": {
|
||||
"version": "3.16.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.7.tgz",
|
||||
@@ -12859,6 +13119,46 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"process": "^0.11.10",
|
||||
"string_decoder": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readline": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
|
||||
@@ -13179,6 +13479,23 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-regex-test": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
|
||||
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"is-regex": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
@@ -13458,6 +13775,23 @@
|
||||
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
@@ -13809,6 +14143,15 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-length": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
||||
@@ -14785,6 +15128,19 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"is-arguments": "^1.0.4",
|
||||
"is-generator-function": "^1.0.7",
|
||||
"is-typed-array": "^1.1.3",
|
||||
"which-typed-array": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
@@ -15053,6 +15409,27 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.19",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
|
||||
"integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"available-typed-arrays": "^1.0.7",
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"for-each": "^0.3.5",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-tostringtag": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/wonka": {
|
||||
"version": "6.3.5",
|
||||
"resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@react-native-async-storage/async-storage": "^2.1.2",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
"@react-navigation/native": "^7.0.14",
|
||||
"@types/jsrsasign": "^10.5.15",
|
||||
"expo": "~52.0.43",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-clipboard": "^7.0.1",
|
||||
@@ -28,6 +29,7 @@
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-linking": "~7.0.5",
|
||||
"expo-router": "~4.0.20",
|
||||
"expo-sharing": "~13.0.1",
|
||||
"expo-splash-screen": "~0.29.22",
|
||||
"expo-sqlite": "~15.1.4",
|
||||
"expo-status-bar": "~2.0.1",
|
||||
@@ -42,6 +44,7 @@
|
||||
"react-native-argon2": "^2.0.1",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-quick-crypto": "^0.7.13",
|
||||
"react-native-reanimated": "~3.16.1",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
|
||||
@@ -113,6 +113,8 @@ class EncryptionUtility {
|
||||
* Generates a new RSA key pair for asymmetric encryption
|
||||
*/
|
||||
public static async generateRsaKeyPair(): Promise<{ publicKey: string, privateKey: string }> {
|
||||
// TODO: ensure the key pair is generated in the correct format where private key is in expected
|
||||
// JWK format that the WASM app already outputs.
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
@@ -179,7 +181,8 @@ class EncryptionUtility {
|
||||
const cipherBuffer = Uint8Array.from(atob(ciphertext), c => c.charCodeAt(0));
|
||||
const plaintextBuffer = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: "RSA-OAEP"
|
||||
name: "RSA-OAEP",
|
||||
hash: "SHA-256",
|
||||
},
|
||||
privateKeyObj,
|
||||
cipherBuffer
|
||||
@@ -188,7 +191,7 @@ class EncryptionUtility {
|
||||
return new Uint8Array(plaintextBuffer);
|
||||
} catch (error) {
|
||||
console.error('RSA decryption failed:', error);
|
||||
throw new Error(`Failed to decrypt: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
throw new Error(`Failed to decrypt: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,13 +253,21 @@ class EncryptionUtility {
|
||||
throw new Error('Encryption key not found');
|
||||
}
|
||||
|
||||
console.log('email.encryptedSymmetricKey: ', email.encryptedSymmetricKey);
|
||||
console.log('encryptionKey.PrivateKey: ', encryptionKey.PrivateKey);
|
||||
|
||||
// Decrypt symmetric key with asymmetric private key
|
||||
const symmetricKey = await EncryptionUtility.decryptWithPrivateKey(
|
||||
email.encryptedSymmetricKey,
|
||||
encryptionKey.PrivateKey
|
||||
);
|
||||
|
||||
console.log('Decrypted symmetricKey: ', symmetricKey);
|
||||
|
||||
const symmetricKeyBase64 = Buffer.from(symmetricKey).toString('base64');
|
||||
|
||||
console.log('Decrypted symmetricKeyBase64: ', symmetricKeyBase64);
|
||||
|
||||
// Create a new object to avoid mutating the original
|
||||
const decryptedEmail = { ...email };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user