Files
PassKeeper/app/models/emergency_access.py
T
2026-04-16 08:27:07 -04:00

66 lines
2.6 KiB
Python

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,
}