mirror of
https://github.com/Discreetly/frontend.git
synced 2026-01-08 20:38:04 -05:00
alert updates and keystore beginnings
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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 ------------------*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user