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.
- pyfastauth-0.1.0/.gitignore +67 -0
- pyfastauth-0.1.0/CHANGELOG.md +253 -0
- pyfastauth-0.1.0/LICENSE +21 -0
- pyfastauth-0.1.0/PKG-INFO +359 -0
- pyfastauth-0.1.0/README.md +292 -0
- pyfastauth-0.1.0/SECURITY.md +143 -0
- pyfastauth-0.1.0/docs/DECISIONS.md +543 -0
- pyfastauth-0.1.0/docs/architecture.md +203 -0
- pyfastauth-0.1.0/docs/configuration.md +527 -0
- pyfastauth-0.1.0/docs/conformance.md +298 -0
- pyfastauth-0.1.0/docs/cookie-vs-bearer.md +314 -0
- pyfastauth-0.1.0/docs/databases-and-migrations.md +235 -0
- pyfastauth-0.1.0/docs/extending.md +359 -0
- pyfastauth-0.1.0/docs/mongodb.md +208 -0
- pyfastauth-0.1.0/docs/ownership.md +326 -0
- pyfastauth-0.1.0/docs/production-checklist.md +418 -0
- pyfastauth-0.1.0/docs/quickstart.md +243 -0
- pyfastauth-0.1.0/docs/roles-and-scopes.md +259 -0
- pyfastauth-0.1.0/docs/threat-model.md +498 -0
- pyfastauth-0.1.0/pyproject.toml +243 -0
- pyfastauth-0.1.0/src/fastauth/__init__.py +29 -0
- pyfastauth-0.1.0/src/fastauth/authorization/__init__.py +31 -0
- pyfastauth-0.1.0/src/fastauth/authorization/roles.py +210 -0
- pyfastauth-0.1.0/src/fastauth/authorization/scopes.py +165 -0
- pyfastauth-0.1.0/src/fastauth/cli/__init__.py +30 -0
- pyfastauth-0.1.0/src/fastauth/cli/_context.py +391 -0
- pyfastauth-0.1.0/src/fastauth/cli/app.py +67 -0
- pyfastauth-0.1.0/src/fastauth/cli/config_cmd.py +120 -0
- pyfastauth-0.1.0/src/fastauth/cli/database.py +120 -0
- pyfastauth-0.1.0/src/fastauth/cli/doctor.py +692 -0
- pyfastauth-0.1.0/src/fastauth/cli/init.py +825 -0
- pyfastauth-0.1.0/src/fastauth/cli/routes.py +98 -0
- pyfastauth-0.1.0/src/fastauth/cli/secret.py +70 -0
- pyfastauth-0.1.0/src/fastauth/cli/sessions.py +192 -0
- pyfastauth-0.1.0/src/fastauth/cli/users.py +319 -0
- pyfastauth-0.1.0/src/fastauth/clock.py +26 -0
- pyfastauth-0.1.0/src/fastauth/config/__init__.py +84 -0
- pyfastauth-0.1.0/src/fastauth/config/defaults.py +214 -0
- pyfastauth-0.1.0/src/fastauth/config/duration.py +126 -0
- pyfastauth-0.1.0/src/fastauth/config/environment.py +171 -0
- pyfastauth-0.1.0/src/fastauth/config/loader.py +167 -0
- pyfastauth-0.1.0/src/fastauth/config/models.py +717 -0
- pyfastauth-0.1.0/src/fastauth/config/ratelimit.py +96 -0
- pyfastauth-0.1.0/src/fastauth/config/validation.py +497 -0
- pyfastauth-0.1.0/src/fastauth/container.py +589 -0
- pyfastauth-0.1.0/src/fastauth/core.py +258 -0
- pyfastauth-0.1.0/src/fastauth/db/__init__.py +44 -0
- pyfastauth-0.1.0/src/fastauth/db/alembic.ini +90 -0
- pyfastauth-0.1.0/src/fastauth/db/base.py +92 -0
- pyfastauth-0.1.0/src/fastauth/db/engine.py +173 -0
- pyfastauth-0.1.0/src/fastauth/db/mappers.py +74 -0
- pyfastauth-0.1.0/src/fastauth/db/migration_runner.py +281 -0
- pyfastauth-0.1.0/src/fastauth/db/migrations/__init__.py +184 -0
- pyfastauth-0.1.0/src/fastauth/db/migrations/env.py +217 -0
- pyfastauth-0.1.0/src/fastauth/db/migrations/script.py.mako +53 -0
- pyfastauth-0.1.0/src/fastauth/db/migrations/versions/0001_initial_schema.py +158 -0
- pyfastauth-0.1.0/src/fastauth/db/models.py +223 -0
- pyfastauth-0.1.0/src/fastauth/db/stores/__init__.py +13 -0
- pyfastauth-0.1.0/src/fastauth/db/stores/session_store.py +279 -0
- pyfastauth-0.1.0/src/fastauth/db/stores/user_store.py +219 -0
- pyfastauth-0.1.0/src/fastauth/db/types.py +101 -0
- pyfastauth-0.1.0/src/fastauth/db/uow.py +118 -0
- pyfastauth-0.1.0/src/fastauth/dependencies.py +159 -0
- pyfastauth-0.1.0/src/fastauth/events.py +68 -0
- pyfastauth-0.1.0/src/fastauth/exceptions.py +271 -0
- pyfastauth-0.1.0/src/fastauth/installer.py +440 -0
- pyfastauth-0.1.0/src/fastauth/mongo/__init__.py +194 -0
- pyfastauth-0.1.0/src/fastauth/mongo/codec.py +104 -0
- pyfastauth-0.1.0/src/fastauth/mongo/database.py +98 -0
- pyfastauth-0.1.0/src/fastauth/mongo/documents.py +160 -0
- pyfastauth-0.1.0/src/fastauth/mongo/indexes.py +111 -0
- pyfastauth-0.1.0/src/fastauth/mongo/stores/__init__.py +13 -0
- pyfastauth-0.1.0/src/fastauth/mongo/stores/session_store.py +291 -0
- pyfastauth-0.1.0/src/fastauth/mongo/stores/user_store.py +310 -0
- pyfastauth-0.1.0/src/fastauth/ownership/__init__.py +45 -0
- pyfastauth-0.1.0/src/fastauth/ownership/_predicates.py +144 -0
- pyfastauth-0.1.0/src/fastauth/ownership/admin.py +163 -0
- pyfastauth-0.1.0/src/fastauth/ownership/mixins.py +120 -0
- pyfastauth-0.1.0/src/fastauth/ownership/repository.py +429 -0
- pyfastauth-0.1.0/src/fastauth/protocols/__init__.py +42 -0
- pyfastauth-0.1.0/src/fastauth/protocols/clock.py +22 -0
- pyfastauth-0.1.0/src/fastauth/protocols/events.py +28 -0
- pyfastauth-0.1.0/src/fastauth/protocols/passwords.py +35 -0
- pyfastauth-0.1.0/src/fastauth/protocols/rate_limit.py +25 -0
- pyfastauth-0.1.0/src/fastauth/protocols/sessions.py +76 -0
- pyfastauth-0.1.0/src/fastauth/protocols/templates.py +25 -0
- pyfastauth-0.1.0/src/fastauth/protocols/tokens.py +66 -0
- pyfastauth-0.1.0/src/fastauth/protocols/transport.py +49 -0
- pyfastauth-0.1.0/src/fastauth/protocols/uow.py +88 -0
- pyfastauth-0.1.0/src/fastauth/protocols/users.py +75 -0
- pyfastauth-0.1.0/src/fastauth/py.typed +0 -0
- pyfastauth-0.1.0/src/fastauth/routes/__init__.py +1 -0
- pyfastauth-0.1.0/src/fastauth/routes/api.py +592 -0
- pyfastauth-0.1.0/src/fastauth/routes/errors.py +239 -0
- pyfastauth-0.1.0/src/fastauth/routes/schemas.py +256 -0
- pyfastauth-0.1.0/src/fastauth/routes/table.py +59 -0
- pyfastauth-0.1.0/src/fastauth/routes/ui.py +771 -0
- pyfastauth-0.1.0/src/fastauth/security/__init__.py +51 -0
- pyfastauth-0.1.0/src/fastauth/security/cookies.py +133 -0
- pyfastauth-0.1.0/src/fastauth/security/csrf.py +135 -0
- pyfastauth-0.1.0/src/fastauth/security/emails.py +106 -0
- pyfastauth-0.1.0/src/fastauth/security/headers.py +144 -0
- pyfastauth-0.1.0/src/fastauth/security/passwords.py +183 -0
- pyfastauth-0.1.0/src/fastauth/security/rate_limit.py +127 -0
- pyfastauth-0.1.0/src/fastauth/security/redirects.py +157 -0
- pyfastauth-0.1.0/src/fastauth/security/secrets.py +314 -0
- pyfastauth-0.1.0/src/fastauth/security/tokens.py +367 -0
- pyfastauth-0.1.0/src/fastauth/services/__init__.py +47 -0
- pyfastauth-0.1.0/src/fastauth/services/access.py +222 -0
- pyfastauth-0.1.0/src/fastauth/services/credentials.py +125 -0
- pyfastauth-0.1.0/src/fastauth/services/event_bus.py +125 -0
- pyfastauth-0.1.0/src/fastauth/services/flows/__init__.py +23 -0
- pyfastauth-0.1.0/src/fastauth/services/flows/login.py +238 -0
- pyfastauth-0.1.0/src/fastauth/services/flows/logout.py +120 -0
- pyfastauth-0.1.0/src/fastauth/services/flows/refresh.py +214 -0
- pyfastauth-0.1.0/src/fastauth/services/flows/signup.py +216 -0
- pyfastauth-0.1.0/src/fastauth/services/identity.py +134 -0
- pyfastauth-0.1.0/src/fastauth/services/sessions.py +305 -0
- pyfastauth-0.1.0/src/fastauth/services/tokens.py +274 -0
- pyfastauth-0.1.0/src/fastauth/testing/__init__.py +69 -0
- pyfastauth-0.1.0/src/fastauth/testing/clock.py +70 -0
- pyfastauth-0.1.0/src/fastauth/testing/sessions.py +498 -0
- pyfastauth-0.1.0/src/fastauth/testing/users.py +384 -0
- pyfastauth-0.1.0/src/fastauth/transport/__init__.py +15 -0
- pyfastauth-0.1.0/src/fastauth/transport/extractors.py +117 -0
- pyfastauth-0.1.0/src/fastauth/types.py +284 -0
- pyfastauth-0.1.0/src/fastauth/ui/__init__.py +15 -0
- pyfastauth-0.1.0/src/fastauth/ui/renderer.py +265 -0
- pyfastauth-0.1.0/src/fastauth/ui/static/fastauth.css +409 -0
- pyfastauth-0.1.0/src/fastauth/ui/templates/account.html +119 -0
- pyfastauth-0.1.0/src/fastauth/ui/templates/base.html +44 -0
- pyfastauth-0.1.0/src/fastauth/ui/templates/error.html +37 -0
- pyfastauth-0.1.0/src/fastauth/ui/templates/login.html +63 -0
- pyfastauth-0.1.0/src/fastauth/ui/templates/signup.html +107 -0
- pyfastauth-0.1.0/tests/__init__.py +0 -0
- pyfastauth-0.1.0/tests/cli/__init__.py +1 -0
- pyfastauth-0.1.0/tests/cli/test_config_and_secret.py +207 -0
- pyfastauth-0.1.0/tests/cli/test_database.py +177 -0
- pyfastauth-0.1.0/tests/cli/test_doctor.py +545 -0
- pyfastauth-0.1.0/tests/cli/test_init.py +328 -0
- pyfastauth-0.1.0/tests/cli/test_routes_and_version.py +192 -0
- pyfastauth-0.1.0/tests/cli/test_sessions.py +296 -0
- pyfastauth-0.1.0/tests/cli/test_users.py +285 -0
- pyfastauth-0.1.0/tests/fakes/__init__.py +28 -0
- pyfastauth-0.1.0/tests/fakes/clock.py +33 -0
- pyfastauth-0.1.0/tests/fakes/events.py +44 -0
- pyfastauth-0.1.0/tests/fakes/rate_limit.py +36 -0
- pyfastauth-0.1.0/tests/fakes/stores.py +278 -0
- pyfastauth-0.1.0/tests/fixtures/__init__.py +1 -0
- pyfastauth-0.1.0/tests/integration/__init__.py +1 -0
- pyfastauth-0.1.0/tests/integration/test_api_routes.py +520 -0
- pyfastauth-0.1.0/tests/integration/test_csp_scope.py +137 -0
- pyfastauth-0.1.0/tests/integration/test_dialect_sql.py +312 -0
- pyfastauth-0.1.0/tests/integration/test_engine_override.py +119 -0
- pyfastauth-0.1.0/tests/integration/test_installer.py +373 -0
- pyfastauth-0.1.0/tests/integration/test_migrations.py +692 -0
- pyfastauth-0.1.0/tests/integration/test_mongo_stores.py +665 -0
- pyfastauth-0.1.0/tests/integration/test_openapi.py +187 -0
- pyfastauth-0.1.0/tests/integration/test_session_store.py +185 -0
- pyfastauth-0.1.0/tests/integration/test_store_conformance_kit.py +139 -0
- pyfastauth-0.1.0/tests/integration/test_ui_routes.py +383 -0
- pyfastauth-0.1.0/tests/integration/test_user_store.py +159 -0
- pyfastauth-0.1.0/tests/packaging/__init__.py +1 -0
- pyfastauth-0.1.0/tests/packaging/test_examples_import.py +136 -0
- pyfastauth-0.1.0/tests/packaging/test_wheel_contents.py +155 -0
- pyfastauth-0.1.0/tests/security/__init__.py +0 -0
- pyfastauth-0.1.0/tests/security/test_csrf_lifecycle.py +153 -0
- pyfastauth-0.1.0/tests/security/test_ownership.py +623 -0
- pyfastauth-0.1.0/tests/security/test_ownership_sql.py +268 -0
- pyfastauth-0.1.0/tests/ui/__init__.py +117 -0
- pyfastauth-0.1.0/tests/ui/test_renderer.py +211 -0
- pyfastauth-0.1.0/tests/ui/test_templates.py +246 -0
- pyfastauth-0.1.0/tests/unit/__init__.py +1 -0
- pyfastauth-0.1.0/tests/unit/test_component_overrides.py +392 -0
- pyfastauth-0.1.0/tests/unit/test_config_duration.py +176 -0
- pyfastauth-0.1.0/tests/unit/test_config_environment.py +179 -0
- pyfastauth-0.1.0/tests/unit/test_config_loader.py +513 -0
- pyfastauth-0.1.0/tests/unit/test_config_validation.py +394 -0
- pyfastauth-0.1.0/tests/unit/test_cookies.py +159 -0
- pyfastauth-0.1.0/tests/unit/test_csrf.py +118 -0
- pyfastauth-0.1.0/tests/unit/test_db_types.py +248 -0
- pyfastauth-0.1.0/tests/unit/test_emails.py +121 -0
- pyfastauth-0.1.0/tests/unit/test_event_bus.py +209 -0
- pyfastauth-0.1.0/tests/unit/test_extractors.py +137 -0
- pyfastauth-0.1.0/tests/unit/test_flow_login.py +336 -0
- pyfastauth-0.1.0/tests/unit/test_flow_logout.py +192 -0
- pyfastauth-0.1.0/tests/unit/test_flow_refresh.py +345 -0
- pyfastauth-0.1.0/tests/unit/test_flow_signup.py +263 -0
- pyfastauth-0.1.0/tests/unit/test_headers.py +126 -0
- pyfastauth-0.1.0/tests/unit/test_jwt.py +385 -0
- pyfastauth-0.1.0/tests/unit/test_passwords.py +190 -0
- pyfastauth-0.1.0/tests/unit/test_rate_limit.py +168 -0
- pyfastauth-0.1.0/tests/unit/test_redirects.py +168 -0
- pyfastauth-0.1.0/tests/unit/test_refresh_tokens.py +171 -0
- pyfastauth-0.1.0/tests/unit/test_roles.py +293 -0
- pyfastauth-0.1.0/tests/unit/test_scopes.py +252 -0
- pyfastauth-0.1.0/tests/unit/test_secrets.py +231 -0
- pyfastauth-0.1.0/tests/unit/test_service_access.py +511 -0
- pyfastauth-0.1.0/tests/unit/test_service_credentials.py +197 -0
- pyfastauth-0.1.0/tests/unit/test_service_identity.py +208 -0
- pyfastauth-0.1.0/tests/unit/test_service_sessions.py +334 -0
- 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
|
pyfastauth-0.1.0/LICENSE
ADDED
|
@@ -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.
|