Improve GDPR implementation
This commit is contained in:
110
services/auth/app/models/consent.py
Normal file
110
services/auth/app/models/consent.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
User consent tracking models for GDPR compliance
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, String, Boolean, DateTime, Text, ForeignKey, Index
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSON
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
|
||||
from shared.database.base import Base
|
||||
|
||||
|
||||
class UserConsent(Base):
|
||||
"""
|
||||
Tracks user consent for various data processing activities
|
||||
GDPR Article 7 - Conditions for consent
|
||||
"""
|
||||
__tablename__ = "user_consents"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
|
||||
# Consent types
|
||||
terms_accepted = Column(Boolean, nullable=False, default=False)
|
||||
privacy_accepted = Column(Boolean, nullable=False, default=False)
|
||||
marketing_consent = Column(Boolean, nullable=False, default=False)
|
||||
analytics_consent = Column(Boolean, nullable=False, default=False)
|
||||
|
||||
# Consent metadata
|
||||
consent_version = Column(String(20), nullable=False, default="1.0")
|
||||
consent_method = Column(String(50), nullable=False) # registration, settings_update, cookie_banner
|
||||
ip_address = Column(String(45), nullable=True)
|
||||
user_agent = Column(Text, nullable=True)
|
||||
|
||||
# Consent text at time of acceptance
|
||||
terms_text_hash = Column(String(64), nullable=True)
|
||||
privacy_text_hash = Column(String(64), nullable=True)
|
||||
|
||||
# Timestamps
|
||||
consented_at = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc))
|
||||
withdrawn_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# Additional metadata (renamed from 'metadata' to avoid SQLAlchemy reserved word)
|
||||
extra_data = Column(JSON, nullable=True)
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_user_consent_user_id', 'user_id'),
|
||||
Index('idx_user_consent_consented_at', 'consented_at'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<UserConsent(user_id={self.user_id}, version={self.consent_version})>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"user_id": str(self.user_id),
|
||||
"terms_accepted": self.terms_accepted,
|
||||
"privacy_accepted": self.privacy_accepted,
|
||||
"marketing_consent": self.marketing_consent,
|
||||
"analytics_consent": self.analytics_consent,
|
||||
"consent_version": self.consent_version,
|
||||
"consent_method": self.consent_method,
|
||||
"consented_at": self.consented_at.isoformat() if self.consented_at else None,
|
||||
"withdrawn_at": self.withdrawn_at.isoformat() if self.withdrawn_at else None,
|
||||
}
|
||||
|
||||
|
||||
class ConsentHistory(Base):
|
||||
"""
|
||||
Historical record of all consent changes
|
||||
Provides audit trail for GDPR compliance
|
||||
"""
|
||||
__tablename__ = "consent_history"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
user_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
consent_id = Column(UUID(as_uuid=True), ForeignKey("user_consents.id", ondelete="SET NULL"), nullable=True)
|
||||
|
||||
# Action type
|
||||
action = Column(String(50), nullable=False) # granted, updated, withdrawn, revoked
|
||||
|
||||
# Consent state at time of action
|
||||
consent_snapshot = Column(JSON, nullable=False)
|
||||
|
||||
# Context
|
||||
ip_address = Column(String(45), nullable=True)
|
||||
user_agent = Column(Text, nullable=True)
|
||||
consent_method = Column(String(50), nullable=True)
|
||||
|
||||
# Timestamp
|
||||
created_at = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), index=True)
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_consent_history_user_id', 'user_id'),
|
||||
Index('idx_consent_history_created_at', 'created_at'),
|
||||
Index('idx_consent_history_action', 'action'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ConsentHistory(user_id={self.user_id}, action={self.action})>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"user_id": str(self.user_id),
|
||||
"action": self.action,
|
||||
"consent_snapshot": self.consent_snapshot,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
}
|
||||
Reference in New Issue
Block a user