messagefoundry 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. messagefoundry/__init__.py +108 -0
  2. messagefoundry/__main__.py +1155 -0
  3. messagefoundry/api/__init__.py +27 -0
  4. messagefoundry/api/app.py +1581 -0
  5. messagefoundry/api/approvals.py +184 -0
  6. messagefoundry/api/auth_models.py +211 -0
  7. messagefoundry/api/auth_routes.py +655 -0
  8. messagefoundry/api/field_authz.py +96 -0
  9. messagefoundry/api/models.py +374 -0
  10. messagefoundry/api/security.py +247 -0
  11. messagefoundry/api/tls.py +47 -0
  12. messagefoundry/auth/__init__.py +39 -0
  13. messagefoundry/auth/data/common_passwords.NOTICE +13 -0
  14. messagefoundry/auth/data/common_passwords.txt +10000 -0
  15. messagefoundry/auth/identity.py +71 -0
  16. messagefoundry/auth/ldap.py +264 -0
  17. messagefoundry/auth/notifications.py +68 -0
  18. messagefoundry/auth/passwords.py +53 -0
  19. messagefoundry/auth/permissions.py +120 -0
  20. messagefoundry/auth/policy.py +153 -0
  21. messagefoundry/auth/ratelimit.py +55 -0
  22. messagefoundry/auth/service.py +1323 -0
  23. messagefoundry/auth/tokens.py +26 -0
  24. messagefoundry/auth/totp.py +174 -0
  25. messagefoundry/checks.py +174 -0
  26. messagefoundry/config/__init__.py +30 -0
  27. messagefoundry/config/active_environment.py +80 -0
  28. messagefoundry/config/ai_policy.py +140 -0
  29. messagefoundry/config/code_sets.py +260 -0
  30. messagefoundry/config/connections_edit.py +200 -0
  31. messagefoundry/config/connections_file.py +287 -0
  32. messagefoundry/config/db_lookup.py +117 -0
  33. messagefoundry/config/environments.py +116 -0
  34. messagefoundry/config/ingest_time.py +83 -0
  35. messagefoundry/config/models.py +240 -0
  36. messagefoundry/config/reference.py +158 -0
  37. messagefoundry/config/response.py +83 -0
  38. messagefoundry/config/run_context.py +153 -0
  39. messagefoundry/config/settings.py +1311 -0
  40. messagefoundry/config/state.py +99 -0
  41. messagefoundry/config/tls_policy.py +110 -0
  42. messagefoundry/config/wiring.py +1918 -0
  43. messagefoundry/console/__init__.py +20 -0
  44. messagefoundry/console/__main__.py +274 -0
  45. messagefoundry/console/_async.py +107 -0
  46. messagefoundry/console/change_password.py +111 -0
  47. messagefoundry/console/client.py +552 -0
  48. messagefoundry/console/connections.py +324 -0
  49. messagefoundry/console/login.py +107 -0
  50. messagefoundry/console/mfa.py +205 -0
  51. messagefoundry/console/reauth.py +94 -0
  52. messagefoundry/console/search.py +57 -0
  53. messagefoundry/console/service_control.py +137 -0
  54. messagefoundry/console/sessions.py +122 -0
  55. messagefoundry/console/shell.py +410 -0
  56. messagefoundry/console/status.py +377 -0
  57. messagefoundry/console/users_page.py +282 -0
  58. messagefoundry/console/widgets.py +553 -0
  59. messagefoundry/generators/README.md +27 -0
  60. messagefoundry/generators/__init__.py +15 -0
  61. messagefoundry/generators/_core.py +589 -0
  62. messagefoundry/generators/_hl7data.py +428 -0
  63. messagefoundry/generators/adt.py +286 -0
  64. messagefoundry/generators/all_types.py +24 -0
  65. messagefoundry/generators/bar.py +28 -0
  66. messagefoundry/generators/dft.py +20 -0
  67. messagefoundry/generators/mdm.py +39 -0
  68. messagefoundry/generators/mfn.py +46 -0
  69. messagefoundry/generators/oml.py +32 -0
  70. messagefoundry/generators/orl.py +30 -0
  71. messagefoundry/generators/orm.py +23 -0
  72. messagefoundry/generators/oru.py +21 -0
  73. messagefoundry/generators/ras.py +20 -0
  74. messagefoundry/generators/rde.py +54 -0
  75. messagefoundry/generators/siu.py +64 -0
  76. messagefoundry/generators/vxu.py +20 -0
  77. messagefoundry/hl7schema.py +75 -0
  78. messagefoundry/last_resort.py +55 -0
  79. messagefoundry/logging_setup.py +332 -0
  80. messagefoundry/parsing/__init__.py +64 -0
  81. messagefoundry/parsing/consistency.py +166 -0
  82. messagefoundry/parsing/groups.py +228 -0
  83. messagefoundry/parsing/message.py +453 -0
  84. messagefoundry/parsing/peek.py +237 -0
  85. messagefoundry/parsing/split.py +120 -0
  86. messagefoundry/parsing/summary.py +46 -0
  87. messagefoundry/parsing/tree.py +128 -0
  88. messagefoundry/parsing/validate.py +95 -0
  89. messagefoundry/parsing/x12/__init__.py +46 -0
  90. messagefoundry/parsing/x12/delimiters.py +140 -0
  91. messagefoundry/parsing/x12/errors.py +30 -0
  92. messagefoundry/parsing/x12/interchange.py +232 -0
  93. messagefoundry/parsing/x12/message.py +200 -0
  94. messagefoundry/parsing/x12/peek.py +207 -0
  95. messagefoundry/pipeline/__init__.py +21 -0
  96. messagefoundry/pipeline/alert_sinks.py +486 -0
  97. messagefoundry/pipeline/alerts.py +100 -0
  98. messagefoundry/pipeline/cert_expiry.py +219 -0
  99. messagefoundry/pipeline/cluster.py +955 -0
  100. messagefoundry/pipeline/cluster_sqlserver.py +444 -0
  101. messagefoundry/pipeline/config_convergence.py +137 -0
  102. messagefoundry/pipeline/dryrun.py +450 -0
  103. messagefoundry/pipeline/engine.py +756 -0
  104. messagefoundry/pipeline/leader_tasks.py +158 -0
  105. messagefoundry/pipeline/reference_sync.py +369 -0
  106. messagefoundry/pipeline/retention.py +289 -0
  107. messagefoundry/pipeline/security_notify.py +168 -0
  108. messagefoundry/pipeline/state_convergence.py +143 -0
  109. messagefoundry/pipeline/wiring_runner.py +1722 -0
  110. messagefoundry/py.typed +0 -0
  111. messagefoundry/redaction.py +71 -0
  112. messagefoundry/scaffold.py +321 -0
  113. messagefoundry/secrets_dpapi.py +129 -0
  114. messagefoundry/store/__init__.py +46 -0
  115. messagefoundry/store/audit_tee.py +67 -0
  116. messagefoundry/store/base.py +758 -0
  117. messagefoundry/store/crypto.py +166 -0
  118. messagefoundry/store/keyprovider.py +192 -0
  119. messagefoundry/store/postgres.py +3447 -0
  120. messagefoundry/store/sqlserver.py +3014 -0
  121. messagefoundry/store/store.py +3790 -0
  122. messagefoundry/timezone.py +207 -0
  123. messagefoundry/transports/__init__.py +50 -0
  124. messagefoundry/transports/base.py +269 -0
  125. messagefoundry/transports/database.py +693 -0
  126. messagefoundry/transports/file.py +551 -0
  127. messagefoundry/transports/framing.py +164 -0
  128. messagefoundry/transports/loopback.py +53 -0
  129. messagefoundry/transports/mllp.py +644 -0
  130. messagefoundry/transports/remotefile.py +664 -0
  131. messagefoundry/transports/rest.py +281 -0
  132. messagefoundry/transports/signing.py +321 -0
  133. messagefoundry/transports/soap.py +507 -0
  134. messagefoundry/transports/tcp.py +307 -0
  135. messagefoundry/transports/timer.py +146 -0
  136. messagefoundry/transports/x12.py +323 -0
  137. messagefoundry-0.1.0.dist-info/METADATA +212 -0
  138. messagefoundry-0.1.0.dist-info/RECORD +142 -0
  139. messagefoundry-0.1.0.dist-info/WHEEL +4 -0
  140. messagefoundry-0.1.0.dist-info/entry_points.txt +2 -0
  141. messagefoundry-0.1.0.dist-info/licenses/LICENSE +662 -0
  142. messagefoundry-0.1.0.dist-info/licenses/NOTICE +27 -0
