Configuration¶
regstack reads its configuration from one RegStackConfig (a
pydantic_settings.BaseSettings). The same fields are addressable from
TOML files, environment variables, and programmatic kwargs.
Loading order¶
Highest priority wins:
Programmatic kwargs to
RegStackConfig(...)orRegStackConfig.load(...).Real environment variables (
REGSTACK_*).The
regstack.secrets.envfile in the current directory (or whatever path you pass assecrets_env_path=). Lines look likeREGSTACK_JWT_SECRET=....A TOML file at
$REGSTACK_CONFIG, or./regstack.tomlif present, or whatever path you pass astoml_path=.Field defaults.
Nested settings (email.from_address, sms.twilio_account_sid) are
addressed in env using a __ separator: REGSTACK_EMAIL__FROM_ADDRESS.
Top-level fields¶
Field |
Default |
Notes |
|---|---|---|
|
|
Branded into UI templates and email subjects. |
|
|
Origin used to build verification / reset / email-change links. |
|
|
Tells the host app to trust |
|
|
SecretStr. Backend selected by URL scheme — see “Backends” below. |
|
|
Mongo-only fallback when the URL has no |
|
|
Table / collection name. Override to share a database with another app. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Backends¶
regstack picks a backend at construction time from the URL scheme of
database_url:
Backend |
URL scheme |
Notes |
|---|---|---|
SQLite |
Relative file: |
Default. Bundled in the base install — no extras needed. See SQLite URL forms below for absolute-path and in-memory variants. |
Postgres |
|
Requires the |
MongoDB |
|
Requires the |
The active backend exposes the same five repository protocols on
RegStack.users, .pending, .blacklist, .attempts,
.mfa_codes. Routers / hooks never branch on backend kind.
SQLite URL forms¶
SQLite is the default backend and the only one whose URL points at a file rather than a network host. The shape is always:
sqlite+aiosqlite:///PATH
…where the prefix is fixed and PATH is whatever you want SQLAlchemy
to open. Three useful values for PATH:
|
Resolves to |
When to use it |
|---|---|---|
|
|
Local dev and the bundled |
|
the absolute file at that path |
Production. Point it at the host’s persistent volume. |
|
per-process in-memory DB |
Per-test fixtures only — contents vanish at process exit. |
So the three full URLs are:
sqlite+aiosqlite:///./dbname.db
sqlite+aiosqlite:////var/lib/app/dbname.db
sqlite+aiosqlite:///:memory:
The absolute form looks like it has four slashes, but it’s the same
three-slash prefix as the others — the fourth slash is the leading
/ of the absolute path. This is the single most common SQLite-URL
paper-cut.
Both file forms create the file on first connection, so a fresh
checkout running uv run regstack init && uv run uvicorn … works
with no mkdir or touch step.
JWT¶
Field |
Default |
Notes |
|---|---|---|
|
(generated) |
64-byte URL-safe by default. |
|
|
One of |
|
|
Session token lifetime. |
|
|
Sets the |
|
|
Only |
|
|
24h. |
|
|
30 min. |
|
|
1 h. |
Feature flags¶
Flag |
Default |
Effect |
|---|---|---|
|
|
Register stores in |
|
|
When false, |
|
|
Mounts |
|
|
When false, |
|
|
Mounts |
|
|
Mounts the SSR pages. |
|
|
Mounts |
|
|
Mounts |
Lockout (login)¶
Field |
Default |
Notes |
|---|---|---|
|
|
Set in tests to skip lockout writes. |
|
|
Fail this many times in the window → 429. |
|
|
Sliding window for failures, also TTL on the |
|
|
Deprecated since 0.5.4; kept for back-compat but no longer
wired. Use |
|
|
Same as above. Use a slowapi-syntax limit string on
|
Per-route rate limits¶
New in 0.5.4. Opt-in: install the rate_limit extra (pip install 'regstack[rate_limit]' — pulls in slowapi) or pass your own
slowapi.Limiter instance as RegStack(rate_limiter=...).
Each field is a slowapi-syntax string. Empty / unset = no limit on
that route. The per-account LockoutService (see “Lockout (login)”
above) is unchanged and stacks on top of login_rate_limit — they
defend different axes: lockout defends one account against
credential-stuffing; the IP rate limits defend each endpoint
against one IP spamming requests across many accounts.
Field |
Default |
Notes |
|---|---|---|
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
|
|
Per-IP on |
If any *_rate_limit is set but neither a rate_limiter= argument
nor the rate_limit extra is available, RegStack.router raises
RuntimeError on first access — failing closed beats silently
disabling the protection. The host owns app.state.limiter and
the RateLimitExceeded exception handler; slowapi itself defines
the 429 response shape.
SMS / 2FA¶
Field |
Default |
Notes |
|---|---|---|
|
|
Numeric code length. |
|
|
5 minutes. |
|
|
Wrong-code attempts before the row is deleted (lockout). |
|
|
Lifetime of the JWT that links the two login steps / the phone-setup steps. |
Sub-tables¶
[email] (EmailConfig):
[email]
backend = "console" # console | smtp | ses
from_address = "noreply@app.example.com"
# from_name defaults to app_name when unset (new in 0.7.0). Set
# explicitly to override — e.g. when app_name is an internal product
# code that shouldn't appear to end users.
from_name = "Example App"
# When true, the console backend logs the full rendered message body
# at INFO instead of DEBUG. Off by default (bodies contain one-time
# tokens). Turn on only for the `console` backend in a dev/staging
# deployment that you intend to probe with `regstack validate`.
log_bodies = false
# smtp
smtp_host = "smtp.app.example.com"
smtp_port = 587
smtp_starttls = true
smtp_username = "<username>"
# smtp_password is a SecretStr — set via REGSTACK_EMAIL__SMTP_PASSWORD
# ses
ses_region = "eu-west-1"
# Pick ONE of these credential sources (they are mutually exclusive,
# validated at config-load time):
ses_profile = "production" # AWS profile name from ~/.aws/credentials
# OR set explicit creds (typically split between regstack.toml and
# regstack.secrets.env so the secret never lands in version control):
# ses_access_key_id = "AKIA..." # SecretStr
# ses_secret_access_key = "..." # SecretStr — set via REGSTACK_EMAIL__SES_SECRET_ACCESS_KEY
# If neither profile nor explicit creds is set, boto3 falls back to its
# default credential chain (env vars, ~/.aws/credentials default profile,
# IAM instance role).
Email-link URL composition¶
Verification, password-reset, and email-change emails contain a URL
that the user clicks to complete the flow. By default the URL is
composed from base_url, the resolved UI prefix, and the
flow-specific path (/verify, /reset-password,
/confirm-email-change). Two layers of overrides are available
when the default doesn’t fit:
Field |
Default |
Notes |
|---|---|---|
|
|
Path prefix prepended to the bare flow path (e.g. |
|
|
Full URL template for the verification email link. Substitutes |
|
|
Same shape, for password-reset links. |
|
|
Same shape, for change-email confirmation links. |
The four URL-building call sites (in routers/register.py,
verify.py, password.py, account.py) all delegate to
RegStackConfig.resolve_{verify,password_reset,email_change}_url(token),
so hosts overriding the URL story only need to set the config
fields — no template-package surgery required.
[sms] (SmsConfig):
[sms]
backend = "null" # null | sns | twilio
from_number = "+15555550100"
# When true (default), the `null` backend logs the SMS body
# (including the 6-digit code) at INFO. Set to false to silence code
# logging in shared environments. Other backends ignore. New in 0.7.0.
log_bodies = true
# sns
sns_region = "eu-west-1"
# twilio
twilio_account_sid = "AC…"
# twilio_auth_token via REGSTACK_SMS__TWILIO_AUTH_TOKEN
[oauth] (OAuthConfig):
[oauth]
google_client_id = "12345.apps.googleusercontent.com"
# google_client_secret via REGSTACK_OAUTH__GOOGLE_CLIENT_SECRET
# google_redirect_uri = "https://your.app/api/auth/oauth/google/callback"
# (default: f"{base_url}{api_prefix}/oauth/google/callback")
# Account-linking policy. Off by default — see docs/oauth.md and
# docs/security.md for the threat model. On = a Google sign-in for an
# existing email-registered user is auto-linked when Google's
# email_verified=true. Hosts choosing on are accepting the email-
# recycling-at-the-provider risk in exchange for less friction.
auto_link_verified_emails = false
# When true, an OAuth sign-in for a user with SMS MFA enabled still
# goes through the second-factor step. Off by default — the OAuth
# provider already authenticated the human.
enforce_mfa_on_oauth_signin = false
state_ttl_seconds = 300 # in-flight state row lifetime
completion_ttl_seconds = 30 # /oauth/exchange window after callback
SSR / theming¶
Field |
Default |
Notes |
|---|---|---|
|
|
Used by |
|
|
Used for self-referential links on SSR pages. |
|
|
Where |
|
|
Optional URL of a host-supplied |
|
|
Rendered in the SSR header. |
|
|
Subtitle in the SSR header. |
|
|
Directories prepended to the Jinja2 ChoiceLoader (host-first). |
|
|
Reserved. |