fastauth-py 0.1.0__py3-none-any.whl

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 (79) hide show
  1. fastauth/__init__.py +9 -0
  2. fastauth/cli/__init__.py +3 -0
  3. fastauth/cli/main.py +207 -0
  4. fastauth/config.py +270 -0
  5. fastauth/domain/__init__.py +0 -0
  6. fastauth/domain/enums.py +125 -0
  7. fastauth/domain/events.py +217 -0
  8. fastauth/domain/models.py +238 -0
  9. fastauth/exceptions.py +191 -0
  10. fastauth/flows/__init__.py +3 -0
  11. fastauth/flows/change_email.py +203 -0
  12. fastauth/flows/change_password.py +75 -0
  13. fastauth/flows/credentials.py +337 -0
  14. fastauth/flows/email_otp.py +818 -0
  15. fastauth/flows/password_reset.py +163 -0
  16. fastauth/flows/refresh.py +63 -0
  17. fastauth/flows/sessions.py +116 -0
  18. fastauth/flows/user_management.py +304 -0
  19. fastauth/flows/verification.py +147 -0
  20. fastauth/messaging/__init__.py +0 -0
  21. fastauth/messaging/email.py +60 -0
  22. fastauth/messaging/templates/delete_account.html +10 -0
  23. fastauth/messaging/templates/delete_account.txt +8 -0
  24. fastauth/messaging/templates/otp_email_change.html +10 -0
  25. fastauth/messaging/templates/otp_email_change.txt +7 -0
  26. fastauth/messaging/templates/otp_password_reset.html +10 -0
  27. fastauth/messaging/templates/otp_password_reset.txt +7 -0
  28. fastauth/messaging/templates/otp_sign_in.html +10 -0
  29. fastauth/messaging/templates/otp_sign_in.txt +7 -0
  30. fastauth/messaging/templates/otp_verification.html +10 -0
  31. fastauth/messaging/templates/otp_verification.txt +7 -0
  32. fastauth/messaging/templates/reset.html +9 -0
  33. fastauth/messaging/templates/reset.txt +6 -0
  34. fastauth/messaging/templates/verification.html +9 -0
  35. fastauth/messaging/templates/verification.txt +6 -0
  36. fastauth/plugins/__init__.py +3 -0
  37. fastauth/plugins/api_key.py +433 -0
  38. fastauth/plugins/audit_logs.py +195 -0
  39. fastauth/plugins/base.py +210 -0
  40. fastauth/plugins/email_otp.py +336 -0
  41. fastauth/plugins/jwt.py +212 -0
  42. fastauth/plugins/openapi.py +137 -0
  43. fastauth/plugins/test_utils.py +137 -0
  44. fastauth/py.typed +0 -0
  45. fastauth/runtime/__init__.py +0 -0
  46. fastauth/runtime/api.py +432 -0
  47. fastauth/runtime/auth.py +281 -0
  48. fastauth/runtime/context.py +45 -0
  49. fastauth/runtime/event_bus.py +43 -0
  50. fastauth/runtime/hooks.py +59 -0
  51. fastauth/security/__init__.py +0 -0
  52. fastauth/security/jwt.py +371 -0
  53. fastauth/security/lockout.py +94 -0
  54. fastauth/security/otp.py +59 -0
  55. fastauth/security/passwords.py +46 -0
  56. fastauth/security/rate_limit.py +199 -0
  57. fastauth/security/refresh_tokens.py +161 -0
  58. fastauth/security/sessions.py +107 -0
  59. fastauth/security/tokens.py +66 -0
  60. fastauth/storage/__init__.py +1 -0
  61. fastauth/storage/base.py +318 -0
  62. fastauth/storage/beanie/__init__.py +73 -0
  63. fastauth/storage/beanie/adapter.py +578 -0
  64. fastauth/storage/beanie/documents.py +276 -0
  65. fastauth/storage/beanie/helpers.py +52 -0
  66. fastauth/storage/memory.py +404 -0
  67. fastauth/storage/postgres/__init__.py +25 -0
  68. fastauth/storage/postgres/adapter.py +779 -0
  69. fastauth/storage/postgres/migrations.py +57 -0
  70. fastauth/storage/postgres/schema.py +244 -0
  71. fastauth/web/__init__.py +0 -0
  72. fastauth/web/csrf.py +122 -0
  73. fastauth/web/fastapi.py +694 -0
  74. fastauth/web/security_headers.py +82 -0
  75. fastauth_py-0.1.0.dist-info/METADATA +326 -0
  76. fastauth_py-0.1.0.dist-info/RECORD +79 -0
  77. fastauth_py-0.1.0.dist-info/WHEEL +4 -0
  78. fastauth_py-0.1.0.dist-info/entry_points.txt +2 -0
  79. fastauth_py-0.1.0.dist-info/licenses/LICENSE +21 -0
