quart-security 1.0.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.
- quart_security-1.0.0/.github/workflows/ci.yml +31 -0
- quart_security-1.0.0/.github/workflows/publish.yml +37 -0
- quart_security-1.0.0/.gitignore +6 -0
- quart_security-1.0.0/LICENSE +21 -0
- quart_security-1.0.0/PKG-INFO +247 -0
- quart_security-1.0.0/README.md +220 -0
- quart_security-1.0.0/pyproject.toml +63 -0
- quart_security-1.0.0/quart_security/__init__.py +34 -0
- quart_security-1.0.0/quart_security/core.py +184 -0
- quart_security-1.0.0/quart_security/datastore.py +169 -0
- quart_security-1.0.0/quart_security/decorators.py +46 -0
- quart_security-1.0.0/quart_security/email.py +29 -0
- quart_security-1.0.0/quart_security/forms.py +107 -0
- quart_security-1.0.0/quart_security/models.py +39 -0
- quart_security-1.0.0/quart_security/password.py +43 -0
- quart_security-1.0.0/quart_security/proxies.py +32 -0
- quart_security-1.0.0/quart_security/signals.py +11 -0
- quart_security-1.0.0/quart_security/templates/security/_macros.html +9 -0
- quart_security-1.0.0/quart_security/templates/security/change_password.html +11 -0
- quart_security-1.0.0/quart_security/templates/security/login_user.html +11 -0
- quart_security-1.0.0/quart_security/templates/security/mf_recovery.html +9 -0
- quart_security-1.0.0/quart_security/templates/security/mf_recovery_codes.html +13 -0
- quart_security-1.0.0/quart_security/templates/security/register_user.html +13 -0
- quart_security-1.0.0/quart_security/templates/security/two_factor_select.html +5 -0
- quart_security-1.0.0/quart_security/templates/security/two_factor_setup.html +12 -0
- quart_security-1.0.0/quart_security/templates/security/two_factor_verify_code.html +10 -0
- quart_security-1.0.0/quart_security/templates/security/wan_register.html +113 -0
- quart_security-1.0.0/quart_security/templates/security/wan_signin.html +92 -0
- quart_security-1.0.0/quart_security/templates/security/wan_verify.html +86 -0
- quart_security-1.0.0/quart_security/totp.py +61 -0
- quart_security-1.0.0/quart_security/utils.py +8 -0
- quart_security-1.0.0/quart_security/views.py +1016 -0
- quart_security-1.0.0/quart_security/webauthn.py +140 -0
- quart_security-1.0.0/tests/conftest.py +233 -0
- quart_security-1.0.0/tests/test_auth_flow.py +45 -0
- quart_security-1.0.0/tests/test_core_flows.py +129 -0
- quart_security-1.0.0/tests/test_password.py +14 -0
- quart_security-1.0.0/tests/test_two_factor.py +107 -0
- quart_security-1.0.0/tests/test_webauthn.py +234 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.11"
|
|
21
|
+
|
|
22
|
+
- name: Set up uv
|
|
23
|
+
uses: astral-sh/setup-uv@v6
|
|
24
|
+
with:
|
|
25
|
+
enable-cache: true
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --group dev
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: uv run pytest -q
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types:
|
|
6
|
+
- published
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish:
|
|
15
|
+
if: github.repository == 'level09/quart-security'
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
environment:
|
|
18
|
+
name: pypi
|
|
19
|
+
url: https://pypi.org/p/quart-security
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
|
|
30
|
+
- name: Set up uv
|
|
31
|
+
uses: astral-sh/setup-uv@v6
|
|
32
|
+
|
|
33
|
+
- name: Build distributions
|
|
34
|
+
run: uv build
|
|
35
|
+
|
|
36
|
+
- name: Publish to PyPI
|
|
37
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 quart-security maintainers
|
|
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.
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: quart-security
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Native async auth for Quart
|
|
5
|
+
Author: quart-security maintainers
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: quart>=0.20.0
|
|
9
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
10
|
+
Requires-Dist: libpass>=1.9
|
|
11
|
+
Requires-Dist: pyotp>=2.9
|
|
12
|
+
Requires-Dist: qrcode>=8.0
|
|
13
|
+
Requires-Dist: webauthn>=2.0
|
|
14
|
+
Requires-Dist: wtforms>=3.1
|
|
15
|
+
Requires-Dist: aiosmtplib>=3.0
|
|
16
|
+
Requires-Dist: blinker>=1.7
|
|
17
|
+
Requires-Dist: itsdangerous>=2.1
|
|
18
|
+
Requires-Dist: email-validator>=2.0
|
|
19
|
+
Requires-Dist: pytest>=8.0 ; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.23 ; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.6 ; extra == "dev"
|
|
22
|
+
Project-URL: Homepage, https://github.com/level09/quart-security
|
|
23
|
+
Project-URL: Issues, https://github.com/level09/quart-security/issues
|
|
24
|
+
Project-URL: Repository, https://github.com/level09/quart-security
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
|
|
27
|
+
# quart-security
|
|
28
|
+
|
|
29
|
+
`quart-security` is a native async authentication extension for Quart.
|
|
30
|
+
|
|
31
|
+
It is designed as a practical replacement path for Flask-Security style session auth in Quart applications, without Flask shims and without Flask-Login.
|
|
32
|
+
|
|
33
|
+
## What You Get
|
|
34
|
+
|
|
35
|
+
### Core auth
|
|
36
|
+
- Session-based login and logout
|
|
37
|
+
- Email/password registration
|
|
38
|
+
- Password change flow (including OAuth-style users that don’t know an initial random password)
|
|
39
|
+
- `current_user` proxy
|
|
40
|
+
- `@auth_required("session")` and `@roles_required(...)`
|
|
41
|
+
|
|
42
|
+
### MFA
|
|
43
|
+
- TOTP setup and verification
|
|
44
|
+
- Recovery code generation and one-time consumption
|
|
45
|
+
|
|
46
|
+
### WebAuthn (passkeys / security keys)
|
|
47
|
+
- Credential registration
|
|
48
|
+
- Passwordless sign-in (first factor)
|
|
49
|
+
- Authenticated verification flow (step-up / second factor)
|
|
50
|
+
- Credential deletion
|
|
51
|
+
|
|
52
|
+
### Extension and compatibility surface
|
|
53
|
+
- Quart extension pattern (`Security(app, datastore)`)
|
|
54
|
+
- Flask-Security-style endpoint naming through `url_for_security()`
|
|
55
|
+
- Signals for auth lifecycle events
|
|
56
|
+
- Overridable templates under `templates/security/`
|
|
57
|
+
|
|
58
|
+
## Non-Goals (Current Scope)
|
|
59
|
+
|
|
60
|
+
This project intentionally focuses on session auth and MFA currently in active use:
|
|
61
|
+
- No token-based API auth
|
|
62
|
+
- No SMS/email OTP
|
|
63
|
+
- No account locking workflow
|
|
64
|
+
- No remember-me token system
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
### Install from repository
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uv add git+https://github.com/level09/quart-security.git
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Local development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv sync --group dev
|
|
78
|
+
uv run pytest -q
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Package build backend: `flit`.
|
|
82
|
+
|
|
83
|
+
## Quick Integration
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from quart import Quart
|
|
87
|
+
from quart_security import Security, SQLAlchemyUserDatastore
|
|
88
|
+
|
|
89
|
+
# your models should include fields used by auth, roles, and MFA/WebAuthn
|
|
90
|
+
from myapp.models import db, User, Role, WebAuthnCredential
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def create_app():
|
|
94
|
+
app = Quart(__name__)
|
|
95
|
+
|
|
96
|
+
app.config.update(
|
|
97
|
+
SECRET_KEY="change-me",
|
|
98
|
+
SECURITY_PASSWORD_SALT="change-me-too",
|
|
99
|
+
SECURITY_POST_LOGIN_VIEW="/dashboard",
|
|
100
|
+
SECURITY_POST_REGISTER_VIEW="/login",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
db.init_app(app)
|
|
104
|
+
datastore = SQLAlchemyUserDatastore(
|
|
105
|
+
db,
|
|
106
|
+
User,
|
|
107
|
+
Role,
|
|
108
|
+
webauthn_model=WebAuthnCredential,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
Security(app, datastore)
|
|
112
|
+
return app
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Required Model Surface
|
|
116
|
+
|
|
117
|
+
Your user/role models are expected to provide the fields used by active features.
|
|
118
|
+
|
|
119
|
+
Minimum practical user fields:
|
|
120
|
+
- `fs_uniquifier`
|
|
121
|
+
- `email`
|
|
122
|
+
- `password`
|
|
123
|
+
- `active`
|
|
124
|
+
- `roles`
|
|
125
|
+
|
|
126
|
+
For tracking / MFA / WebAuthn features:
|
|
127
|
+
- `last_login_at`, `current_login_at`, `last_login_ip`, `current_login_ip`, `login_count`
|
|
128
|
+
- `tf_primary_method`, `tf_totp_secret`, `mf_recovery_codes`
|
|
129
|
+
- `fs_webauthn_user_handle`
|
|
130
|
+
- relationship/association for stored WebAuthn credentials
|
|
131
|
+
|
|
132
|
+
## Key Configuration
|
|
133
|
+
|
|
134
|
+
The extension uses `SECURITY_*` keys for migration-friendly configuration.
|
|
135
|
+
|
|
136
|
+
Core:
|
|
137
|
+
- `SECURITY_PASSWORD_HASH` (default: `pbkdf2_sha512`)
|
|
138
|
+
- `SECURITY_PASSWORD_SALT` (recommended)
|
|
139
|
+
- `SECURITY_PASSWORD_LENGTH_MIN` (default: `12`)
|
|
140
|
+
- `SECURITY_REGISTERABLE`
|
|
141
|
+
- `SECURITY_CHANGEABLE`
|
|
142
|
+
- `SECURITY_TRACKABLE`
|
|
143
|
+
- `SECURITY_CSRF_PROTECT` (default: `True`)
|
|
144
|
+
|
|
145
|
+
2FA:
|
|
146
|
+
- `SECURITY_TWO_FACTOR`
|
|
147
|
+
- `SECURITY_TOTP_ISSUER`
|
|
148
|
+
- `SECURITY_MULTI_FACTOR_RECOVERY_CODES`
|
|
149
|
+
- `SECURITY_MULTI_FACTOR_RECOVERY_CODES_N`
|
|
150
|
+
|
|
151
|
+
WebAuthn:
|
|
152
|
+
- `SECURITY_WEBAUTHN`
|
|
153
|
+
- `SECURITY_WAN_ALLOW_AS_FIRST_FACTOR`
|
|
154
|
+
- `SECURITY_WAN_ALLOW_AS_MULTI_FACTOR`
|
|
155
|
+
- `SECURITY_WAN_RP_ID` (optional override)
|
|
156
|
+
- `SECURITY_WAN_RP_NAME` (optional override)
|
|
157
|
+
- `SECURITY_WAN_EXPECTED_ORIGIN` (optional override)
|
|
158
|
+
- `SECURITY_WAN_REQUIRE_USER_VERIFICATION` (default: `True`)
|
|
159
|
+
|
|
160
|
+
Routing:
|
|
161
|
+
- `SECURITY_POST_LOGIN_VIEW`
|
|
162
|
+
- `SECURITY_POST_REGISTER_VIEW`
|
|
163
|
+
|
|
164
|
+
## Route Map
|
|
165
|
+
|
|
166
|
+
Core:
|
|
167
|
+
- `/login`
|
|
168
|
+
- `/register`
|
|
169
|
+
- `/logout`
|
|
170
|
+
- `/change`
|
|
171
|
+
|
|
172
|
+
2FA:
|
|
173
|
+
- `/tf-setup`
|
|
174
|
+
- `/tf-validate`
|
|
175
|
+
- `/tf-select`
|
|
176
|
+
- `/mf-recovery-codes`
|
|
177
|
+
- `/mf-recovery`
|
|
178
|
+
|
|
179
|
+
WebAuthn:
|
|
180
|
+
- `/wan-register`
|
|
181
|
+
- `/wan-register-response`
|
|
182
|
+
- `/wan-signin`
|
|
183
|
+
- `/wan-signin-response`
|
|
184
|
+
- `/wan-verify`
|
|
185
|
+
- `/wan-verify-response`
|
|
186
|
+
- `/wan-delete`
|
|
187
|
+
|
|
188
|
+
## Template Overrides
|
|
189
|
+
|
|
190
|
+
Default templates are intentionally simple and framework-neutral.
|
|
191
|
+
|
|
192
|
+
Override by placing templates with the same names under your app’s
|
|
193
|
+
`templates/security/` directory.
|
|
194
|
+
|
|
195
|
+
## Public API
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from quart_security import (
|
|
199
|
+
Security,
|
|
200
|
+
SQLAlchemyUserDatastore,
|
|
201
|
+
current_user,
|
|
202
|
+
auth_required,
|
|
203
|
+
roles_required,
|
|
204
|
+
UserMixin,
|
|
205
|
+
RoleMixin,
|
|
206
|
+
hash_password,
|
|
207
|
+
verify_password,
|
|
208
|
+
user_authenticated,
|
|
209
|
+
user_logged_out,
|
|
210
|
+
password_changed,
|
|
211
|
+
tf_profile_changed,
|
|
212
|
+
user_registered,
|
|
213
|
+
url_for_security,
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Testing
|
|
218
|
+
|
|
219
|
+
Project tests cover:
|
|
220
|
+
- password hashing/validation
|
|
221
|
+
- auth and role decorators
|
|
222
|
+
- register/login/logout/change-password
|
|
223
|
+
- TOTP and recovery code flows
|
|
224
|
+
- WebAuthn register/sign-in/verify/delete route behavior
|
|
225
|
+
|
|
226
|
+
Run:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
uv run pytest -q
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### WebAuthn staging validation (recommended before release tags)
|
|
233
|
+
|
|
234
|
+
Run this in a HTTPS staging environment with production-like hostnames and real browser prompts:
|
|
235
|
+
|
|
236
|
+
1. Register a passkey from `/wan-register` and verify it is persisted with expected `name`, `usage`, and `sign_count`.
|
|
237
|
+
2. Complete passwordless sign-in from `/wan-signin` with the same credential.
|
|
238
|
+
3. Complete authenticated verify flow from `/wan-verify` while already signed in.
|
|
239
|
+
4. Delete credential from `/wan-delete` and confirm subsequent passkey auth fails for that credential.
|
|
240
|
+
5. Repeat step 1 and step 2 with a second authenticator type (for example platform passkey + hardware key) to validate device portability assumptions.
|
|
241
|
+
|
|
242
|
+
## Notes for Production
|
|
243
|
+
|
|
244
|
+
- Run behind HTTPS for WebAuthn in non-local environments.
|
|
245
|
+
- Set explicit WebAuthn RP values (`SECURITY_WAN_RP_ID`, `SECURITY_WAN_EXPECTED_ORIGIN`) when behind proxies or multiple domains.
|
|
246
|
+
- Keep CSRF protection enabled unless you have a deliberate replacement.
|
|
247
|
+
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# quart-security
|
|
2
|
+
|
|
3
|
+
`quart-security` is a native async authentication extension for Quart.
|
|
4
|
+
|
|
5
|
+
It is designed as a practical replacement path for Flask-Security style session auth in Quart applications, without Flask shims and without Flask-Login.
|
|
6
|
+
|
|
7
|
+
## What You Get
|
|
8
|
+
|
|
9
|
+
### Core auth
|
|
10
|
+
- Session-based login and logout
|
|
11
|
+
- Email/password registration
|
|
12
|
+
- Password change flow (including OAuth-style users that don’t know an initial random password)
|
|
13
|
+
- `current_user` proxy
|
|
14
|
+
- `@auth_required("session")` and `@roles_required(...)`
|
|
15
|
+
|
|
16
|
+
### MFA
|
|
17
|
+
- TOTP setup and verification
|
|
18
|
+
- Recovery code generation and one-time consumption
|
|
19
|
+
|
|
20
|
+
### WebAuthn (passkeys / security keys)
|
|
21
|
+
- Credential registration
|
|
22
|
+
- Passwordless sign-in (first factor)
|
|
23
|
+
- Authenticated verification flow (step-up / second factor)
|
|
24
|
+
- Credential deletion
|
|
25
|
+
|
|
26
|
+
### Extension and compatibility surface
|
|
27
|
+
- Quart extension pattern (`Security(app, datastore)`)
|
|
28
|
+
- Flask-Security-style endpoint naming through `url_for_security()`
|
|
29
|
+
- Signals for auth lifecycle events
|
|
30
|
+
- Overridable templates under `templates/security/`
|
|
31
|
+
|
|
32
|
+
## Non-Goals (Current Scope)
|
|
33
|
+
|
|
34
|
+
This project intentionally focuses on session auth and MFA currently in active use:
|
|
35
|
+
- No token-based API auth
|
|
36
|
+
- No SMS/email OTP
|
|
37
|
+
- No account locking workflow
|
|
38
|
+
- No remember-me token system
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
### Install from repository
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv add git+https://github.com/level09/quart-security.git
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Local development
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv sync --group dev
|
|
52
|
+
uv run pytest -q
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Package build backend: `flit`.
|
|
56
|
+
|
|
57
|
+
## Quick Integration
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from quart import Quart
|
|
61
|
+
from quart_security import Security, SQLAlchemyUserDatastore
|
|
62
|
+
|
|
63
|
+
# your models should include fields used by auth, roles, and MFA/WebAuthn
|
|
64
|
+
from myapp.models import db, User, Role, WebAuthnCredential
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_app():
|
|
68
|
+
app = Quart(__name__)
|
|
69
|
+
|
|
70
|
+
app.config.update(
|
|
71
|
+
SECRET_KEY="change-me",
|
|
72
|
+
SECURITY_PASSWORD_SALT="change-me-too",
|
|
73
|
+
SECURITY_POST_LOGIN_VIEW="/dashboard",
|
|
74
|
+
SECURITY_POST_REGISTER_VIEW="/login",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
db.init_app(app)
|
|
78
|
+
datastore = SQLAlchemyUserDatastore(
|
|
79
|
+
db,
|
|
80
|
+
User,
|
|
81
|
+
Role,
|
|
82
|
+
webauthn_model=WebAuthnCredential,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
Security(app, datastore)
|
|
86
|
+
return app
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Required Model Surface
|
|
90
|
+
|
|
91
|
+
Your user/role models are expected to provide the fields used by active features.
|
|
92
|
+
|
|
93
|
+
Minimum practical user fields:
|
|
94
|
+
- `fs_uniquifier`
|
|
95
|
+
- `email`
|
|
96
|
+
- `password`
|
|
97
|
+
- `active`
|
|
98
|
+
- `roles`
|
|
99
|
+
|
|
100
|
+
For tracking / MFA / WebAuthn features:
|
|
101
|
+
- `last_login_at`, `current_login_at`, `last_login_ip`, `current_login_ip`, `login_count`
|
|
102
|
+
- `tf_primary_method`, `tf_totp_secret`, `mf_recovery_codes`
|
|
103
|
+
- `fs_webauthn_user_handle`
|
|
104
|
+
- relationship/association for stored WebAuthn credentials
|
|
105
|
+
|
|
106
|
+
## Key Configuration
|
|
107
|
+
|
|
108
|
+
The extension uses `SECURITY_*` keys for migration-friendly configuration.
|
|
109
|
+
|
|
110
|
+
Core:
|
|
111
|
+
- `SECURITY_PASSWORD_HASH` (default: `pbkdf2_sha512`)
|
|
112
|
+
- `SECURITY_PASSWORD_SALT` (recommended)
|
|
113
|
+
- `SECURITY_PASSWORD_LENGTH_MIN` (default: `12`)
|
|
114
|
+
- `SECURITY_REGISTERABLE`
|
|
115
|
+
- `SECURITY_CHANGEABLE`
|
|
116
|
+
- `SECURITY_TRACKABLE`
|
|
117
|
+
- `SECURITY_CSRF_PROTECT` (default: `True`)
|
|
118
|
+
|
|
119
|
+
2FA:
|
|
120
|
+
- `SECURITY_TWO_FACTOR`
|
|
121
|
+
- `SECURITY_TOTP_ISSUER`
|
|
122
|
+
- `SECURITY_MULTI_FACTOR_RECOVERY_CODES`
|
|
123
|
+
- `SECURITY_MULTI_FACTOR_RECOVERY_CODES_N`
|
|
124
|
+
|
|
125
|
+
WebAuthn:
|
|
126
|
+
- `SECURITY_WEBAUTHN`
|
|
127
|
+
- `SECURITY_WAN_ALLOW_AS_FIRST_FACTOR`
|
|
128
|
+
- `SECURITY_WAN_ALLOW_AS_MULTI_FACTOR`
|
|
129
|
+
- `SECURITY_WAN_RP_ID` (optional override)
|
|
130
|
+
- `SECURITY_WAN_RP_NAME` (optional override)
|
|
131
|
+
- `SECURITY_WAN_EXPECTED_ORIGIN` (optional override)
|
|
132
|
+
- `SECURITY_WAN_REQUIRE_USER_VERIFICATION` (default: `True`)
|
|
133
|
+
|
|
134
|
+
Routing:
|
|
135
|
+
- `SECURITY_POST_LOGIN_VIEW`
|
|
136
|
+
- `SECURITY_POST_REGISTER_VIEW`
|
|
137
|
+
|
|
138
|
+
## Route Map
|
|
139
|
+
|
|
140
|
+
Core:
|
|
141
|
+
- `/login`
|
|
142
|
+
- `/register`
|
|
143
|
+
- `/logout`
|
|
144
|
+
- `/change`
|
|
145
|
+
|
|
146
|
+
2FA:
|
|
147
|
+
- `/tf-setup`
|
|
148
|
+
- `/tf-validate`
|
|
149
|
+
- `/tf-select`
|
|
150
|
+
- `/mf-recovery-codes`
|
|
151
|
+
- `/mf-recovery`
|
|
152
|
+
|
|
153
|
+
WebAuthn:
|
|
154
|
+
- `/wan-register`
|
|
155
|
+
- `/wan-register-response`
|
|
156
|
+
- `/wan-signin`
|
|
157
|
+
- `/wan-signin-response`
|
|
158
|
+
- `/wan-verify`
|
|
159
|
+
- `/wan-verify-response`
|
|
160
|
+
- `/wan-delete`
|
|
161
|
+
|
|
162
|
+
## Template Overrides
|
|
163
|
+
|
|
164
|
+
Default templates are intentionally simple and framework-neutral.
|
|
165
|
+
|
|
166
|
+
Override by placing templates with the same names under your app’s
|
|
167
|
+
`templates/security/` directory.
|
|
168
|
+
|
|
169
|
+
## Public API
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from quart_security import (
|
|
173
|
+
Security,
|
|
174
|
+
SQLAlchemyUserDatastore,
|
|
175
|
+
current_user,
|
|
176
|
+
auth_required,
|
|
177
|
+
roles_required,
|
|
178
|
+
UserMixin,
|
|
179
|
+
RoleMixin,
|
|
180
|
+
hash_password,
|
|
181
|
+
verify_password,
|
|
182
|
+
user_authenticated,
|
|
183
|
+
user_logged_out,
|
|
184
|
+
password_changed,
|
|
185
|
+
tf_profile_changed,
|
|
186
|
+
user_registered,
|
|
187
|
+
url_for_security,
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Testing
|
|
192
|
+
|
|
193
|
+
Project tests cover:
|
|
194
|
+
- password hashing/validation
|
|
195
|
+
- auth and role decorators
|
|
196
|
+
- register/login/logout/change-password
|
|
197
|
+
- TOTP and recovery code flows
|
|
198
|
+
- WebAuthn register/sign-in/verify/delete route behavior
|
|
199
|
+
|
|
200
|
+
Run:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
uv run pytest -q
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### WebAuthn staging validation (recommended before release tags)
|
|
207
|
+
|
|
208
|
+
Run this in a HTTPS staging environment with production-like hostnames and real browser prompts:
|
|
209
|
+
|
|
210
|
+
1. Register a passkey from `/wan-register` and verify it is persisted with expected `name`, `usage`, and `sign_count`.
|
|
211
|
+
2. Complete passwordless sign-in from `/wan-signin` with the same credential.
|
|
212
|
+
3. Complete authenticated verify flow from `/wan-verify` while already signed in.
|
|
213
|
+
4. Delete credential from `/wan-delete` and confirm subsequent passkey auth fails for that credential.
|
|
214
|
+
5. Repeat step 1 and step 2 with a second authenticator type (for example platform passkey + hardware key) to validate device portability assumptions.
|
|
215
|
+
|
|
216
|
+
## Notes for Production
|
|
217
|
+
|
|
218
|
+
- Run behind HTTPS for WebAuthn in non-local environments.
|
|
219
|
+
- Set explicit WebAuthn RP values (`SECURITY_WAN_RP_ID`, `SECURITY_WAN_EXPECTED_ORIGIN`) when behind proxies or multiple domains.
|
|
220
|
+
- Keep CSRF protection enabled unless you have a deliberate replacement.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["flit_core>=3.9,<4"]
|
|
3
|
+
build-backend = "flit_core.buildapi"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "quart-security"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Native async auth for Quart"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "quart-security maintainers" },
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"quart>=0.20.0",
|
|
17
|
+
"sqlalchemy>=2.0",
|
|
18
|
+
"libpass>=1.9",
|
|
19
|
+
"pyotp>=2.9",
|
|
20
|
+
"qrcode>=8.0",
|
|
21
|
+
"webauthn>=2.0",
|
|
22
|
+
"wtforms>=3.1",
|
|
23
|
+
"aiosmtplib>=3.0",
|
|
24
|
+
"blinker>=1.7",
|
|
25
|
+
"itsdangerous>=2.1",
|
|
26
|
+
"email-validator>=2.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest>=8.0",
|
|
32
|
+
"pytest-asyncio>=0.23",
|
|
33
|
+
"ruff>=0.6",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/level09/quart-security"
|
|
38
|
+
Repository = "https://github.com/level09/quart-security"
|
|
39
|
+
Issues = "https://github.com/level09/quart-security/issues"
|
|
40
|
+
|
|
41
|
+
[tool.flit.module]
|
|
42
|
+
name = "quart_security"
|
|
43
|
+
|
|
44
|
+
[dependency-groups]
|
|
45
|
+
dev = [
|
|
46
|
+
"pytest>=8.0",
|
|
47
|
+
"pytest-asyncio>=0.23",
|
|
48
|
+
"ruff>=0.6",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
asyncio_mode = "auto"
|
|
53
|
+
testpaths = ["tests"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
line-length = 88
|
|
57
|
+
target-version = "py311"
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
select = ["E", "F", "I", "B"]
|
|
61
|
+
|
|
62
|
+
[tool.uv]
|
|
63
|
+
default-groups = ["dev"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Public API for quart-security."""
|
|
2
|
+
|
|
3
|
+
from .core import Security
|
|
4
|
+
from .datastore import SQLAlchemyUserDatastore
|
|
5
|
+
from .decorators import auth_required, roles_required
|
|
6
|
+
from .models import RoleMixin, UserMixin
|
|
7
|
+
from .password import hash_password, verify_password
|
|
8
|
+
from .proxies import current_user
|
|
9
|
+
from .signals import (
|
|
10
|
+
password_changed,
|
|
11
|
+
tf_profile_changed,
|
|
12
|
+
user_authenticated,
|
|
13
|
+
user_logged_out,
|
|
14
|
+
user_registered,
|
|
15
|
+
)
|
|
16
|
+
from .utils import url_for_security
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Security",
|
|
20
|
+
"SQLAlchemyUserDatastore",
|
|
21
|
+
"auth_required",
|
|
22
|
+
"roles_required",
|
|
23
|
+
"UserMixin",
|
|
24
|
+
"RoleMixin",
|
|
25
|
+
"hash_password",
|
|
26
|
+
"verify_password",
|
|
27
|
+
"current_user",
|
|
28
|
+
"user_authenticated",
|
|
29
|
+
"user_logged_out",
|
|
30
|
+
"password_changed",
|
|
31
|
+
"tf_profile_changed",
|
|
32
|
+
"user_registered",
|
|
33
|
+
"url_for_security",
|
|
34
|
+
]
|