from datetime import datetime, timezone import enum from sqlalchemy.dialects.mysql import INTEGER from app import db class ItemType(str, enum.Enum): PASSWORD = 'password' NOTE = 'note' ADDRESS = 'address' CARD = 'card' BANK = 'bank' SSN = 'ssn' PASSKEY = 'passkey' class VaultItem(db.Model): __tablename__ = 'vault_items' id = db.Column(INTEGER(unsigned=True), autoincrement=True, primary_key=True) user_id = db.Column(INTEGER(unsigned=True), db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False) folder_id = db.Column(INTEGER(unsigned=True), db.ForeignKey('folders.id', ondelete='SET NULL'), nullable=True) item_type = db.Column(db.String(20), nullable=False, default=ItemType.PASSWORD.value) # NOT the user-visible name — that lives encrypted in enc_name/iv_name below. # This column holds the item TYPE string only (the same value as item_type), # kept because the column is NOT NULL and predates enc_name. Writing a real # item name here would hand the server plaintext the zero-knowledge model # promises it never sees. name = db.Column(db.String(255), nullable=False) enc_data = db.Column(db.Text, nullable=False) # base64-encoded AES-256-GCM ciphertext iv = db.Column(db.String(64), nullable=False) # base64-encoded 12-byte GCM nonce # enc_name / iv_name: AES-256-GCM encrypted item name (client-side, same vault key). # Nullable for backward-compatibility with existing rows — clients fall back to # the plaintext 'name' column when enc_name is absent. enc_name = db.Column(db.Text, nullable=True) iv_name = db.Column(db.String(64), nullable=True) created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc).replace(tzinfo=None), nullable=False) updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc).replace(tzinfo=None), onupdate=lambda: datetime.now(timezone.utc).replace(tzinfo=None), nullable=False) def to_dict(self): # item_type is stored as a plain string; handle both str and enum safely item_type = self.item_type if isinstance(item_type, ItemType): item_type = item_type.value return { 'id': self.id, 'item_type': item_type, 'name': self.name, 'folder_id': self.folder_id, 'enc_data': self.enc_data, 'iv': self.iv, 'enc_name': self.enc_name, 'iv_name': self.iv_name, 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None, } def __repr__(self): return f''