fastauth/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """fastauth — a modular FastAPI authentication library."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastauth.config import FastAuthConfig
6
+ from fastauth.runtime.auth import FastAuth
7
+
8
+ __all__ = ["FastAuth", "FastAuthConfig", "__version__"]
9
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ """fastauth CLI."""
2
+
3
+ from __future__ import annotations
fastauth/cli/main.py ADDED
@@ -0,0 +1,207 @@
1
+ """Typer-based CLI for fastauth."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import pathlib
7
+ import secrets
8
+ from typing import Any
9
+
10
+ import typer
11
+ from rich import print as rich_print
12
+
13
+ __all__ = ["AUTH_SCAFFOLD", "AUTH_SCAFFOLDS", "app", "cli"]
14
+
15
+
16
+ app = typer.Typer(no_args_is_help=True, help="fastauth CLI")
17
+
18
+
19
+ MEMORY_AUTH_SCAFFOLD = '''\
20
+ """Authkit instance for this application.
21
+
22
+ This scaffold demonstrates explicit dependency injection. Build your
23
+ ``FastAuthConfig`` in your application code, then pass it to ``create_auth``.
24
+ fastauth never reads process-level configuration.
25
+ """
26
+ from __future__ import annotations
27
+
28
+ from fastauth import FastAuth, FastAuthConfig
29
+ from fastauth.storage.memory import InMemoryAdapter
30
+
31
+
32
+ def create_auth(config: FastAuthConfig) -> FastAuth:
33
+ return FastAuth(config, adapter=InMemoryAdapter())
34
+ '''
35
+
36
+
37
+ MONGO_AUTH_SCAFFOLD = '''\
38
+ """Mongo-backed fastauth instance for this application.
39
+
40
+ Build ``FastAuthConfig`` in your application code. The Mongo URL and database
41
+ name come from ``config.database.mongo``; fastauth never reads process-level
42
+ configuration.
43
+ """
44
+ from __future__ import annotations
45
+
46
+ from typing import Any
47
+
48
+ from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
49
+
50
+ from fastauth import FastAuth, FastAuthConfig
51
+ from fastauth.storage.beanie import BeanieAdapter, init_beanie_documents
52
+
53
+
54
+ def create_mongo_database(config: FastAuthConfig) -> AsyncIOMotorDatabase[Any]:
55
+ client: AsyncIOMotorClient[Any] = AsyncIOMotorClient(
56
+ config.database.mongo.url,
57
+ uuidRepresentation="standard",
58
+ )
59
+ return client[config.database.mongo.database_name]
60
+
61
+
62
+ def create_auth(
63
+ config: FastAuthConfig,
64
+ database: AsyncIOMotorDatabase[Any],
65
+ ) -> FastAuth:
66
+ return FastAuth(config, adapter=BeanieAdapter(database))
67
+
68
+
69
+ async def init_auth_database(database: AsyncIOMotorDatabase[Any]) -> None:
70
+ await init_beanie_documents(database)
71
+ '''
72
+
73
+
74
+ POSTGRES_AUTH_SCAFFOLD = '''\
75
+ """Postgres-backed fastauth instance for this application.
76
+
77
+ Build ``FastAuthConfig`` in your application code. The Postgres URL and table
78
+ prefix come from ``config.database.postgres``; fastauth never reads
79
+ process-level configuration.
80
+ """
81
+ from __future__ import annotations
82
+
83
+ from fastapi import FastAPI
84
+
85
+ from fastauth import FastAuth, FastAuthConfig
86
+ from fastauth.storage.postgres import PostgresAdapter
87
+
88
+
89
+ def create_auth(config: FastAuthConfig) -> FastAuth:
90
+ adapter = PostgresAdapter.from_url(
91
+ config.database.postgres.url,
92
+ table_prefix=config.database.postgres.table_prefix,
93
+ )
94
+ return FastAuth(config, adapter=adapter)
95
+
96
+
97
+ def create_app(config: FastAuthConfig) -> FastAPI:
98
+ auth = create_auth(config)
99
+ adapter = auth.context.adapter
100
+ if not isinstance(adapter, PostgresAdapter):
101
+ raise RuntimeError("expected PostgresAdapter")
102
+ app = FastAPI(lifespan=adapter.checked_lifespan(auth))
103
+ auth.install(app)
104
+ return app
105
+ '''
106
+
107
+
108
+ AUTH_SCAFFOLD = MEMORY_AUTH_SCAFFOLD
109
+ AUTH_SCAFFOLDS = {
110
+ "memory": MEMORY_AUTH_SCAFFOLD,
111
+ "mongo": MONGO_AUTH_SCAFFOLD,
112
+ "postgres": POSTGRES_AUTH_SCAFFOLD,
113
+ }
114
+
115
+
116
+ @app.command("init")
117
+ def init_command(
118
+ path: pathlib.Path = typer.Option(pathlib.Path("."), "--path", "-p"), # noqa: B008
119
+ backend: str = typer.Option(
120
+ "memory",
121
+ "--backend",
122
+ "-b",
123
+ help="Scaffold backend: memory, mongo, or postgres",
124
+ ),
125
+ ) -> None:
126
+ """Scaffold an ``auth.py`` showing explicit FastAuthConfig construction."""
127
+ backend_key = backend.lower()
128
+ if backend_key not in AUTH_SCAFFOLDS:
129
+ rich_print("[red]--backend must be one of: memory, mongo, postgres[/red]")
130
+ raise typer.Exit(code=1)
131
+ path.mkdir(parents=True, exist_ok=True)
132
+ (path / "auth.py").write_text(AUTH_SCAFFOLDS[backend_key], encoding="utf-8")
133
+ rich_print(f"[green]wrote auth.py to {path}[/green]")
134
+
135
+
136
+ @app.command("migrate")
137
+ def migrate_command(
138
+ mongo_url: str | None = typer.Option(None, "--mongo-url", "-m", help="MongoDB connection URL"),
139
+ postgres_url: str | None = typer.Option(
140
+ None,
141
+ "--postgres-url",
142
+ help="Postgres connection URL, for example postgresql+asyncpg://...",
143
+ ),
144
+ database: str = typer.Option(
145
+ "fastauth",
146
+ "--database",
147
+ "-d",
148
+ help="MongoDB database name",
149
+ ),
150
+ postgres_table_prefix: str = typer.Option(
151
+ "fastauth_",
152
+ "--postgres-table-prefix",
153
+ help="Table prefix for Postgres schema creation",
154
+ ),
155
+ ) -> None:
156
+ """Initialise database schema/indexes for fastauth storage adapters.
157
+
158
+ Connection details are passed via CLI flags. fastauth does not read
159
+ them from the environment.
160
+ """
161
+ selected_backends = [mongo_url is not None, postgres_url is not None]
162
+ if sum(selected_backends) != 1:
163
+ rich_print("[red]Pass exactly one of --mongo-url or --postgres-url[/red]")
164
+ raise typer.Exit(code=1)
165
+
166
+ async def run() -> None:
167
+ if mongo_url is not None:
168
+ from motor.motor_asyncio import AsyncIOMotorClient
169
+
170
+ from fastauth.storage.beanie import init_beanie_documents
171
+
172
+ client: AsyncIOMotorClient[Any] = AsyncIOMotorClient(
173
+ mongo_url, uuidRepresentation="standard"
174
+ )
175
+ try:
176
+ await init_beanie_documents(client[database])
177
+ rich_print("[green]indexes ensured on every fastauth collection[/green]")
178
+ finally:
179
+ client.close()
180
+ return
181
+
182
+ from fastauth.storage.postgres import PostgresAdapter
183
+
184
+ assert postgres_url is not None
185
+ adapter = PostgresAdapter.from_url(postgres_url, table_prefix=postgres_table_prefix)
186
+ try:
187
+ applied = await adapter.apply_migrations()
188
+ version = await adapter.schema_version()
189
+ if applied:
190
+ rich_print(f"[green]Postgres migrations applied: {applied}[/green]")
191
+ else:
192
+ rich_print("[green]Postgres schema already current[/green]")
193
+ rich_print(f"[green]Postgres fastauth schema version: {version}[/green]")
194
+ finally:
195
+ await adapter.engine.dispose()
196
+
197
+ asyncio.run(run())
198
+
199
+
200
+ @app.command("generate-secret")
201
+ def generate_secret_command() -> None:
202
+ """Print a fresh 64-char URL-safe secret."""
203
+ rich_print(secrets.token_urlsafe(48))
204
+
205
+
206
+ def cli() -> None:
207
+ app()
fastauth/config.py ADDED
@@ -0,0 +1,270 @@
1
+ """Pydantic configuration for fastauth.
2
+
3
+ :class:`FastAuthConfig` is a plain Pydantic v2 ``BaseModel``. All values are
4
+ passed explicitly at instantiation time, with Pydantic's validation enforced
5
+ on construction. The framework has no notion of "environment variables" —
6
+ reading from process-level configuration, files, AWS Secrets Manager,
7
+ HashiCorp Vault, or any other source is the consumer's responsibility. Pass
8
+ the values in as constructor arguments and fastauth will validate them.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Literal
14
+
15
+ from pydantic import BaseModel, ConfigDict, Field, SecretStr
16
+
17
+ from fastauth.domain.enums import (
18
+ DatabaseBackendKind,
19
+ RateLimitStorageKind,
20
+ SessionStrategyKind,
21
+ WireFormat,
22
+ )
23
+
24
+ __all__ = [
25
+ "AdvancedConfig",
26
+ "AppConfig",
27
+ "CookieConfig",
28
+ "CsrfConfig",
29
+ "DatabaseConfig",
30
+ "DeleteAccountConfig",
31
+ "EmailChangeConfig",
32
+ "EmailConfig",
33
+ "EmailVerificationConfig",
34
+ "FastAuthConfig",
35
+ "LockoutConfig",
36
+ "MemoryDatabaseConfig",
37
+ "MongoDatabaseConfig",
38
+ "PasswordConfig",
39
+ "PasswordResetConfig",
40
+ "PostgresDatabaseConfig",
41
+ "RateLimitConfig",
42
+ "RefreshTokenConfig",
43
+ "SecurityHeadersConfig",
44
+ "SessionConfig",
45
+ ]
46
+
47
+
48
+ class ConfigSection(BaseModel):
49
+ model_config = ConfigDict(extra="forbid", validate_assignment=True)
50
+
51
+
52
+ class AppConfig(ConfigSection):
53
+ name: str = "fastauth"
54
+ base_url: str = "http://localhost:8000"
55
+ base_path: str = "/auth"
56
+
57
+
58
+ class SessionConfig(ConfigSection):
59
+ strategy: SessionStrategyKind = SessionStrategyKind.DATABASE
60
+ max_age_seconds: int = 60 * 60 * 24 * 7
61
+ idle_timeout_seconds: int | None = None
62
+ rotate_on_refresh: bool = True
63
+
64
+
65
+ class CookieConfig(ConfigSection):
66
+ name: str = "fastauth.session_token"
67
+ domain: str | None = None
68
+ path: str = "/"
69
+ secure: bool = True
70
+ http_only: bool = True
71
+ same_site: Literal["lax", "strict", "none"] = "lax"
72
+
73
+
74
+ class PasswordConfig(ConfigSection):
75
+ min_length: int = 8
76
+ argon2_time_cost: int = 3
77
+ argon2_memory_cost_kib: int = 64 * 1024 # 64 MiB
78
+ argon2_parallelism: int = 4
79
+
80
+
81
+ class EmailConfig(ConfigSection):
82
+ from_address: str = "no-reply@localhost"
83
+ from_name: str = "fastauth"
84
+ verification_subject: str = "Verify your email"
85
+ password_reset_subject: str = "Reset your password" # noqa: S105
86
+ template_directory: str | None = None
87
+
88
+
89
+ class EmailVerificationConfig(ConfigSection):
90
+ token_ttl_minutes: int = 15
91
+ require_verified_for_sign_in: bool = False
92
+ base_verify_url: str = "http://localhost:8000/auth/verify-email"
93
+
94
+
95
+ class PasswordResetConfig(ConfigSection):
96
+ token_ttl_minutes: int = 30
97
+ base_reset_url: str = "http://localhost:8000/auth/reset-password"
98
+
99
+
100
+ class EmailChangeConfig(ConfigSection):
101
+ token_ttl_minutes: int = 15
102
+ base_confirm_url: str = "http://localhost:8000/auth/change-email/confirm"
103
+ subject: str = "Confirm your new email address"
104
+
105
+
106
+ class DeleteAccountConfig(ConfigSection):
107
+ token_ttl_minutes: int = 15
108
+ base_confirm_url: str = "http://localhost:8000/auth/delete-account/confirm"
109
+ subject: str = "Confirm account deletion"
110
+
111
+
112
+ class RateLimitConfig(ConfigSection):
113
+ enabled: bool = True
114
+ window_seconds: int = 60
115
+ max_requests: int = 100
116
+ storage: RateLimitStorageKind = RateLimitStorageKind.MEMORY
117
+
118
+
119
+ class CsrfConfig(ConfigSection):
120
+ enabled: bool = True
121
+ trusted_origins: list[str] = Field(default_factory=list)
122
+ allow_relative_paths: bool = True
123
+
124
+
125
+ class LockoutConfig(ConfigSection):
126
+ """Account-lockout policy: lock an identifier after N failed sign-ins.
127
+
128
+ ``window_seconds`` doubles as the lockout duration — failures older than
129
+ the window are forgotten, and a triggered lockout naturally expires at the
130
+ same horizon. ``max_failures=5`` matches NIST 800-63B's guidance for
131
+ consumer auth (5 is the typical default in libraries like Devise and
132
+ fastapi-users); raise it for low-risk applications or to combat false
133
+ positives from shared NAT.
134
+ """
135
+
136
+ enabled: bool = True
137
+ max_failures: int = 5
138
+ window_seconds: int = 15 * 60
139
+
140
+
141
+ class RefreshTokenConfig(ConfigSection):
142
+ """Long-lived refresh-token policy.
143
+
144
+ Refresh tokens piggyback on the bearer-token transport: when ``enabled``
145
+ is true (default) AND the sign-up / sign-in request opts into a bearer
146
+ response via ``include_token=true``, the response carries a
147
+ ``refresh_token`` that the client can later exchange at
148
+ ``POST /auth/refresh`` for a fresh access session. Cookie-only clients
149
+ (``include_token=false``) skip the refresh token entirely — their cookie
150
+ *is* the long-lived credential, so a separate refresh token would be
151
+ redundant.
152
+
153
+ Tokens are rotated on every use (one-time-use; OAuth 2.1 recommendation):
154
+ presenting a refresh token returns a *new* token and marks the old one
155
+ consumed. Presenting a previously-consumed token revokes the entire
156
+ rotation chain (theft-detection) — the user is forced to sign in again.
157
+
158
+ ``max_age_seconds`` defaults to 30 days. ``absolute_max_age_seconds``
159
+ caps the total lifetime of a single rotation chain — even with continuous
160
+ rotation, a chain expires after this many seconds since the initial
161
+ sign-in. Set to ``None`` to disable the absolute cap (rotation can extend
162
+ sessions indefinitely as long as the user is active).
163
+ """
164
+
165
+ enabled: bool = True
166
+ max_age_seconds: int = 30 * 24 * 60 * 60
167
+ absolute_max_age_seconds: int | None = None
168
+
169
+
170
+ class SecurityHeadersConfig(ConfigSection):
171
+ """Response-header hardening (HSTS, frame-ancestors, MIME-sniffing, …).
172
+
173
+ Defaults match the OWASP Secure Headers Project's recommendations for a
174
+ typical SaaS web application. Every header is individually toggleable —
175
+ set ``hsts=None`` (etc.) to omit. The default ``hsts`` value
176
+ (``"max-age=31536000; includeSubDomains"``) is conservative; production
177
+ deployments preloaded into the HSTS preload list should add ``; preload``.
178
+
179
+ ``content_security_policy`` defaults to ``None`` because a meaningful CSP
180
+ is application-specific. Set it to a string and the middleware will emit
181
+ a ``Content-Security-Policy`` header verbatim.
182
+ """
183
+
184
+ enabled: bool = True
185
+ hsts: str | None = "max-age=31536000; includeSubDomains"
186
+ x_frame_options: str | None = "DENY"
187
+ x_content_type_options: str | None = "nosniff"
188
+ referrer_policy: str | None = "strict-origin-when-cross-origin"
189
+ permissions_policy: str | None = None
190
+ content_security_policy: str | None = None
191
+
192
+
193
+ class MongoDatabaseConfig(ConfigSection):
194
+ url: str = "mongodb://localhost:27017"
195
+ database_name: str = "fastauth"
196
+
197
+
198
+ class PostgresDatabaseConfig(ConfigSection):
199
+ url: str = "postgresql+asyncpg://localhost/fastauth"
200
+ table_prefix: str = "fastauth_"
201
+
202
+
203
+ class MemoryDatabaseConfig(ConfigSection):
204
+ pass
205
+
206
+
207
+ class DatabaseConfig(ConfigSection):
208
+ backend: DatabaseBackendKind = DatabaseBackendKind.MEMORY
209
+ memory: MemoryDatabaseConfig = Field(default_factory=MemoryDatabaseConfig)
210
+ mongo: MongoDatabaseConfig = Field(default_factory=MongoDatabaseConfig)
211
+ postgres: PostgresDatabaseConfig = Field(default_factory=PostgresDatabaseConfig)
212
+
213
+
214
+ class AdvancedConfig(ConfigSection):
215
+ ip_address_headers: list[str] = Field(default_factory=lambda: ["x-forwarded-for"])
216
+ ipv6_subnet: int = 64
217
+ cookie_secure_prefix: bool = True
218
+
219
+
220
+ def empty_secret_str_list() -> list[SecretStr]:
221
+ """Typed default factory for ``secret_key_rotation`` (keeps pyright strict happy)."""
222
+ return []
223
+
224
+
225
+ class FastAuthConfig(BaseModel):
226
+ """Top-level fastauth configuration.
227
+
228
+ A plain Pydantic v2 ``BaseModel``. Construction validates the entire tree
229
+ eagerly. **The framework never reads process-level configuration or any
230
+ other external source** — every value comes from the constructor. Consumers
231
+ should read their chosen configuration source in their own code and pass
232
+ the values in explicitly.
233
+
234
+ Example::
235
+
236
+ from pydantic import SecretStr
237
+ from fastauth import FastAuth, FastAuthConfig
238
+ from fastauth.config import DatabaseConfig, MongoDatabaseConfig
239
+
240
+ config = FastAuthConfig(
241
+ secret_key=SecretStr("..."),
242
+ database=DatabaseConfig(
243
+ backend="mongo",
244
+ mongo=MongoDatabaseConfig(url="mongodb://localhost:27017"),
245
+ ),
246
+ )
247
+ auth = FastAuth(config, adapter=...)
248
+ """
249
+
250
+ model_config = ConfigDict(extra="forbid", validate_assignment=True)
251
+
252
+ secret_key: SecretStr
253
+ secret_key_rotation: list[SecretStr] = Field(default_factory=empty_secret_str_list)
254
+ app: AppConfig = Field(default_factory=AppConfig)
255
+ session: SessionConfig = Field(default_factory=SessionConfig)
256
+ cookie: CookieConfig = Field(default_factory=CookieConfig)
257
+ password: PasswordConfig = Field(default_factory=PasswordConfig)
258
+ email: EmailConfig = Field(default_factory=EmailConfig)
259
+ email_verification: EmailVerificationConfig = Field(default_factory=EmailVerificationConfig)
260
+ password_reset: PasswordResetConfig = Field(default_factory=PasswordResetConfig)
261
+ email_change: EmailChangeConfig = Field(default_factory=EmailChangeConfig)
262
+ delete_account: DeleteAccountConfig = Field(default_factory=DeleteAccountConfig)
263
+ rate_limit: RateLimitConfig = Field(default_factory=RateLimitConfig)
264
+ csrf: CsrfConfig = Field(default_factory=CsrfConfig)
265
+ lockout: LockoutConfig = Field(default_factory=LockoutConfig)
266
+ refresh_token: RefreshTokenConfig = Field(default_factory=RefreshTokenConfig)
267
+ security_headers: SecurityHeadersConfig = Field(default_factory=SecurityHeadersConfig)
268
+ database: DatabaseConfig = Field(default_factory=DatabaseConfig)
269
+ advanced: AdvancedConfig = Field(default_factory=AdvancedConfig)
270
+ wire_format: WireFormat = WireFormat.SNAKE
File without changes
@@ -0,0 +1,125 @@
1
+ """Project-wide string enumerations. Every closed set of strings lives here."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+
7
+ __all__ = [
8
+ "AuditEventType",
9
+ "DatabaseBackendKind",
10
+ "EmailMessageKind",
11
+ "HookPhase",
12
+ "JwtAlgorithm",
13
+ "ProviderId",
14
+ "RateLimitStorageKind",
15
+ "SessionStrategyKind",
16
+ "TokenType",
17
+ "VerificationPurpose",
18
+ "WireFormat",
19
+ ]
20
+
21
+
22
+ class ProviderId(StrEnum):
23
+ CREDENTIAL = "credential"
24
+ EMAIL_OTP = "email-otp"
25
+
26
+
27
+ class VerificationPurpose(StrEnum):
28
+ EMAIL_VERIFICATION = "email-verification"
29
+ PASSWORD_RESET = "password-reset" # noqa: S105
30
+ EMAIL_CHANGE = "email-change"
31
+ ACCOUNT_DELETION = "account-deletion"
32
+ EMAIL_OTP_SIGN_IN = "email-otp-sign-in"
33
+ EMAIL_OTP_VERIFICATION = "email-otp-verification"
34
+ EMAIL_OTP_PASSWORD_RESET = "email-otp-password-reset" # noqa: S105
35
+ EMAIL_OTP_EMAIL_CHANGE = "email-otp-email-change"
36
+
37
+
38
+ class AuditEventType(StrEnum):
39
+ USER_SIGNED_UP = "user_signed_up"
40
+ USER_SIGNED_IN = "user_signed_in"
41
+ USER_SIGNED_OUT = "user_signed_out"
42
+ USER_EMAIL_VERIFIED = "user_email_verified"
43
+ USER_UPDATED = "user_updated"
44
+ USER_EMAIL_CHANGE_REQUESTED = "user_email_change_requested"
45
+ USER_EMAIL_CHANGED = "user_email_changed"
46
+ USER_DELETE_REQUESTED = "user_delete_requested"
47
+ USER_DELETED = "user_deleted"
48
+ SESSION_CREATED = "session_created"
49
+ SESSION_REVOKED = "session_revoked"
50
+ SESSIONS_REVOKED_ALL = "sessions_revoked_all"
51
+ ACCOUNT_LINKED = "account_linked"
52
+ ACCOUNT_UNLINKED = "account_unlinked"
53
+ PASSWORD_CHANGED = "password_changed" # noqa: S105
54
+ PASSWORD_RESET_REQUESTED = "password_reset_requested" # noqa: S105
55
+ PASSWORD_RESET_COMPLETED = "password_reset_completed" # noqa: S105
56
+ EMAIL_VERIFICATION_SENT = "email_verification_sent"
57
+ API_KEY_CREATED = "api_key_created"
58
+ API_KEY_REVOKED = "api_key_revoked"
59
+ API_KEY_VERIFIED_FAILED = "api_key_verified_failed"
60
+ SECURITY_VELOCITY_EXCEEDED = "security_velocity_exceeded"
61
+ ACCOUNT_LOCKED = "account_locked"
62
+ OTP_REQUESTED = "otp_requested"
63
+ OTP_VERIFIED = "otp_verified"
64
+ OTP_VERIFY_FAILED = "otp_verify_failed"
65
+
66
+
67
+ class SessionStrategyKind(StrEnum):
68
+ DATABASE = "database"
69
+ JWT = "jwt"
70
+
71
+
72
+ class DatabaseBackendKind(StrEnum):
73
+ MEMORY = "memory"
74
+ MONGO = "mongo"
75
+ POSTGRES = "postgres"
76
+
77
+
78
+ class WireFormat(StrEnum):
79
+ """JSON casing convention applied to public request / response bodies.
80
+
81
+ ``SNAKE`` (default) emits Pythonic ``snake_case`` field names — the
82
+ historical and back-compat behaviour. ``CAMEL`` emits ``camelCase``
83
+ (e.g. ``email_verified`` → ``emailVerified``, ``refresh_token`` →
84
+ ``refreshToken``).
85
+
86
+ Both casings are always **accepted** on input regardless of this
87
+ setting — toggling only affects output.
88
+ """
89
+
90
+ SNAKE = "snake"
91
+ CAMEL = "camel"
92
+
93
+
94
+ class TokenType(StrEnum):
95
+ SESSION = "session"
96
+ VERIFICATION = "verification"
97
+ API_KEY = "api-key"
98
+
99
+
100
+ class HookPhase(StrEnum):
101
+ BEFORE_CREATE = "before_create"
102
+ AFTER_CREATE = "after_create"
103
+ BEFORE_UPDATE = "before_update"
104
+ AFTER_UPDATE = "after_update"
105
+ BEFORE_DELETE = "before_delete"
106
+ AFTER_DELETE = "after_delete"
107
+
108
+
109
+ class RateLimitStorageKind(StrEnum):
110
+ MEMORY = "memory"
111
+ DATABASE = "database"
112
+
113
+
114
+ class EmailMessageKind(StrEnum):
115
+ VERIFICATION = "verification"
116
+ PASSWORD_RESET = "password-reset" # noqa: S105
117
+ ACCOUNT_DELETION = "account-deletion"
118
+
119
+
120
+ class JwtAlgorithm(StrEnum):
121
+ EDDSA = "EdDSA"
122
+ ES256 = "ES256"
123
+ RS256 = "RS256"
124
+ PS256 = "PS256"
125
+ ES512 = "ES512"