browser-vault-gui/src/components/LoginForm.vue

290 lines
9.8 KiB
Vue

<script setup lang="ts">
import { ref, watch } from 'vue'
import type { VaultServer, VaultCredentials } from '../types'
import { useSweetAlert } from '../composables/useSweetAlert'
interface Props {
server: VaultServer
}
const props = defineProps<Props>()
const emit = defineEmits<{
login: [credentials: VaultCredentials, saveCredentials: boolean]
clearCredentials: [serverId: string]
}>()
const { confirm, error } = useSweetAlert()
const authMethod = ref<'token' | 'userpass' | 'ldap'>('token')
const token = ref('')
const username = ref('')
const password = ref('')
const isLoading = ref(false)
const saveCredentials = ref(false)
const showSecurityWarning = ref(false)
// Function to load credentials from server
const loadCredentialsFromServer = (server: VaultServer) => {
if (server.savedCredentials) {
// Load saved credentials
authMethod.value = server.savedCredentials.authMethod
token.value = server.savedCredentials.token || ''
username.value = server.savedCredentials.username || ''
password.value = server.savedCredentials.password || ''
saveCredentials.value = true
} else {
// Clear form when no saved credentials
authMethod.value = 'token'
token.value = ''
username.value = ''
password.value = ''
saveCredentials.value = false
}
}
// Load credentials on initial mount
loadCredentialsFromServer(props.server)
// Watch for server changes and reload credentials
watch(
() => props.server,
newServer => {
loadCredentialsFromServer(newServer)
showSecurityWarning.value = false // Close any open warning modal
},
{ immediate: false }
)
const handleSubmit = async () => {
// Show warning if user is trying to save credentials for the first time
if (saveCredentials.value && !props.server.savedCredentials) {
showSecurityWarning.value = true
return
}
await performLogin()
}
const performLogin = async () => {
isLoading.value = true
const credentials: VaultCredentials = {
serverId: props.server.id,
authMethod: authMethod.value,
token: authMethod.value === 'token' ? token.value : undefined,
username: authMethod.value !== 'token' ? username.value : undefined,
password: authMethod.value !== 'token' ? password.value : undefined,
}
try {
await emit('login', credentials, saveCredentials.value)
} catch (err) {
console.error('Login error:', err)
error('Login failed. Please check your credentials.')
} finally {
isLoading.value = false
}
}
const confirmSaveCredentials = () => {
showSecurityWarning.value = false
performLogin()
}
const handleClearCredentials = async (serverId: string, serverName: string) => {
const result = await confirm(`Clear saved credentials for "${serverName}"?`, 'Clear Credentials')
if (result.isConfirmed) {
emit('clearCredentials', serverId)
token.value = ''
username.value = ''
password.value = ''
saveCredentials.value = false
}
}
const cancelSaveCredentials = () => {
showSecurityWarning.value = false
saveCredentials.value = false
}
</script>
<template>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<!-- Header -->
<div class="mb-4">
<h2 class="card-title text-2xl mb-2">Connect to {{ server.name }}</h2>
<p class="text-sm font-mono opacity-70">
{{ server.url }}
</p>
</div>
<!-- Form -->
<form class="space-y-4" @submit.prevent="handleSubmit">
<!-- Auth Method -->
<div class="form-control">
<label class="label">
<span class="label-text">Authentication Method</span>
</label>
<select v-model="authMethod" class="select select-bordered w-full">
<option value="token">Token</option>
<option value="userpass">Username & Password</option>
<option value="ldap">LDAP</option>
</select>
</div>
<!-- Token Auth -->
<div v-if="authMethod === 'token'" class="form-control">
<label class="label">
<span class="label-text">Vault Token *</span>
</label>
<input
v-model="token"
type="password"
placeholder="Enter your vault token"
class="input input-bordered w-full"
autocomplete="off"
required
/>
<label class="label">
<span class="label-text-alt">Your token will be used to authenticate with the vault server</span>
</label>
</div>
<!-- Username/Password or LDAP -->
<template v-else>
<div class="form-control">
<label class="label">
<span class="label-text">Username *</span>
</label>
<input
v-model="username"
type="text"
placeholder="Enter your username"
class="input input-bordered w-full"
autocomplete="username"
required
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Password *</span>
</label>
<input
v-model="password"
type="password"
placeholder="Enter your password"
class="input input-bordered w-full"
autocomplete="current-password"
required
/>
</div>
</template>
<!-- Save Credentials Option -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input v-model="saveCredentials" type="checkbox" class="checkbox checkbox-warning" />
<span class="label-text">
<span class="font-semibold text-warning flex items-center gap-2">
<i class="mdi mdi-alert text-warning" />
Save credentials locally
</span>
</span>
</label>
<label class="label">
<span class="label-text-alt text-warning"> Not recommended! Credentials will be stored in plain text in localStorage </span>
</label>
</div>
<!-- Submit Button -->
<button type="submit" class="btn btn-primary w-full" :class="{ loading: isLoading }" :disabled="isLoading">
{{ isLoading ? 'Connecting...' : 'Connect' }}
</button>
<button v-if="server.savedCredentials" class="btn btn-warning btn-sm" @click.prevent.stop="handleClearCredentials(server.id, server.name)">
<i class="mdi mdi-key-remove mr-1" />
Clear Credentials
</button>
</form>
<!-- Security Warning Modal -->
<div v-if="showSecurityWarning" class="modal modal-open">
<div class="modal-box border-2 border-error">
<h3 class="font-bold text-lg text-error mb-4 flex items-center gap-2">
<i class="mdi mdi-alert-circle text-error" />
Security Warning
</h3>
<div class="space-y-4">
<div class="alert alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span class="font-semibold">This is NOT recommended for security reasons!</span>
</div>
<div class="text-sm space-y-2">
<p class="font-semibold">If you save credentials:</p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>
Your token/password will be stored in
<strong>plain text</strong>
</li>
<li>Anyone with access to your browser can read them</li>
<li>Browser extensions can access localStorage</li>
<li>If your computer is compromised, credentials are exposed</li>
<li>This violates most security policies</li>
</ul>
<p class="font-semibold mt-4">Only use this if:</p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>You're on a personal, secure device</li>
<li>You understand the security risks</li>
<li>You're using a development/test Vault server</li>
</ul>
</div>
<div class="bg-base-300 p-3 rounded text-xs">
<p class="font-mono">
<strong>Better alternatives:</strong><br />
• Use short-lived tokens<br />
• Re-login each session<br />
• Use a password manager<br />
• Enable auto-logout timeout
</p>
</div>
</div>
<div class="modal-action">
<button class="btn btn-ghost" @click="cancelSaveCredentials">Cancel</button>
<button class="btn btn-error" @click="confirmSaveCredentials">I Understand the Risks - Save Anyway</button>
</div>
</div>
</div>
<!-- Security Notice -->
<div class="alert mt-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-info shrink-0 w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div class="text-xs">
<p class="font-semibold flex items-center gap-2">
<i class="mdi mdi-shield-alert text-warning" />
Security Notice:
</p>
<p>
This application connects directly to your Vault server. Credentials are not stored permanently and are only kept in memory during your
session.
</p>
</div>
</div>
</div>
</div>
</template>