feat: auto detect first login (#2722)

* 'hide' default email and password env variables

* first login API endpoint

* run code-generators

* frontend indicators for default username and pw

* remove old env variables from docs

* fix env set variable

* remove password from tests
This commit is contained in:
Hayden
2023-11-15 09:24:24 -06:00
committed by GitHub
parent 71f95ca3c6
commit bc575ec5ae
22 changed files with 234 additions and 120 deletions

View File

@@ -104,7 +104,6 @@ async def system_startup():
indent=4,
exclude={
"SECRET",
"DEFAULT_PASSWORD",
"SFTP_PASSWORD",
"SFTP_USERNAME",
"DB_URL", # replace by DB_URL_PUBLIC for logs

View File

@@ -85,8 +85,17 @@ class AppSettings(BaseSettings):
return self.DB_PROVIDER.db_url_public if self.DB_PROVIDER else None
DEFAULT_GROUP: str = "Home"
DEFAULT_EMAIL: str = "changeme@example.com"
DEFAULT_PASSWORD: str = "MyPassword"
_DEFAULT_EMAIL: str = "changeme@example.com"
"""
This is the default email used for the first user created in the database. This is only used if no users
exist in the database. it should no longer be set by end users.
"""
_DEFAULT_PASSWORD: str = "MyPassword"
"""
This is the default password used for the first user created in the database. This is only used if no users
exist in the database. it should no longer be set by end users.
"""
# ===============================================
# Email Configuration

View File

@@ -13,7 +13,7 @@ def dev_users() -> list[dict]:
"full_name": "Jason",
"username": "jason",
"email": "jason@example.com",
"password": hash_password(settings.DEFAULT_PASSWORD),
"password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP,
"admin": False,
},
@@ -21,7 +21,7 @@ def dev_users() -> list[dict]:
"full_name": "Bob",
"username": "bob",
"email": "bob@example.com",
"password": hash_password(settings.DEFAULT_PASSWORD),
"password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP,
"admin": False,
},
@@ -29,7 +29,7 @@ def dev_users() -> list[dict]:
"full_name": "Sarah",
"username": "sarah",
"email": "sarah@example.com",
"password": hash_password(settings.DEFAULT_PASSWORD),
"password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP,
"admin": False,
},
@@ -37,7 +37,7 @@ def dev_users() -> list[dict]:
"full_name": "Sammy",
"username": "sammy",
"email": "sammy@example.com",
"password": hash_password(settings.DEFAULT_PASSWORD),
"password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP,
"admin": False,
},
@@ -48,8 +48,8 @@ def default_user_init(db: AllRepositories):
default_user = {
"full_name": "Change Me",
"username": "admin",
"email": settings.DEFAULT_EMAIL,
"password": hash_password(settings.DEFAULT_PASSWORD),
"email": settings._DEFAULT_EMAIL,
"password": hash_password(settings._DEFAULT_PASSWORD),
"group": settings.DEFAULT_GROUP,
"admin": True,
}

View File

@@ -1,8 +1,11 @@
from fastapi import APIRouter, Response
from fastapi import APIRouter, Depends, Response
from sqlalchemy.orm.session import Session
from mealie.core.config import get_app_settings
from mealie.core.settings.static import APP_VERSION
from mealie.schema.admin.about import AppInfo, AppTheme
from mealie.db.db_setup import generate_session
from mealie.db.models.users.users import User
from mealie.schema.admin.about import AppInfo, AppStartupInfo, AppTheme
router = APIRouter(prefix="/about")
@@ -20,6 +23,21 @@ def get_app_info():
)
@router.get("/startup-info", response_model=AppStartupInfo)
def get_startup_info(session: Session = Depends(generate_session)):
"""returns helpful startup information"""
settings = get_app_settings()
is_first_login = False
with session as db:
if db.query(User).filter_by(email=settings._DEFAULT_EMAIL).count() > 0:
is_first_login = True
return AppStartupInfo(
is_first_login=is_first_login,
)
@router.get("/theme", response_model=AppTheme)
def get_app_theme(resp: Response):
"""Get's the current theme settings"""

View File

