feat(mobile): crypto + secret-store + session-storage adapters
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import * as s from 'react-native-libsodium';
|
||||
import type { CryptoBackend } from '@chat-app/shared/crypto';
|
||||
|
||||
// react-native-libsodium re-exports libsodium-wrappers' API shape, so
|
||||
// this adapter is the synchronous twin of the desktop one
|
||||
// (`apps/desktop/src/lib/cryptoBackend.ts`). No WASM warm-up gate is
|
||||
// needed — the native module is ready as soon as the module loads.
|
||||
export function createLibsodiumBackend(): CryptoBackend {
|
||||
return {
|
||||
name: 'react-native-libsodium',
|
||||
nonceLength: s.crypto_box_NONCEBYTES,
|
||||
publicKeyLength: s.crypto_box_PUBLICKEYBYTES,
|
||||
privateKeyLength: s.crypto_box_SECRETKEYBYTES,
|
||||
secretboxKeyLength: s.crypto_secretbox_KEYBYTES,
|
||||
secretboxNonceLength: s.crypto_secretbox_NONCEBYTES,
|
||||
randomBytes: (n) => s.randombytes_buf(n),
|
||||
generateKeyPair: () => {
|
||||
const kp = s.crypto_box_keypair();
|
||||
return { publicKey: kp.publicKey, privateKey: kp.privateKey };
|
||||
},
|
||||
box: (plaintext, nonce, recipientPublicKey, senderPrivateKey) =>
|
||||
s.crypto_box_easy(plaintext, nonce, recipientPublicKey, senderPrivateKey),
|
||||
boxOpen: (ciphertext, nonce, senderPublicKey, recipientPrivateKey) =>
|
||||
s.crypto_box_open_easy(ciphertext, nonce, senderPublicKey, recipientPrivateKey),
|
||||
secretbox: (plaintext, nonce, key) => s.crypto_secretbox_easy(plaintext, nonce, key),
|
||||
secretboxOpen: (ciphertext, nonce, key) => s.crypto_secretbox_open_easy(ciphertext, nonce, key),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Buffer } from 'buffer';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import type { SecretStore } from '@chat-app/shared/auth';
|
||||
|
||||
// SecretStore contract uses Uint8Array values; SecureStore only takes
|
||||
// strings, so we base64 at the boundary. iOS Keychain max value size
|
||||
// is generous (a few MB); private keys are 32 bytes so we are well
|
||||
// within limits.
|
||||
export const secretStore: SecretStore = {
|
||||
async getSecret(key) {
|
||||
const v = await SecureStore.getItemAsync(key);
|
||||
return v ? new Uint8Array(Buffer.from(v, 'base64')) : null;
|
||||
},
|
||||
async setSecret(key, value) {
|
||||
await SecureStore.setItemAsync(key, Buffer.from(value).toString('base64'));
|
||||
},
|
||||
async removeSecret(key) {
|
||||
await SecureStore.deleteItemAsync(key);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
// supabase-js v2 accepts any object with async getItem / setItem /
|
||||
// removeItem returning Promise<string | null> / Promise<void>. RN's
|
||||
// AsyncStorage matches that shape for shape; we just re-export it
|
||||
// under a name that signals intent at the call site.
|
||||
export const sessionStorage = AsyncStorage;
|
||||
Reference in New Issue
Block a user