from datetime import datetime, timezone from sqlalchemy.dialects.mysql import INTEGER from app import db class SharedItem(db.Model): """ A vault item shared by one user with another. Zero-knowledge: enc_data is re-encrypted by the SENDER using the ECDH shared secret derived from sender's private key + recipient's P-256 public key. The server stores only the ciphertext — it cannot decrypt it. """ __tablename__ = 'shared_items' id = db.Column(INTEGER(unsigned=True), autoincrement=True, primary_key=True) item_id = db.Column( INTEGER(unsigned=True), db.ForeignKey('vault_items.id', ondelete='CASCADE'), nullable=False, ) owner_id = db.Column( INTEGER(unsigned=True), db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, ) recipient_email = db.Column(db.String(255), nullable=False) recipient_id = db.Column( INTEGER(unsigned=True), db.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, ) # Non-sensitive fallback label (set to item_type, e.g. "password"). # The actual display name is stored encrypted in enc_name/iv_name below. item_name = db.Column(db.String(255), nullable=False) item_type = db.Column(db.String(20), nullable=False, default='password') # ECDH-encrypted payload enc_data = db.Column(db.Text, nullable=False) iv = db.Column(db.String(64), nullable=False) # Encrypted item name — AES-256-GCM with the ECDH shared secret. # enc_name: base64 ciphertext of the display name string. # iv_name : base64 12-byte GCM nonce. # Nullable for backwards compatibility with rows created before this column existed. enc_name = db.Column(db.String(512), nullable=True) iv_name = db.Column(db.String(64), nullable=True) accepted = db.Column(db.Boolean, default=False, nullable=False) created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc).replace(tzinfo=None), nullable=False) def to_dict(self): return { 'id': self.id, 'item_id': self.item_id, 'owner_id': self.owner_id, 'recipient_email': self.recipient_email, 'recipient_id': self.recipient_id, 'item_name': self.item_name, 'item_type': self.item_type, 'enc_data': self.enc_data, 'iv': self.iv, 'enc_name': self.enc_name, 'iv_name': self.iv_name, 'accepted': self.accepted, 'created_at': self.created_at.isoformat() if self.created_at else None, }