pyfastauth 0.1.0__tar.gz

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 (202) hide show
  1. pyfastauth-0.1.0/.gitignore +67 -0
  2. pyfastauth-0.1.0/CHANGELOG.md +253 -0
  3. pyfastauth-0.1.0/LICENSE +21 -0
  4. pyfastauth-0.1.0/PKG-INFO +359 -0
  5. pyfastauth-0.1.0/README.md +292 -0
  6. pyfastauth-0.1.0/SECURITY.md +143 -0
  7. pyfastauth-0.1.0/docs/DECISIONS.md +543 -0
  8. pyfastauth-0.1.0/docs/architecture.md +203 -0
  9. pyfastauth-0.1.0/docs/configuration.md +527 -0
  10. pyfastauth-0.1.0/docs/conformance.md +298 -0
  11. pyfastauth-0.1.0/docs/cookie-vs-bearer.md +314 -0
  12. pyfastauth-0.1.0/docs/databases-and-migrations.md +235 -0
  13. pyfastauth-0.1.0/docs/extending.md +359 -0
  14. pyfastauth-0.1.0/docs/mongodb.md +208 -0
  15. pyfastauth-0.1.0/docs/ownership.md +326 -0
  16. pyfastauth-0.1.0/docs/production-checklist.md +418 -0
  17. pyfastauth-0.1.0/docs/quickstart.md +243 -0
  18. pyfastauth-0.1.0/docs/roles-and-scopes.md +259 -0
  19. pyfastauth-0.1.0/docs/threat-model.md +498 -0
  20. pyfastauth-0.1.0/pyproject.toml +243 -0
  21. pyfastauth-0.1.0/src/fastauth/__init__.py +29 -0
  22. pyfastauth-0.1.0/src/fastauth/authorization/__init__.py +31 -0
  23. pyfastauth-0.1.0/src/fastauth/authorization/roles.py +210 -0
  24. pyfastauth-0.1.0/src/fastauth/authorization/scopes.py +165 -0
  25. pyfastauth-0.1.0/src/fastauth/cli/__init__.py +30 -0
  26. pyfastauth-0.1.0/src/fastauth/cli/_context.py +391 -0
  27. pyfastauth-0.1.0/src/fastauth/cli/app.py +67 -0
  28. pyfastauth-0.1.0/src/fastauth/cli/config_cmd.py +120 -0
  29. pyfastauth-0.1.0/src/fastauth/cli/database.py +120 -0
  30. pyfastauth-0.1.0/src/fastauth/cli/doctor.py +692 -0
  31. pyfastauth-0.1.0/src/fastauth/cli/init.py +825 -0
  32. pyfastauth-0.1.0/src/fastauth/cli/routes.py +98 -0
  33. pyfastauth-0.1.0/src/fastauth/cli/secret.py +70 -0
  34. pyfastauth-0.1.0/src/fastauth/cli/sessions.py +192 -0
  35. pyfastauth-0.1.0/src/fastauth/cli/users.py +319 -0
  36. pyfastauth-0.1.0/src/fastauth/clock.py +26 -0
  37. pyfastauth-0.1.0/src/fastauth/config/__init__.py +84 -0
  38. pyfastauth-0.1.0/src/fastauth/config/defaults.py +214 -0
  39. pyfastauth-0.1.0/src/fastauth/config/duration.py +126 -0
  40. pyfastauth-0.1.0/src/fastauth/config/environment.py +171 -0
  41. pyfastauth-0.1.0/src/fastauth/config/loader.py +167 -0
  42. pyfastauth-0.1.0/src/fastauth/config/models.py +717 -0
  43. pyfastauth-0.1.0/src/fastauth/config/ratelimit.py +96 -0
  44. pyfastauth-0.1.0/src/fastauth/config/validation.py +497 -0
  45. pyfastauth-0.1.0/src/fastauth/container.py +589 -0
  46. pyfastauth-0.1.0/src/fastauth/core.py +258 -0
  47. pyfastauth-0.1.0/src/fastauth/db/__init__.py +44 -0
  48. pyfastauth-0.1.0/src/fastauth/db/alembic.ini +90 -0
  49. pyfastauth-0.1.0/src/fastauth/db/base.py +92 -0
  50. pyfastauth-0.1.0/src/fastauth/db/engine.py +173 -0
  51. pyfastauth-0.1.0/src/fastauth/db/mappers.py +74 -0
  52. pyfastauth-0.1.0/src/fastauth/db/migration_runner.py +281 -0
  53. pyfastauth-0.1.0/src/fastauth/db/migrations/__init__.py +184 -0
  54. pyfastauth-0.1.0/src/fastauth/db/migrations/env.py +217 -0
  55. pyfastauth-0.1.0/src/fastauth/db/migrations/script.py.mako +53 -0
  56. pyfastauth-0.1.0/src/fastauth/db/migrations/versions/0001_initial_schema.py +158 -0
  57. pyfastauth-0.1.0/src/fastauth/db/models.py +223 -0
  58. pyfastauth-0.1.0/src/fastauth/db/stores/__init__.py +13 -0
  59. pyfastauth-0.1.0/src/fastauth/db/stores/session_store.py +279 -0
  60. pyfastauth-0.1.0/src/fastauth/db/stores/user_store.py +219 -0
  61. pyfastauth-0.1.0/src/fastauth/db/types.py +101 -0
  62. pyfastauth-0.1.0/src/fastauth/db/uow.py +118 -0
  63. pyfastauth-0.1.0/src/fastauth/dependencies.py +159 -0
  64. pyfastauth-0.1.0/src/fastauth/events.py +68 -0
  65. pyfastauth-0.1.0/src/fastauth/exceptions.py +271 -0
  66. pyfastauth-0.1.0/src/fastauth/installer.py +440 -0
  67. pyfastauth-0.1.0/src/fastauth/mongo/__init__.py +194 -0
  68. pyfastauth-0.1.0/src/fastauth/mongo/codec.py +104 -0
  69. pyfastauth-0.1.0/src/fastauth/mongo/database.py +98 -0
  70. pyfastauth-0.1.0/src/fastauth/mongo/documents.py +160 -0
  71. pyfastauth-0.1.0/src/fastauth/mongo/indexes.py +111 -0
  72. pyfastauth-0.1.0/src/fastauth/mongo/stores/__init__.py +13 -0
  73. pyfastauth-0.1.0/src/fastauth/mongo/stores/session_store.py +291 -0
  74. pyfastauth-0.1.0/src/fastauth/mongo/stores/user_store.py +310 -0
  75. pyfastauth-0.1.0/src/fastauth/ownership/__init__.py +45 -0
  76. pyfastauth-0.1.0/src/fastauth/ownership/_predicates.py +144 -0
  77. pyfastauth-0.1.0/src/fastauth/ownership/admin.py +163 -0
  78. pyfastauth-0.1.0/src/fastauth/ownership/mixins.py +120 -0
  79. pyfastauth-0.1.0/src/fastauth/ownership/repository.py +429 -0
  80. pyfastauth-0.1.0/src/fastauth/protocols/__init__.py +42 -0
  81. pyfastauth-0.1.0/src/fastauth/protocols/clock.py +22 -0
  82. pyfastauth-0.1.0/src/fastauth/protocols/events.py +28 -0
  83. pyfastauth-0.1.0/src/fastauth/protocols/passwords.py +35 -0
  84. pyfastauth-0.1.0/src/fastauth/protocols/rate_limit.py +25 -0
  85. pyfastauth-0.1.0/src/fastauth/protocols/sessions.py +76 -0
  86. pyfastauth-0.1.0/src/fastauth/protocols/templates.py +25 -0
  87. pyfastauth-0.1.0/src/fastauth/protocols/tokens.py +66 -0
  88. pyfastauth-0.1.0/src/fastauth/protocols/transport.py +49 -0
  89. pyfastauth-0.1.0/src/fastauth/protocols/uow.py +88 -0
  90. pyfastauth-0.1.0/src/fastauth/protocols/users.py +75 -0
  91. pyfastauth-0.1.0/src/fastauth/py.typed +0 -0
  92. pyfastauth-0.1.0/src/fastauth/routes/__init__.py +1 -0
  93. pyfastauth-0.1.0/src/fastauth/routes/api.py +592 -0
  94. pyfastauth-0.1.0/src/fastauth/routes/errors.py +239 -0
  95. pyfastauth-0.1.0/src/fastauth/routes/schemas.py +256 -0
  96. pyfastauth-0.1.0/src/fastauth/routes/table.py +59 -0
  97. pyfastauth-0.1.0/src/fastauth/routes/ui.py +771 -0
  98. pyfastauth-0.1.0/src/fastauth/security/__init__.py +51 -0
  99. pyfastauth-0.1.0/src/fastauth/security/cookies.py +133 -0
  100. pyfastauth-0.1.0/src/fastauth/security/csrf.py +135 -0
  101. pyfastauth-0.1.0/src/fastauth/security/emails.py +106 -0
  102. pyfastauth-0.1.0/src/fastauth/security/headers.py +144 -0
  103. pyfastauth-0.1.0/src/fastauth/security/passwords.py +183 -0
  104. pyfastauth-0.1.0/src/fastauth/security/rate_limit.py +127 -0
  105. pyfastauth-0.1.0/src/fastauth/security/redirects.py +157 -0
  106. pyfastauth-0.1.0/src/fastauth/security/secrets.py +314 -0
  107. pyfastauth-0.1.0/src/fastauth/security/tokens.py +367 -0
  108. pyfastauth-0.1.0/src/fastauth/services/__init__.py +47 -0
  109. pyfastauth-0.1.0/src/fastauth/services/access.py +222 -0
  110. pyfastauth-0.1.0/src/fastauth/services/credentials.py +125 -0
  111. pyfastauth-0.1.0/src/fastauth/services/event_bus.py +125 -0
  112. pyfastauth-0.1.0/src/fastauth/services/flows/__init__.py +23 -0
  113. pyfastauth-0.1.0/src/fastauth/services/flows/login.py +238 -0
  114. pyfastauth-0.1.0/src/fastauth/services/flows/logout.py +120 -0
  115. pyfastauth-0.1.0/src/fastauth/services/flows/refresh.py +214 -0
  116. pyfastauth-0.1.0/src/fastauth/services/flows/signup.py +216 -0
  117. pyfastauth-0.1.0/src/fastauth/services/identity.py +134 -0
  118. pyfastauth-0.1.0/src/fastauth/services/sessions.py +305 -0
  119. pyfastauth-0.1.0/src/fastauth/services/tokens.py +274 -0
  120. pyfastauth-0.1.0/src/fastauth/testing/__init__.py +69 -0
  121. pyfastauth-0.1.0/src/fastauth/testing/clock.py +70 -0
  122. pyfastauth-0.1.0/src/fastauth/testing/sessions.py +498 -0
  123. pyfastauth-0.1.0/src/fastauth/testing/users.py +384 -0
  124. pyfastauth-0.1.0/src/fastauth/transport/__init__.py +15 -0
  125. pyfastauth-0.1.0/src/fastauth/transport/extractors.py +117 -0
  126. pyfastauth-0.1.0/src/fastauth/types.py +284 -0
  127. pyfastauth-0.1.0/src/fastauth/ui/__init__.py +15 -0
  128. pyfastauth-0.1.0/src/fastauth/ui/renderer.py +265 -0
  129. pyfastauth-0.1.0/src/fastauth/ui/static/fastauth.css +409 -0
  130. pyfastauth-0.1.0/src/fastauth/ui/templates/account.html +119 -0
  131. pyfastauth-0.1.0/src/fastauth/ui/templates/base.html +44 -0
  132. pyfastauth-0.1.0/src/fastauth/ui/templates/error.html +37 -0
  133. pyfastauth-0.1.0/src/fastauth/ui/templates/login.html +63 -0
  134. pyfastauth-0.1.0/src/fastauth/ui/templates/signup.html +107 -0
  135. pyfastauth-0.1.0/tests/__init__.py +0 -0
  136. pyfastauth-0.1.0/tests/cli/__init__.py +1 -0
  137. pyfastauth-0.1.0/tests/cli/test_config_and_secret.py +207 -0
  138. pyfastauth-0.1.0/tests/cli/test_database.py +177 -0
  139. pyfastauth-0.1.0/tests/cli/test_doctor.py +545 -0
  140. pyfastauth-0.1.0/tests/cli/test_init.py +328 -0
  141. pyfastauth-0.1.0/tests/cli/test_routes_and_version.py +192 -0
  142. pyfastauth-0.1.0/tests/cli/test_sessions.py +296 -0
  143. pyfastauth-0.1.0/tests/cli/test_users.py +285 -0
  144. pyfastauth-0.1.0/tests/fakes/__init__.py +28 -0
  145. pyfastauth-0.1.0/tests/fakes/clock.py +33 -0
  146. pyfastauth-0.1.0/tests/fakes/events.py +44 -0
  147. pyfastauth-0.1.0/tests/fakes/rate_limit.py +36 -0
  148. pyfastauth-0.1.0/tests/fakes/stores.py +278 -0
  149. pyfastauth-0.1.0/tests/fixtures/__init__.py +1 -0
  150. pyfastauth-0.1.0/tests/integration/__init__.py +1 -0
  151. pyfastauth-0.1.0/tests/integration/test_api_routes.py +520 -0
  152. pyfastauth-0.1.0/tests/integration/test_csp_scope.py +137 -0
  153. pyfastauth-0.1.0/tests/integration/test_dialect_sql.py +312 -0
  154. pyfastauth-0.1.0/tests/integration/test_engine_override.py +119 -0
  155. pyfastauth-0.1.0/tests/integration/test_installer.py +373 -0
  156. pyfastauth-0.1.0/tests/integration/test_migrations.py +692 -0
  157. pyfastauth-0.1.0/tests/integration/test_mongo_stores.py +665 -0
  158. pyfastauth-0.1.0/tests/integration/test_openapi.py +187 -0
  159. pyfastauth-0.1.0/tests/integration/test_session_store.py +185 -0
  160. pyfastauth-0.1.0/tests/integration/test_store_conformance_kit.py +139 -0
  161. pyfastauth-0.1.0/tests/integration/test_ui_routes.py +383 -0
  162. pyfastauth-0.1.0/tests/integration/test_user_store.py +159 -0
  163. pyfastauth-0.1.0/tests/packaging/__init__.py +1 -0
  164. pyfastauth-0.1.0/tests/packaging/test_examples_import.py +136 -0
  165. pyfastauth-0.1.0/tests/packaging/test_wheel_contents.py +155 -0
  166. pyfastauth-0.1.0/tests/security/__init__.py +0 -0
  167. pyfastauth-0.1.0/tests/security/test_csrf_lifecycle.py +153 -0
  168. pyfastauth-0.1.0/tests/security/test_ownership.py +623 -0
  169. pyfastauth-0.1.0/tests/security/test_ownership_sql.py +268 -0
  170. pyfastauth-0.1.0/tests/ui/__init__.py +117 -0
  171. pyfastauth-0.1.0/tests/ui/test_renderer.py +211 -0
  172. pyfastauth-0.1.0/tests/ui/test_templates.py +246 -0
  173. pyfastauth-0.1.0/tests/unit/__init__.py +1 -0
  174. pyfastauth-0.1.0/tests/unit/test_component_overrides.py +392 -0
  175. pyfastauth-0.1.0/tests/unit/test_config_duration.py +176 -0
  176. pyfastauth-0.1.0/tests/unit/test_config_environment.py +179 -0
  177. pyfastauth-0.1.0/tests/unit/test_config_loader.py +513 -0
  178. pyfastauth-0.1.0/tests/unit/test_config_validation.py +394 -0
  179. pyfastauth-0.1.0/tests/unit/test_cookies.py +159 -0
  180. pyfastauth-0.1.0/tests/unit/test_csrf.py +118 -0
  181. pyfastauth-0.1.0/tests/unit/test_db_types.py +248 -0
  182. pyfastauth-0.1.0/tests/unit/test_emails.py +121 -0
  183. pyfastauth-0.1.0/tests/unit/test_event_bus.py +209 -0
  184. pyfastauth-0.1.0/tests/unit/test_extractors.py +137 -0
  185. pyfastauth-0.1.0/tests/unit/test_flow_login.py +336 -0
  186. pyfastauth-0.1.0/tests/unit/test_flow_logout.py +192 -0
  187. pyfastauth-0.1.0/tests/unit/test_flow_refresh.py +345 -0
  188. pyfastauth-0.1.0/tests/unit/test_flow_signup.py +263 -0
  189. pyfastauth-0.1.0/tests/unit/test_headers.py +126 -0
  190. pyfastauth-0.1.0/tests/unit/test_jwt.py +385 -0
  191. pyfastauth-0.1.0/tests/unit/test_passwords.py +190 -0
  192. pyfastauth-0.1.0/tests/unit/test_rate_limit.py +168 -0
  193. pyfastauth-0.1.0/tests/unit/test_redirects.py +168 -0
  194. pyfastauth-0.1.0/tests/unit/test_refresh_tokens.py +171 -0
  195. pyfastauth-0.1.0/tests/unit/test_roles.py +293 -0
  196. pyfastauth-0.1.0/tests/unit/test_scopes.py +252 -0
  197. pyfastauth-0.1.0/tests/unit/test_secrets.py +231 -0
  198. pyfastauth-0.1.0/tests/unit/test_service_access.py +511 -0
  199. pyfastauth-0.1.0/tests/unit/test_service_credentials.py +197 -0
  200. pyfastauth-0.1.0/tests/unit/test_service_identity.py +208 -0
  201. pyfastauth-0.1.0/tests/unit/test_service_sessions.py +334 -0
  202. pyfastauth-0.1.0/tests/unit/test_service_tokens.py +292 -0
