checkpoint: reorg lib components, saltStore, new Onboarding, sidebar/bottombar

This commit is contained in:
2023-10-23 17:15:33 -04:00
parent f5a3a00181
commit cd2032227c
37 changed files with 255 additions and 139 deletions

View File

@@ -4,7 +4,7 @@
import { ActionRepresentationE } from '$lib/types';
import Shields from '$lib/components/ActionPoints/Shields.svelte';
import { configStore } from '$lib/stores';
import Battery from './ActionPoints/Battery.svelte';
import Battery from './Battery.svelte';
export let health: number;
export let maxHealth: number;
</script>

View File

@@ -2,6 +2,8 @@
import { inviteCode } from '$lib/gateways/inviteCode';
import { alertQueue } from '$lib/stores';
export let code = '';
export let buttonText = 'Submit';
export let hideInput = false;
let acceptedRoomNames: string[] = [];
let loading = false;
@@ -79,23 +81,25 @@
}
</script>
<label class="label" for="inviteCode">
<span class="h5">Enter Invite Code:</span>
<input
class="input"
type="text"
placeholder="Invite Code"
id="inviteCode"
bind:value={code}
on:keydown={(event) => inviteCodeKeyPress(event)}
/>
</label>
{#if !hideInput}
<label class="label" for="inviteCode">
<span class="h5">Enter Invite Code:</span>
<input
class="input"
type="text"
placeholder="Invite Code"
id="inviteCode"
bind:value={code}
on:keydown={(event) => inviteCodeKeyPress(event)}
/>
</label>
{/if}
{#if !loading}
<button
class="btn variant-ghost-success mt-3"
type="button"
disabled={!code}
on:click={() => addCode(code)}>Submit</button
on:click={() => addCode(code)}>{buttonText}</button
>
{:else}
<p class="italic">Loading...</p>

View File

@@ -96,6 +96,7 @@
<header class="card-header">
<h3 class="h4">Restore Your Identity</h3>
</header>
<section class="px-4 pt-4">
<label class="label w-full pb-1">
<span class="h5"> Recover From File:</span>
@@ -113,7 +114,7 @@
<textarea
name="textarea"
id="jsonRecovery"
class="mb-2 p-2 rounded-token"
class="mb-2 p-2 rounded-token max-h-24"
cols="30"
rows="10"
placeholder="Paste your Identity Here"

View File

@@ -1,9 +1,8 @@
<script lang="ts">
import Container from '$lib/components/Container.svelte';
import EthereumGroupGateway from '$lib/components/Gateways/EthereumGroup.svelte';
import InviteCodeGateway from '$lib/components/Gateways/InviteCode.svelte';
import SelectServer from '$lib/components/SelectServer.svelte';
import Card from '$lib/components/card.svelte';
import SelectServer from '$lib/components/Server/SelectServer.svelte';
import Card from '$lib/components/Utils/Card.svelte';
import { configStore, serverStore } from '$lib/stores';
</script>
@@ -19,6 +18,14 @@
<svelte:fragment slot="description">If you were given an invite code, you can</svelte:fragment>
<InviteCodeGateway code={$configStore.signUpStatus.inviteCode} />
</Card>
<Card>
<svelte:fragment slot="header">Join the alpha testing room:</svelte:fragment>
<InviteCodeGateway
code={'layer-spot-gravity-fossil'}
hideInput={true}
buttonText="Join Alpha Testers"
/>
</Card>
<Card>
<svelte:fragment slot="header">Discord Bot</svelte:fragment>
<svelte:fragment slot="description">Add the discord bot to your server Today!</svelte:fragment>
@@ -34,8 +41,7 @@
<Card>
<svelte:fragment slot="header">Join via Ethereum Address:</svelte:fragment>
<svelte:fragment slot="description"
>Are you a genesis validator, daohack survivor, nouns holder? You can join using your Ethereum
address.
>Are you a genesis validator? You can join using your Ethereum address.
</svelte:fragment>
<EthereumGroupGateway />
</Card>

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import Button from '$lib/components/button.svelte';
import Button from '$lib/components/Utils/Button.svelte';
import { identityExists } from '$lib/stores';
</script>

View File

@@ -0,0 +1,18 @@
<script lang="ts">
import SetPasswordInput from './SetPasswordInput.svelte';
import Container from '../Utils/Container.svelte';
</script>
<Container heading="Set A Password">
<p class="mt-3">
In order to secure your identity and other sensitive data that is stored in your browser, we
need you to set a password.
</p>
<p class="my-3">
This password is only used locally, there is no username and password for your account, so don't
forget to <a class="link" href="/settings/identity/backup" title="Backup Identity"
>backup your identity</a
>.
</p>
<SetPasswordInput />
</Container>

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import { setPassword } from '$lib/utils';
const minPasswordLength = 4;
let r = '';
function onSubmit() {
if (r != '' && r != null && r != undefined && r.length >= minPasswordLength) {
setPassword(r);
}
}
</script>
<form on:submit|preventDefault={() => onSubmit()} class="flex flex-col w-full">
<label for="setPasswordInput" class="label" />
<input
id="setPasswordInput"
type="password"
name="password"
inputmode="numeric"
class="input"
bind:value={r}
minlength={minPasswordLength}
required
/>
<small>Minimum length: {minPasswordLength}</small>
<button
class="btn variant-filled-primary mt-3"
disabled={r.length < minPasswordLength}
type="submit">Set Unlock Code</button
>
</form>

View File

@@ -1,6 +1,38 @@
import { alertQueue, configStore } from '$lib/stores';
import { alertQueue, configStore, saltStore } from '$lib/stores';
import { get } from 'svelte/store';
/**
* Retrieves a salt value. If a salt is not present in the store,
* it generates a new 16-byte salt using the window.crypto API,
* converts it to a hexadecimal string, and stores it. If a salt
* is present in the store, it converts the stored hexadecimal
* string back to a Uint8Array.
*
* @returns {Uint8Array} A 16-byte salt value.
*/
function getSalt(): Uint8Array {
const salt: Uint8Array = new Uint8Array(16);
const saltFromStore = get(saltStore);
// Generate new salt if salt is not set
if (saltFromStore === '') {
window.crypto.getRandomValues(salt);
// Convert to hexadecimal string
const saltString = Array.from(salt)
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
saltStore.set(saltString);
} else {
for (let i = 0; i < salt.length; i++) {
salt[i] = parseInt(saltFromStore.substring(i * 2, i * 2 + 2), 16);
}
}
return salt;
}
/**
* Derives an encryption key from a given password using PBKDF2.
* The key is derived on password entry and stays in memory until page refresh
@@ -11,8 +43,8 @@ export async function deriveKey(password: string): Promise<CryptoKey> {
// TextEncoder will be used for converting strings to byte arrays
const textEncoder = new TextEncoder();
// Salt for PBKDF2. 420+69+a bunch of randomness from my laptop
const salt = textEncoder.encode('42069210482402528527906392650230853');
// Salt for PBKDF2 stored in local storage
const salt = getSalt();
// Importing the password as a cryptographic key
const passwordKey = await window.crypto.subtle.importKey(

View File

@@ -81,6 +81,11 @@ export const currentRoomMessages = derived(
*/
export const rateLimitStore = storable({} as rateLimitStoreI, 'rateLimit');
/**
* @description This stores the salt used to derive the encryption key from the user's password
*/
export const saltStore = storable('', 'salt');
/* ------------------ Configuration / Misc Stores ------------------*/
/**

View File

@@ -31,8 +31,14 @@ export async function setPassword(password: string): Promise<'success' | string>
console.error(`Error decrypting: ${e}`);
return 'Error decrypting data while setting new password';
}
/******************************
* STAGE2: ENCRYPT EVERYTHING
* STAGE2: Derive and set new Key
* ******************************/
keyStore.set(await deriveKey(password));
/******************************
* STAGE3: ENCRYPT EVERYTHING
******************************/
try {
if (identity) {
@@ -43,16 +49,13 @@ export async function setPassword(password: string): Promise<'success' | string>
return 'Error encrypting data while setting new password';
}
/******************************
* STAGE3: SET PASSWORD HASH
* STAGE4: SET PASSWORD HASH
******************************/
configStore.update((config) => {
config.hashedPwd = hashedPassword;
return config;
});
/******************************
* STAGE4: Derive and set new Key
* ******************************/
keyStore.set(await deriveKey(password));
return 'success';
}
return 'error';

View File

@@ -5,13 +5,13 @@
import '../app.postcss';
import { onMount } from 'svelte';
import AppHeader from './AppHeader.svelte';
import Loading from '$lib/components/loading.svelte';
import Loading from '$lib/components/Utils/Loading.svelte';
import { selectedServer, alertQueue } from '$lib/stores';
import { getServerList, isInputFieldFocused, setDefaultServers } from '$lib/utils/';
import { updateServer } from '$lib/utils/';
import { Drawer, getDrawerStore } from '@skeletonlabs/skeleton';
import SelectServer from '$lib/components/SelectServer.svelte';
import SelectRoom from '$lib/components/SelectRoom.svelte';
import SelectServer from '$lib/components/Server/SelectServer.svelte';
import SelectRoom from '$lib/components/Server/SelectRoom.svelte';
import Console from './console/Console.svelte';
import Sidebar from './Sidebar.svelte';
import AppFooter from './Footer.svelte';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import Welcome from '$lib/components/Welcome.svelte';
import Welcome from '$lib/components/Onboarding/Welcome.svelte';
console.info(
'I see you are checking out the logs, let us know what you think on our discord: https://discord.gg/brJQ36KVxk'

View File

@@ -1,6 +1,6 @@
<script>
import Demo from '$lib/components/Demo.svelte';
import Introduction from '$lib/components/Introduction.svelte';
import Demo from '$lib/components/Onboarding/Demo.svelte';
import Introduction from '$lib/components/Onboarding/Introduction.svelte';
</script>
<div class="mx-auto mt-10 max-w-[80ch]">

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import AP from '$lib/components/AP.svelte';
import Clock from '$lib/components/Clock.svelte';
import AP from '$lib/components/ActionPoints/AP.svelte';
import Clock from '$lib/components/Utils/Clock.svelte';
import { currentSelectedRoom, configStore } from '$lib/stores';
import { ProgressBar } from '@skeletonlabs/skeleton';
import FullCircle from 'svelte-material-icons/Circle.svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import SelectRoom from '$lib/components/SelectRoom.svelte';
import SelectServer from '$lib/components/SelectServer.svelte';
import SelectRoom from '$lib/components/Server/SelectRoom.svelte';
import SelectServer from '$lib/components/Server/SelectServer.svelte';
</script>
<div id="sidebar" class="hidden sm:grid grid-rows-[auto_1fr_auto] border-r border-surface-500/30">

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import Gateways from './Gateways.svelte';
import Gateways from '$lib/components/Onboarding/Gateways.svelte';
import Container from '$lib/components/Container.svelte';
import Container from '$lib/components/Utils/Container.svelte';
</script>
<Container heading="Join More Communities">

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import DeleteIdentity from './identity/DeleteIdentity.svelte';
import BackupIdentity from './identity/BackupIdentity.svelte';
import RestoreIdentity from './identity/RestoreIdentity.svelte';
import DeleteIdentity from '$lib/components/Identity/DeleteIdentity.svelte';
import BackupIdentity from './identity/BackupIdentityWrapper.svelte';
import RestoreIdentity from '$lib/components/Identity/RestoreIdentity.svelte';
import { createIdentity } from '$lib/utils/';
import ActionRepresentation from './ui/ActionRepresentation.svelte';
import IdentityIcon from 'svelte-material-icons/Account.svelte';
import Eye from 'svelte-material-icons/Eye.svelte';
import Container from '../../lib/components/Container.svelte';
import Container from '$lib/components/Utils/Container.svelte';
import { identityExists } from '$lib/stores';
</script>

View File

@@ -1,8 +1,8 @@
<script>
import Container from '../../../lib/components/Container.svelte';
import BackupIdentity from './BackupIdentity.svelte';
import DeleteIdentity from './DeleteIdentity.svelte';
import RestoreIdentity from './RestoreIdentity.svelte';
import Container from '$lib/components/Utils/Container.svelte';
import BackupIdentity from './BackupIdentityWrapper.svelte';
import DeleteIdentity from '$lib/components/Identity/DeleteIdentity.svelte';
import RestoreIdentity from '$lib/components/Identity/RestoreIdentity.svelte';
</script>
<Container heading="Manage Your Identity">

View File

@@ -1,5 +1,5 @@
<script>
import BackupIdentity from '$lib/components/BackupIdentity.svelte';
import BackupIdentity from '$lib/components/Identity/BackupIdentity.svelte';
</script>
<div class="card variant-ghost-secondary">

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import BackupIdentity from '../BackupIdentity.svelte';
import BackupIdentity from '../BackupIdentityWrapper.svelte';
</script>
<BackupIdentity />

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import RestoreIdentity from '../RestoreIdentity.svelte';
import RestoreIdentity from '$lib/components/Identity/RestoreIdentity.svelte';
</script>
<RestoreIdentity />

View File

@@ -1,49 +1,12 @@
<script lang="ts">
import Container from '../../../lib/components/Container.svelte';
import { setPassword } from '$lib/utils';
import SetPassword from '$lib/components/Security/SetPasswordContainer.svelte';
import Container from '$lib/components/Utils/Container.svelte';
import { passwordSet } from '$lib/stores';
const minPasswordLength = 4;
let r = '';
function onSubmit() {
if (r != '' && r != null && r != undefined && r.length >= minPasswordLength) {
setPassword(r);
}
}
</script>
{#if !$passwordSet}
<Container heading="Set A Password">
<p class="mt-3">
In order to secure your identity and other sensitive data that is stored in your browser, we
need you to set a password.
</p>
<p class="my-3">
This password is only used locally, there is no username and password for your account, so
don't forget to <a class="link" href="/settings/identity/backup" title="Backup Identity"
>backup your identity</a
>.
</p>
<form on:submit|preventDefault={() => onSubmit()} class="flex flex-col w-full">
<label for="setPasswordInput" class="label" />
<input
id="setPasswordInput"
type="password"
name="password"
class="input"
bind:value={r}
minlength={minPasswordLength}
required
/>
<small>Minimum password length: {minPasswordLength}</small>
<button
class="btn variant-filled-primary mt-3"
disabled={r.length < minPasswordLength}
type="submit">Set Password</button
>
</form>
</Container>
<SetPassword />
{:else}
<Container heading="Password set" />
{/if}

View File

@@ -1,5 +1,5 @@
<script>
import Container from '../../../lib/components/Container.svelte';
import Container from '$lib/components/Utils/Container.svelte';
import ActionRepresentation from './ActionRepresentation.svelte';
</script>

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import AP from '$lib/components/AP.svelte';
import AP from '$lib/components/ActionPoints/AP.svelte';
import { configStore } from '$lib/stores';
import { ActionRepresentationE } from '$lib/types';
import { RangeSlider } from '@skeletonlabs/skeleton';

View File

@@ -1,11 +1,34 @@
<script lang="ts">
import { configStore, identityExists } from '$lib/stores';
import { configStore, identityExists, passwordSet } from '$lib/stores';
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 { inviteCode } from '$lib/gateways/inviteCode';
import { addConsoleMessage } from '$lib/utils/console';
import { goto } from '$app/navigation';
import { Stepper, Step, getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
import { createIdentity, addConsoleMessage, unlockPadlock } from '$lib/utils';
import Lock from 'svelte-material-icons/Lock.svelte';
import Introduction from '$lib/components/Onboarding/Introduction.svelte';
import SetPassword from '$lib/components/Security/SetPasswordInput.svelte';
import RestoreIdentity from '$lib/components/Identity/RestoreIdentity.svelte';
import Backup from '$lib/components/Identity/BackupIdentity.svelte';
import Gateways from '$lib/components/Onboarding/Gateways.svelte';
const modalStore = getModalStore();
function unlock() {
const modal: ModalSettings = {
type: 'prompt',
title: 'Unlock',
body: 'Enter your password to unlock your keystores',
value: '',
valueAttr: { type: 'password', minlength: 4, required: true },
response: async (r: string) => {
if (r != 'false' && r != '' && r != null && r != undefined) {
unlockPadlock(r);
}
}
};
modalStore.trigger(modal);
}
function checkForIdentity() {
const idStatus = $identityExists;
@@ -20,43 +43,72 @@
}
}
async function code() {
if (!$configStore.signUpStatus.inviteCode) {
addConsoleMessage(
'No Invite Code Provided ❌ please use `/join INVITE-CODE` to join via invite code',
'warning'
);
} else {
let { acceptedRoomNames, err } = await inviteCode($configStore.signUpStatus.inviteCode);
if (acceptedRoomNames) {
addConsoleMessage('Invite Code Accepted 🎉');
acceptedRoomNames.forEach((roomName) => {
addConsoleMessage(`Joined Room: ${roomName}`);
});
}
if (err) {
addConsoleMessage(`Invite Code Error: ${err}`, 'error');
}
}
}
onMount(() => {
addConsoleMessage('👋 Welcome to Discreetly');
checkForIdentity();
code();
});
</script>
<div>
<a href="/settings" class="btn btn-sm variant-ghost-primary">
<span>Restore Identity</span>
<Account />
</a>
<a href="/about" class="btn btn-sm variant-ghost-secondary ms-2 mt-2">
<span>Read More</span>
<Text />
</a>
<a href="/chat" class="btn btn-sm variant-ghost-success ms-2 mt-2">
<span>Continue</span>
<ArrowRight />
</a>
</div>
<Stepper
class="max-w-sm sm:max-w-md md:max-w-3xl mx-auto mt-16"
on:complete={() => {
goto('/chat');
}}
buttonCompleteLabel="Lets Go Chat Anon"
buttonNext="variant-filled-surface-50-900-token"
buttonComplete="variant-filled-success"
>
<Step class="px-10">
<svelte:fragment slot="header"
><h2 class="h2 text-center">Welcome to Discreetly</h2>
</svelte:fragment>
<Introduction />
</Step>
<Step locked={!$passwordSet}>
<svelte:fragment slot="header">
{#if !$passwordSet}
<h2 class="h2 text-center">Set Unlock Code</h2>
{:else}
<h2 class="h2 text-center">Unlock Code has been set ✅</h2>
{/if}
</svelte:fragment>
{#if !$passwordSet}
<SetPassword />
{:else}
<p class="h4 text-center mt-5">Press next to continue, you are almost done</p>
{/if}
</Step>
<Step locked={$identityExists !== 'safe'}>
<svelte:fragment slot="header">
{#if !$identityExists}
<h2 class="h2 text-center">Generate or Restore an Identity</h2>
{:else if $identityExists == 'encrypted'}
<h2 class="h2 text-center">Identity Wallet Locked</h2>
{:else}
<h2 class="h2 text-center">Identity Created ✅</h2>
{/if}
</svelte:fragment>
{#if $identityExists == null}
<button on:click={() => createIdentity()} class="btn variant-ghost-success" type="button">
Generate Identity
</button>
<RestoreIdentity />
{:else if $identityExists == 'encrypted'}
<div on:click={unlock}>
<p class="h4 text-center">Please unlock your wallet</p>
<Lock class="h3 m-auto" />
</div>
{:else}
<p class="h4 text-center mt-5">
Backup your identity and then press next to continue, last step
</p>
<Backup />
{/if}
</Step>
<Step>
<svelte:fragment slot="header"
><div class="h3 text-center">Join Communities</div></svelte:fragment
>
<Gateways />
</Step>
</Stepper>

View File

@@ -1,14 +1,14 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { configStore, identityStore } from '$lib/stores';
import { configStore, identityExists } from '$lib/stores';
import { onMount } from 'svelte';
$configStore.signUpStatus.inviteCode = $page.params.invite;
console.log(`Invited with code: ${$page.params.invite}`);
$: identityExists = !!$identityStore._commitment;
onMount(() => {
if (identityExists) {
if ($identityExists) {
goto('/settings#join-more');
}
});