Files
lasuite-messages/src/backend/pyproject.toml
T
jbpenrath 1824212be4 🔖(minor) release version 0.10.0
Added
- Compose messages in floating windows: several drafts side by side,
  minimized or full-screen, restored after a reload and synced across tabs
- Mobile UI for the native apps: bottom navigation bar, swipe actions on
  threads, pull-to-refresh, drawer, formatting toolbar above the keyboard,
  haptics
- Publish mobile OTA bundles from Scalingo deploys
- Persist the last active mailbox and select it on the next visit
- Clean snippet of the latest message in the thread list
  (`FEATURE_THREAD_SNIPPET`, after `backfill_thread_snippets`) and on folded
  messages
- Allow domain admins to export mailboxes #789
- Provisioning endpoint listing the DNS records of all domains #780
- ARC relay trust: `arc` inbound_auth mode and `arc_verdict` spam rule #763
- Support internationalized email addresses (IDNA domains, ASCII-folded
  local parts) #785
- Keep Gmail labels, Thunderbird read/starred flags and
  sent/archived/spam/trashed states through MBOX export and import
- Setup guides for the authentication provider and the identity provider
- IP allowlist for the Django admin URL in the frontend Caddy proxy
- Smoke test for the frontend production image (make test-front-distroless)
- IP allowlist for the Keycloak admin console and master realm in the Keycloak image (Caddy) #793
- Smoke test for the Keycloak production image (make test-keycloak-image) #793

Changed
- Migrate the frontend from Cunningham to the `@gouvfr-lasuite/ui-components`
  and `@gouvfr-lasuite/ui-tokens` packages
- Upgrade Capacitor to 8.5 and adopt the iOS UIScene life cycle
- Upgrade jmap-email to 0.3.0: an unparseable inbound message is abandoned
  instead of retried, a compose error on send returns a 400, a stored message
  the parser now refuses is flagged unreadable
- Force-lowercase mailbox addresses #785
- Route mailbox export tasks to the imports queue #805
- Redirect to the inbox when switching mailbox
- Thread a reply by its In-Reply-To even when its subject was rewritten #765
- Exclude spam and trashed messages from folder stats by default
- Stop fetching stats for the Sent folder
- Page titles follow "Mailbox - Folder - App name"
- Harden pymta with new settings and limits, and improve its env vars,
  documentation and logging #777 #783
- Bump keycloak to 26.7.4 and Alpine to 3.24 in the Keycloak image #776 #784 #798
- Bump Caddy to 2.11.4 and lprobe to v0.2.0 in the frontend image
- Keycloak image: Keycloak listens on 127.0.0.1:8081, Caddy serves port 8080
- Keycloak image: proxy headers are fixed to xforwarded from Caddy, which sends X-Forwarded-Proto https

Removed
- The mta-out service: relay mode now needs an external `MTA_OUT_RELAY_HOST` #785
- Scalingo buildpack for Keycloak (src/keycloak/buildpack, Procfile, system.properties)

Fixed
- Fix a draft update / send race that could re-send an already delivered
  email, and keep the autosave out of the send window
- Add the channel to messages sent through the submit API #794
- Accept more valid SPF records by implementing the full RFC #782
- Ignore whitespace in DKIM keys in the DNS check #778
- Select IMAP folders whose unquoted name carried extra spaces, and stop
  retrying unselectable folders
- Give nameless attachments the same name in the UI, downloads and drafts
- Keep only BlockNote-supported colors in pasted content, drop unsupported
  blocks and embed external images again
- Resize the mail iframe with a resize observer (Safari, width changes,
  lazy-loaded images)
- Stretch the thread sender on a narrow thread panel
- Fix file uploads outside a secure context (`crypto.randomUUID` fallback)
- Fallback for avatar colors on browsers without `color-mix()` support
- Mobile: send mutations through CapacitorHttp with an explicit Origin so
  an HTTPS backend's CSRF check accepts them
