diff --git a/src/components/Passwordgenerator.tsx b/src/components/Passwordgenerator.tsx
index 7c8dba9..21e4b9d 100644
--- a/src/components/Passwordgenerator.tsx
+++ b/src/components/Passwordgenerator.tsx
@@ -1,4 +1,5 @@
import { useState } from "react";
+import { api } from "../lib/api";
import { Button } from "./ui/button";
import { Label } from "./ui/label";
import { Input } from "./ui/input";
@@ -145,8 +146,12 @@ export default function PasswordGenerator({
setCopied(false);
};
- const handleCopy = () => {
- navigator.clipboard.writeText(password);
+ const handleCopy = async () => {
+ try {
+ await api.copyToClipboard(password, 10);
+ } catch {
+ navigator.clipboard.writeText(password);
+ }
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
diff --git a/src/components/Vault.tsx b/src/components/Vault.tsx
index 8e466c1..26d1f03 100644
--- a/src/components/Vault.tsx
+++ b/src/components/Vault.tsx
@@ -43,6 +43,7 @@ import {
ArrowLeft,
LucideDice3,
Pencil,
+ Fingerprint,
} from "lucide-react";
import PasswordGenerator from "./Passwordgenerator";
@@ -106,6 +107,8 @@ export default function Vault({ onLogout, theme, toggleTheme }: VaultProps) {
{},
);
const [showMobileDetail, setShowMobileDetail] = useState(false);
+ const [biometricAvailable, setBiometricAvailable] = useState(false);
+ const [biometricLoading, setBiometricLoading] = useState(false);
const navigate = useNavigate();
const [entryForm, setEntryForm] = useState({
@@ -143,6 +146,51 @@ export default function Vault({ onLogout, theme, toggleTheme }: VaultProps) {
loadVault();
}, []);
+ // Check biometric availability and whether we have a stored password
+ useEffect(() => {
+ const checkBiometric = async () => {
+ const hasSavedPw = !!sessionStorage.getItem("_bp");
+ if (hasSavedPw) {
+ const available = await api.isBiometricAvailable();
+ setBiometricAvailable(available);
+ }
+ };
+ checkBiometric();
+ }, [unlocked]);
+
+ const handleBiometricUnlock = async () => {
+ setBiometricLoading(true);
+ setError("");
+ try {
+ const authenticated = await api.authenticateWithBiometric();
+ if (!authenticated) {
+ setError("Biometric authentication failed");
+ return;
+ }
+ const savedPw = sessionStorage.getItem("_bp");
+ if (!savedPw) {
+ setError("No saved credentials. Please unlock with your master password first.");
+ return;
+ }
+ const pw = atob(savedPw);
+ setMasterPassword(pw);
+
+ const response = await api.getVault();
+ if (!response.encrypted_vault) {
+ setError("No vault found");
+ return;
+ }
+ const decrypted = await decryptVault(response.encrypted_vault, pw);
+ setVault(decrypted);
+ setUnlocked(true);
+ } catch (err) {
+ console.error("Biometric unlock error:", err);
+ setError("Biometric unlock failed. Please use your master password.");
+ } finally {
+ setBiometricLoading(false);
+ }
+ };
+
const handleUnlock = async () => {
try {
setError("");
@@ -159,6 +207,8 @@ export default function Vault({ onLogout, theme, toggleTheme }: VaultProps) {
);
setVault(decrypted);
setUnlocked(true);
+ // Store password for biometric unlock in current session
+ sessionStorage.setItem("_bp", btoa(masterPassword));
} catch (err) {
console.error("Unlock error:", err);
setError("Failed to unlock vault. Wrong password?");
@@ -270,8 +320,17 @@ export default function Vault({ onLogout, theme, toggleTheme }: VaultProps) {
setShowMobileDetail(false);
};
- const handleCopy = (text: string) => {
- navigator.clipboard.writeText(text);
+
+ const handleCopy = async (text: string) => {
+ try {
+ // Use Tauri's clipboard with auto-clear after 10 seconds
+ await api.copyToClipboard(text, 10);
+ } catch (err) {
+ // Fallback to regular clipboard if Tauri method fails
+ navigator.clipboard.writeText(text).catch(() => {
+ console.error("Failed to copy to clipboard");
+ });
+ }
};
const handleGeneratePassword = () => {
@@ -335,6 +394,30 @@ export default function Vault({ onLogout, theme, toggleTheme }: VaultProps) {
{error}
)}
+
+ {biometricAvailable && (
+ <>
+
+
+ >
+ )}
+