mirror of
https://github.com/Discreetly/frontend.git
synced 2026-01-09 21:08:02 -05:00
checkpoint
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
|
||||
import { getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
|
||||
import { deriveKey, encryptData, hashPassword } from '$lib/crypto/crypto';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const modalStore = getModalStore();
|
||||
|
||||
@@ -26,24 +27,12 @@
|
||||
value: 'Discreetly',
|
||||
valueAttr: { type: 'password', minlength: 3, required: true },
|
||||
response: async (r: string) => {
|
||||
console.log('Prompt Response: ', r);
|
||||
const hashedPassword = await hashPassword(r);
|
||||
$configStore.hashedPwd = hashedPassword;
|
||||
console.debug(`Hashed Password: ${hashedPassword}`);
|
||||
$keyStore = await deriveKey(r);
|
||||
encryptData(
|
||||
JSON.stringify({
|
||||
_commitment: $identityStore._commitment,
|
||||
_nullifier: $identityStore._nullifier,
|
||||
_trapdoor: $identityStore._trapdoor,
|
||||
_secret: $identityStore._secret
|
||||
}),
|
||||
$keyStore
|
||||
).then((data) => {
|
||||
$identityKeyStore.identity = data;
|
||||
});
|
||||
$identityStore._commitment = '';
|
||||
$identityStore._nullifier = '';
|
||||
$identityStore._trapdoor = '';
|
||||
$identityStore._secret = '';
|
||||
console.debug(`Derived Key: ${$keyStore}`);
|
||||
}
|
||||
};
|
||||
modalStore.trigger(modal);
|
||||
@@ -57,9 +46,15 @@
|
||||
value: '',
|
||||
valueAttr: { type: 'password', minlength: 3, required: true },
|
||||
response: async (r: string) => {
|
||||
const hashedPassword = await hashPassword(r);
|
||||
$configStore.hashedPwd = hashedPassword;
|
||||
$keyStore = await deriveKey(r);
|
||||
if (r != 'false') {
|
||||
console.log('Prompt Response: ', r);
|
||||
const hashedPassword = await hashPassword(r);
|
||||
if ($configStore.hashedPwd == hashedPassword) {
|
||||
$keyStore = await deriveKey(r);
|
||||
} else {
|
||||
$keyStore = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
modalStore.trigger(modal);
|
||||
@@ -68,6 +63,13 @@
|
||||
function lock() {
|
||||
$keyStore = null;
|
||||
}
|
||||
onMount(() => {
|
||||
console.debug(
|
||||
'PasswordLock: ',
|
||||
$passwordSet ? 'password set,' : 'password not set,',
|
||||
$keyExists ? 'unlocked' : 'locked'
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class={cls} id="lock">
|
||||
|
||||
@@ -66,7 +66,8 @@ export async function encryptData(plainText: string, key: CryptoKey): Promise<st
|
||||
* @param {CryptoKey} key - The cryptographic key for decryption.
|
||||
* @returns {Promise<string>} - Returns the decrypted text as a Promise.
|
||||
*/
|
||||
export async function decryptData(encryptedData: string, key: CryptoKey): Promise<string> {
|
||||
export async function decryptData(encryptedData: string, key: CryptoKey): Promise<string | null> {
|
||||
key = key as CryptoKey;
|
||||
// Decode the base64 encrypted string
|
||||
const rawData = atob(encryptedData);
|
||||
|
||||
@@ -78,11 +79,20 @@ export async function decryptData(encryptedData: string, key: CryptoKey): Promis
|
||||
const iv = new TextEncoder().encode(rawIv);
|
||||
const encrypted = new TextEncoder().encode(rawEncryptedData).buffer;
|
||||
|
||||
// Decrypt the data
|
||||
const decrypted = await window.crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encrypted);
|
||||
try {
|
||||
if (!window) {
|
||||
throw new Error('Window not found');
|
||||
}
|
||||
|
||||
// Return the decrypted text
|
||||
return new TextDecoder().decode(decrypted);
|
||||
// Decrypt the data
|
||||
const decrypted = await window.crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encrypted);
|
||||
|
||||
// Return the decrypted text
|
||||
return new TextDecoder().decode(decrypted);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw new Error('Error decrypting data');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { storable, sessionable, encryptable } from './storeFactory';
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
import { derived, get, writable, type Readable, type Writable } from 'svelte/store';
|
||||
import { configDefaults } from '$lib/defaults';
|
||||
import type {
|
||||
ConfigurationI,
|
||||
@@ -12,9 +12,7 @@ import type {
|
||||
selectedRoomStoreI,
|
||||
serverStoreI,
|
||||
roomKeyStoreI,
|
||||
keyStoreI,
|
||||
DecryptedIdentityStoreI,
|
||||
CryptedIdentityStoreI
|
||||
keyStoreI
|
||||
} from '$lib/types';
|
||||
import { decryptData } from '$lib/crypto/crypto';
|
||||
|
||||
@@ -84,18 +82,6 @@ export const currentRoomMessages = derived(
|
||||
*/
|
||||
export const rateLimitStore = sessionable({} as rateLimitStoreI, 'rateLimitStore');
|
||||
|
||||
/* ------------------ Identity Stores ------------------*/
|
||||
/**
|
||||
* @description Identity store, this is the user's identity UNENCRYPTED
|
||||
* @deprecated Use identityKeyStore when you can, alert user to set password first
|
||||
*/
|
||||
export const identityStore = storable({} as IdentityStoreI, 'identity');
|
||||
|
||||
/**
|
||||
* @description Identity store, this is the user's identity ENCRYPTED
|
||||
*/
|
||||
export const identityKeyStore = encryptable({} as CryptedIdentityStoreI, 'identity');
|
||||
|
||||
/* ------------------ Configuration / Misc Stores ------------------*/
|
||||
|
||||
/**
|
||||
@@ -142,15 +128,45 @@ export const consoleStore = sessionable(
|
||||
'consoleStore'
|
||||
);
|
||||
|
||||
export const identityKeyStoreDecrypted = derived(identityKeyStore, ($identityKeyStore) => {
|
||||
if (keyExists) {
|
||||
try {
|
||||
decryptData($identityKeyStore.identity as string, get(keyStore)!).then((decrypted) => {
|
||||
return { identity: JSON.parse(decrypted) } as DecryptedIdentityStoreI;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Error decrypting identity: ${e}`);
|
||||
return { identity: {} as IdentityStoreI } as DecryptedIdentityStoreI;
|
||||
/* ------------------ Identity Stores ------------------*/
|
||||
/**
|
||||
* @description Identity store, this is the user's identity UNENCRYPTED
|
||||
* @deprecated Use identityKeyStore when you can, alert user to set password first
|
||||
*/
|
||||
export const identityStore = storable({} as IdentityStoreI, 'identity');
|
||||
|
||||
/**
|
||||
* @description Identity store, this is the user's identity ENCRYPTED
|
||||
*/
|
||||
export const identityKeyStore = encryptable('' as string, 'identity');
|
||||
|
||||
/**
|
||||
* @description Derived Store: The user's identity DECRYPTED in memory
|
||||
*/
|
||||
export const identityKeyStoreDecrypted: Readable<string | null | undefined> = derived(
|
||||
[identityKeyStore, keyStore, keyExists],
|
||||
([$identityKeyStore, $keyStore, $keyExists]) => {
|
||||
const key = $keyStore;
|
||||
if ($keyExists && key !== undefined && key !== null) {
|
||||
try {
|
||||
if (key !== undefined && key !== null) {
|
||||
try {
|
||||
decryptData($identityKeyStore as string, key).then((decrypted) => {
|
||||
if (decrypted !== null) {
|
||||
return JSON.parse(decrypted) as IdentityStoreI;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Error decrypting identity: ${e}`);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error decrypting identity: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
@@ -83,11 +83,14 @@ export function encryptable<Type>(data: Type, localStorageKey: string): Writable
|
||||
if (storedValue) {
|
||||
try {
|
||||
const key = get(keyStore);
|
||||
if (!key) {
|
||||
if (!key || typeof key !== CryptoKey) {
|
||||
throw new Error('Key store not initialized, cannot encrypt data');
|
||||
}
|
||||
console.debug('Encryptable State: ', storedValue, key);
|
||||
decryptData(storedValue, key).then((decryptedData) => {
|
||||
set(JSON.parse(decryptedData) as Type);
|
||||
if (decryptedData !== null) {
|
||||
set(JSON.parse(decryptedData) as Type);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`Error reading local storage for key: ${localStorageKey}; ${e}`);
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface ConfigurationI {
|
||||
experience?: Experiences;
|
||||
beta?: boolean;
|
||||
numMessagesToSave: number;
|
||||
hashedPwd: string;
|
||||
hashedPwd: string | null | undefined;
|
||||
}
|
||||
|
||||
export interface RoomI extends RI {
|
||||
|
||||
@@ -33,19 +33,6 @@ export interface IdentityStoreI {
|
||||
_secret: string;
|
||||
}
|
||||
|
||||
export type EncryptedIdentityStoreI = string;
|
||||
|
||||
export interface DecryptedIdentityStoreI {
|
||||
identity: {
|
||||
_commitment: string;
|
||||
_trapdoor: string;
|
||||
_nullifier: string;
|
||||
_secret: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type CryptedIdentityStoreI = EncryptedIdentityStoreI | DecryptedIdentityStoreI;
|
||||
|
||||
export interface rateLimitStoreI {
|
||||
[key: string]: {
|
||||
lastEpoch: number;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { consoleStoreI, consoleMessageI } from '$lib/types/';
|
||||
|
||||
export const addConsoleMessage = (
|
||||
message: string,
|
||||
type: 'info' | 'userinput' | 'error' | 'warning' | 'space'
|
||||
type: 'info' | 'userinput' | 'error' | 'warning' | 'space' = 'info'
|
||||
) => {
|
||||
let idx: number | undefined;
|
||||
consoleStore.update((state) => {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { get } from 'svelte/store';
|
||||
import { identityStore } from '../stores';
|
||||
import { identityKeyStore, identityKeyStoreDecrypted, identityStore, keyStore } from '../stores';
|
||||
import { Identity } from '@semaphore-protocol/identity';
|
||||
import type { IdentityStoreI } from '$lib/types';
|
||||
import { encryptData } from '$lib/crypto/crypto';
|
||||
|
||||
export function createIdentity(regenerate = false) {
|
||||
if (!get(identityStore)._commitment || regenerate) {
|
||||
console.debug('Creating identity');
|
||||
const id = new Identity() as unknown as IdentityStoreI;
|
||||
|
||||
identityStore.set(id);
|
||||
console.log('Identity Created! Congrats on your new journey');
|
||||
return 'created';
|
||||
@@ -16,8 +18,20 @@ export function createIdentity(regenerate = false) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getIdentity(): IdentityStoreI {
|
||||
return get(identityStore) as unknown as IdentityStoreI;
|
||||
export function getIdentity(): IdentityStoreI | null {
|
||||
const decryptedIdentity = get(identityKeyStoreDecrypted) as unknown as IdentityStoreI;
|
||||
if (decryptedIdentity !== null) {
|
||||
return decryptedIdentity;
|
||||
} else {
|
||||
const identity = get(identityStore);
|
||||
if (identity !== null) {
|
||||
console.warn('Identity not decrypted, please set a password!');
|
||||
return identity;
|
||||
} else {
|
||||
console.warn('Identity not created, please create an identity!');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getCommitment() {
|
||||
@@ -31,3 +45,26 @@ export function getIdentityBackup() {
|
||||
export function doesIdentityExist(): boolean {
|
||||
return !!get(identityStore)._commitment;
|
||||
}
|
||||
|
||||
export function encryptIdentity(identity: Identity) {
|
||||
const key = get(keyStore);
|
||||
if (key !== undefined && key !== null) {
|
||||
encryptData(
|
||||
JSON.stringify({
|
||||
_commitment: identity['_commitment'],
|
||||
_nullifier: identity['_nullifier'],
|
||||
_trapdoor: identity['_trapdoor'],
|
||||
_secret: identity['_secret']
|
||||
}),
|
||||
key
|
||||
).then((data) => {
|
||||
identityKeyStore.set(data);
|
||||
});
|
||||
identityStore.set({
|
||||
_commitment: '',
|
||||
_trapdoor: '',
|
||||
_nullifier: '',
|
||||
_secret: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
{:else}
|
||||
<a class="btn btn-sm variant-ringed-secondary" href="/signup">Sign Up</a>
|
||||
{/if}
|
||||
<a
|
||||
class="btn btn-sm variant-ringed-secondary font-medium text-sm hidden sm:inline me-2"
|
||||
href="/console">Console</a
|
||||
>
|
||||
<svelte:fragment slot="trail">
|
||||
<span class="inline sm:hidden me-2 text-primary-500">Alpha Version!</span>
|
||||
<a href="/about" class="hidden sm:inline me-2"><Information size="1.2em" /></a>
|
||||
|
||||
@@ -1,22 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { deriveKey, hashPassword } from '$lib/crypto/crypto';
|
||||
import { configStore, keyStore, passwordSet } from '$lib/stores';
|
||||
import { decryptData, deriveKey, hashPassword } from '$lib/crypto/crypto';
|
||||
import {
|
||||
configStore,
|
||||
identityKeyStore,
|
||||
identityKeyStoreDecrypted,
|
||||
keyExists,
|
||||
keyStore,
|
||||
passwordSet
|
||||
} from '$lib/stores';
|
||||
import { addConsoleMessage, clearConsoleMessages } from '$lib/utils/';
|
||||
import { inviteCode } from '$lib/utils/inviteCode';
|
||||
|
||||
function help() {
|
||||
addConsoleMessage('/clear Clears the console', 'info');
|
||||
addConsoleMessage(' ', 'space');
|
||||
addConsoleMessage('/join Joins a room via invite code, Example:', 'info');
|
||||
addConsoleMessage('/join apple-banana-candy-donut', 'info');
|
||||
addConsoleMessage('Commands: /clear, /join, /help, /unlock, /export');
|
||||
addConsoleMessage(' ', 'space');
|
||||
addConsoleMessage('Commands: /clear, /join, /help', 'info');
|
||||
addConsoleMessage('`/clear` Clears the console');
|
||||
addConsoleMessage(' ', 'space');
|
||||
addConsoleMessage('`/join invite-code` Joins a room via invite code, Example:');
|
||||
addConsoleMessage(' ', 'space');
|
||||
addConsoleMessage('`/password Password`');
|
||||
addConsoleMessage(' ', 'space');
|
||||
addConsoleMessage('`/clearPassword`');
|
||||
addConsoleMessage(' ', 'space');
|
||||
addConsoleMessage('`/unlock Password`');
|
||||
addConsoleMessage(' ', 'space');
|
||||
addConsoleMessage('`/lock`');
|
||||
addConsoleMessage(' ', 'space');
|
||||
addConsoleMessage('`/backup`');
|
||||
}
|
||||
|
||||
async function processCommand(command: string) {
|
||||
const [cmd, ...args] = command.split(' ');
|
||||
if (['/unlock', '/password'].includes(cmd)) {
|
||||
addConsoleMessage(cmd, 'userinput');
|
||||
} else {
|
||||
addConsoleMessage(command, 'userinput');
|
||||
}
|
||||
switch (cmd) {
|
||||
case '/help':
|
||||
case 'help' || '/help':
|
||||
help();
|
||||
break;
|
||||
case '/clear':
|
||||
@@ -30,7 +52,7 @@
|
||||
addConsoleMessage(err, 'error');
|
||||
console.log(err.toString());
|
||||
} else {
|
||||
addConsoleMessage(`Added to: ${acceptedRoomNames.join(', ')}`, 'info');
|
||||
addConsoleMessage(`Added to: ${acceptedRoomNames.join(', ')}`);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -43,16 +65,24 @@
|
||||
const hashedNewPwd = await hashPassword(args[1]);
|
||||
if (hashedOldPwd === $configStore.hashedPwd || $configStore.hashedPwd === undefined) {
|
||||
$configStore.hashedPwd = hashedNewPwd;
|
||||
addConsoleMessage('New Password Set', 'info');
|
||||
addConsoleMessage('New Password Set');
|
||||
} else {
|
||||
addConsoleMessage('Invalid Old Password', 'error');
|
||||
addConsoleMessage('/password OLDPASSWORD NEWPASSWORD', 'warning');
|
||||
}
|
||||
break;
|
||||
case '/clearPassword':
|
||||
$configStore.hashedPwd = null;
|
||||
addConsoleMessage('Password Cleared');
|
||||
break;
|
||||
case '/lock':
|
||||
$keyStore = null;
|
||||
addConsoleMessage('Locked!');
|
||||
break;
|
||||
case '/unlock':
|
||||
const hashedPwd = await hashPassword(args[0]);
|
||||
if (hashedPwd === $configStore.hashedPwd) {
|
||||
if ($keyStore && !(args[1] === 'force')) {
|
||||
if ($keyExists && $keyStore && !(args[1] === 'force')) {
|
||||
addConsoleMessage(
|
||||
'Already Unlocked! use `/unlock PASSWORD force` to override this and derive the key again',
|
||||
'warning'
|
||||
@@ -62,7 +92,7 @@
|
||||
deriveKey(args[0])
|
||||
.then((key) => {
|
||||
$keyStore = key;
|
||||
addConsoleMessage('Unlocked!', 'info');
|
||||
addConsoleMessage('Unlocked!');
|
||||
})
|
||||
.catch((err) => {
|
||||
addConsoleMessage(`Could NOT derive key from password: ${err}`, 'error');
|
||||
@@ -72,15 +102,22 @@
|
||||
}
|
||||
break;
|
||||
case '/export' || '/backup':
|
||||
addConsoleMessage('Exporting Identity', 'info');
|
||||
addConsoleMessage('Exporting Identity');
|
||||
if ($passwordSet) {
|
||||
const identity = await decryptData($identityKeyStore, $keyStore);
|
||||
const blob = new Blob([identity], { type: 'text/plain;charset=utf-8' });
|
||||
saveAs(blob, 'identity.json');
|
||||
addConsoleMessage('Identity Exported', 'info');
|
||||
if ($keyExists && $keyStore) {
|
||||
addConsoleMessage('Decrypting Data');
|
||||
console.log($identityKeyStore, $keyStore);
|
||||
const identity = $identityKeyStoreDecrypted;
|
||||
if (identity) {
|
||||
addConsoleMessage(identity);
|
||||
}
|
||||
} else {
|
||||
addConsoleMessage('Please Unlock Keystore!', 'error');
|
||||
}
|
||||
} else {
|
||||
addConsoleMessage('Password not set!', 'error');
|
||||
}
|
||||
addConsoleMessage('Exporting Identity Complete');
|
||||
break;
|
||||
default:
|
||||
help();
|
||||
@@ -92,7 +129,6 @@
|
||||
if (['Enter'].includes(event.key)) {
|
||||
if ((event.currentTarget as HTMLInputElement).value) {
|
||||
const value = (event.currentTarget as HTMLInputElement).value;
|
||||
addConsoleMessage(value, 'userinput');
|
||||
if (value.startsWith('/')) {
|
||||
processCommand(value);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import ArrowRight from 'svelte-material-icons/ArrowRightBold.svelte';
|
||||
import Text from 'svelte-material-icons/TextBox.svelte';
|
||||
import Account from 'svelte-material-icons/AccountHardhat.svelte';
|
||||
import Account from 'svelte-material-icons/AccountHardHat.svelte';
|
||||
import { inviteCode } from '$lib/utils/inviteCode';
|
||||
import { addConsoleMessage } from '$lib/utils/console';
|
||||
import Console from '../console/ConsoleInputPrompt.svelte';
|
||||
@@ -13,9 +13,9 @@
|
||||
function identity() {
|
||||
const idStatus = createIdentity();
|
||||
if (idStatus == 'created') {
|
||||
addConsoleMessage('Identity Generated 🎉', 'info');
|
||||
addConsoleMessage('Identity Generated 🎉');
|
||||
} else if (idStatus == 'exists') {
|
||||
addConsoleMessage('Identity Exists Already ✅', 'info');
|
||||
addConsoleMessage('Identity Exists Already ✅');
|
||||
} else {
|
||||
addConsoleMessage('Error Creating Identity ❌', 'error');
|
||||
}
|
||||
@@ -30,9 +30,9 @@
|
||||
} else {
|
||||
let { acceptedRoomNames, err } = await inviteCode($configStore.signUpStatus.inviteCode);
|
||||
if (acceptedRoomNames) {
|
||||
addConsoleMessage('Invite Code Accepted 🎉', 'info');
|
||||
addConsoleMessage('Invite Code Accepted 🎉');
|
||||
acceptedRoomNames.forEach((roomName) => {
|
||||
addConsoleMessage(`Joined Room: ${roomName}`, 'info');
|
||||
addConsoleMessage(`Joined Room: ${roomName}`);
|
||||
});
|
||||
}
|
||||
if (err) {
|
||||
@@ -43,9 +43,9 @@
|
||||
|
||||
onMount(() => {
|
||||
if (identityExists) {
|
||||
addConsoleMessage('Identity Exists Already ✅', 'info');
|
||||
addConsoleMessage('Identity Exists Already ✅');
|
||||
} else {
|
||||
addConsoleMessage('Creating Identity...', 'info');
|
||||
addConsoleMessage('Creating Identity...');
|
||||
identity();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user