from datetime import datetime, timedelta from sqlalchemy.dialects.mysql import INTEGER from app import db class EmergencyAccess(db.Model): """ Emergency access grant from a vault owner (grantor) to a trusted contact (grantee). Status flow: invited → grantee calls /accept → accepted accepted → grantor calls /provide → ready (enc_vault stored) ready → grantee calls /request → pending (wait timer starts) pending → grantor calls /deny → ready (reset, grantee can request again) pending (wait_days elapsed) → grantable (grantee fetches vault) Zero-knowledge: enc_vault is a JSON array of vault items re-encrypted by the grantor using the ECDH shared secret (grantor private key + grantee public key). """ __tablename__ = 'emergency_access' id = db.Column(INTEGER(unsigned=True), autoincrement=True, primary_key=True) grantor_id = db.Column( INTEGER(unsigned=True), db.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, ) grantee_email = db.Column(db.String(255), nullable=False) grantee_id = db.Column( INTEGER(unsigned=True), db.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, ) wait_days = db.Column(db.Integer, default=7, nullable=False) # invited | accepted | ready | pending | denied status = db.Column(db.String(20), default='invited', nullable=False) request_initiated_at = db.Column(db.DateTime, nullable=True) # JSON string: [{ id, name, item_type, enc_data, iv }, ...] enc_vault = db.Column(db.Text, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) @property def wait_elapsed(self): """True if the wait period has passed since the access request.""" if self.status != 'pending' or not self.request_initiated_at: return False return datetime.utcnow() >= self.request_initiated_at + timedelta(days=self.wait_days) def to_dict(self, grantor_email=None): return { 'id': self.id, 'grantor_id': self.grantor_id, 'grantor_email': grantor_email, 'grantee_email': self.grantee_email, 'grantee_id': self.grantee_id, 'wait_days': self.wait_days, 'status': self.status, 'wait_elapsed': self.wait_elapsed, 'request_initiated_at': ( self.request_initiated_at.isoformat() if self.request_initiated_at else None ), 'created_at': self.created_at.isoformat() if self.created_at else None, }