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.
Summary
users/endpoints/views.py:login_pageresolves the bootstrap credentials it surfaces asdev_accounts(the "Dev quick-login" buttons on/users/login) from only two sources:UsersSettingsandos.environ. The companion code inusers/bootstrap.py:bootstrap_admin_from_envresolves the same fields from three sources, with a directparse_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):users/bootstrap.py:203-225(the correct three-source pattern):bootstrap.pyeven has a comment explaining why the direct.envread is needed:That rationale applies equally to the view code path.
Repro
In any host where
.envhasSM_USERS_BOOTSTRAP_EMAIL/SM_USERS_BOOTSTRAP_PASSWORDset butos.environdoes NOT (e.g. when uvicorn is started from a cwd whereparse_dotenv()'s default lookup doesn't find the file — see sibling issue abouthost/main.pynot loading.envintoos.environ):bootstrap_admin_from_envcorrectly creates the admin via itsdotenv_varsfallback.views.pydoesn't have that fallback. The admin exists but the convenience button doesn't show.Suggested fix
Have
views.pyuse the sameBOOTSTRAP_ENV_KEYSmap and_read_dotenv_bootstrap_vars()helper thatbootstrap.pyalready exports (or move the resolution into a single shared helper). Concretely:If
_read_dotenv_bootstrap_varsshould stay private, promote a small public helperusers.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.pynot loading.envintoos.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.