fastapi-fullauth 0.17.1__tar.gz → 0.17.2__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.
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/CHANGELOG.md +8 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/PKG-INFO +1 -1
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/configuration.md +5 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/__init__.py +1 -1
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/config.py +22 -13
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/protection/challenges.py +7 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/protection/ratelimit.py +16 -2
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_config.py +48 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_passkey.py +22 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_security.py +56 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/.github/pull_request_template.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/.github/workflows/ci.yml +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/.github/workflows/docs.yml +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/.github/workflows/publish.yml +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/.gitignore +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/.python-version +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/AGENTS.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/CONTRIBUTING.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/LICENSE +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/Makefile +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/README.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/SECURITY.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/adapters/beanie.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/adapters/custom.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/adapters/index.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/adapters/sqlalchemy.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/adapters/sqlmodel.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/adapters/tortoise.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/api-reference.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/architecture.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/auth/custom-claims.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/auth/dependencies.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/auth/hooks.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/auth/passwords.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/auth/sessions.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/contributing.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/customization.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/frontend-integration.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/getting-started.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/index.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/migrating-from-fastapi-users.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/migrations.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/oauth.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/passkeys.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/recipes.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/security/middleware.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/security/rate-limiting.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/security/threat-model.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/stylesheets/home.css +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/testing.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/docs/troubleshooting.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/beanie_app/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/beanie_app/auth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/beanie_app/config.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/beanie_app/main.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/beanie_app/models.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/beanie_app/routes.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlalchemy_app/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlalchemy_app/auth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlalchemy_app/config.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlalchemy_app/main.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlalchemy_app/models.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlalchemy_app/routes.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlmodel_app/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlmodel_app/auth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlmodel_app/config.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlmodel_app/main.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlmodel_app/models.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/sqlmodel_app/routes.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/tortoise_app/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/tortoise_app/auth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/tortoise_app/config.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/tortoise_app/main.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/tortoise_app/models.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/examples/tortoise_app/routes.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/adapters/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/adapters/_sqlalchemy_base.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/adapters/base.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/adapters/beanie.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/adapters/sqlalchemy.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/adapters/sqlmodel.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/adapters/tortoise.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/backends/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/backends/base.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/backends/bearer.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/backends/cookie.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/cli.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/core/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/core/_redis.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/core/blacklist.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/core/crypto.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/core/tokens.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/dependencies/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/dependencies/current_user.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/dependencies/rbac.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/exceptions.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/change_password.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/credentials.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/email_verify.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/login.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/logout.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/oauth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/passkey.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/password_reset.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/profile.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/reauth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/refresh.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/register.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/sessions.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/tokens.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/fullauth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/hooks.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/middleware/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/middleware/csrf.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/middleware/ratelimit.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/middleware/security_headers.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/_common.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/base.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/oauth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/passkey.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/permission.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/role.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/base.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/oauth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/passkey.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/permission.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/role.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/base.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/oauth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/passkey.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/permission.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/role.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/_common.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/base.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/oauth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/passkey.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/permission.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/role.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/oauth/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/oauth/base.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/oauth/discord.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/oauth/github.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/oauth/gitlab.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/oauth/google.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/protection/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/protection/lockout.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/py.typed +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/_schemas.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/_transport.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/admin.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/auth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/oauth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/passkey.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/profile.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/sessions.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/routers/verify.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/types.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/utils.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/validators.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/mkdocs.yml +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/pyproject.toml +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/scripts/llmstxt_preprocess.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/SKILL.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/adapters.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/api-reference.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/composable-design.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/getting-started.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/hooks.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/migrations.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/oauth.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/passkeys.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/production.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/rbac.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/sessions.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/testing.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/troubleshooting.md +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/__init__.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/adapter_conformance.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/conftest.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_account_safety.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_auth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_beanie_adapter.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_cli.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_cookie_backend.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_crypto.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_hooks.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_oauth.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_passkey_flows.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_polish.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_profile.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_rbac.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_session_revocation.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_sessions.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_sqlalchemy_adapter.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_sqlmodel_adapter.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_tokens.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_tortoise_adapter.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_tortoise_int_keys.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/tests/test_user_id_types.py +0 -0
- {fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/uv.lock +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.2
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The Redis challenge store raised on a non-positive TTL** where the in-memory one wrote an entry that was simply already expired. With `PASSKEY_CHALLENGE_TTL` set to `0`, beginning a passkey sign-in answered `500` on Redis, on a route that needs no account. Both backends now agree: nothing is stored and the next `pop()` returns `None`.
|
|
8
|
+
- **Numeric settings reject values that cannot work.** Durations, expiries, attempt counts and rate-limit caps are validated at construction, so a mistake fails at startup instead of somewhere unrelated later: `MAX_LOGIN_ATTEMPTS=0` locked every account on its first failed login, and `PASSKEY_CHALLENGE_TTL=0` raised from Redis mid-request. The three settings that document a meaningful zero keep it: `JWT_LEEWAY_SECONDS`, `PASSWORD_MAX_LENGTH` and `REAUTH_MAX_AGE_SECONDS`. **If your configuration holds one of these impossible values, the application now refuses to start and says which setting.**
|
|
9
|
+
- **Auth rate limits shared one bucket per IP on Redis.** Each route gets its own limiter with its own cap, but the key was the client IP alone, and every Redis-backed limiter writes into the same keyspace, so all six auth routes counted into a single sorted set. The smallest cap won for all of them: with `register` at 3, any three auth requests, a token refresh or a failed login included, returned `429` on the next registration attempt for the rest of the window. It also broke the separation `reauth` was given in 0.17.0, where failed password checks were meant not to touch the sign-in budget. The key now carries the route name. Only Redis was affected; the in-memory backend keeps its counts per limiter instance, which is why the test suite never saw it, and why the new regression test runs against both backends. **Note:** the key format changed, so existing counters reset once on deploy.
|
|
10
|
+
|
|
3
11
|
## 0.17.1
|
|
4
12
|
|
|
5
13
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fastapi-fullauth
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.2
|
|
4
4
|
Summary: Production-grade, async-native authentication and authorization library for FastAPI
|
|
5
5
|
Project-URL: Changelog, https://github.com/mdfarhankc/fastapi-fullauth/blob/main/CHANGELOG.md
|
|
6
6
|
Project-URL: Documentation, https://mdfarhankc.github.io/fastapi-fullauth
|
|
@@ -124,6 +124,11 @@ If you want to be defensively explicit that no file is ever read, pass `FullAuth
|
|
|
124
124
|
|
|
125
125
|
## Reference
|
|
126
126
|
|
|
127
|
+
Durations, expiries, attempt counts and rate-limit caps must be at least 1, and a
|
|
128
|
+
value that cannot work is rejected when the config is constructed rather than
|
|
129
|
+
failing somewhere unrelated later. `JWT_LEEWAY_SECONDS`, `PASSWORD_MAX_LENGTH`
|
|
130
|
+
and `REAUTH_MAX_AGE_SECONDS` accept `0`, which means something for each of them.
|
|
131
|
+
|
|
127
132
|
### Core
|
|
128
133
|
|
|
129
134
|
| Option | Type | Default | Description |
|
|
@@ -40,6 +40,14 @@ class AuthRateLimits:
|
|
|
40
40
|
# bucket, so failed attempts cannot exhaust an IP's sign-in budget.
|
|
41
41
|
reauth: int = 5
|
|
42
42
|
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
for name, value in vars(self).items():
|
|
45
|
+
if value < 1:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"AUTH_RATE_LIMITS.{name} must be at least 1; "
|
|
48
|
+
f"{value} would reject every request to that route."
|
|
49
|
+
)
|
|
50
|
+
|
|
43
51
|
|
|
44
52
|
class FullAuthConfig(BaseSettings):
|
|
45
53
|
model_config = SettingsConfigDict(
|
|
@@ -61,19 +69,20 @@ class FullAuthConfig(BaseSettings):
|
|
|
61
69
|
SECRET_KEY: str | None = None
|
|
62
70
|
ALGORITHM: Literal["HS256", "HS384", "HS512"] = "HS256"
|
|
63
71
|
|
|
64
|
-
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
|
65
|
-
REFRESH_TOKEN_EXPIRE_DAYS: int = 30
|
|
72
|
+
ACCESS_TOKEN_EXPIRE_MINUTES: int = Field(default=30, ge=1)
|
|
73
|
+
REFRESH_TOKEN_EXPIRE_DAYS: int = Field(default=30, ge=1)
|
|
66
74
|
REFRESH_TOKEN_ROTATION: bool = True
|
|
67
|
-
JWT_LEEWAY_SECONDS: int = 30
|
|
75
|
+
JWT_LEEWAY_SECONDS: int = Field(default=30, ge=0)
|
|
68
76
|
|
|
69
|
-
PASSWORD_RESET_EXPIRE_MINUTES: int = 15
|
|
70
|
-
EMAIL_VERIFY_EXPIRE_MINUTES: int = 1440
|
|
77
|
+
PASSWORD_RESET_EXPIRE_MINUTES: int = Field(default=15, ge=1)
|
|
78
|
+
EMAIL_VERIFY_EXPIRE_MINUTES: int = Field(default=1440, ge=1)
|
|
71
79
|
|
|
72
80
|
PASSWORD_HASH_ALGORITHM: Literal["argon2id", "bcrypt"] = "argon2id"
|
|
73
|
-
PASSWORD_MIN_LENGTH: int = 8
|
|
81
|
+
PASSWORD_MIN_LENGTH: int = Field(default=8, ge=1)
|
|
74
82
|
# Upper bound so an attacker can't spend the server's CPU hashing a huge
|
|
75
83
|
# body. bcrypt caps at 72 bytes anyway; argon2 will hash whatever it is given.
|
|
76
|
-
|
|
84
|
+
# 0 disables the cap, hence ge=0 rather than ge=1.
|
|
85
|
+
PASSWORD_MAX_LENGTH: int = Field(default=4096, ge=0)
|
|
77
86
|
|
|
78
87
|
LOGIN_FIELD: str = "email"
|
|
79
88
|
# When True, login runs a dummy password verify on unknown-user / no-password
|
|
@@ -86,12 +95,12 @@ class FullAuthConfig(BaseSettings):
|
|
|
86
95
|
# destructive action (deleting the account, setting a first password).
|
|
87
96
|
# Supplying the current password proves it too. 0 accepts only the password,
|
|
88
97
|
# which locks passwordless accounts out of those actions.
|
|
89
|
-
REAUTH_MAX_AGE_SECONDS: int = 300
|
|
98
|
+
REAUTH_MAX_AGE_SECONDS: int = Field(default=300, ge=0)
|
|
90
99
|
|
|
91
100
|
LOCKOUT_ENABLED: bool = True
|
|
92
101
|
LOCKOUT_BACKEND: Literal["memory", "redis"] = "memory"
|
|
93
|
-
MAX_LOGIN_ATTEMPTS: int = 5
|
|
94
|
-
LOCKOUT_DURATION_MINUTES: int = 15
|
|
102
|
+
MAX_LOGIN_ATTEMPTS: int = Field(default=5, ge=1)
|
|
103
|
+
LOCKOUT_DURATION_MINUTES: int = Field(default=15, ge=1)
|
|
95
104
|
|
|
96
105
|
RATE_LIMIT_BACKEND: Literal["memory", "redis"] = "memory"
|
|
97
106
|
TRUSTED_PROXY_HEADERS: Annotated[list[str], NoDecode] = []
|
|
@@ -104,14 +113,14 @@ class FullAuthConfig(BaseSettings):
|
|
|
104
113
|
|
|
105
114
|
AUTH_RATE_LIMIT_ENABLED: bool = True
|
|
106
115
|
AUTH_RATE_LIMITS: AuthRateLimits = Field(default_factory=AuthRateLimits)
|
|
107
|
-
AUTH_RATE_LIMIT_WINDOW_SECONDS: int = 60
|
|
116
|
+
AUTH_RATE_LIMIT_WINDOW_SECONDS: int = Field(default=60, ge=1)
|
|
108
117
|
|
|
109
118
|
REDIS_URL: str | None = None
|
|
110
119
|
|
|
111
120
|
BLACKLIST_ENABLED: bool = True
|
|
112
121
|
BLACKLIST_BACKEND: Literal["memory", "redis"] = "memory"
|
|
113
122
|
|
|
114
|
-
OAUTH_STATE_EXPIRE_SECONDS: int = 300
|
|
123
|
+
OAUTH_STATE_EXPIRE_SECONDS: int = Field(default=300, ge=1)
|
|
115
124
|
OAUTH_AUTO_LINK_BY_EMAIL: bool = True
|
|
116
125
|
OAUTH_PKCE_ENABLED: bool = True
|
|
117
126
|
|
|
@@ -124,7 +133,7 @@ class FullAuthConfig(BaseSettings):
|
|
|
124
133
|
PASSKEY_RP_NAME: str | None = None
|
|
125
134
|
PASSKEY_ORIGINS: Annotated[list[str], NoDecode] = []
|
|
126
135
|
PASSKEY_CHALLENGE_BACKEND: Literal["memory", "redis"] = "memory"
|
|
127
|
-
PASSKEY_CHALLENGE_TTL: int = 60
|
|
136
|
+
PASSKEY_CHALLENGE_TTL: int = Field(default=60, ge=1)
|
|
128
137
|
PASSKEY_REQUIRE_USER_VERIFICATION: bool = True
|
|
129
138
|
|
|
130
139
|
API_PREFIX: str = "/api/v1"
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/protection/challenges.py
RENAMED
|
@@ -82,6 +82,13 @@ class RedisChallengeStore(ChallengeStore):
|
|
|
82
82
|
self._prefix = "fullauth:challenge:"
|
|
83
83
|
|
|
84
84
|
async def store(self, key: str, challenge: str, ttl: int = 60) -> None:
|
|
85
|
+
# Redis rejects a non-positive expiry outright, where the in-memory store
|
|
86
|
+
# just writes an entry that is already expired. Skip the write instead,
|
|
87
|
+
# so both backends answer None to the next pop() rather than one of them
|
|
88
|
+
# raising into a 500 on a route that needs no account.
|
|
89
|
+
if ttl <= 0:
|
|
90
|
+
logger.warning("Challenge not stored; ttl=%s is not positive", ttl)
|
|
91
|
+
return
|
|
85
92
|
await self._redis.set(f"{self._prefix}{key}", challenge, ex=ttl)
|
|
86
93
|
|
|
87
94
|
async def pop(self, key: str) -> str | None:
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/protection/ratelimit.py
RENAMED
|
@@ -257,10 +257,24 @@ class AuthRateLimiter:
|
|
|
257
257
|
|
|
258
258
|
async def check(self, route_name: str, client_ip: str) -> None:
|
|
259
259
|
limiter = self._limiters.get(route_name)
|
|
260
|
-
if limiter
|
|
260
|
+
if limiter is None:
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
# The route belongs in the key, not just in the limiter lookup. Every
|
|
264
|
+
# Redis-backed limiter shares one keyspace, so keying on the IP alone
|
|
265
|
+
# put all six routes in a single bucket and the smallest cap, register
|
|
266
|
+
# at 3, became the effective limit for all of them: three refreshes
|
|
267
|
+
# locked out registration for a minute. The in-memory backend keeps its
|
|
268
|
+
# counts per instance, which is why only Redis showed it. Namespacing
|
|
269
|
+
# here rather than inside a backend keeps custom backends working
|
|
270
|
+
# unchanged, and keeps these buckets clear of any limiter the
|
|
271
|
+
# application passes to RateLimitMiddleware.
|
|
272
|
+
key = f"{route_name}:{client_ip}"
|
|
273
|
+
|
|
274
|
+
if not await limiter.is_allowed(key):
|
|
261
275
|
from fastapi import HTTPException
|
|
262
276
|
|
|
263
|
-
reset_in = await limiter.reset_time(
|
|
277
|
+
reset_in = await limiter.reset_time(key)
|
|
264
278
|
logger.warning("Auth rate limit exceeded: route=%s, ip=%s", route_name, client_ip)
|
|
265
279
|
raise HTTPException(
|
|
266
280
|
status_code=429,
|
|
@@ -7,6 +7,7 @@ import warnings
|
|
|
7
7
|
import pytest
|
|
8
8
|
from fastapi import FastAPI, Request
|
|
9
9
|
from httpx import ASGITransport, AsyncClient
|
|
10
|
+
from pydantic import ValidationError
|
|
10
11
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
11
12
|
from sqlmodel import SQLModel
|
|
12
13
|
|
|
@@ -15,6 +16,8 @@ from fastapi_fullauth.dependencies import current_user
|
|
|
15
16
|
from fastapi_fullauth.types import CreateUserSchema, UserSchema
|
|
16
17
|
from tests.conftest import make_test_adapter
|
|
17
18
|
|
|
19
|
+
SECRET = "test-secret-key-that-is-long-enough-32b"
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
async def _make_db():
|
|
20
23
|
engine = create_async_engine("sqlite+aiosqlite://", echo=False)
|
|
@@ -974,3 +977,48 @@ async def test_cookie_backend_with_csrf_no_warning(config, adapter):
|
|
|
974
977
|
with warnings.catch_warnings():
|
|
975
978
|
warnings.simplefilter("error")
|
|
976
979
|
fa.init_app(app)
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
# ── Numeric settings reject values that cannot work ──────────────────
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
@pytest.mark.parametrize(
|
|
986
|
+
"setting",
|
|
987
|
+
[
|
|
988
|
+
"ACCESS_TOKEN_EXPIRE_MINUTES",
|
|
989
|
+
"REFRESH_TOKEN_EXPIRE_DAYS",
|
|
990
|
+
"PASSWORD_RESET_EXPIRE_MINUTES",
|
|
991
|
+
"EMAIL_VERIFY_EXPIRE_MINUTES",
|
|
992
|
+
"MAX_LOGIN_ATTEMPTS",
|
|
993
|
+
"LOCKOUT_DURATION_MINUTES",
|
|
994
|
+
"AUTH_RATE_LIMIT_WINDOW_SECONDS",
|
|
995
|
+
"OAUTH_STATE_EXPIRE_SECONDS",
|
|
996
|
+
"PASSKEY_CHALLENGE_TTL",
|
|
997
|
+
],
|
|
998
|
+
)
|
|
999
|
+
def test_non_positive_durations_and_counts_are_rejected(setting):
|
|
1000
|
+
"""Zero or negative here is never meaningful, and the failure it causes is
|
|
1001
|
+
remote from the cause: PASSKEY_CHALLENGE_TTL=0 raised from Redis on a public
|
|
1002
|
+
route, and MAX_LOGIN_ATTEMPTS=0 locked every account on its first failure.
|
|
1003
|
+
Fail at construction instead."""
|
|
1004
|
+
with pytest.raises(ValidationError):
|
|
1005
|
+
FullAuthConfig(SECRET_KEY=SECRET, **{setting: 0})
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
def test_the_meaningful_zeros_are_still_accepted():
|
|
1009
|
+
"""Three settings document zero as a real choice, so they must keep it."""
|
|
1010
|
+
config = FullAuthConfig(
|
|
1011
|
+
SECRET_KEY=SECRET,
|
|
1012
|
+
JWT_LEEWAY_SECONDS=0, # clock-sensitive tests rely on this
|
|
1013
|
+
PASSWORD_MAX_LENGTH=0, # disables the cap
|
|
1014
|
+
REAUTH_MAX_AGE_SECONDS=0, # accept only the current password
|
|
1015
|
+
)
|
|
1016
|
+
assert config.JWT_LEEWAY_SECONDS == 0
|
|
1017
|
+
assert config.PASSWORD_MAX_LENGTH == 0
|
|
1018
|
+
assert config.REAUTH_MAX_AGE_SECONDS == 0
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
def test_a_rate_limit_cap_below_one_is_rejected():
|
|
1022
|
+
"""A cap of 0 rejects every request to that route, which nobody means."""
|
|
1023
|
+
with pytest.raises(ValueError, match="at least 1"):
|
|
1024
|
+
AuthRateLimits(login=0)
|
|
@@ -278,3 +278,25 @@ async def test_challenge_store_evicts_challenges_that_were_never_used():
|
|
|
278
278
|
await store.store("fresh", "c", ttl=60)
|
|
279
279
|
|
|
280
280
|
assert list(store._store) == ["fresh"]
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@pytest.mark.asyncio
|
|
284
|
+
async def test_challenge_stores_agree_on_a_non_positive_ttl():
|
|
285
|
+
"""Redis rejects a non-positive expiry where the in-memory store writes an
|
|
286
|
+
entry that is already expired. Production picks the Redis backend whenever
|
|
287
|
+
REDIS_URL is set, so a divergence here is a 500 on a route that needs no
|
|
288
|
+
account, reachable by setting PASSKEY_CHALLENGE_TTL badly."""
|
|
289
|
+
import fakeredis.aioredis
|
|
290
|
+
|
|
291
|
+
from fastapi_fullauth.protection.challenges import RedisChallengeStore
|
|
292
|
+
|
|
293
|
+
memory = InMemoryChallengeStore()
|
|
294
|
+
redis = RedisChallengeStore.__new__(RedisChallengeStore)
|
|
295
|
+
redis._redis = fakeredis.aioredis.FakeRedis(decode_responses=True)
|
|
296
|
+
redis._prefix = "fullauth:challenge:"
|
|
297
|
+
|
|
298
|
+
for ttl in (0, -5):
|
|
299
|
+
await memory.store("k", "challenge", ttl=ttl)
|
|
300
|
+
await redis.store("k", "challenge", ttl=ttl)
|
|
301
|
+
assert await memory.pop("k") is None
|
|
302
|
+
assert await redis.pop("k") is None
|
|
@@ -790,3 +790,59 @@ async def test_in_memory_sweep_never_drops_live_entries():
|
|
|
790
790
|
await blacklist.add("trigger", ttl_seconds=10)
|
|
791
791
|
assert await blacklist.is_blacklisted("long-lived")
|
|
792
792
|
assert "short" not in blacklist._blacklisted
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
# ── Auth rate limits are per route ───────────────────────────────────
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _auth_limiter(backend: str):
|
|
799
|
+
"""An AuthRateLimiter on the given backend, with Redis faked in place.
|
|
800
|
+
|
|
801
|
+
The Redis limiters are built by the real code path, then pointed at one
|
|
802
|
+
shared fake server, which is what makes a cross-route collision visible.
|
|
803
|
+
"""
|
|
804
|
+
from fastapi_fullauth import FullAuthConfig
|
|
805
|
+
from fastapi_fullauth.protection.ratelimit import AuthRateLimiter
|
|
806
|
+
|
|
807
|
+
kwargs = {"SECRET_KEY": "test-secret-key-that-is-long-enough-32b", "BACKEND": backend}
|
|
808
|
+
if backend == "redis":
|
|
809
|
+
kwargs["REDIS_URL"] = "redis://rate-limit-test:6379/0"
|
|
810
|
+
limiter = AuthRateLimiter(FullAuthConfig(**kwargs))
|
|
811
|
+
|
|
812
|
+
if backend == "redis":
|
|
813
|
+
import fakeredis.aioredis
|
|
814
|
+
|
|
815
|
+
server = fakeredis.aioredis.FakeServer()
|
|
816
|
+
for each in limiter._limiters.values():
|
|
817
|
+
each._redis = fakeredis.aioredis.FakeRedis(server=server, decode_responses=True)
|
|
818
|
+
return limiter
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
@pytest.mark.parametrize("backend", ["memory", "redis"])
|
|
822
|
+
@pytest.mark.asyncio
|
|
823
|
+
async def test_auth_routes_do_not_share_a_rate_limit_bucket(backend):
|
|
824
|
+
"""Every Redis limiter shares one keyspace, so the key has to carry the
|
|
825
|
+
route. Keyed on the IP alone, all six routes counted into one bucket and the
|
|
826
|
+
smallest cap won: three refreshes locked out registration. The in-memory
|
|
827
|
+
backend counts per instance and never showed it, hence both backends here."""
|
|
828
|
+
from fastapi import HTTPException
|
|
829
|
+
|
|
830
|
+
limiter = _auth_limiter(backend)
|
|
831
|
+
ip = "203.0.113.9"
|
|
832
|
+
|
|
833
|
+
# refresh allows 30; spend enough to exhaust register's cap of 3.
|
|
834
|
+
for _ in range(5):
|
|
835
|
+
await limiter.check("refresh", ip)
|
|
836
|
+
|
|
837
|
+
# register has not been called at all, so it must still have its full cap.
|
|
838
|
+
for _ in range(3):
|
|
839
|
+
await limiter.check("register", ip)
|
|
840
|
+
|
|
841
|
+
with pytest.raises(HTTPException) as exc:
|
|
842
|
+
await limiter.check("register", ip)
|
|
843
|
+
assert exc.value.status_code == 429
|
|
844
|
+
|
|
845
|
+
# And exhausting register must leave the documented separate bucket alone.
|
|
846
|
+
await limiter.check("reauth", ip)
|
|
847
|
+
|
|
848
|
+
await limiter.aclose()
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/.github/ISSUE_TEMPLATE/feature_request.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/adapters/_sqlalchemy_base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/dependencies/__init__.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/dependencies/current_user.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/change_password.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/flows/password_reset.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/middleware/ratelimit.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/middleware/security_headers.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/__init__.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/_common.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/passkey.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/beanie/permission.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/__init__.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/base.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/oauth.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/passkey.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/permission.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlalchemy/role.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/__init__.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/base.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/oauth.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/passkey.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/permission.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/sqlmodel/role.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/__init__.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/_common.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/base.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/oauth.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/passkey.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/permission.py
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/fastapi_fullauth/models/tortoise/role.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/adapters.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/hooks.md
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/migrations.md
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/oauth.md
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/passkeys.md
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/production.md
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/rbac.md
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/sessions.md
RENAMED
|
File without changes
|
{fastapi_fullauth-0.17.1 → fastapi_fullauth-0.17.2}/skills/fastapi-fullauth/references/testing.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|