authwarden 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. authwarden-0.7.0/.github/workflows/publish.yml +55 -0
  2. authwarden-0.7.0/.gitignore +12 -0
  3. authwarden-0.7.0/LICENSE +21 -0
  4. authwarden-0.7.0/PKG-INFO +431 -0
  5. authwarden-0.7.0/README.md +380 -0
  6. authwarden-0.7.0/authwarden/__init__.py +43 -0
  7. authwarden-0.7.0/authwarden/authentication/__init__.py +0 -0
  8. authwarden-0.7.0/authwarden/authentication/encryption.py +47 -0
  9. authwarden-0.7.0/authwarden/authentication/jwt.py +345 -0
  10. authwarden-0.7.0/authwarden/authentication/oauth.py +516 -0
  11. authwarden-0.7.0/authwarden/authentication/oauth_state.py +94 -0
  12. authwarden-0.7.0/authwarden/authentication/password.py +133 -0
  13. authwarden-0.7.0/authwarden/core/__init__.py +0 -0
  14. authwarden-0.7.0/authwarden/core/config.py +143 -0
  15. authwarden-0.7.0/authwarden/core/context.py +44 -0
  16. authwarden-0.7.0/authwarden/core/manager.py +207 -0
  17. authwarden-0.7.0/authwarden/dependencies/__init__.py +0 -0
  18. authwarden-0.7.0/authwarden/dependencies/current_user.py +85 -0
  19. authwarden-0.7.0/authwarden/dependencies/permissions.py +81 -0
  20. authwarden-0.7.0/authwarden/email/__init__.py +0 -0
  21. authwarden-0.7.0/authwarden/email/base.py +77 -0
  22. authwarden-0.7.0/authwarden/email/console.py +53 -0
  23. authwarden-0.7.0/authwarden/email/mailgun.py +97 -0
  24. authwarden-0.7.0/authwarden/email/sendgrid.py +96 -0
  25. authwarden-0.7.0/authwarden/email/smtp.py +103 -0
  26. authwarden-0.7.0/authwarden/email/templates.py +139 -0
  27. authwarden-0.7.0/authwarden/exceptions.py +241 -0
  28. authwarden-0.7.0/authwarden/flows/__init__.py +0 -0
  29. authwarden-0.7.0/authwarden/flows/change_password.py +49 -0
  30. authwarden-0.7.0/authwarden/flows/forgot_password.py +63 -0
  31. authwarden-0.7.0/authwarden/flows/login.py +134 -0
  32. authwarden-0.7.0/authwarden/flows/logout.py +32 -0
  33. authwarden-0.7.0/authwarden/flows/oauth_accounts.py +24 -0
  34. authwarden-0.7.0/authwarden/flows/oauth_authorize.py +53 -0
  35. authwarden-0.7.0/authwarden/flows/oauth_callback.py +147 -0
  36. authwarden-0.7.0/authwarden/flows/oauth_connect.py +85 -0
  37. authwarden-0.7.0/authwarden/flows/oauth_disconnect.py +35 -0
  38. authwarden-0.7.0/authwarden/flows/refresh.py +36 -0
  39. authwarden-0.7.0/authwarden/flows/register.py +85 -0
  40. authwarden-0.7.0/authwarden/flows/resend_verification.py +58 -0
  41. authwarden-0.7.0/authwarden/flows/reset_password.py +55 -0
  42. authwarden-0.7.0/authwarden/flows/reset_password_otp.py +73 -0
  43. authwarden-0.7.0/authwarden/flows/set_password.py +38 -0
  44. authwarden-0.7.0/authwarden/flows/verify_email.py +50 -0
  45. authwarden-0.7.0/authwarden/flows/verify_otp.py +85 -0
  46. authwarden-0.7.0/authwarden/mfa/__init__.py +0 -0
  47. authwarden-0.7.0/authwarden/mfa/backup_codes.py +37 -0
  48. authwarden-0.7.0/authwarden/mfa/totp.py +127 -0
  49. authwarden-0.7.0/authwarden/models/__init__.py +0 -0
  50. authwarden-0.7.0/authwarden/models/requests.py +119 -0
  51. authwarden-0.7.0/authwarden/models/token.py +49 -0
  52. authwarden-0.7.0/authwarden/models/user.py +184 -0
  53. authwarden-0.7.0/authwarden/notifications/__init__.py +0 -0
  54. authwarden-0.7.0/authwarden/notifications/service.py +165 -0
  55. authwarden-0.7.0/authwarden/permissions/__init__.py +0 -0
  56. authwarden-0.7.0/authwarden/permissions/policies.py +46 -0
  57. authwarden-0.7.0/authwarden/permissions/roles.py +75 -0
  58. authwarden-0.7.0/authwarden/py.typed +0 -0
  59. authwarden-0.7.0/authwarden/routers/__init__.py +0 -0
  60. authwarden-0.7.0/authwarden/routers/_errors.py +33 -0
  61. authwarden-0.7.0/authwarden/routers/auth.py +201 -0
  62. authwarden-0.7.0/authwarden/routers/mfa.py +65 -0
  63. authwarden-0.7.0/authwarden/routers/oauth.py +129 -0
  64. authwarden-0.7.0/authwarden/session/__init__.py +0 -0
  65. authwarden-0.7.0/authwarden/session/base.py +98 -0
  66. authwarden-0.7.0/authwarden/session/memory.py +116 -0
  67. authwarden-0.7.0/authwarden/session/redis.py +149 -0
  68. authwarden-0.7.0/authwarden/sms/__init__.py +0 -0
  69. authwarden-0.7.0/authwarden/sms/base.py +42 -0
  70. authwarden-0.7.0/authwarden/sms/console.py +20 -0
  71. authwarden-0.7.0/authwarden/sms/sns.py +47 -0
  72. authwarden-0.7.0/authwarden/sms/templates.py +36 -0
  73. authwarden-0.7.0/authwarden/sms/twilio.py +55 -0
  74. authwarden-0.7.0/authwarden/storage/__init__.py +0 -0
  75. authwarden-0.7.0/authwarden/storage/base.py +162 -0
  76. authwarden-0.7.0/authwarden/storage/memory.py +208 -0
  77. authwarden-0.7.0/authwarden/utils.py +125 -0
  78. authwarden-0.7.0/pyproject.toml +72 -0
  79. authwarden-0.7.0/pytest.ini +3 -0
  80. authwarden-0.7.0/tests/__init__.py +0 -0
  81. authwarden-0.7.0/tests/conftest.py +7 -0
  82. authwarden-0.7.0/tests/test_phase1.py +575 -0
  83. authwarden-0.7.0/tests/test_phase2.py +488 -0
  84. authwarden-0.7.0/tests/test_phase3.py +706 -0
  85. authwarden-0.7.0/tests/test_phase4.py +633 -0
  86. authwarden-0.7.0/tests/test_phase5.py +780 -0
  87. authwarden-0.7.0/tests/test_phase6.py +328 -0
  88. authwarden-0.7.0/tests/test_phase7.py +809 -0
