feat(auth): B-2.1 ConsentLog model (Loi 25) + User MFA/OAuth/Stripe fields

- New src/models/consent.py — ConsentLog with user_id FK, consent_type
  ('cgu' | 'confidentialite' | 'marketing' | 'analytics'), version, granted
  bool, granted_at/revoked_at timestamps, ip_address (45 chars for IPv6),
  user_agent (500 chars). User.consent_logs backref. Audit trail per
  LPRPSP art. 14 (consent tracé) + art. 3.5 (journal).
- src/models/user.py: add 7 new columns (totp_secret, totp_enabled DEFAULT 0,
  webauthn_credentials JSON, ordre_pro, cabinet, stripe_customer_id,
  subscription_status). Do NOT duplicate existing sso_provider/sso_subject/
  email_verified/etc. (per compatibility-audit C4).
- src/init_db.py: 7 add_column_if_not_exists() calls for the new User
  columns + 2 create_index_if_not_exists() for stripe_customer_id and
  subscription_status. NO Alembic — init_db.py pattern matches
  compatibility-audit C3.
- src/models/__init__.py: register ConsentLog import.
- tests/test_consent_log.py: 7 tests — grant flow, 4 consent types, revoke
  preserves audit trail, User backref, NOT NULL on ip/UA, User.B-2.1 fields
  round-trip, defaults safe.
This commit is contained in:
Allison
2026-04-27 21:44:37 -04:00
parent d45c9c9349
commit 48d2abfa74
5 changed files with 274 additions and 0 deletions

View File

@@ -9,6 +9,8 @@ from datetime import datetime
from flask_login import UserMixin
from src.database import db
# ConsentLog backref defined in src/models/consent.py — accessible as User.consent_logs
class User(db.Model, UserMixin):
"""User model for authentication and profile management."""
@@ -62,6 +64,23 @@ class User(db.Model, UserMixin):
transcription_hotwords = db.Column(db.Text, nullable=True)
transcription_initial_prompt = db.Column(db.Text, nullable=True)
# === B-2.1: MFA / WebAuthn / Stripe / Loi 25 fields (Phase 2 backend) ===
# TOTP MFA (B-2.5) — chiffré au repos via SECRET_KEY (handled in service layer)
totp_secret = db.Column(db.String(64), nullable=True)
totp_enabled = db.Column(db.Boolean, default=False, nullable=False)
# WebAuthn / Passkey credentials (B-2.6) — list of credential dicts
webauthn_credentials = db.Column(db.JSON, nullable=True)
# Loi 25 + ordre professionnel context (used at signup B-2.2)
ordre_pro = db.Column(db.String(50), nullable=True) # 'barreau', 'cpa', 'chad', etc.
cabinet = db.Column(db.String(255), nullable=True)
# Stripe billing (B-2.7 / B-2.8)
stripe_customer_id = db.Column(db.String(120), nullable=True, index=True)
# 'trialing' | 'active' | 'past_due' | 'canceled' | 'incomplete' | None
subscription_status = db.Column(db.String(20), nullable=True, index=True)
def __repr__(self):
return f"User('{self.username}', '{self.email}')"