@@ -0,0 +1,247 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Copyright (C) 2026 MessageFoundry Organization and contributors
3
+ """FastAPI authentication + authorization dependencies (deny-by-default).
4
+
5
+ ``require(*permissions)`` is a dependency factory applied to every protected route. Once an enabled
6
+ :class:`AuthService` is wired (the ``serve`` path) it enforces the bearer token plus the listed
7
+ permissions. When **no** AuthService is attached the behaviour is **fail-closed**: the route is
8
+ denied unless the app was explicitly built with ``allow_no_auth=True`` (the in-process embedding /
9
+ local-dev opt-in), in which case it returns a full-access *system* identity. This prevents an
10
+ ``create_app(engine)`` that is accidentally served from silently granting unauthenticated full
11
+ access (SYS-1). ``authorize_ws`` is the WebSocket equivalent (it returns ``None`` instead of
12
+ raising, so the caller can close the socket cleanly).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from collections.abc import Awaitable, Callable
19
+
20
+ from fastapi import HTTPException, Request, WebSocket, status
21
+
22
+ from messagefoundry.auth import AuthProvider, Identity, Permission, Role
23
+ from messagefoundry.auth.service import AuthService
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+ # Identity used when auth is explicitly disabled via allow_no_auth (embedding/dev): full access.
28
+ _SYSTEM_IDENTITY = Identity.build(
29
+ user_id="system", username="system", auth_provider=AuthProvider.LOCAL, roles=list(Role)
30
+ )
31
+
32
+ # While an account is flagged to rotate its password, only these self-service routes stay reachable.
33
+ _MUST_CHANGE_EXEMPT_PATHS = frozenset({"/auth/logout", "/auth/me", "/me/password"})
34
+
35
+
36
+ def get_auth(request: Request) -> AuthService | None:
37
+ """The attached :class:`AuthService`, or ``None`` when auth is not configured."""
38
+ auth: AuthService | None = getattr(request.app.state, "auth", None)
39
+ return auth
40
+
41
+
42
+ def _allow_no_auth(app_state: object) -> bool:
43
+ """Whether this app explicitly opted out of auth (embedding/dev). Default: fail-closed."""
44
+ return bool(getattr(app_state, "allow_no_auth", False))
45
+
46
+
47
+ def bearer_token(request: Request) -> str | None:
48
+ """Extract a ``Bearer`` token from the Authorization header, if present."""
49
+ header = request.headers.get("Authorization", "")
50
+ if header.startswith("Bearer "):
51
+ return header[len("Bearer ") :].strip() or None
52
+ return None
53
+
54
+
55
+ def _client_ip(request: Request) -> str | None:
56
+ """The caller's client address, matching how login records it on the session (``_client`` in
57
+ ``auth_routes``). Used by the WP-L3-13 new-client-IP risk signal so the comparison is
58
+ apples-to-apples. (Forwarded-header resolution behind a trusted proxy is WP-15, not yet built.)"""
59
+ return request.client.host if request.client else None
60
+
61
+
62
+ def require(*permissions: Permission) -> Callable[[Request], Awaitable[Identity]]:
63
+ """Build a dependency that authenticates the caller and asserts each of ``permissions``."""
64
+
65
+ async def dependency(request: Request) -> Identity:
66
+ auth = get_auth(request)
67
+ if auth is None or not auth.enabled:
68
+ if _allow_no_auth(request.app.state):
69
+ return _SYSTEM_IDENTITY
70
+ raise HTTPException(
71
+ status.HTTP_503_SERVICE_UNAVAILABLE, "authentication is not configured"
72
+ )
73
+ identity = await auth.identity_for_token(bearer_token(request))
74
+ if identity is None:
75
+ raise HTTPException(status.HTTP_401_UNAUTHORIZED, "not authenticated")
76
+ if identity.must_change_password and request.url.path not in _MUST_CHANGE_EXEMPT_PATHS:
77
+ raise HTTPException(status.HTTP_403_FORBIDDEN, "password change required")
78
+ for permission in permissions:
79
+ if not identity.has(permission):
80
+ await auth.audit_permission_denied(identity, permission, request.url.path)
81
+ raise HTTPException(
82
+ status.HTTP_403_FORBIDDEN, f"missing permission: {permission.value}"
83
+ )
84
+ return identity
85
+
86
+ return dependency
87
+
88
+
89
+ def require_phi_read(*permissions: Permission) -> Callable[[Request], Awaitable[Identity]]:
90
+ """Like :func:`require`, plus a **per-actor anti-automation throttle** for the PHI-read endpoints
91
+ (`/messages`, `/messages/{id}`, `/dead-letters`) — bounds scripted PHI harvesting beyond the
92
+ pagination + access-audit controls (ASVS 2.4.1). A throttled read is **logged** (not silent) and
93
+ returns 429. No throttle on the embedding/no-auth path (there's no per-actor identity to key on)."""
94
+ base = require(*permissions)
95
+
96
+ async def dependency(request: Request) -> Identity:
97
+ identity = await base(request)
98
+ auth = get_auth(request)
99
+ if auth is not None and not auth.allow_phi_read(identity.user_id):
100
+ log.warning(
101
+ "PHI-read throttled (anti-automation): actor=%s path=%s",
102
+ identity.username,
103
+ request.url.path,
104
+ )
105
+ raise HTTPException(
106
+ status.HTTP_429_TOO_MANY_REQUESTS,
107
+ "too many requests; please slow down",
108
+ headers={"Retry-After": "10"},
109
+ )
110
+ return identity
111
+
112
+ return dependency
113
+
114
+
115
+ def require_step_up(*permissions: Permission) -> Callable[[Request], Awaitable[Identity]]:
116
+ """Like :func:`require`, plus **step-up re-verification** (ASVS 7.5.3): the caller's session must
117
+ have re-proved its credential — at login or via ``POST /me/reauth`` — within
118
+ ``[auth].step_up_max_age_seconds``. Gates the highly sensitive admin / replay / config flows; a
119
+ stale session is refused with 403 (the console then prompts to re-authenticate and retries). The
120
+ embedding/no-auth path is unaffected (there is no session to step up)."""
121
+ base = require(*permissions)
122
+
123
+ async def dependency(request: Request) -> Identity:
124
+ identity = await base(request)
125
+ auth = get_auth(request)
126
+ if auth is not None and auth.enabled:
127
+ token = bearer_token(request)
128
+ # Second factor first (WP-14, ASVS 6.3.3): an MFA-required session that has not verified
129
+ # its TOTP / recovery code cannot perform a sensitive op until it does. A distinct header
130
+ # tells the console to prompt for a code rather than a password reauth.
131
+ if not await auth.mfa_satisfied(token):
132
+ raise HTTPException(
133
+ status.HTTP_403_FORBIDDEN,
134
+ "multi-factor verification required; POST /auth/mfa-verify then retry",
135
+ headers={"X-MFA-Required": "1"},
136
+ )
137
+ # Contextual-risk layer (WP-L3-13, ASVS 8.4.2): a sensitive admin action from a client IP
138
+ # the session has not verified from forces a fresh step-up (and audits + notifies). A
139
+ # successful POST /me/reauth re-anchors the session to the new IP, so this then clears.
140
+ new_ip = await auth.flag_new_client_ip(
141
+ token, _client_ip(request), path=request.url.path
142
+ )
143
+ if new_ip or not await auth.has_recent_step_up(token):
144
+ raise HTTPException(
145
+ status.HTTP_403_FORBIDDEN,
146
+ "step-up re-verification required; POST /me/reauth then retry",
147
+ headers={"X-Step-Up-Required": "1"},
148
+ )
149
+ return identity
150
+
151
+ return dependency
152
+
153
+
154
+ def require_reauth_only(*permissions: Permission) -> Callable[[Request], Awaitable[Identity]]:
155
+ """Like :func:`require_step_up` but with **only** the password step-up — **not** the MFA gate.
156
+
157
+ Used by the MFA *enrollment* endpoints: a user enrolling their first second factor (or a
158
+ ``require_mfa`` administrator who has not enrolled yet) cannot satisfy an MFA gate, so a
159
+ :func:`require_step_up` there would deadlock. Re-proving the password still defends a stolen
160
+ session from silently enrolling an attacker-controlled authenticator (WP-14)."""
161
+ base = require(*permissions)
162
+
163
+ async def dependency(request: Request) -> Identity:
164
+ identity = await base(request)
165
+ auth = get_auth(request)
166
+ if auth is not None and auth.enabled:
167
+ token = bearer_token(request)
168
+ # Same new-client-IP contextual-risk layer as require_step_up (WP-L3-13); the MFA gate is
169
+ # intentionally skipped here (enrollment would otherwise deadlock — see the docstring).
170
+ new_ip = await auth.flag_new_client_ip(
171
+ token, _client_ip(request), path=request.url.path
172
+ )
173
+ if new_ip or not await auth.has_recent_step_up(token):
174
+ raise HTTPException(
175
+ status.HTTP_403_FORBIDDEN,
176
+ "step-up re-verification required; POST /me/reauth then retry",
177
+ headers={"X-Step-Up-Required": "1"},
178
+ )
179
+ return identity
180
+
181
+ return dependency
182
+
183
+
184
+ async def optional_identity(request: Request) -> Identity | None:
185
+ """Best-effort caller identity that **never raises** — for read-only, non-PHI endpoints (e.g.
186
+ ``GET /ai/policy``) that must answer even to a tokenless client, while still reporting the
187
+ caller's RBAC when a valid token is present.
188
+
189
+ Returns the full-access system identity when auth is disabled-with-``allow_no_auth`` (embedding/
190
+ dev); ``None`` when auth is unconfigured/fail-closed or the token is missing/invalid. The
191
+ ``must_change_password`` gate is intentionally *not* applied — this surfaces non-sensitive policy,
192
+ not PHI."""
193
+ auth = get_auth(request)
194
+ if auth is None or not auth.enabled:
195
+ return _SYSTEM_IDENTITY if _allow_no_auth(request.app.state) else None
196
+ return await auth.identity_for_token(bearer_token(request))
197
+
198
+
199
+ def ws_token(websocket: WebSocket) -> str | None:
200
+ """Extract a WebSocket bearer token from the Authorization header.
201
+
202
+ Header-only: the legacy ``?token=`` query-string fallback was removed because a session token in
203
+ a URL leaks into proxy/access logs and the Referer header (ASVS Session Management; API-3). The
204
+ console already sends the token via the ``Authorization`` header."""
205
+ header = websocket.headers.get("Authorization", "")
206
+ if header.startswith("Bearer "):
207
+ return header[len("Bearer ") :].strip() or None
208
+ return None
209
+
210
+
211
+ def _ws_origin_allowed(websocket: WebSocket) -> bool:
212
+ """Whether the WebSocket handshake's ``Origin`` is acceptable (ASVS 4.4.2).
213
+
214
+ A native (non-browser) client like the desktop console sends **no** ``Origin`` header — that is
215
+ allowed. A browser always sends one; it is allowed only if listed in ``[api].ws_allowed_origins``
216
+ (default empty → every browser Origin is rejected). This blocks cross-site WebSocket hijacking
217
+ at the handshake, before ``accept()``."""
218
+ origin = websocket.headers.get("origin")
219
+ if not origin:
220
+ return True # native client (no browser Origin) — the only shipped client
221
+ allowed = getattr(websocket.app.state, "ws_allowed_origins", ()) or ()
222
+ return origin in allowed
223
+
224
+
225
+ async def authorize_ws(websocket: WebSocket, *permissions: Permission) -> Identity | None:
226
+ """Authorize a WebSocket upgrade: validate the ``Origin`` (4.4.2), then the bearer token from the
227
+ Authorization header and the listed permissions.
228
+
229
+ Returns the :class:`Identity` on success, or ``None`` if auth fails (caller should close).
230
+ """
231
+ if not _ws_origin_allowed(websocket):
232
+ return None # cross-site / disallowed browser Origin — reject before accept()
233
+ auth: AuthService | None = getattr(websocket.app.state, "auth", None)
234
+ if auth is None or not auth.enabled:
235
+ return _SYSTEM_IDENTITY if _allow_no_auth(websocket.app.state) else None
236
+ identity = await auth.identity_for_token(ws_token(websocket))
237
+ if identity is None:
238
+ return None
239
+ if identity.must_change_password:
240
+ return None # a not-yet-rotated account is locked out of the WS too (mirrors require())
241
+ for permission in permissions:
242
+ if not identity.has(permission):
243
+ # Audit the denial like the HTTP require() path does, so a revoked/under-privileged
244
+ # user probing the stats feed leaves a trail too (review low-9).
245
+ await auth.audit_permission_denied(identity, permission, websocket.url.path)
246
+ return None
247
+ return identity
@@ -0,0 +1,47 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Copyright (C) 2026 MessageFoundry Organization and contributors
3
+ """In-process API / WebSocket TLS context (WP-13a, ADR 0002).
4
+
5
+ Builds the ``ssl.SSLContext`` uvicorn terminates the engine API + ``/ws/stats`` WebSocket with, from the
6
+ ``[api]`` ``tls_*`` settings. Pure stdlib ``ssl`` — no FastAPI/uvicorn import — so it is unit-testable in
7
+ isolation. The ``tls_min_version`` floor (NIST SP 800-52r2: 1.2+) is enforced via
8
+ ``SSLContext.minimum_version``; an encrypted key's passphrase comes from ``MEFOR_API_TLS_KEY_PASSWORD``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import ssl
14
+
15
+ from messagefoundry.config.settings import ApiSettings
16
+ from messagefoundry.config.tls_policy import harden_kex_groups, harden_verify_flags
17
+
18
+ __all__ = ["build_api_ssl_context"]
19
+
20
+ # Map the validated tls_min_version floor to the SSLContext minimum (TLS < 1.2 is never allowed).
21
+ _MIN_VERSION = {"1.2": ssl.TLSVersion.TLSv1_2, "1.3": ssl.TLSVersion.TLSv1_3}
22
+
23
+
24
+ def build_api_ssl_context(api: ApiSettings) -> ssl.SSLContext:
25
+ """Build the server ``SSLContext`` for the API listener from ``[api].tls_*``.
26
+
27
+ Requires ``api.tls_cert_file`` (the caller checks ``api.tls_enabled`` first). The private key may be
28
+ embedded in the cert PEM (``tls_key_file`` optional). mTLS is **opt-in**: when ``tls_client_ca_file``
29
+ is set, a client cert is **required** and verified against it (console mutual auth); otherwise no
30
+ client auth (the default)."""
31
+ if not api.tls_cert_file:
32
+ raise ValueError("build_api_ssl_context requires [api].tls_cert_file")
33
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
34
+ ctx.minimum_version = _MIN_VERSION[api.tls_min_version]
35
+ ctx.load_cert_chain(
36
+ certfile=api.tls_cert_file,
37
+ keyfile=api.tls_key_file,
38
+ password=api.tls_key_password,
39
+ )
40
+ if api.tls_ciphers:
41
+ ctx.set_ciphers(api.tls_ciphers)
42
+ harden_kex_groups(ctx) # pin approved ECDHE groups where the runtime supports it (ASVS 11.6.2)
43
+ harden_verify_flags(ctx) # strict RFC 5280 cert validation (ASVS 12.1.4)
44
+ if api.tls_client_ca_file:
45
+ ctx.load_verify_locations(cafile=api.tls_client_ca_file)
46
+ ctx.verify_mode = ssl.CERT_REQUIRED
47
+ return ctx
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ # Copyright (C) 2026 MessageFoundry Organization and contributors
3
+ """Authentication & RBAC core — provider-agnostic, with no FastAPI/Qt imports.
4
+
5
+ Pure building blocks the API layer composes: the permission catalog and fixed built-in roles
6
+ (:mod:`~messagefoundry.auth.permissions`), the resolved :class:`~messagefoundry.auth.identity.Identity`,
7
+ argon2id password hashing, the password/lockout policy, and opaque session tokens. Like ``store``,
8
+ this package is importable by ``api`` but never imports it (one-way dependency direction).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from messagefoundry.auth.identity import AuthProvider, Identity
14
+ from messagefoundry.auth.passwords import hash_password, needs_rehash, verify_password
15
+ from messagefoundry.auth.permissions import (
16
+ BUILTIN_ROLE_PERMISSIONS,
17
+ ROLE_METADATA,
18
+ Permission,
19
+ Role,
20
+ permissions_for_roles,
21
+ )
22
+ from messagefoundry.auth.policy import PasswordPolicy
23
+ from messagefoundry.auth.tokens import hash_token, mint_token
24
+
25
+ __all__ = [
26
+ "AuthProvider",
27
+ "Identity",
28
+ "Permission",
29
+ "Role",
30
+ "BUILTIN_ROLE_PERMISSIONS",
31
+ "ROLE_METADATA",
32
+ "permissions_for_roles",
33
+ "PasswordPolicy",
34
+ "hash_password",
35
+ "verify_password",
36
+ "needs_rehash",
37
+ "mint_token",
38
+ "hash_token",
39
+ ]
@@ -0,0 +1,13 @@
1
+ common_passwords.txt — offline common/breached-password screening corpus
2
+ =========================================================================
3
+
4
+ Source : SecLists — Passwords/Common-Credentials/Pwdb_top-10000.txt
5
+ https://github.com/danielmiessler/SecLists
6
+ License: MIT (SecLists is MIT-licensed; the underlying lists aggregate publicly
7
+ disclosed breach corpora). Redistributed here under that license.
8
+
9
+ Contents: the 10,000 most common passwords, one per line, used by
10
+ messagefoundry.auth.policy as an offline membership check (ASVS 6.2.4/6.2.12) so
11
+ a local password that is a known-common/breached value is rejected. No network
12
+ call (no live HIBP lookup); a fuller k-anonymity (SHA-1 prefix) corpus is a
13
+ planned follow-up. The file is data, never executed.