Files
archy/packages/app/src/App.vue
T
DorianandClaude Opus 4.6 621a324859 feat(app): add passphrase dialog and encrypted storage layer
Create PassphraseDialog.vue with create/enter passphrase flows, glass
morphism styling, and skip option. Wire into App.vue to prompt on
startup when crypto is enabled. Salt stored in localStorage, key
derived via PBKDF2 and held in memory for the session only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 20:52:53 +00:00

60 lines
1.5 KiB
Vue

<template>
<div class="h-dvh flex flex-col" :class="currentTheme">
<RouterView />
<ArticleOverlay />
<PassphraseDialog
:visible="showPassphrase"
:is-creating="isCreatingPassphrase"
@submit="handlePassphraseSubmit"
@skip="showPassphrase = false"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RouterView } from 'vue-router'
import { useTheme } from '@/composables/useTheme'
import ArticleOverlay from '@/components/content/ArticleOverlay.vue'
import PassphraseDialog from '@/components/ui/PassphraseDialog.vue'
import {
isCryptoEnabled,
deriveKey,
generateSalt,
setSessionKey,
} from '@/utils/crypto'
const { currentTheme, initTheme } = useTheme()
const showPassphrase = ref(false)
const isCreatingPassphrase = ref(false)
const SALT_KEY = 'aiui-crypto-salt'
async function handlePassphraseSubmit(passphrase: string) {
let saltHex = localStorage.getItem(SALT_KEY)
let salt: Uint8Array
if (saltHex) {
salt = new Uint8Array(saltHex.match(/.{2}/g)!.map(b => parseInt(b, 16)))
} else {
salt = await generateSalt()
saltHex = Array.from(salt).map(b => b.toString(16).padStart(2, '0')).join('')
localStorage.setItem(SALT_KEY, saltHex)
}
const key = await deriveKey(passphrase, salt)
setSessionKey(key, salt)
showPassphrase.value = false
}
onMounted(() => {
initTheme()
if (isCryptoEnabled()) {
const hasSalt = !!localStorage.getItem(SALT_KEY)
isCreatingPassphrase.value = !hasSalt
showPassphrase.value = true
}
})
</script>