Skip to main content
SecureVault provides comprehensive password management capabilities with client-side encryption and intuitive workflows.

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

The usePasswords 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

1

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: []
};
2

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
// }
3

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
};
4

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

1

Load Current Data

Fetch and decrypt the current password entry:
const { getById } = usePasswords();
const entry = getById(passwordId);
2

Modify Data

Update the fields that changed:
const updates = {
  password: newPassword,
  notes: newNotes,
  tags: [...entry.tags, 'updated']
};
3

Re-encrypt

Encrypt the updated data:
const { updatePassword } = usePasswords();

const success = await updatePassword(passwordId, updates);
4

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

Build docs developers (and LLMs) love