65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
from datetime import datetime
|
|
from sqlalchemy import create_engine, Column, String, Integer, Boolean, DateTime, Text
|
|
from sqlalchemy.orm import DeclarativeBase, sessionmaker
|
|
|
|
from config import settings
|
|
|
|
engine = create_engine(
|
|
settings.database_url,
|
|
connect_args={"check_same_thread": False},
|
|
)
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
def get_db():
|
|
db = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
class Share(Base):
|
|
__tablename__ = "shares"
|
|
|
|
id = Column(String(36), primary_key=True) # UUID4
|
|
share_type = Column(String(10), nullable=False) # 'file' | 'text'
|
|
|
|
# Metadata provided by client before encryption
|
|
original_filename = Column(String(512), nullable=True)
|
|
mimetype = Column(String(128), nullable=True)
|
|
filesize = Column(Integer, nullable=True) # bytes of the encrypted blob
|
|
|
|
# Storage — blob stored under a random name, never the original
|
|
storage_path = Column(String(512), nullable=False)
|
|
# AES-256-GCM key (hex-encoded, 64 chars). Server generated, stored at rest.
|
|
encryption_key = Column(String(64), nullable=False)
|
|
|
|
# Access control
|
|
password_hash = Column(String(512), nullable=True) # Argon2id; NULL = no password
|
|
|
|
# Lifecycle — all datetimes are naive UTC
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
expires_at = Column(DateTime, nullable=False)
|
|
burn_after_read = Column(Boolean, default=False)
|
|
downloaded = Column(Boolean, default=False)
|
|
|
|
# Brute-force protection
|
|
failed_attempts = Column(Integer, default=0)
|
|
locked_until = Column(DateTime, nullable=True)
|
|
|
|
|
|
class SecurityEvent(Base):
|
|
__tablename__ = "security_events"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
timestamp = Column(DateTime, default=datetime.utcnow)
|
|
event_type = Column(String(50), nullable=False)
|
|
share_id = Column(String(36), nullable=True)
|
|
ip_hash = Column(String(64), nullable=True) # SHA-256 of IP — no raw IPs
|
|
details = Column(Text, nullable=True)
|