add password to frontend

This commit is contained in:
Niccolo Borgioli 2023-05-23 09:39:19 +02:00
parent 6da28a701e
commit fdc2722fb9
No known key found for this signature in database
GPG Key ID: D93C615F75EE4F0B
8 changed files with 124 additions and 70 deletions

View File

@ -92,7 +92,7 @@ button {
}
*:disabled,
*[disabled='true'] {
.disabled {
opacity: 0.5;
}
@ -126,3 +126,13 @@ fieldset {
.tr {
text-align: right;
}
hr {
border: none;
border-bottom: 2px solid var(--ui-bg-1);
margin: 1rem 0;
}
p {
margin: 0;
}

View File

@ -8,48 +8,69 @@
export let note: Note
export let timeExpiration = false
let customPassword = false
$: if (!customPassword) note.password = undefined
</script>
<div class="fields">
<TextInput
data-testid="field-views"
type="number"
label={$t('common.views', { values: { n: 0 } })}
bind:value={note.views}
disabled={timeExpiration}
max={$status?.max_views}
min={1}
validate={(v) =>
($status && v <= $status?.max_views && v > 0) ||
$t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })}
/>
<div class="middle-switch">
<div class="flex col">
<div class="flex">
<TextInput
data-testid="field-views"
type="number"
label={$t('common.views', { values: { n: 0 } })}
bind:value={note.views}
disabled={timeExpiration}
max={$status?.max_views}
min={1}
validate={(v) =>
($status && v <= $status?.max_views && v > 0) ||
$t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })}
/>
<Switch
data-testid="switch-advanced-toggle"
label={$t('common.mode')}
bind:value={timeExpiration}
color={false}
/>
<TextInput
data-testid="field-expiration"
type="number"
label={$t('common.minutes', { values: { n: 0 } })}
bind:value={note.expiration}
disabled={!timeExpiration}
max={$status?.max_expiration}
validate={(v) =>
($status && v < $status?.max_expiration) ||
$t('home.errors.max', { values: { n: $status?.max_expiration ?? 0 } })}
/>
</div>
<div class="flex">
<Switch bind:value={customPassword} label={$t('home.advanced.custom_password')} />
<TextInput
type="password"
bind:value={note.password}
label={$t('common.password')}
disabled={!customPassword}
random
/>
</div>
<div>
{$t('home.advanced.explanation')}
</div>
<TextInput
data-testid="field-expiration"
type="number"
label={$t('common.minutes', { values: { n: 0 } })}
bind:value={note.expiration}
disabled={!timeExpiration}
max={$status?.max_expiration}
validate={(v) =>
($status && v < $status?.max_expiration) ||
$t('home.errors.max', { values: { n: $status?.max_expiration ?? 0 } })}
/>
</div>
<style>
.middle-switch {
margin: 0 1rem;
.flex {
display: flex;
align-items: flex-end;
gap: 1rem;
width: 100%;
}
.fields {
display: flex;
.col {
gap: 1.5rem;
flex-direction: column;
}
</style>

View File

@ -1,7 +1,7 @@
<script lang="ts" context="module">
export type NoteResult = {
password: string
id: string
password?: string
}
</script>
@ -14,7 +14,8 @@
export let result: NoteResult
$: url = `${window.location.origin}/note/${result.id}#${result.password}`
let url = `${window.location.origin}/note/${result.id}`
if (result.password) url += `#${result.password}`
function reset() {
window.location.reload()

View File

@ -4,43 +4,35 @@
export let color = true
</script>
<div {...$$restProps}>
<label class="switch">
<small>{label}</small>
<input type="checkbox" bind:checked={value} />
<span class:color class="slider" />
</label>
</div>
<label {...$$restProps}>
<small>{label}</small>
<input type="checkbox" bind:checked={value} />
<span class:color class="slider" />
</label>
<style>
div {
height: 3.75rem;
}
.switch {
label {
position: relative;
display: inline-block;
width: 4rem;
height: 2.5rem;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
label input {
display: none;
}
small {
display: block;
width: max-content;
}
.slider {
position: absolute;
display: block;
width: 4rem;
height: 2.5rem;
position: relative;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 2px solid var(--ui-bg-1);
background-color: var(--ui-bg-0);
transition: var(--ui-anim);
transform: translateY(1.2rem);
}
.slider:before {

View File

@ -30,7 +30,7 @@
</script>
<label>
<small disabled={$$restProps.disabled}>
<small class:disabled={$$restProps.disabled}>
{label}
{#if valid !== true}
<span class="error-text">{valid}</span>
@ -54,6 +54,7 @@
label {
position: relative;
display: block;
width: 100%;
}
label > small {

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { AES, Hex } from 'occulto'
import { AES, Hex, Bytes } from 'occulto'
import { t } from 'svelte-intl-precompile'
import { blur } from 'svelte/transition'
@ -57,13 +57,14 @@
try {
loading = $t('common.encrypting')
const key = await AES.generateKey()
const password = Hex.encode(key)
const derived = note.password && (await AES.derive(note.password))
const key = derived ? derived[0] : await AES.generateKey()
const data: Note = {
contents: '',
meta: note.meta,
}
if (derived) data.meta.derivation = derived[1]
if (isFile) {
if (files.length === 0) throw new EmptyContentError()
data.contents = await Adapters.Files.encrypt(files, key)
@ -77,8 +78,8 @@
loading = $t('common.uploading')
const response = await create(data)
result = {
password: password,
id: response.id,
password: note.password ? undefined : Hex.encode(key),
}
notify.success($t('home.messages.note_created'))
} catch (e) {
@ -148,7 +149,7 @@
{#if advanced}
<div transition:blur={{ duration: 250 }}>
<br />
<hr />
<AdvancedParameters bind:note bind:timeExpiration />
</div>
{/if}

View File

@ -23,6 +23,7 @@
right: 0;
width: 100%;
background-color: var(--ui-bg-0-85);
backdrop-filter: blur(2px);
}
a {

View File

@ -1,30 +1,35 @@
<script lang="ts">
import { Hex } from 'occulto'
import { AES, Hex } from 'occulto'
import { onMount } from 'svelte'
import { t } from 'svelte-intl-precompile'
import Button from '$lib/ui/Button.svelte'
import Loader from '$lib/ui/Loader.svelte'
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
import { Adapters, get, info } from '@cryptgeon/shared'
import TextInput from '$lib/ui/TextInput.svelte'
import { Adapters, get, info, type NoteMeta } from '@cryptgeon/shared'
import type { PageData } from './$types'
export let data: PageData
let id = data.id
let password: string
let password: string | null = null
let note: DecryptedNote | null = null
let exists = false
let meta: NoteMeta | null = null
let loading: string | null = null
let error: string | null = null
$: valid = !!password?.length
onMount(async () => {
// Check if note exists
try {
loading = $t('common.loading')
password = window.location.hash.slice(1)
await info(id)
const note = await info(id)
meta = note.meta
exists = true
} catch {
exists = false
@ -38,11 +43,18 @@
*/
async function show() {
try {
if (!valid) {
error = $t('show.errors.no_password')
return
}
// Load note
error = null
loading = $t('common.downloading')
const data = await get(id)
loading = $t('common.decrypting')
const key = Hex.decode(password)
const derived = meta?.derivation && (await AES.derive(password!, meta.derivation))
const key = derived ? derived[0] : Hex.decode(password!)
switch (data.meta.type) {
case 'text':
note = {
@ -77,9 +89,18 @@
<form on:submit|preventDefault={show}>
<fieldset>
<p>{$t('show.explanation')}</p>
<Button data-testid="show-note-button" type="submit">{$t('show.show_note')}</Button>
{#if meta?.derivation}
<TextInput
data-testid="show-note-password"
type="password"
bind:value={password}
label={$t('common.password')}
/>
{/if}
<Button disabled={!valid} data-testid="show-note-button" type="submit"
>{$t('show.show_note')}</Button
>
{#if error}
<br />
<p class="error-text">
{error}
<br />
@ -97,4 +118,10 @@
.loader {
text-align: center;
}
fieldset {
display: flex;
flex-direction: column;
gap: 1rem;
}
</style>