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:

  1. Programmatic kwargs to RegStackConfig(...) or RegStackConfig.load(...).

  2. Real environment variables (REGSTACK_*).

  3. The regstack.secrets.env file in the current directory (or whatever path you pass as secrets_env_path=). Lines look like REGSTACK_JWT_SECRET=....

  4. A TOML file at $REGSTACK_CONFIG, or ./regstack.toml if present, or whatever path you pass as toml_path=.

  5. 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

app_name

"RegStack"

Branded into UI templates and email subjects.

base_url

http://localhost:8000

Origin used to build verification / reset / email-change links.

behind_proxy

false

Tells the host app to trust X-Forwarded-*.

database_url

sqlite+aiosqlite:///./regstack.db

SecretStr. Backend selected by URL scheme — see “Backends” below.

mongodb_database

"regstack"

Mongo-only fallback when the URL has no /dbname path.

user_collection

"users"

Table / collection name. Override to share a database with another app.

pending_collection

"pending_registrations"

blacklist_collection

"token_blacklist"

login_attempt_collection

"login_attempts"

mfa_code_collection

"mfa_codes"

oauth_identity_collection

"oauth_identities"

oauth_state_collection

"oauth_states"

Backends

regstack picks a backend at construction time from the URL scheme of database_url:

Backend

URL scheme

Notes

SQLite

Relative file: sqlite+aiosqlite:///./dbname.db

Default. Bundled in the base install — no extras needed. See SQLite URL forms below for absolute-path and in-memory variants.

Postgres

postgresql+asyncpg://<username>:<password>@dbhost.example.com:5432/dbname

Requires the postgres extra (pulls in asyncpg). The driver is pinned to +asyncpg — sync drivers won’t work.

MongoDB

mongodb://<username>:<password>@dbhost.example.com:27017/dbname (or mongodb+srv://<username>:<password>@app.abc123.mongodb.net/dbname)

Requires the mongo extra (pulls in pymongo). Database is taken from the URL path; falls back to mongodb_database if absent.

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:

PATH

Resolves to

When to use it

./dbname.db

dbname.db in the process working directory

Local dev and the bundled examples/ apps.

/var/lib/app/dbname.db

the absolute file at that path

Production. Point it at the host’s persistent volume.

:memory:

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

jwt_secret

(generated)

64-byte URL-safe by default. RegStack.__init__ raises if empty.

jwt_algorithm

"HS256"

One of HS256, HS384, HS512.

jwt_ttl_seconds

7200

Session token lifetime.

jwt_audience

None

Sets the aud claim if non-null and validates on decode.

transport

"bearer"

Only "bearer" is accepted. "cookie" was removed in 0.7.0 — the option was never wired and silently no-op’d. If cookie transport ships later, the literal will be widened back.

verification_token_ttl_seconds

86400

24h.

password_reset_token_ttl_seconds

1800

30 min.

email_change_token_ttl_seconds

3600

1 h.

Feature flags

Flag

Default

Effect

require_verification

true

Register stores in pending_registrations until the verification email is clicked.

allow_registration

true

When false, /register returns 403.

enable_password_reset

true

Mounts /forgot-password and /reset-password.

enable_account_deletion

true

When false, DELETE /account returns 404.

enable_admin_router

false

Mounts /admin/* routes (requires is_superuser).

enable_ui_router

false

Mounts the SSR pages.

enable_sms_2fa

false

Mounts /phone/* routes and gates the MFA second step in /login.

enable_oauth

false

Mounts /oauth/* routes when at least one provider is registered (currently Google). Requires the oauth extra (pip install 'regstack[oauth]').

Lockout (login)

Field

Default

Notes

rate_limit_disabled

false

Set in tests to skip lockout writes.

login_lockout_threshold

5

Fail this many times in the window → 429.

login_lockout_window_seconds

900

Sliding window for failures, also TTL on the login_attempts collection.

login_max_per_minute

5

Deprecated since 0.5.4; kept for back-compat but no longer wired. Use login_rate_limit (see “Per-route rate limits” below).

login_max_per_hour

20

Same as above. Use a slowapi-syntax limit string on login_rate_limit like "5/minute;20/hour".

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

login_rate_limit

""

Per-IP on POST /login.

login_mfa_confirm_rate_limit

""

Per-IP on POST /login/mfa-confirm. Stacks on top of the per-code attempts counter on mfa_codes; defends the SMS second factor against distributed guessing across many source IPs. New in 0.7.0.

register_rate_limit

""

Per-IP on POST /register.

forgot_password_rate_limit

""

Per-IP on POST /forgot-password.

reset_password_rate_limit

""

Per-IP on POST /reset-password.

verify_rate_limit

""

Per-IP on POST /verify.

resend_verification_rate_limit

""

Per-IP on POST /resend-verification.

change_password_rate_limit

""

Per-IP on POST /change-password.

change_email_rate_limit

""

Per-IP on POST /change-email.

confirm_email_change_rate_limit

""

Per-IP on POST /confirm-email-change.

delete_account_rate_limit

""

Per-IP on DELETE /account.

oauth_exchange_rate_limit

""

Per-IP on POST /oauth/exchange (the SPA token-pickup endpoint). New in 0.7.0.

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

sms_code_length

6

Numeric code length.

sms_code_ttl_seconds

300

5 minutes.

sms_code_max_attempts

5

Wrong-code attempts before the row is deleted (lockout).

mfa_pending_token_ttl_seconds

600

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).

SSR / theming

Field

Default

Notes

api_prefix

"/api/auth"

Used by regstack.js to call back into the JSON API.

ui_prefix

"/account"

Used for self-referential links on SSR pages.

static_prefix

"/regstack-static"

Where app.mount(... regstack.static_files) should live.

theme_css_url

None

Optional URL of a host-supplied theme.css loaded after the bundled defaults.

brand_logo_url

None

Rendered in the SSR header.

brand_tagline

None

Subtitle in the SSR header.

extra_template_dirs

[]

Directories prepended to the Jinja2 ChoiceLoader (host-first).

extra_static_dirs

[]

Reserved.