mirror of
https://github.com/suitenumerique/messages.git
synced 2026-09-30 21:55:00 +02:00
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
192 lines
6.0 KiB
TOML
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",
|
|
]
|