- Grant view-realm to the Keycloak service account, needed by the 2FA toggle
- Run make test-keycloak against a freshly imported Keycloak realm
- List only assignable users and current assignees in the quick assign picker

Security
- Turn the direct access grant off on the Keycloak rest-api client
- Deny API actions that don't declare an access check by default
- Check mailbox access for mailbox_id on message list and thread search
- Rate-limit the image proxy, restrict it to ports 80 and 443 and mark its
  responses private
- SSRF-check per-domain relays (internal ones need SSRF_ALLOWED_HOSTS)
- Validate the MailDomain custom_settings schema
- Restrict IMAP import ports (MESSAGES_IMPORT_IMAP_ALLOWED_PORTS)
- Use only the Referer hostname in widget message subjects
- Scope the DNS check endpoint through the admin queryset
- Refuse MTA requests with a 401 instead of a 500 when `MDA_API_SECRET` is
  unset
- Scope the flag endpoint draft cascade to editable messages and suspend
  personal API keys of deactivated users #804
- Harden email parsing against hostile mail (forged sender, smuggled
  recipients, quadratic regexes) through jmap-email 0.3.0, and bound the
  reply-unquoting regexes
2026-09-29 14:54:16 +02:00

192 lines
6.0 KiB
TOML

#
# messages backend package
#
[project]
name = "messages-backend"
version = "0.10.0"
authors = [{ "name" = "ANCT", "email" = "contact@suite.anct.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Django",
"Framework :: Django :: 5",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.14",
]
description = "A Django MDA"
keywords = ["Django", "Contacts", "Templates", "RBAC"]
license = "MIT"
readme = "README.md"
requires-python = ">=3.14.7,<4.0"
# Note: after changing this list you must re-run `make deps-lock-back`
dependencies = [
"boto3==1.42.53",
"botocore==1.42.53",
"celery[redis]==5.6.2",
"cryptography==50.0.1",
"defusedxml==0.7.1",
"dj-database-url==3.1.2",
"django==5.2.17",
"django-celery-beat==2.8.1",
"django-celery-results==2.6.0",
"django-configurations==2.5.1",
"django-cors-headers==4.9.0",
"django-countries==8.2.0",
"django-fernet-encrypted-fields==0.3.1",
"django-filter==25.2",
"django-lasuite[all]==0.0.27",
"django-prometheus==2.4.1",
"django-redis==6.0.0",
"django-storages==1.14.6",
"django-timezone-field==7.2.1",
"djangorestframework==3.17.2",
"dkimpy==1.1.8",
"dnspython==2.8.0",
"drf_spectacular==0.29.0",
"google-auth==2.41.1",
"httpx[http2]==0.28.1",
"opensearch-py==2.8.0",
"factory_boy==3.3.3",
"gunicorn==25.1.0",
"icalendar==7.0.3",
"jmap-email==0.3.0",
"jsonschema==4.26.0",
"nested-multipart-parser==1.6.0",
"openai==2.21.0",
"psycopg[binary]==3.3.3",
"PyJWT==2.13.0",
"PySocks==1.7.1",
"python-keycloak==5.5.1",
"python-magic==0.4.27",
"pyzstd==0.19.1",
"redis==6.4.0",
"requests==2.34.2",
"sentry-sdk[django]==2.53.0",
"libpff-python==20231205",
"url-normalize==2.2.1",
"whitenoise==6.11.0",
"prometheus-client==0.24.1",
"py-vapid==1.9.4",
"http-ece==1.2.1",
]
[project.urls]
"Bug Tracker" = "https://github.com/suitenumerique/st-messages/issues/new"
"Changelog" = "https://github.com/suitenumerique/st-messages/blob/main/CHANGELOG.md"
"Homepage" = "https://github.com/suitenumerique/st-messages"
"Repository" = "https://github.com/suitenumerique/st-messages"
[project.optional-dependencies]
dev = [
"django-extensions==4.1",
"drf-spectacular-sidecar==2026.1.1",
"flower==2.0.1",
"hypothesis==6.151.9",
"pip-audit==2.10.0",
"pipdeptree==2.31.0",
"pylint-django==2.7.0",
"pylint==4.0.4",
"pytest-cov==7.0.0",
"pytest-django==4.12.0",
"pytest==9.0.3",
"pytest-icdiff==0.9",
"pytest-repeat==0.9.4",
"pytest-xdist==3.8.0",
"radicale==3.6.1",
"responses==0.26.0",
"ruff==0.15.2"
]
[build-system]
requires = ["uv_build>=0.12.0,<0.13.0"]
build-backend = "uv_build"
[tool.uv.build-backend]
module-root = ""
source-include = ["core/**"]
source-exclude = ["core/tests/**"]
[tool.ruff]
# Pin ruff at py313 even though our runtime floor is higher (chosen for
# stdlib `email` fixes — see ``src/jmap-email/README.md``). Targeting py313
# keeps ruff's formatter from applying PEP 758 (un-parenthesized `except
# E1, E2:`), which is valid Python 3.14 but trips up most static analysis
# (CodeRabbit, IDEs on older Python, copy-pasted snippets).
target-version = "py313"
exclude = [
".git",
".venv",
"build",
"venv",
"__pycache__",
"*/migrations/*",
]
line-length = 88
[tool.ruff.lint]
ignore = ["DJ001", "PLC0415", "PLR2004", "PLR0915", "PLR0912", "PLR0913", "PLR0911", "BLE001"]
select = [
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"DJ", # flake8-django
"I", # isort
"PLC", # pylint-convention
"PLE", # pylint-error
"PLR", # pylint-refactoring
"PLW", # pylint-warning
"RUF100", # Ruff unused-noqa
"RUF200", # Ruff check pyproject.toml
"S", # flake8-bandit
"SLF", # flake8-self
"T20", # flake8-print
"TID251", # flake8-tidy-imports: banned-api only (not TID252 relative-imports)
"F" # pyflakes
]
[tool.ruff.lint.isort]
section-order = ["future","standard-library","django","third-party","messages","first-party","local-folder"]
sections = { messages=["core"], django=["django"] }
known-third-party = ["dns"]
extra-standard-library = ["tomllib"]
[tool.ruff.lint.flake8-tidy-imports.banned-api]
# jmap-email's public API is the top-level `jmap_email` package; the
# TypedDict shapes live in `jmap_email.types`. Everything else is private
# implementation — reaching into it couples us to internal layout.
"jmap_email.parser".msg = "Import from the top-level `jmap_email` package (shapes: `jmap_email.types`)."
"jmap_email.helpers".msg = "Import from the top-level `jmap_email` package (shapes: `jmap_email.types`)."
"jmap_email.composer".msg = "Import from the top-level `jmap_email` package (shapes: `jmap_email.types`)."
"jmap_email.options".msg = "Import from the top-level `jmap_email` package (shapes: `jmap_email.types`)."
"jmap_email.preview".msg = "Import from the top-level `jmap_email` package (shapes: `jmap_email.types`)."
"jmap_email.addresses".msg = "Import from the top-level `jmap_email` package (shapes: `jmap_email.types`)."
"jmap_email.filenames".msg = "Import from the top-level `jmap_email` package (shapes: `jmap_email.types`)."
[tool.ruff.lint.per-file-ignores]
"**/tests/*" = ["S", "SLF"]
[tool.pytest.ini_options]
addopts = [
"-v",
"--cov-report",
"term-missing",
# Allow test files to have the same name in different directories.
"--import-mode=importlib",
# Exclude fuzz tests by default (run with: pytest -m fuzz)
"-m",
"not fuzz",
]
python_files = [
"test_*.py",
"tests.py",
]
markers = [
"fuzz: marks tests as fuzz tests (run with: pytest -m fuzz)",
"redis: marks tests that need a real Redis service (skip with: pytest -m 'not redis')",
"caldav_ssrf_real: opt out of the test-suite-wide SSRF bypass and exercise the real per-channel guard",
]