Skip to content

users.endpoints.views.login_page dev_accounts resolution skips .env fallback — inconsistent with users.bootstrap #159

Description

@antosubash

Summary

users/endpoints/views.py:login_page resolves the bootstrap credentials it surfaces as dev_accounts (the "Dev quick-login" buttons on /users/login) from only two sources: UsersSettings and os.environ. The companion code in users/bootstrap.py:bootstrap_admin_from_env resolves the same fields from three sources, with a direct parse_dotenv() fallback. This asymmetry means an admin can be successfully bootstrapped on one boot path while the login page shows no quick-login button on another — even though the design intent is that both surface the same dev credentials.

Code

users/endpoints/views.py:50-69 (current):

if request.app.state.sm.settings.is_development:
    admin_email = users_settings.bootstrap_email or os.environ.get(
        "SM_USERS_BOOTSTRAP_EMAIL", ""
    )
    admin_password = users_settings.bootstrap_password or os.environ.get(
        "SM_USERS_BOOTSTRAP_PASSWORD", ""
    )
    if admin_email and admin_password:
        dev_accounts.append({"label": "Admin", "email": admin_email, "password": admin_password})
    user_email = users_settings.bootstrap_user_email or os.environ.get(
        "SM_USERS_BOOTSTRAP_USER_EMAIL", ""
    )
    user_password = users_settings.bootstrap_user_password or os.environ.get(
        "SM_USERS_BOOTSTRAP_USER_PASSWORD", ""
    )
    if user_email and user_password:
        dev_accounts.append({"label": "User", "email": user_email, "password": user_password})

users/bootstrap.py:203-225 (the correct three-source pattern):

async def bootstrap_admin_from_env(app: FastAPI) -> None:
    settings: UsersSettings = app.state.users.settings
    dotenv_vars = _read_dotenv_bootstrap_vars()
    resolved = {
        attr: getattr(settings, attr) or os.environ.get(env_key) or dotenv_vars.get(env_key, "")
        for attr, env_key in BOOTSTRAP_ENV_KEYS.items()
    }

bootstrap.py even has a comment explaining why the direct .env read is needed:

UsersSettings deliberately doesn't use env_file — runtime fields come from the DB, and pulling the whole .env in would re-expose every SMTP/cookie/token secret as an env knob. So we re-read .env just for the four documented seed keys.

That rationale applies equally to the view code path.

Repro

In any host where .env has SM_USERS_BOOTSTRAP_EMAIL/SM_USERS_BOOTSTRAP_PASSWORD set but os.environ does NOT (e.g. when uvicorn is started from a cwd where parse_dotenv()'s default lookup doesn't find the file — see sibling issue about host/main.py not loading .env into os.environ):

  • bootstrap_admin_from_env correctly creates the admin via its dotenv_vars fallback.
  • The login page renders without a "Dev quick-login" section because views.py doesn't have that fallback. The admin exists but the convenience button doesn't show.

Suggested fix

Have views.py use the same BOOTSTRAP_ENV_KEYS map and _read_dotenv_bootstrap_vars() helper that bootstrap.py already exports (or move the resolution into a single shared helper). Concretely:

from users.bootstrap import BOOTSTRAP_ENV_KEYS, _read_dotenv_bootstrap_vars  # or a public re-export

if request.app.state.sm.settings.is_development:
    dotenv_vars = _read_dotenv_bootstrap_vars()
    resolved = {
        attr: getattr(users_settings, attr) or os.environ.get(env_key) or dotenv_vars.get(env_key, "")
        for attr, env_key in BOOTSTRAP_ENV_KEYS.items()
    }
    if resolved["bootstrap_email"] and resolved["bootstrap_password"]:
        dev_accounts.append({
            "label": "Admin",
            "email": resolved["bootstrap_email"],
            "password": resolved["bootstrap_password"],
        })
    if resolved["bootstrap_user_email"] and resolved["bootstrap_user_password"]:
        dev_accounts.append({
            "label": "User",
            "email": resolved["bootstrap_user_email"],
            "password": resolved["bootstrap_user_password"],
        })

If _read_dotenv_bootstrap_vars should stay private, promote a small public helper users.bootstrap.resolve_bootstrap_credentials(settings) -> dict[str, str] and have both call sites use it.

Framework version

0.0.13.

Context

Cross-referenced with the sibling issue on host/main.py not loading .env into os.environ. Fixing this one independently would mask the host-scaffold issue but make the dev-quick-login UX work, so they're orthogonal — both worth landing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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