feat: profile avatar upload + share_conv_keys rpc + favicon + smtp tweaks
This commit is contained in:
@@ -126,9 +126,17 @@ async function fetchKeyBundle(
|
||||
};
|
||||
}
|
||||
|
||||
// Strips the leading `\x` postgres bytea hex prefix so the RPC's
|
||||
// `decode(text, 'hex')` accepts it.
|
||||
function hexNoPrefix(bytes: Uint8Array): string {
|
||||
return bytesToPgHex(bytes).slice(2);
|
||||
}
|
||||
|
||||
// Bootstraps a brand-new conv-key, wrapping it for every member device that
|
||||
// currently exists (including the caller's own devices). Used the first time
|
||||
// a conversation needs a key, or when rotation is requested.
|
||||
// a conversation needs a key, or when rotation is requested. All inserts go
|
||||
// through `share_conv_keys` (SECURITY DEFINER) — silently skips invalid
|
||||
// recipients, no per-row 403 console spam.
|
||||
export async function bootstrapConvKey(
|
||||
client: AppSupabaseClient,
|
||||
conversationId: string,
|
||||
@@ -141,29 +149,23 @@ export async function bootstrapConvKey(
|
||||
throw new Error('cannot bootstrap conv key — no recipient devices');
|
||||
}
|
||||
|
||||
const rows: Array<{
|
||||
conversation_id: string;
|
||||
recipient_device_id: string;
|
||||
key_version: number;
|
||||
sender_device_id: string;
|
||||
encrypted_key: string;
|
||||
nonce: string;
|
||||
}> = [];
|
||||
const bundles: Array<{ recipient_device_id: string; encrypted_key: string; nonce: string }> = [];
|
||||
for (const r of recipients) {
|
||||
const wrapped = await wrapConvKeyForRecipient(convKey, r.publicKey, own.privateKey);
|
||||
rows.push({
|
||||
conversation_id: conversationId,
|
||||
bundles.push({
|
||||
recipient_device_id: r.deviceId,
|
||||
key_version: keyVersion,
|
||||
sender_device_id: own.deviceId,
|
||||
encrypted_key: bytesToPgHex(wrapped.ciphertext),
|
||||
nonce: bytesToPgHex(wrapped.nonce),
|
||||
encrypted_key: hexNoPrefix(wrapped.ciphertext),
|
||||
nonce: hexNoPrefix(wrapped.nonce),
|
||||
});
|
||||
}
|
||||
|
||||
const { error } = await rawFrom(client, 'conversation_keys').upsert(rows, {
|
||||
onConflict: 'conversation_id,recipient_device_id,key_version',
|
||||
ignoreDuplicates: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const rpc = (client as unknown as { rpc: (n: string, p: object) => Promise<{ error: any }> }).rpc;
|
||||
const { error } = await rpc.call(client, 'share_conv_keys', {
|
||||
p_conv_id: conversationId,
|
||||
p_sender_device_id: own.deviceId,
|
||||
p_key_version: keyVersion,
|
||||
p_bundles: bundles,
|
||||
});
|
||||
if (error) throw error;
|
||||
|
||||
@@ -265,20 +267,20 @@ export async function shareConvKeyToDevice(
|
||||
recipientPublicKey,
|
||||
own.privateKey,
|
||||
);
|
||||
const { error } = await rawFrom(client, 'conversation_keys').upsert(
|
||||
{
|
||||
conversation_id: conversationId,
|
||||
recipient_device_id: recipientDeviceId,
|
||||
key_version: version,
|
||||
sender_device_id: own.deviceId,
|
||||
encrypted_key: bytesToPgHex(wrapped.ciphertext),
|
||||
nonce: bytesToPgHex(wrapped.nonce),
|
||||
},
|
||||
{
|
||||
onConflict: 'conversation_id,recipient_device_id,key_version',
|
||||
ignoreDuplicates: true,
|
||||
},
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const rpc = (client as unknown as { rpc: (n: string, p: object) => Promise<{ error: any }> }).rpc;
|
||||
const { error } = await rpc.call(client, 'share_conv_keys', {
|
||||
p_conv_id: conversationId,
|
||||
p_sender_device_id: own.deviceId,
|
||||
p_key_version: version,
|
||||
p_bundles: [
|
||||
{
|
||||
recipient_device_id: recipientDeviceId,
|
||||
encrypted_key: hexNoPrefix(wrapped.ciphertext),
|
||||
nonce: hexNoPrefix(wrapped.nonce),
|
||||
},
|
||||
],
|
||||
});
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user