Password Entry Structure
Each password entry contains encrypted credentials and metadata.Data Model
interface PasswordEntry {
_id: string;
userId: string;
// Encrypted data blob
encryptedData: string;
iv: string;
// Metadata (not encrypted)
categoryId?: string;
tags: string[];
favorite: boolean;
// Security analysis
passwordStrength: 0 | 1 | 2 | 3 | 4;
isCompromised: boolean;
isReused: boolean;
// Timestamps
createdAt: Date;
updatedAt: Date;
lastUsedAt?: Date;
passwordChangedAt: Date;
deletedAt?: Date;
encryptionVersion: number;
}
Creating Passwords
Using the usePasswords Hook
TheusePasswords hook provides a convenient interface for password operations:
import { usePasswords } from '@/hooks/usePasswords';
function AddPasswordForm() {
const { addPassword, generateSecurePassword } = usePasswords();
const [formData, setFormData] = useState({
title: '',
username: '',
password: '',
url: '',
notes: '',
categoryId: undefined,
tags: [],
favorite: false
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const passwordId = await addPassword({
title: formData.title,
username: formData.username,
password: formData.password,
url: formData.url,
notes: formData.notes,
categoryId: formData.categoryId,
tags: formData.tags,
favorite: formData.favorite
});
if (passwordId) {
router.push(`/vault/${passwordId}`);
}
};
const handleGenerate = () => {
const newPassword = generateSecurePassword({
length: 20,
includeUppercase: true,
includeLowercase: true,
includeNumbers: true,
includeSymbols: true,
excludeAmbiguous: false
});
setFormData(prev => ({ ...prev, password: newPassword }));
};
return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
</form>
);
}
Client-Side Encryption Flow
Prepare Data
Collect password entry data from the form:
const plainData = {
name: 'Gmail',
username: '[email protected]',
password: 'MySecurePassword123!',
urls: ['https://gmail.com'],
notes: 'Personal email account',
customFields: []
};
Encrypt on Client
Encrypt the data using the vault encryption key:
import { encryptObject } from '@/lib/crypto/client';
const encryptedData = await encryptObject(
plainData,
vaultEncryptionKey
);
// Result:
// {
// ciphertext: "base64-encoded-encrypted-data",
// iv: "base64-encoded-iv",
// version: 1
// }
Calculate Metadata
Analyze password for strength and reuse:
import { calculatePasswordStrength } from '@/lib/crypto/client';
const strength = calculatePasswordStrength(plainData.password);
const isReused = checkForReuse(plainData.password, existingPasswords);
const metadata = {
categoryId: selectedCategory,
tags: ['work', 'email'],
favorite: false,
passwordStrength: strength.score,
isCompromised: false,
isReused: isReused
};
Send to Server
Post encrypted data and metadata to API:
const response = await fetch('/api/passwords', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({
encryptedData: encryptedData.ciphertext,
iv: encryptedData.iv,
metadata
})
});
const { passwordId } = await response.json();
Viewing Passwords
Password Card Component
The password card component displays password information with security indicators:View Password Card
function PasswordCard({ entry }: { entry: DecryptedPasswordEntry }) {
const [showPassword, setShowPassword] = useState(false);
const strengthColors = [
'bg-red-500',
'bg-orange-500',
'bg-yellow-500',
'bg-lime-500',
'bg-green-500'
];
return (
<div className="p-5 bg-card rounded-3xl border shadow-sm">
{/* Header */}
<div className="flex items-start gap-4 mb-4">
<div className="h-12 w-12 rounded-2xl bg-primary/10 flex items-center justify-center">
<span className="text-lg font-semibold text-primary">
{entry.title[0].toUpperCase()}
</span>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h3 className="font-semibold truncate">{entry.title}</h3>
{entry.favorite && (
<Star className="h-4 w-4 text-yellow-500 fill-yellow-500" />
)}
</div>
<p className="text-sm text-muted-foreground truncate">
{entry.username || 'No username'}
</p>
</div>
</div>
{/* Password field */}
<div className="mb-4">
<label className="text-xs text-muted-foreground">Password</label>
<div className="flex items-center gap-2 mt-1">
<code className="flex-1 px-3 py-2 bg-muted rounded-xl font-mono text-sm">
{showPassword ? entry.password : '••••••••••••'}
</code>
<button onClick={() => setShowPassword(!showPassword)}>
{showPassword ? <EyeOff /> : <Eye />}
</button>
</div>
</div>
{/* Strength indicator */}
<div className="flex items-center gap-2">
<div className="flex-1 flex gap-1.5">
{[0, 1, 2, 3, 4].map((i) => (
<div
key={i}
className={cn(
"h-2 flex-1 rounded-full",
i <= entry.passwordStrength
? strengthColors[entry.passwordStrength]
: "bg-muted"
)}
/>
))}
</div>
<span className="text-xs text-muted-foreground">
{['Very Weak', 'Weak', 'Fair', 'Strong', 'Very Strong'][entry.passwordStrength]}
</span>
</div>
{/* Security badges */}
<div className="flex flex-wrap gap-2 mt-3">
{entry.isCompromised && (
<span className="px-2 py-1 text-xs bg-red-500/10 text-red-500 rounded-xl">
Compromised
</span>
)}
{entry.isReused && (
<span className="px-2 py-1 text-xs bg-yellow-500/10 text-yellow-500 rounded-xl">
Reused
</span>
)}
</div>
</div>
);
}
Detail View
Full password details with all fields:function PasswordDetailView({ id }: { id: string }) {
const { getById } = usePasswords();
const entry = getById(id);
if (!entry) return <div>Password not found</div>;
return (
<div className="max-w-2xl mx-auto space-y-6">
{/* Title */}
<h1 className="text-3xl font-bold">{entry.title}</h1>
{/* Credentials */}
<div className="space-y-4">
<PasswordField label="Username" value={entry.username} />
<PasswordField label="Password" value={entry.password} secret />
</div>
{/* URLs */}
{entry.urls.length > 0 && (
<div>
<h3 className="font-semibold mb-2">Websites</h3>
{entry.urls.map((url, i) => (
<a
key={i}
href={url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-primary hover:underline"
>
<ExternalLink className="h-4 w-4" />
{url}
</a>
))}
</div>
)}
{/* Notes */}
{entry.notes && (
<div>
<h3 className="font-semibold mb-2">Notes</h3>
<p className="text-muted-foreground">{entry.notes}</p>
</div>
)}
{/* Custom fields */}
{entry.customFields.length > 0 && (
<div>
<h3 className="font-semibold mb-2">Additional Fields</h3>
{entry.customFields.map((field) => (
<PasswordField
key={field.id}
label={field.name}
value={field.value}
secret={field.type === 'hidden'}
/>
))}
</div>
)}
{/* Metadata */}
<div className="pt-6 border-t text-sm text-muted-foreground">
<p>Created: {new Date(entry.createdAt).toLocaleString()}</p>
<p>Last updated: {new Date(entry.updatedAt).toLocaleString()}</p>
{entry.lastUsedAt && (
<p>Last used: {new Date(entry.lastUsedAt).toLocaleString()}</p>
)}
</div>
</div>
);
}
Editing Passwords
Update Flow
Load Current Data
Fetch and decrypt the current password entry:
const { getById } = usePasswords();
const entry = getById(passwordId);
Modify Data
Update the fields that changed:
const updates = {
password: newPassword,
notes: newNotes,
tags: [...entry.tags, 'updated']
};
Re-encrypt
Encrypt the updated data:
const { updatePassword } = usePasswords();
const success = await updatePassword(passwordId, updates);
Update Metadata
Recalculate strength if password changed:
// This happens automatically in updatePassword
if (updates.password) {
const strength = calculatePasswordStrength(updates.password);
updates.passwordStrength = strength.score;
updates.isReused = checkForReuse(updates.password, otherPasswords);
}
Edit Form Component
function EditPasswordForm({ id }: { id: string }) {
const { getById, updatePassword } = usePasswords();
const entry = getById(id);
const [formData, setFormData] = useState(entry);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const success = await updatePassword(id, {
title: formData.title,
username: formData.username,
password: formData.password,
url: formData.url,
notes: formData.notes,
categoryId: formData.categoryId,
tags: formData.tags
});
if (success) {
router.push(`/vault/${id}`);
}
};
return (
<form onSubmit={handleSubmit}>
{/* Edit form fields */}
</form>
);
}
Deleting Passwords
Soft Delete (Move to Trash)
const { deletePassword } = usePasswords();
// Soft delete - moves to trash
await deletePassword(passwordId);
// Entry is kept with deletedAt timestamp
// Can be restored within 30 days
Permanent Delete
// Permanent deletion - no recovery
await deletePassword(passwordId, true);
Restore from Trash
const { restorePassword } = usePasswords();
await restorePassword(passwordId);
Batch Operations
Perform operations on multiple passwords:// Delete multiple passwords
for (const id of selectedIds) {
await deletePassword(id);
}
// Update category for multiple
for (const id of selectedIds) {
await updatePassword(id, { categoryId: newCategoryId });
}
// Add tag to multiple
for (const id of selectedIds) {
const entry = getById(id);
await updatePassword(id, {
tags: [...entry.tags, newTag]
});
}
Next Steps
Security Details
Learn about encryption and key derivation
Categories & Tags
Organize passwords efficiently