@@ -0,0 +1,55 @@
1
+ name: Publish to PyPI
2
+
3
+ # Fires when a GitHub Release is published — NOT on every phase tag.
4
+ # This decouples "phase complete" tagging from "publish to PyPI",
5
+ # so only intentional releases (triggered by clicking "Publish release")
6
+ # ever reach PyPI.
7
+ on:
8
+ release:
9
+ types: [published]
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distribution
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+
23
+ - name: Install build tool
24
+ run: python -m pip install --upgrade build
25
+
26
+ - name: Build sdist and wheel
27
+ run: python -m build
28
+
29
+ - name: Upload build artifacts
30
+ uses: actions/upload-artifact@v4
31
+ with:
32
+ name: dist
33
+ path: dist/
34
+
35
+ publish:
36
+ name: Publish on PyPI
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+ # This environment name must match exactly what is configured as the
40
+ # trusted publisher on PyPI (Settings → Publishing on the project page).
41
+ environment:
42
+ name: pypi
43
+ url: https://pypi.org/p/authwarden
44
+ permissions:
45
+ # Required for PyPI trusted publishing (OIDC) — no API token needed.
46
+ id-token: write
47
+ steps:
48
+ - name: Download build artifacts
49
+ uses: actions/download-artifact@v4
50
+ with:
51
+ name: dist
52
+ path: dist/
53
+
54
+ - name: Publish to PyPI
55
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .pytest_cache/
6
+ .mypy_cache/
7
+ dist/
8
+ *.egg-info/
9
+ .env
10
+ .env.*
11
+ *.pem
12
+ ../__pycache__/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adeniran John
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,431 @@
1
+ Metadata-Version: 2.4
2
+ Name: authwarden
3
+ Version: 0.7.0
4
+ Summary: A production-grade, pluggable authentication library for FastAPI — JWT, OAuth2 (8 providers), MFA, RBAC, and full flow flexibility (email/SMS, link/OTP, multi-identifier login).
5
+ Project-URL: Homepage, https://github.com/timihack/authwarden
6
+ Project-URL: Repository, https://github.com/timihack/authwarden
7
+ Project-URL: Issues, https://github.com/timihack/authwarden/issues
8
+ Project-URL: Changelog, https://github.com/timihack/authwarden/releases
9
+ Author: John Adeniran (timihack)
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: authentication,fastapi,jwt,mfa,oauth2,rbac,security
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: FastAPI
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: Session
22
+ Classifier: Topic :: Security :: Cryptography
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: aiosmtplib>=3.0.0
26
+ Requires-Dist: authlib>=1.3.0
27
+ Requires-Dist: cryptography>=42.0.0
28
+ Requires-Dist: fastapi>=0.110.0
29
+ Requires-Dist: itsdangerous>=2.1.0
30
+ Requires-Dist: pwdlib[argon2,bcrypt]>=0.2.0
31
+ Requires-Dist: pydantic-settings>=2.0.0
32
+ Requires-Dist: pydantic>=2.0.0
33
+ Requires-Dist: pydantic[email]>=2.0.0
34
+ Requires-Dist: pyjwt>=2.8.0
35
+ Requires-Dist: pyotp>=2.9.0
36
+ Requires-Dist: python-multipart>=0.0.9
37
+ Provides-Extra: all
38
+ Requires-Dist: boto3>=1.34.0; extra == 'all'
39
+ Requires-Dist: redis>=5.0.0; extra == 'all'
40
+ Provides-Extra: dev
41
+ Requires-Dist: boto3>=1.34.0; extra == 'dev'
42
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
43
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
44
+ Requires-Dist: redis>=5.0.0; extra == 'dev'
45
+ Requires-Dist: respx>=0.21.0; extra == 'dev'
46
+ Provides-Extra: redis
47
+ Requires-Dist: redis>=5.0.0; extra == 'redis'
48
+ Provides-Extra: sns
49
+ Requires-Dist: boto3>=1.34.0; extra == 'sns'
50
+ Description-Content-Type: text/markdown
51
+
52
+ # authwarden
53
+
54
+ Production-grade FastAPI authentication and authorization library. Wraps proven cryptographic libraries — never rolls its own.
55
+
56
+ [![Python](https://img.shields.io/badge/python-3.11+-blue.svg)](https://python.org)
57
+ [![FastAPI](https://img.shields.io/badge/fastapi-0.110+-green.svg)](https://fastapi.tiangolo.com)
58
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
59
+ [![PyPI version](https://img.shields.io/pypi/v/authwarden.svg)](https://pypi.org/project/authwarden/)
60
+
61
+ ---
62
+
63
+ ## Features
64
+
65
+ - **JWT authentication** — access + refresh tokens, rotation, per-token revocation via `jti`
66
+ - **Password hashing** — argon2 (default) or bcrypt via `pwdlib`, auto-rehash on login
67
+ - **Full auth flows** — register, verify email, login, logout, forgot/reset/change password
68
+ - **MFA** — TOTP setup/confirm/disable (pyotp), hashed backup codes
69
+ - **OAuth 2.0 / Social login** — Google, GitHub, Facebook, Apple, Twitter/X, Microsoft, LinkedIn, Discord
70
+ - **RBAC** — role hierarchy, scope guards, `Depends()` factories
71
+ - **Session management** — pluggable backends (in-memory, Redis)
72
+ - **Email** — SMTP + console backends, HTML + plain-text templates for every flow
73
+ - **Plug-and-play router** — mount all endpoints with one line
74
+ - **Storage-agnostic** — implement `AbstractUserStore` for any ORM or database
75
+
76
+ ---
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ pip install authwarden
82
+ ```
83
+
84
+ With Redis session support:
85
+
86
+ ```bash
87
+ pip install authwarden[redis]
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Quickstart
93
+
94
+ ```python
95
+ from fastapi import FastAPI, Depends
96
+ from authwarden import AuthWarden, WardenConfig
97
+ from authwarden.storage.memory import MemoryUserStore
98
+
99
+ app = FastAPI()
100
+
101
+ warden = AuthWarden(
102
+ config=WardenConfig(secret_key="your-secret-key"),
103
+ user_store=MemoryUserStore(),
104
+ )
105
+
106
+ # Mount all auth endpoints under /auth
107
+ app.include_router(warden.router, prefix="/auth", tags=["auth"])
108
+
109
+ # Protect your own routes
110
+ @app.get("/profile")
111
+ async def profile(user=Depends(warden.current_user)):
112
+ return user
113
+
114
+ @app.delete("/admin/users/{user_id}")
115
+ async def delete_user(user=Depends(warden.require_roles("admin"))):
116
+ ...
117
+ ```
118
+
119
+ This mounts:
120
+
121
+ ```
122
+ POST /auth/register
123
+ POST /auth/verify-email
124
+ POST /auth/resend-verification
125
+ POST /auth/login
126
+ POST /auth/logout
127
+ POST /auth/refresh
128
+ POST /auth/forgot-password
129
+ POST /auth/reset-password
130
+ POST /auth/change-password
131
+ POST /auth/mfa/setup
132
+ POST /auth/mfa/confirm
133
+ POST /auth/mfa/disable
134
+ GET /auth/oauth/{provider}/authorize
135
+ POST /auth/oauth/{provider}/callback
136
+ POST /auth/oauth/{provider}/connect
137
+ DEL /auth/oauth/{provider}/disconnect
138
+ GET /auth/oauth/accounts
139
+ POST /auth/set-password
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Configuration
145
+
146
+ ```python
147
+ from authwarden import WardenConfig
148
+
149
+ config = WardenConfig(
150
+ # Required
151
+ secret_key="a-long-random-secret",
152
+
153
+ # JWT
154
+ algorithm="HS256",
155
+ access_token_ttl=900, # 15 minutes
156
+ refresh_token_ttl=604800, # 7 days
157
+ enable_refresh_rotation=True,
158
+
159
+ # Passwords
160
+ password_hasher="argon2", # or "bcrypt"
161
+ min_password_length=8,
162
+ require_password_uppercase=False,
163
+ require_password_digit=False,
164
+ require_password_special=False,
165
+
166
+ # Email
167
+ email_backend="smtp", # or "console" for dev
168
+ smtp_host="smtp.example.com",
169
+ smtp_port=587,
170
+ smtp_username="user@example.com",
171
+ smtp_password="...",
172
+ emails_from_address="noreply@example.com",
173
+
174
+ # Registration
175
+ require_email_verification=True,
176
+ allow_registration=True,
177
+
178
+ # MFA
179
+ enable_mfa=True,
180
+ mfa_issuer_name="MyApp",
181
+
182
+ # Session (optional)
183
+ session_backend="redis",
184
+ redis_url="redis://localhost:6379",
185
+
186
+ # Frontend URLs (for email links)
187
+ frontend_base_url="https://myapp.com",
188
+ verify_email_path="/auth/verify-email",
189
+ reset_password_path="/auth/reset-password",
190
+ )
191
+ ```
192
+
193
+ All settings can also be loaded from environment variables (via `pydantic-settings`):
194
+
195
+ ```env
196
+ SECRET_KEY=your-secret-key
197
+ EMAIL_BACKEND=smtp
198
+ SMTP_HOST=smtp.example.com
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Custom User Store
204
+
205
+ Implement `AbstractUserStore` to connect any database:
206
+
207
+ ```python
208
+ from authwarden.storage.base import AbstractUserStore
209
+ from authwarden.models.user import UserInDB
210
+
211
+ class SQLAlchemyUserStore(AbstractUserStore):
212
+ async def get_by_id(self, user_id: str) -> UserInDB | None:
213
+ ...
214
+
215
+ async def get_by_email(self, email: str) -> UserInDB | None:
216
+ ...
217
+
218
+ async def create(self, user: UserInDB) -> UserInDB:
219
+ ...
220
+
221
+ async def update(self, user: UserInDB) -> UserInDB:
222
+ ...
223
+
224
+ async def delete(self, user_id: str) -> None:
225
+ ...
226
+ ```
227
+
228
+ ---
229
+
230
+ ## OAuth / Social Login
231
+
232
+ ```python
233
+ from authwarden.authentication.oauth import OAuthProviderConfig
234
+
235
+ warden = AuthWarden(
236
+ config=WardenConfig(
237
+ secret_key="...",
238
+ oauth_providers={
239
+ "google": OAuthProviderConfig(
240
+ client_id="...",
241
+ client_secret="...",
242
+ redirect_uri="https://myapp.com/auth/oauth/google/callback",
243
+ ),
244
+ "github": OAuthProviderConfig(
245
+ client_id="...",
246
+ client_secret="...",
247
+ redirect_uri="https://myapp.com/auth/oauth/github/callback",
248
+ ),
249
+ }
250
+ ),
251
+ user_store=MyUserStore(),
252
+ )
253
+ ```
254
+
255
+ **Apple Sign In** requires additional fields:
256
+
257
+ ```python
258
+ config = WardenConfig(
259
+ ...
260
+ apple_team_id="TEAM123",
261
+ apple_key_id="KEY123",
262
+ apple_private_key_pem="-----BEGIN PRIVATE KEY-----\n...",
263
+ oauth_providers={
264
+ "apple": OAuthProviderConfig(
265
+ client_id="com.myapp.service",
266
+ client_secret="", # auto-generated from private key
267
+ redirect_uri="https://myapp.com/auth/oauth/apple/callback",
268
+ )
269
+ }
270
+ )
271
+ ```
272
+
273
+ ---
274
+
275
+ ## MFA (TOTP)
276
+
277
+ ```python
278
+ # Enable globally
279
+ config = WardenConfig(secret_key="...", enable_mfa=True)
280
+
281
+ # Endpoints are automatically mounted:
282
+ # POST /auth/mfa/setup → returns { secret, qr_uri, backup_codes }
283
+ # POST /auth/mfa/confirm → activates MFA after verifying first code
284
+ # POST /auth/mfa/disable → requires password + TOTP or backup code
285
+ ```
286
+
287
+ Login with MFA:
288
+
289
+ ```python
290
+ POST /auth/login
291
+ {
292
+ "email": "user@example.com",
293
+ "password": "hunter2",
294
+ "totp_code": "123456"
295
+ }
296
+ ```
297
+
298
+ ---
299
+
300
+ ## RBAC
301
+
302
+ ```python
303
+ from fastapi import Depends
304
+
305
+ # Require a single role
306
+ @app.get("/admin")
307
+ async def admin(user=Depends(warden.require_roles("admin"))):
308
+ ...
309
+
310
+ # Require any of multiple roles
311
+ @app.get("/reports")
312
+ async def reports(user=Depends(warden.require_roles("admin", "analyst"))):
313
+ ...
314
+
315
+ # Require a scope
316
+ @app.post("/items")
317
+ async def create_item(user=Depends(warden.require_scopes("items:write"))):
318
+ ...
319
+ ```
320
+
321
+ ---
322
+
323
+ ## Email Templates
324
+
325
+ Override any template by subclassing `EmailTemplates`:
326
+
327
+ ```python
328
+ from authwarden.email.templates import EmailTemplates
329
+
330
+ class MyTemplates(EmailTemplates):
331
+ def verify_email(self, user, link: str) -> tuple[str, str, str]:
332
+ # returns (subject, plain_text, html)
333
+ return (
334
+ "Verify your account",
335
+ f"Click here: {link}",
336
+ f"<a href='{link}'>Verify your account</a>",
337
+ )
338
+
339
+ warden = AuthWarden(config=..., user_store=..., email_templates=MyTemplates())
340
+ ```
341
+
342
+ ---
343
+
344
+ ## Auth Flows Reference
345
+
346
+ | Flow | Endpoint | Auth required |
347
+ |---|---|---|
348
+ | Register | `POST /auth/register` | No |
349
+ | Verify email | `POST /auth/verify-email` | No |
350
+ | Resend verification | `POST /auth/resend-verification` | No |
351
+ | Login | `POST /auth/login` | No |
352
+ | Logout | `POST /auth/logout` | Bearer token |
353
+ | Refresh token | `POST /auth/refresh` | No |
354
+ | Forgot password | `POST /auth/forgot-password` | No |
355
+ | Reset password | `POST /auth/reset-password` | No |
356
+ | Change password | `POST /auth/change-password` | Bearer token |
357
+ | MFA setup | `POST /auth/mfa/setup` | Bearer token |
358
+ | MFA confirm | `POST /auth/mfa/confirm` | Bearer token |
359
+ | MFA disable | `POST /auth/mfa/disable` | Bearer token |
360
+ | OAuth authorize | `GET /auth/oauth/{provider}/authorize` | No |
361
+ | OAuth callback | `POST /auth/oauth/{provider}/callback` | No |
362
+ | Connect provider | `POST /auth/oauth/{provider}/connect` | Bearer token |
363
+ | Disconnect provider | `DELETE /auth/oauth/{provider}/disconnect` | Bearer token |
364
+ | List linked accounts | `GET /auth/oauth/accounts` | Bearer token |
365
+ | Set password (OAuth) | `POST /auth/set-password` | Bearer token |
366
+
367
+ ---
368
+
369
+ ## Security Notes
370
+
371
+ - Passwords hashed with **argon2** by default (bcrypt available)
372
+ - JWT tokens include a `jti` (UUID) claim — enables per-token revocation
373
+ - Password reset and email verification tokens stored as **hashes only**
374
+ - Forgot password and resend verification always return `200` (anti-enumeration)
375
+ - Constant-time comparison used for all token lookups (`hmac.compare_digest`)
376
+ - PKCE (S256) used for all OAuth flows
377
+ - Refresh token rotation enabled by default — old `jti` blacklisted on use
378
+ - MFA backup codes stored as **argon2 hashes**, single-use
379
+ - OAuth provider tokens encrypted at rest (Fernet)
380
+
381
+ ---
382
+
383
+ ## Development
384
+
385
+ ```bash
386
+ git clone https://github.com/yourusername/authwarden.git
387
+ cd authwarden
388
+ python -m venv .venv && source .venv/bin/activate
389
+ pip install -e ".[dev]"
390
+ pytest
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Running Tests
396
+
397
+ ```bash
398
+ # All tests
399
+ pytest
400
+
401
+ # With coverage
402
+ pytest --cov=authwarden --cov-report=term-missing
403
+
404
+ # Specific suite
405
+ pytest tests/test_oauth.py -v
406
+ ```
407
+
408
+ ---
409
+
410
+ ## Project Structure
411
+
412
+ ```
413
+ authwarden/
414
+ ├── core/ # WardenConfig, AuthWarden facade, request context
415
+ ├── authentication/ # JWT, password hashing, OAuth provider base
416
+ ├── flows/ # One module per auth flow
417
+ ├── mfa/ # TOTP + backup codes
418
+ ├── permissions/ # Roles + scope guards
419
+ ├── session/ # Memory + Redis session backends
420
+ ├── dependencies/ # FastAPI Depends() factories
421
+ ├── routers/ # Plug-and-play FastAPI routers
422
+ ├── email/ # SMTP, console backends + templates
423
+ ├── models/ # Pydantic v2 models
424
+ └── storage/ # AbstractUserStore + MemoryUserStore
425
+ ```
426
+
427
+ ---
428
+
429
+ ## License
430
+
431
+ MIT — see [LICENSE](LICENSE).