@@ -0,0 +1,67 @@
1
+ # NOTE ON ANCHORING
2
+ # Hatchling's file selection is VCS-aware and honours this file. An unanchored
3
+ # pattern such as `data/` or `versions/` would silently strip packaged assets
4
+ # (templates, static CSS, Alembic revisions) out of the built wheel with no
5
+ # warning. Every pattern below that could collide with a package directory is
6
+ # therefore anchored to the repo root with a leading slash.
7
+
8
+ # --- Python -----------------------------------------------------------------
9
+ __pycache__/
10
+ *.py[cod]
11
+ *$py.class
12
+ *.so
13
+ .Python
14
+ /build/
15
+ /dist/
16
+ /wheels/
17
+ *.egg-info/
18
+ .eggs/
19
+ /.venv/
20
+ /venv/
21
+
22
+ # --- Tooling caches ---------------------------------------------------------
23
+ .pytest_cache/
24
+ .mypy_cache/
25
+ .ruff_cache/
26
+ .coverage
27
+ .coverage.*
28
+ /htmlcov/
29
+ /coverage.xml
30
+ .hypothesis/
31
+
32
+ # --- Secrets and local config (spec 6.2) ------------------------------------
33
+ .env
34
+ .env.*
35
+ !.env.example
36
+ *.secret
37
+ /secrets/
38
+
39
+ # --- Local databases (spec 6.2) ---------------------------------------------
40
+ # Anchored: must not match src/fastauth/db/
41
+ /data/
42
+ *.db
43
+ *.db-journal
44
+ *.db-wal
45
+ *.db-shm
46
+ *.sqlite
47
+ *.sqlite3
48
+
49
+ # --- OS / editor ------------------------------------------------------------
50
+ .DS_Store
51
+ .idea/
52
+ *.swp
53
+
54
+ # --- Project ----------------------------------------------------------------
55
+ # The owner's private specification document. It is the rationale behind every
56
+ # design decision here, but it is not ours to publish: it must never reach a
57
+ # public commit, a GitHub push or a PyPI sdist. The rationale that belongs in
58
+ # the open lives in docs/DECISIONS.md instead.
59
+ #
60
+ # Anchored to the repo root, per the note at the top of this file. `text-*.txt`
61
+ # unanchored would also match any future text-*.txt fixture anywhere under
62
+ # src/fastauth/ and silently drop it from the wheel; the leading slash confines
63
+ # the pattern to this one file at this one location.
64
+ /text-2BF8422D3F8F-1.txt
65
+
66
+ # JUnit reports written by the CI database-coverage guard.
67
+ /junit-*.xml
@@ -0,0 +1,253 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and the project follows [semantic versioning](https://semver.org/).
7
+
8
+ While the version is `0.x`, a **minor** version may contain breaking changes;
9
+ they are listed under a `Breaking` heading and never appear in a patch release.
10
+ The meaning of an `auth.yaml` key never changes silently — a format change
11
+ raises `schema_version`, and a version this release does not recognise refuses
12
+ to boot rather than being interpreted optimistically.
13
+
14
+ ## [Unreleased]
15
+
16
+ Nothing yet.
17
+
18
+ ## [0.1.0] — 2026-08-21
19
+
20
+ First release. Alpha: usable and tested, but the API may still move.
21
+
22
+ ### Added
23
+
24
+ **Integration**
25
+
26
+ - `FastAuth(config="auth.yaml")` and `auth.install(app)` — the whole
27
+ integration. Installing twice raises `AlreadyInstalledError`; a path that
28
+ already exists on the host application raises `RouteCollisionError` before
29
+ anything is mounted.
30
+ - Public API: `AuthConfig`, `AuthContext`, `AuthUser`, `FastAuth` from
31
+ `fastauth`; `OwnedModelMixin`, `OwnedRepository`, `SystemRepository` from
32
+ `fastauth.ownership`. `py.typed`, `mypy --strict` clean.
33
+
34
+ **Routes** — 16 declared, mounted according to configuration
35
+
36
+ - HTML: `GET`/`POST /auth/login`, `GET`/`POST /auth/signup`,
37
+ `GET /auth/account`, `POST /auth/logout`, `POST /auth/logout-all`.
38
+ - JSON: `POST /auth/api/signup`, `POST /auth/api/login`, `POST /auth/token`,
39
+ `POST /auth/refresh`, `POST /auth/api/logout`, `POST /auth/api/logout-all`,
40
+ `GET /auth/me`, `GET /auth/sessions`,
41
+ `DELETE /auth/sessions/{session_id}`.
42
+
43
+ **Authentication**
44
+
45
+ - Argon2 password hashing via `pwdlib`, with transparent rehash when the
46
+ policy tightens and a configurable length policy.
47
+ - HS256 JWT access tokens with issuer, audience, type, token-version and
48
+ clock-skew validation, and an algorithm allowlist of exactly one entry.
49
+ - Opaque refresh tokens backed by a server-side session row, rotated on every
50
+ use, stored only as keyed HMAC fingerprints, with reuse detection.
51
+ - Sliding session expiry with an absolute ceiling
52
+ (`jwt.session_absolute_ttl`, default 90 days).
53
+ - Cookie and bearer transports, both accepted on a request, with the
54
+ `Authorization` header taking precedence.
55
+ - Logout, and logout-all with token-version invalidation.
56
+
57
+ **Authorization**
58
+
59
+ - Roles and scopes declared in `auth.yaml`, with inheritance and cycle
60
+ detection at load time.
61
+ - Wildcard scope matching: `contracts:*`, `*:read`, and a global `*`.
62
+ Mid-string wildcards are rejected at configuration load.
63
+ - `auth.current_user`, `auth.optional_user`, `auth.current_context`,
64
+ `auth.require_scopes(...)` and `auth.protected_router(...)`.
65
+ - 401 for an absent or invalid credential, 403 for a valid credential without
66
+ the required scope.
67
+
68
+ **Ownership**
69
+
70
+ - `OwnedModelMixin` adds a non-null, indexed `owner_id` column.
71
+ - `OwnedRepository` with `create_for_owner`, `list_for_owner`,
72
+ `get_for_owner`, `update_for_owner`, `delete_for_owner` and
73
+ `count_for_owner`. There is no unscoped variant of any of them.
74
+ - Foreign-owned and nonexistent rows are indistinguishable in every method.
75
+ - `SystemRepository.list_all_as_system(session, reason=...)` as the single,
76
+ audited escape hatch.
77
+
78
+ **Security**
79
+
80
+ - HKDF key ring: one key per purpose, derived from `jwt.secret`.
81
+ - Signed, session-bound double-submit CSRF tokens, rotated at login against
82
+ fixation, enforced on cookie-authenticated requests only.
83
+ - Open-redirect defence with a strict allowlist for `?next=`.
84
+ - Security-header middleware, including a `script-src 'none'` CSP, which never
85
+ overwrites a header the host application set.
86
+ - Sliding-window rate limiting on login by IP and by identifier hash, signup
87
+ by IP, and refresh by session. No permanent lockout.
88
+ - `X-Forwarded-For` honoured only from a configured `server.trusted_proxies`.
89
+ - Production hardening rules refuse to boot on an insecure configuration,
90
+ including a non-null `identity.bootstrap.first_user_role`.
91
+
92
+ **UI**
93
+
94
+ - Server-rendered login, signup, account and error pages with a bundled
95
+ stylesheet. No JavaScript, no build step, and overridable template and
96
+ static directories.
97
+
98
+ **Database**
99
+
100
+ - Async SQLAlchemy 2.x over SQLite and PostgreSQL, with a UUID column type
101
+ that is native on PostgreSQL, and timezone-aware datetimes enforced at the
102
+ column boundary.
103
+ - Bundled Alembic migrations. The version table carries the configured table
104
+ prefix so it cannot collide with the host application's own Alembic state.
105
+
106
+ **Substituted persistence**
107
+
108
+ - `FastAuth(user_store=…, session_store=…)` replaces the bundled SQLAlchemy
109
+ stores. The composition root wires the pair into the `UnitOfWork` that every
110
+ service already takes, so the service, route and authorization layers run
111
+ unchanged on a foreign backend (D-30).
112
+ - Substituting **both** builds no SQL engine at all, and `database.url` becomes
113
+ optional — an application on a non-relational backend is not asked to invent a
114
+ SQLite URL for a database nobody reads, nor to hold a pool open for the
115
+ process lifetime. Everything engine-shaped then raises `ConfigurationError`
116
+ naming the cause: `session_dependency`, `AuthComponents.engine`, and the
117
+ `fastauth db`, `users` and `sessions` commands. `dispose()` is the deliberate
118
+ exception, because "nothing to close" is a successful shutdown, and
119
+ `fastauth doctor` reports its four database checks as warnings rather than
120
+ failing a correct deployment.
121
+ - Substituting only one keeps the engine; the unit of work then spans two
122
+ backends and `commit()` acts on the SQL half only. Supported, and documented
123
+ as the weakest of the three configurations.
124
+ - A backend with no transactions is explicitly supported: `commit()` may be a
125
+ no-op, "user plus roles" is one write through `UserStore.create(roles=…)`, and
126
+ multi-write service operations are ordered to fail safe.
127
+ - `AuthComponents.engine` is a property returning the engine provider, or
128
+ raising `ConfigurationError` when there is none; the nullable provider lives
129
+ on a new `sql_engine` field. Returning `None` would have surfaced as an
130
+ `AttributeError` several frames away from the constructor argument that caused
131
+ it. Anyone tracking `main` before this first release should read
132
+ `components.sql_engine` where they previously read a nullable
133
+ `components.engine`.
134
+
135
+ **MongoDB backend** — `pip install "pyfastauth[mongo]"`
136
+
137
+ - `fastauth.mongo.build_mongo_stores(...)` accepts a connection string or an
138
+ already-open `AsyncIOMotorDatabase` and returns a `MongoBackend` carrying a
139
+ `MongoUserStore`, a `MongoSessionStore`, the collection handles, and the
140
+ client if this package opened one.
141
+ - Roles are an array on the user document, so creating a user with its roles is
142
+ a single atomic `insertOne`; refresh rotation is a single-document
143
+ compare-and-swap, a `findOneAndUpdate` whose filter carries both the expected
144
+ fingerprint and `revoked_at: null`. Nothing in the backend spans two documents
145
+ atomically, so it needs no multi-document transactions and therefore **no
146
+ replica set** — a standalone `mongod` is enough (D-32).
147
+ - MongoDB has no DDL, so `fastauth db upgrade` is meaningless here. Its
148
+ replacement is `await create_indexes(backend.collections)`, run once per
149
+ deployment. It is not optional: the unique index on `email` *is* the
150
+ account-identity rule, and without it two concurrent signups for one address
151
+ produce two accounts.
152
+ - Timestamps are truncated to BSON's millisecond resolution on write, so a
153
+ `create()` and a later `get()` agree, and truncated rather than rounded so a
154
+ derived `expires_at` can only ever move earlier (G-15).
155
+ - See [`docs/mongodb.md`](docs/mongodb.md).
156
+
157
+ **Store conformance kit** — `pip install "pyfastauth[testing]"`
158
+
159
+ - `fastauth.testing` ships `UserStoreConformance` and `SessionStoreConformance`
160
+ as executable specifications of the two persistence protocols, so a third
161
+ party can prove a MongoDB, DynamoDB, Firestore or Redis backend correct
162
+ without this project's involvement (D-31). Subclass, supply a `store` fixture,
163
+ run pytest.
164
+ - The suites cover the semantics that fail silently when implemented wrongly:
165
+ duplicate-email detection that must not check-then-insert, a rotation that
166
+ must be a real compare-and-swap, a second `revoke` that must not overwrite the
167
+ first one's audit trail.
168
+ - `tests/integration/` inherits the same suites, so the bundled SQLAlchemy store
169
+ is validated by exactly the suite a third party runs and the two cannot drift.
170
+ The kit itself imports no SQLAlchemy, enforced by an import-linter contract
171
+ and a static import-closure test.
172
+ - `MutableClock`, `FIXED_NOW`, and the rotation-race seams `ConcurrentRotations`
173
+ and `SameStoreRotations` are public. `honours_injected_clock` is the single
174
+ opt-out, for a backend whose timestamps are minted server-side; everything
175
+ else is required.
176
+ - `pytest` therefore becomes an import of shipped code. It is confined to this
177
+ subpackage, nothing under `fastauth` imports the subpackage, and it is
178
+ declared as the `testing` extra rather than a dependency, so a production
179
+ install never sees it.
180
+ - See [`docs/conformance.md`](docs/conformance.md).
181
+
182
+ **CLI**
183
+
184
+ - `fastauth init | config validate | secret generate | doctor |
185
+ db current|history|upgrade|downgrade | users
186
+ create|list|grant-role|revoke-role|activate|deactivate | sessions
187
+ list|revoke|purge-expired | routes | version`.
188
+ - `--config/-c` defaults to `./auth.yaml` and is overridable with
189
+ `$FASTAUTH_CONFIG`. Exit `0` on success, `1` on a check or validation
190
+ failure, `2` on a usage error.
191
+
192
+ **Observability**
193
+
194
+ - Twelve auditable events with an `@auth.on(...)` decorator. Handlers run
195
+ after commit and a failing handler is logged and swallowed.
196
+ - Structured logging with field redaction and request-id correlation.
197
+
198
+ - `fastauth doctor` now cross-checks the cookie attributes against `app.base_url`
199
+ and `server.auth_mount_path`: `transport.cookie.scheme`, `.domain` and `.path`
200
+ catch the configurations where login returns 200 and every request after it
201
+ fails with `missing_token`. `secure: true` over `http://` was previously
202
+ reported as a clean pass.
203
+
204
+ ### Security
205
+
206
+ - **`logout-all` failed open on a backend without transactions.**
207
+ `SessionService.revoke_all` revoked the user's sessions and *then* bumped
208
+ `token_version`. Under SQL both writes share one transaction and the order is
209
+ immaterial, but a substituted store may have no multi-document transaction at
210
+ all — and there, a crash between the two writes left the sessions revoked
211
+ while every outstanding access JWT stayed cryptographically valid until its
212
+ own expiry. That is the opposite of what "log out everywhere" has to mean.
213
+ The order is now reversed: `token_version` is bumped first, so an interrupted
214
+ logout-all over-invalidates (tokens rejected, some session rows surviving)
215
+ instead of under-invalidating. Found while implementing D-30; no released
216
+ version was affected, because this is the first release.
217
+
218
+ ### Known limitations
219
+
220
+ Documented, not defects. See [`docs/threat-model.md`](docs/threat-model.md).
221
+
222
+ - The rate limiter is in-process: under *N* workers the effective limit is *N*
223
+ times the configured one. `fastauth doctor` warns when this combination is
224
+ used in production.
225
+ - No multi-factor authentication, no email verification, no password reset, no
226
+ social login, no organisations, no admin dashboard.
227
+ - One signing secret, and no key-rotation mechanism.
228
+ - `SessionRecord.ip_hash` exists in the schema but is never populated; IP
229
+ hashing is disabled by default, so the column is permanently `NULL` (G-02 in
230
+ `docs/DECISIONS.md`).
231
+
232
+ ### Deviations from the originating specification
233
+
234
+ Each is deliberate and recorded in [`docs/DECISIONS.md`](docs/DECISIONS.md).
235
+
236
+ - Refresh tokens are **opaque**, not JWTs (D-02).
237
+ - Sessions **slide** on refresh, bounded by `jwt.session_absolute_ttl` — a
238
+ configuration key the specification does not define (D-03).
239
+ - Refresh reuse revokes **only the offending session**, not the whole family
240
+ (D-04).
241
+ - **Both** transports are accepted on any request, with the header winning; CSRF
242
+ is enforced from how *this* request authenticated, never from configuration
243
+ (D-05).
244
+ - `identity.bootstrap.first_user_role` is **fatal** in production (D-07).
245
+ - `POST /auth/token` is mounted by default **only in bearer mode** (D-08).
246
+ - Exception classes carry an `Error` suffix per PEP 8
247
+ (`InvalidCredentialsError`). The machine-readable `code` values on the wire
248
+ are unchanged and match specification section 9.3 exactly (D-22).
249
+ - Added configuration keys not in the specification: `jwt.session_absolute_ttl`
250
+ (D-03), `server.trusted_proxies` (D-18), `sessions.retention`.
251
+
252
+ [Unreleased]: https://github.com/Aadik1ng/fastauth/compare/v0.1.0...HEAD
253
+ [0.1.0]: https://github.com/Aadik1ng/fastauth/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aadi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.