Skip to content

UsersSettings ignores SM_USERS_* env vars but validator still requires non-placeholder secrets #81

Description

@antosubash

Summary

users.settings.UsersSettings no longer reads SM_USERS_* env vars at boot — but the validator that requires RESET_PASSWORD_TOKEN_SECRET and VERIFICATION_TOKEN_SECRET to be non-default in production fires anyway. So the documented escape hatch (set SM_USERS_RESET_PASSWORD_TOKEN_SECRET=...) silently has no effect, and the user can't get past the validator without DB-backed settings setup.

What the docstring says

"""Users module settings — DB-backed via ``register_module_settings``.

Construction no longer reads ``SM_USERS_*`` environment variables. Values
come from pydantic defaults at boot, then get hydrated from the DB by the
hosting lifespan before module ``on_startup`` runs. Runtime changes go
through ``settings.reload.apply_changes_and_reload``.

The one remaining env read is ``SM_ENVIRONMENT``, consulted by the
``@model_validator`` to refuse placeholder token secrets in production —
that's a host-level setting, not a users-module field.
"""

What actually happens

SM_ENVIRONMENT=production \
SM_USERS_RESET_PASSWORD_TOKEN_SECRET="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')" \
SM_USERS_VERIFICATION_TOKEN_SECRET="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')" \
uv run uvicorn main:app

Boot fails:

pydantic_core._pydantic_core.ValidationError: 1 validation error for UsersSettings
  Value error, users.RESET_PASSWORD_TOKEN_SECRET, VERIFICATION_TOKEN_SECRET must
  be set to non-default value(s) when SM_ENVIRONMENT='production'.

The env vars are ignored. The model is constructed with extra="ignore" so they're dropped, then defaults ("dev-reset-token-secret-change-me") are used, then the validator rejects.

What you have to do

The new model says secrets come from DB-backed settings. So before booting the app in production, you have to:

  1. Boot the app once in development mode.
  2. Use the runtime settings UI / API to override users.reset_password_token_secret and users.verification_token_secret to non-placeholder values, persisted in the DB.
  3. Restart in production mode.

This is an awful onboarding story. There's a chicken-and-egg problem the moment you provision a fresh production DB: you can't boot to set the values, but you can't set the values without booting.

Suggested fixes

  1. Restore env var override for the secrets. Module-level settings hydrated from DB are a fine UX for runtime tweaks, but startup bootstrapping needs the env-var path. Add the two fields back to the env-readable list.

  2. Or document the bootstrap procedure clearly. A migration / management command that seeds the secrets from env vars on a fresh DB would also work:

    SM_USERS_RESET_PASSWORD_TOKEN_SECRET=... \
    SM_USERS_VERIFICATION_TOKEN_SECRET=... \
    uv run sm-users seed-settings-from-env
  3. Or relax the validator when the values are non-placeholder strings, regardless of where they came from. The validator currently checks secret == _PLACEHOLDER_RESET_SECRET. If the user set a real string in the DB-backed settings, the validator won't fire — but the env-var path doesn't reach the model in the first place.

The current state combines the worst of both worlds: validator rejects placeholders, env vars don't override, no obvious bootstrap path.

Impact

I tried SM_ENVIRONMENT=production to use the built assets during the smoke test and was completely blocked. Falling back to SM_ENVIRONMENT=development worked but kept Vite in the path, exposing the preamble bug (#66). Without DB-backed settings tooling, fresh production deployments need an out-of-band write to the settings table.

Environment

  • simple_module_users==0.0.3
  • Discovered Phase 5 smoke test, laco_wiki_python rewrite.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions