alert updates and keystore beginnings

This commit is contained in:
AtHeartEngineer
2023-10-10 21:30:15 -04:00
parent 764e4cf60e
commit 35d9fd7753
8 changed files with 168 additions and 89 deletions

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { passwordSet, configStore, keyStore, identityStore, identityKeyStore } from '$lib/stores';
import { passwordSet, configStore, keyStore } from '$lib/stores';
import Lock from 'svelte-material-icons/Lock.svelte';
import LockOpen from 'svelte-material-icons/LockOpenVariant.svelte';
import NoPassword from 'svelte-material-icons/LockOff.svelte';
@@ -7,12 +7,13 @@
import { getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
import { deriveKey, hashPassword } from '$lib/crypto/crypto';
import { onMount } from 'svelte';
import { setPassword } from '$lib/utils';
const modalStore = getModalStore();
export let cls: string = '';
function setPassword() {
function setPasswordModal() {
const modal: ModalSettings = {
type: 'prompt',
title: 'Set a Password',
@@ -61,7 +62,7 @@
<div class={cls} id="lock">
{#if $passwordSet}
{#if $keyStore !== null && $keyStore !== undefined}
{#if $keyStore instanceof CryptoKey}
<div on:click={lock} title="Unlocked, click to lock">
<LockOpen class="text-warning-300-600-token" />
</div>
@@ -69,7 +70,7 @@
<div on:click={unlock} title="Locked" class="text-success-500"><Lock /></div>
{/if}
{:else}
<div on:click={setPassword} title="Password not set">
<div on:click={setPasswordModal} title="Password not set">
<NoPassword class="text-primary-500" />
</div>
{/if}

View File

@@ -31,7 +31,7 @@ export const selectedServer = storable('' as string, 'selectedServer');
/**
* @description Room information keyed by the roomId
*/
export const roomsStore = storable({} as roomStoreI, 'roomsStore');
export const roomsStore = storable({} as roomStoreI, 'rooms');
export const currentRoomsStore = derived(
[selectedServer, roomsStore],
@@ -65,7 +65,7 @@ export const pixelStore = sessionable({} as pixelStoreI, 'pixelmaps');
/**
* @description Stores the encrypted key for each room keyed by the roomId
*/
export const roomKeyStore = encryptable({} as roomKeyStoreI, 'roomKeyStore');
export const roomKeyStore = encryptable({} as roomKeyStoreI, 'roomKey');
/**
* @description Derived Store: The messages of the currently selected room
@@ -80,7 +80,7 @@ export const currentRoomMessages = derived(
/**
* @description Stores the rate limit information for each room keyed by the roomId; this is to track the number of messages sent in a given epoch to make sure the user doesn't break the rate limit. Modifying how his store works, or how the store is written to/read from, may allow the user to break the rate limit and be banned from the room.
*/
export const rateLimitStore = storable({} as rateLimitStoreI, 'rateLimitStore');
export const rateLimitStore = storable({} as rateLimitStoreI, 'rateLimit');
/* ------------------ Configuration / Misc Stores ------------------*/
@@ -93,7 +93,7 @@ export const keyStore = writable({} as keyStoreI);
/**
* @description Configuration store, stores the user's settings
*/
export const configStore = storable(configDefaults as ConfigurationI, 'configStore');
export const configStore = storable(configDefaults as ConfigurationI, 'config');
export const passwordSet = derived(configStore, ($configStore) => {
if (!$configStore.hashedPwd) {
@@ -115,7 +115,7 @@ export const consoleStore = storable(
messages: [{ message: 'Welcome User', type: 'info' }],
settings: {}
} as consoleStoreI,
'consoleStore'
'console'
);
/* ------------------ Identity Stores ------------------*/

View File

@@ -1,7 +1,18 @@
import { getToastStore } from '@skeletonlabs/skeleton';
import { addConsoleMessage } from './console';
export function alert(alertMessage: string, timeout = 5000) {
export function alertToast(alertMessage: string, timeout = 5000) {
const toastStore = getToastStore();
toastStore.trigger({ message: alertMessage, timeout: timeout });
console.warn(alertMessage);
}
export function alertAll(
alertMessage: string,
level: 'warning' | 'info' | 'userinput' | 'error' | 'space' | undefined = 'warning',
timeout = 5000
) {
alertToast(alertMessage, timeout);
console.warn(alertMessage);
addConsoleMessage(alertMessage, level);
}

View File

@@ -90,7 +90,6 @@ export function getIdentityBackup() {
export function doesIdentityExist(): 'safe' | 'unsafe' | 'none' {
const id = get(identityKeyStore);
const id_old = get(identityStore);
console.log(id, id_old);
if (id._commitment !== null && id._commitment !== undefined) {
return 'safe';
}
@@ -105,11 +104,6 @@ export function encryptIdentity(identity: IdentityStoreI) {
const key = get(keyStore);
if (key !== undefined && key !== null) {
identityKeyStore.set(identity as unknown as IdentityStoreI);
identityStore.set({
_commitment: '',
_trapdoor: '',
_nullifier: '',
_secret: ''
});
identityStore.set({} as unknown as IdentityStoreI);
}
}

View File

@@ -60,7 +60,7 @@
<a href="/console" class="inline"><Console size="1.2em" /></a>
{/if}
<PasswordLock cls="hidden sm:inline" />
<PasswordLock cls="inline" />
{#if identityExists}
<a href="/settings"><Settings size="1.2em" /></a>
{/if}

View File

@@ -1,9 +1,10 @@
<script lang="ts">
import { currentSelectedRoom, rateLimitStore } from '$lib/stores';
import { currentSelectedRoom, keyStore, rateLimitStore, roomKeyStore } from '$lib/stores';
import { genProof } from '$lib/crypto/rlnProver';
import type { Socket } from 'socket.io-client';
import { getIdentity, alert, clearMessageHistory } from '$lib/utils';
import { getIdentity, alertToast, clearMessageHistory, alertAll } from '$lib/utils';
import Send from 'svelte-material-icons/Send.svelte';
import { decrypt, encrypt } from '$lib/crypto/crypto';
export let socket: Socket;
export let connected: boolean;
@@ -31,23 +32,23 @@
function checkStatus(): boolean {
if (!connected) {
alert('NOT CONNECTED TO CHAT SERVER');
alertToast('NOT CONNECTED TO CHAT SERVER');
sendingMessage = false;
return false;
}
if (messageText.length < 1) {
alert('MESSAGE IS EMPTY');
alertToast('MESSAGE IS EMPTY');
sendingMessage = false;
return false;
}
if (messageText.length > 2000) {
alert('MESSAGE IS TOO LONG');
alertToast('MESSAGE IS TOO LONG, SENDING MAY FAIL UNDER NETWORK CONSTRAINED CONDITIONS');
sendingMessage = false;
return false;
}
// This is 100% thanks to Violet for spamming the chat with spaces
if (messageText.replaceAll(' ', '') == '') {
alert('MESSAGE IS EMPTY');
alertToast('MESSAGE IS EMPTY');
sendingMessage = false;
return false;
}
@@ -55,7 +56,7 @@
}
function help() {
alert('Commands: /clear, /help');
alertToast('Commands: /clear, /help');
}
function processCommand(value: string) {
@@ -73,51 +74,95 @@
}
}
function sendMessage() {
sendingMessage = true;
console.debug('Sending message: ', messageText);
setTimeout(() => {
sendingMessage = false;
}, 2500);
if (!checkStatus()) return;
const identity = getIdentity();
const room = $currentSelectedRoom;
if (room.encrypted) {
// Helper function to handle encrypted room messages
async function handleEncryptedMessage(
messageText: string,
roomId: string,
keyStore: any,
roomKeyStore: any
): Promise<string> {
if (!roomKeyStore[roomId]) {
throw new Error('ROOM IS ENCRYPTED BUT NO PASSWORD WAS FOUND');
}
if (!keyStore) {
throw new Error('NO KEYSTORE FOUND');
}
const key = await decrypt(roomKeyStore[roomId], keyStore);
if (!key) {
throw new Error('NO KEY FOUND');
}
const encryptedMessage = await encrypt(messageText, key as unknown as CryptoKey);
if (encryptedMessage == null) {
throw new Error('ENCRYPTION FAILED');
} else {
return encryptedMessage;
}
}
// Helper function to handle rate limiting
function handleRateLimiting(currentEpoch: number, roomId: string, rateLimitStore: any) {
const rateInfo = rateLimitStore[roomId];
if (rateInfo.lastEpoch === currentEpoch) {
rateInfo.messagesSent++;
} else {
rateInfo.lastEpoch = currentEpoch;
rateInfo.messagesSent = 1;
}
console.debug(rateInfo.messagesSent, 'messages sent this epoch');
}
async function sendMessage() {
try {
sendingMessage = true;
console.debug('Sending message: ', messageText);
if (!checkStatus()) return;
const identity = getIdentity();
const room = $currentSelectedRoom;
if (!identity) {
throw new Error('NO IDENTITY FOUND');
}
let messageToSend: string = messageText;
if (room.encrypted) {
messageToSend = await handleEncryptedMessage(
messageText,
room.roomId!.toString(),
$keyStore,
$roomKeyStore
);
}
const msg = await genProof(
room,
messageToSend,
identity,
currentEpoch,
messageId,
userMessageLimit
);
handleRateLimiting(currentEpoch, room.roomId!.toString(), $rateLimitStore);
socket.emit('validateMessage', msg);
console.debug('Sending message: ', msg);
messageText = '';
} catch (err: any) {
console.error('Error sending message: ', err);
if (err.message.includes('Merkle Proof')) {
alertToast(
"Couldn't generate Merkle Proof. Maybe you don't belong in the room or don't have an updated member list."
);
} else {
alertToast(err as string);
}
} finally {
sendingMessage = false;
}
genProof(room, messageText, identity, currentEpoch, messageId, userMessageLimit)
.then((msg) => {
if ($rateLimitStore[$currentSelectedRoom.roomId!.toString()].lastEpoch == currentEpoch) {
$rateLimitStore[$currentSelectedRoom.roomId!.toString()].messagesSent++;
console.debug(
$rateLimitStore[$currentSelectedRoom.roomId!.toString()].messagesSent,
'messages sent this epoch'
);
} else {
$rateLimitStore[$currentSelectedRoom.roomId!.toString()].lastEpoch = currentEpoch;
$rateLimitStore[$currentSelectedRoom.roomId!.toString()].messagesSent = 1;
console.debug(
$rateLimitStore[$currentSelectedRoom.roomId!.toString()].messagesSent,
'messages sent this epoch'
);
}
socket.emit('validateMessage', msg);
console.debug('Sending message: ', msg);
messageText = '';
sendingMessage = false;
})
.catch((err) => {
if (err.message.includes('Merkle Proof')) {
alert(
"Could not generate Merkle Proof. You either don't belong in the room or you don't have an updated member list."
);
} else {
alert(err);
}
console.error('Error sending message: ', err);
sendingMessage = false;
});
}
function onPromptKeydown(event: KeyboardEvent): void {

View File

@@ -12,12 +12,9 @@
$: identityExists = !!$identityStore._commitment;
let tabSet: number = 0;
onMount(() => {
console.log('Page: ', $page);
if ($page.url.hash) {
const hash = $page.url.hash.replace('#', '');
console.log('Hash: ', hash);
if (hash === 'join-more') {
console.log('setting tab to join');
tabSet = 1;
}
}

View File

@@ -1,40 +1,57 @@
<script lang="ts">
import { identityStore } from '$lib/stores';
import { alert } from '$lib/utils';
import { identityKeyStore, keyStore } from '$lib/stores';
import { alertAll } from '$lib/utils';
import { FileDropzone } from '@skeletonlabs/skeleton';
import { poseidon2 } from 'poseidon-lite/poseidon2';
import { poseidon1 } from 'poseidon-lite/poseidon1';
import { onMount } from 'svelte';
let files: FileList;
let status = '';
function restoreBackup(backup: any) {
console.debug('Attempting restore of identity from backup file...');
console.debug(backup);
const id = JSON.parse(backup);
let id;
try {
id = JSON.parse(backup);
} catch (e) {
console.warn('Could not parse json as string');
try {
id = backup;
} catch (e) {
console.warn('Could not parse json as object');
alertAll('Invalid JSON detected');
return;
}
}
if (!id._nullifier) {
alert("_nullifier doesn't exist in backup");
alertAll("_nullifier doesn't exist in backup");
}
if (!id._trapdoor) {
alert("_trapdoor doesn't exist in backup");
alertAll("_trapdoor doesn't exist in backup");
}
if (!id._secret) {
alert("_secret doesn't exist in backup");
alertAll("_secret doesn't exist in backup");
}
const checkSecret = poseidon2([id._nullifier, id._trapdoor]);
if (checkSecret != id._secret) {
alert('Secret does not match secret from backup');
alertAll('Secret does not match secret from backup');
}
if (!id._commitment) {
alert("_commitment doesn't exist in backup");
alertAll("_commitment doesn't exist in backup");
}
const checkCommitment = poseidon1([id._secret]);
if (checkCommitment != id._commitment) {
alert('Commitment does not match commitment backup');
alertAll('Commitment does not match commitment backup');
}
$identityStore = id;
alert(
console.log('Restoring identity from backup file...');
if ($keyStore !== undefined || $keyStore !== null) {
$identityKeyStore = id;
} else {
alertAll('Please set a password or unlock before restoring your identity');
}
alertAll(
`Identity restored from backup file with identity commitment:
${$identityStore._commitment}`
${$identityKeyStore._commitment}`
);
}
@@ -43,7 +60,7 @@
console.debug(`Backup/recovery file type detected as ${f?.type}`);
let unverifiedBackup: any;
if (!f) {
alert('No file selected');
alertAll('No file selected');
return;
}
if (f.type == 'application/json' || f.type == 'text/plain') {
@@ -52,12 +69,25 @@
restoreBackup(unverifiedBackup);
});
} else {
alert(
alertAll(
'Invalid file type, must be a JSON object with the _nullifier, _trapdoor, _secret, and _commitment as stringified bigints'
);
console.warn('Invalid file type');
}
}
function recoverFromJSON() {
const textBox = document.getElementById('jsonRecovery') as HTMLInputElement;
const json = textBox.value;
if (!json || json == '') {
alertAll('No JSON detected');
return;
} else {
restoreBackup(json);
}
}
onMount(() => {});
</script>
<div class="card variant-ghost-tertiary">
@@ -80,12 +110,13 @@
<span class="h5"> Recover From JSON:</span>
<textarea
name="textarea"
id=""
class="mb-4 p-2 rounded-token"
id="jsonRecovery"
class="mb-2 p-2 rounded-token"
cols="30"
rows="10"
placeholder="Paste your Identity Here"
/></label
>
<a class="btn btn-sm variant-ringed-success mb-5" on:click={recoverFromJSON}>Recover</a>
</section>
</div>