wraith/frontend/src/layouts/UnlockLayout.vue
Vantz Stockwell 163af456b4
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m5s
fix: SSH password prompt on auth failure, version from Go backend, visible errors
- ConnectSSH returns NO_CREDENTIALS error when no credential is stored
- Frontend catches auth failures and prompts for username/password
- ConnectSSHWithPassword method for ad-hoc password auth
- Version loaded from Go backend (build-time -ldflags) in settings + unlock screen
- Connection errors shown as alert() instead of silent console.error
- Added UpdateService.CurrentVersion() and WraithApp.GetVersion()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 11:38:08 -04:00

130 lines
4.3 KiB
Vue

<template>
<div class="h-screen w-screen flex items-center justify-center bg-[var(--wraith-bg-primary)]">
<div class="w-full max-w-sm px-6">
<!-- Branding -->
<div class="text-center mb-8">
<h1 class="text-4xl font-bold tracking-widest text-[var(--wraith-accent-blue)]">
WRAITH
</h1>
<p class="text-[var(--wraith-text-secondary)] mt-2 text-sm">
{{ appStore.isFirstRun ? "Create a master password" : "Enter your master password" }}
</p>
</div>
<!-- Card -->
<form
class="bg-[var(--wraith-bg-secondary)] border border-[var(--wraith-border)] rounded-lg p-6 space-y-4"
@submit.prevent="handleSubmit"
>
<!-- Error -->
<div
v-if="appStore.error"
class="text-sm text-[var(--wraith-accent-red)] bg-[var(--wraith-accent-red)]/10 border border-[var(--wraith-accent-red)]/20 rounded px-3 py-2"
>
{{ appStore.error }}
</div>
<!-- Password -->
<div>
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-1.5">
Master Password
</label>
<input
ref="passwordInput"
v-model="password"
type="password"
autocomplete="current-password"
placeholder="Enter password..."
class="w-full px-3 py-2 text-sm rounded bg-[var(--wraith-bg-tertiary)] border border-[var(--wraith-border)] text-[var(--wraith-text-primary)] placeholder-[var(--wraith-text-muted)] outline-none focus:border-[var(--wraith-accent-blue)] transition-colors"
@input="appStore.clearError()"
/>
</div>
<!-- Confirm password (first run only) -->
<div v-if="appStore.isFirstRun">
<label class="block text-xs text-[var(--wraith-text-secondary)] mb-1.5">
Confirm Password
</label>
<input
v-model="confirmPassword"
type="password"
autocomplete="new-password"
placeholder="Confirm password..."
class="w-full px-3 py-2 text-sm rounded bg-[var(--wraith-bg-tertiary)] border border-[var(--wraith-border)] text-[var(--wraith-text-primary)] placeholder-[var(--wraith-text-muted)] outline-none focus:border-[var(--wraith-accent-blue)] transition-colors"
@input="appStore.clearError()"
/>
</div>
<!-- Submit -->
<button
type="submit"
:disabled="submitting"
class="w-full py-2 text-sm font-medium rounded bg-[var(--wraith-accent-blue)] text-white hover:opacity-90 disabled:opacity-50 transition-opacity cursor-pointer disabled:cursor-not-allowed"
>
<span v-if="submitting">{{ appStore.isFirstRun ? "Creating..." : "Unlocking..." }}</span>
<span v-else>{{ appStore.isFirstRun ? "Create Vault" : "Unlock" }}</span>
</button>
</form>
<!-- Version -->
<p class="text-center text-xs text-[var(--wraith-text-muted)] mt-4">
v{{ appVersion }}
</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { Call } from "@wailsio/runtime";
import { useAppStore } from "@/stores/app.store";
const APP = "github.com/vstockwell/wraith/internal/app.WraithApp";
const appStore = useAppStore();
const password = ref("");
const confirmPassword = ref("");
const submitting = ref(false);
const passwordInput = ref<HTMLInputElement | null>(null);
const appVersion = ref("...");
onMounted(async () => {
passwordInput.value?.focus();
try {
const ver = await Call.ByName(`${APP}.GetVersion`) as string;
if (ver) appVersion.value = ver;
} catch { /* ignore */ }
});
async function handleSubmit(): Promise<void> {
if (!password.value) {
appStore.error = "Password is required";
return;
}
if (appStore.isFirstRun) {
if (password.value.length < 8) {
appStore.error = "Password must be at least 8 characters";
return;
}
if (password.value !== confirmPassword.value) {
appStore.error = "Passwords do not match";
return;
}
}
submitting.value = true;
try {
if (appStore.isFirstRun) {
await appStore.createVault(password.value);
} else {
await appStore.unlock(password.value);
}
} catch {
// Error is set in the store
} finally {
submitting.value = false;
}
}
</script>