mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-04 00:47:01 +01:00
vault: files sealed under a password that is never stored
Argon2id turns the vault password into a key that seals a random master key; only the sealed copy is kept. Each file gets its own key, sealed under the master key in its header with its media id, and its bytes are AES-256-GCM STREAM records, so a swapped, cut or altered file refuses to open. Uploads with ?encrypt=true are sealed as they stream in. :vault, :unlock, :lock, :passwd, :encrypt, :decrypt and :upload! drive it. The vault locks after WORKSPACE_VAULT_IDLE_MIN idle minutes, on logout and on restart; while locked, sealed files answer 423, share links never serve them, and :export! refuses rather than leave them out. Sealing an existing file rewrites it under a new id and retires the old one: the mirror deletes the plaintext from Proton Drive outright, trash included, whatever WORKSPACE_BACKUP_PRUNE says, and drops it from the readable files/ view. The mirror now compares on-disk sizes, so sealed files are not re-hashed every run.
This commit is contained in:
1 parent
364ba61963
commit
a641d9b6ba
33 files changed
+1573
-54
No files matched your search
@@ -6,6 +6,8 @@
|
||||
|
||||
const node = $derived(workspace.open!)
|
||||
const file = $derived(node.file!)
|
||||
/* A sealed file cannot be fetched while the vault is shut. */
|
||||
const locked = $derived(file.encrypted && !workspace.vault?.unlocked)
|
||||
const kind = $derived(file.mime.split('/')[0])
|
||||
/* What the browser can show by itself; everything else is a download. */
|
||||
const viewable = $derived(file.mime !== 'application/octet-stream')
|
||||
@@ -18,7 +20,7 @@
|
||||
let text = $state<string | null>(null)
|
||||
$effect(() => {
|
||||
text = null
|
||||
if (file.mime !== 'text/plain') return
|
||||
if (file.mime !== 'text/plain' || locked) return
|
||||
const url = file.url
|
||||
void head(url, TEXT_PREVIEW).then((body) => {
|
||||
if (body !== null && url === file.url) text = body
|
||||
@@ -77,13 +79,24 @@
|
||||
<dd>{file.mime}</dd>
|
||||
<dt>uploaded</dt>
|
||||
<dd title={formatStamp(node.created_at)}>{relative(node.created_at)}</dd>
|
||||
{#if file.encrypted}
|
||||
<dt>vault</dt>
|
||||
<dd class="light">{locked ? 'sealed, locked' : 'sealed, open'}</dd>
|
||||
{/if}
|
||||
</dl>
|
||||
|
||||
<div class="actions">
|
||||
{#if viewable}
|
||||
<a class="button" href={file.url} target="_blank" rel="noopener">open</a>
|
||||
{#if locked}
|
||||
<button onclick={() => runCommand('unlock')}>unlock</button>
|
||||
{:else}
|
||||
{#if viewable}
|
||||
<a class="button" href={file.url} target="_blank" rel="noopener">open</a>
|
||||
{/if}
|
||||
<a class="button" href={file.url} download={node.name}>download</a>
|
||||
{/if}
|
||||
<a class="button" href={file.url} download={node.name}>download</a>
|
||||
<button onclick={() => runCommand(file.encrypted ? 'decrypt' : 'encrypt')}>
|
||||
{file.encrypted ? 'decrypt' : 'encrypt'}
|
||||
</button>
|
||||
<button class="danger" onclick={() => runCommand('d')}>delete</button>
|
||||
<button class="close" onclick={() => runCommand('q')}>close</button>
|
||||
</div>
|
||||
@@ -92,8 +105,13 @@
|
||||
|
||||
<div class="body" bind:this={reader}>
|
||||
<h1 class="name">{node.name}</h1>
|
||||
{#key file.url}
|
||||
{#if kind === 'image'}
|
||||
{#key `${file.url}:${locked}`}
|
||||
{#if locked}
|
||||
<p class="sealed">
|
||||
<Icon name="lock" size={16} />
|
||||
sealed in the vault — <button onclick={() => runCommand('unlock')}>unlock</button> to open it.
|
||||
</p>
|
||||
{:else if kind === 'image'}
|
||||
<img src={file.url} alt={node.name} />
|
||||
{:else if kind === 'video'}
|
||||
<!-- svelte-ignore a11y_media_has_caption -->
|
||||
@@ -197,6 +215,9 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.light { color: var(--light); }
|
||||
.sealed { display: flex; align-items: center; gap: 8px; color: var(--subtle); }
|
||||
|
||||
.back { display: none; }
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
||||
Reference in new issue
Block a user