@@ -1,5 +1,5 @@
# This file is auto-generated by gen_schema_exports.py
from .about import AdminAboutInfo, AppInfo, AppStatistics, CheckAppConfig, DockerVolumeText
from .about import AdminAboutInfo, AppInfo, AppStartupInfo, AppStatistics, AppTheme, CheckAppConfig, DockerVolumeText
from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob
from .email import EmailReady, EmailSuccess, EmailTest
from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary
@@ -27,7 +27,9 @@ __all__ = [
"MaintenanceSummary",
"AdminAboutInfo",
"AppInfo",
"AppStartupInfo",
"AppStatistics",
"AppTheme",
"CheckAppConfig",
"DockerVolumeText",
"EmailReady",

View File

@@ -34,6 +34,15 @@ class AppTheme(MealieModel):
dark_error: str = "#EF5350"
class AppStartupInfo(MealieModel):
is_first_login: bool
"""
The applications best guess that a user hasn't logged in. Currently, it really
on indicates that the 'changeme@example.com' user is still in the database. Once
it is removed, this will always return False.
"""
class AdminAboutInfo(AppInfo):
versionLatest: str
api_port: int

View File

@@ -92,6 +92,25 @@ __all__ = [
"RecipeToolOut",
"RecipeToolResponse",
"RecipeToolSave",
"RecipeTimelineEventCreate",
"RecipeTimelineEventIn",
"RecipeTimelineEventOut",
"RecipeTimelineEventPagination",
"RecipeTimelineEventUpdate",
"TimelineEventImage",
"TimelineEventType",
"RecipeAsset",
"RecipeSettings",
"RecipeShareToken",
"RecipeShareTokenCreate",
"RecipeShareTokenSave",
"RecipeShareTokenSummary",
"RecipeDuplicate",
"RecipeSlug",
"RecipeZipTokenResponse",
"SlugResponse",
"UpdateImageResponse",
"RecipeNote",
"CategoryBase",
"CategoryIn",
"CategoryOut",
@@ -102,6 +121,12 @@ __all__ = [
"TagIn",
"TagOut",
"TagSave",
"RecipeCommentCreate",
"RecipeCommentOut",
"RecipeCommentPagination",
"RecipeCommentSave",
"RecipeCommentUpdate",
"UserBase",
"AssignCategories",
"AssignSettings",
"AssignTags",
@@ -109,34 +134,10 @@ __all__ = [
"ExportBase",
"ExportRecipes",
"ExportTypes",
"RecipeShareToken",
"RecipeShareTokenCreate",
"RecipeShareTokenSave",
"RecipeShareTokenSummary",
"ScrapeRecipe",
"ScrapeRecipeTest",
"RecipeCommentCreate",
"RecipeCommentOut",
"RecipeCommentPagination",
"RecipeCommentSave",
"RecipeCommentUpdate",
"UserBase",
"RecipeImageTypes",
"CreateRecipe",
"CreateRecipeBulk",
"CreateRecipeByUrlBulk",
"Recipe",
"RecipeCategory",
"RecipeCategoryPagination",
"RecipeLastMade",
"RecipePagination",
"RecipeSummary",
"RecipeTag",
"RecipeTagPagination",
"RecipeTool",
"RecipeToolPagination",
"IngredientReferences",
"RecipeStep",
"RecipeImageTypes",
"Nutrition",
"CreateIngredientFood",
"CreateIngredientFoodAlias",
"CreateIngredientUnit",
@@ -159,20 +160,19 @@ __all__ = [
"SaveIngredientFood",
"SaveIngredientUnit",
"UnitFoodBase",
"RecipeAsset",
"RecipeTimelineEventCreate",
"RecipeTimelineEventIn",
"RecipeTimelineEventOut",
"RecipeTimelineEventPagination",
"RecipeTimelineEventUpdate",
"TimelineEventImage",
"TimelineEventType",
"RecipeDuplicate",
"RecipeSlug",
"RecipeZipTokenResponse",
"SlugResponse",
"UpdateImageResponse",
"Nutrition",
"RecipeSettings",
"RecipeNote",
"CreateRecipe",
"CreateRecipeBulk",
"CreateRecipeByUrlBulk",
"Recipe",
"RecipeCategory",
"RecipeCategoryPagination",
"RecipeLastMade",
"RecipePagination",
"RecipeSummary",
"RecipeTag",
"RecipeTagPagination",
"RecipeTool",
"RecipeToolPagination",
"ScrapeRecipe",
"ScrapeRecipeTest",
]

View File

@@ -1,5 +1,5 @@
# This file is auto-generated by gen_schema_exports.py
from .pagination import OrderDirection, PaginationBase, PaginationQuery, RecipeSearchQuery
from .pagination import OrderByNullPosition, OrderDirection, PaginationBase, PaginationQuery, RecipeSearchQuery
from .query_filter import LogicalOperator, QueryFilter, QueryFilterComponent, RelationalKeyword, RelationalOperator
from .query_search import SearchFilter
from .responses import ErrorResponse, FileTokenResponse, SuccessResponse
@@ -15,6 +15,7 @@ __all__ = [
"QueryFilterComponent",
"RelationalKeyword",
"RelationalOperator",
"OrderByNullPosition",
"OrderDirection",
"PaginationBase",
"PaginationQuery",

View File

@@ -116,7 +116,7 @@ class UserOut(UserBase):
@property
def is_default_user(self) -> bool:
return self.email == settings.DEFAULT_EMAIL.strip().lower()
return self.email == settings._DEFAULT_EMAIL.strip().lower()
@classmethod
def loader_options(cls) -> list[LoaderOption]: