mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-14 20:03:14 -05:00
add soft fail user dependency (#479)
* add soft fail user dependency * filter private recipes on get_recipe_summary * code clean-up * restrict single recipe * cleanup dependencies * add auto_error oauth2 scheme * update make file * update make file * fix early return * bump python deps * restrict category/tags * format deps Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
@@ -12,9 +12,42 @@ from mealie.schema.user import LongLiveTokenInDB, UserInDB
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
|
||||
oauth2_scheme_soft_fail = OAuth2PasswordBearer(tokenUrl="/api/auth/token", auto_error=False)
|
||||
ALGORITHM = "HS256"
|
||||
|
||||
|
||||
async def is_logged_in(token: str = Depends(oauth2_scheme_soft_fail), session=Depends(generate_session)) -> bool:
|
||||
"""
|
||||
When you need to determine if the user is logged in, but don't need the user, you can use this
|
||||
function to return a boolean value to represent if the user is logged in. No Auth exceptions are raised
|
||||
if the user is not logged in. This behavior is not the same as 'get_current_user'
|
||||
|
||||
Args:
|
||||
token (str, optional): [description]. Defaults to Depends(oauth2_scheme_soft_fail).
|
||||
session ([type], optional): [description]. Defaults to Depends(generate_session).
|
||||
|
||||
Returns:
|
||||
bool: True = Valid User / False = Not User
|
||||
"""
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
long_token: str = payload.get("long_token")
|
||||
|
||||
if long_token is not None:
|
||||
try:
|
||||
user = validate_long_live_token(session, token, payload.get("id"))
|
||||
if user:
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return username is not None
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme), session=Depends(generate_session)) -> UserInDB:
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user, is_logged_in
|
||||
from mealie.schema.recipe import RecipeSummary
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm.session import Session
|
||||
@@ -10,9 +11,7 @@ router = APIRouter(tags=["Query All Recipes"])
|
||||
|
||||
@router.get("/api/recipes/summary", response_model=list[RecipeSummary])
|
||||
async def get_recipe_summary(
|
||||
start=0,
|
||||
limit=9999,
|
||||
session: Session = Depends(generate_session),
|
||||
start=0, limit=9999, session: Session = Depends(generate_session), user: bool = Depends(is_logged_in)
|
||||
):
|
||||
"""
|
||||
Returns key the recipe summary data for recipes in the database. You can perform
|
||||
@@ -26,20 +25,32 @@ async def get_recipe_summary(
|
||||
|
||||
"""
|
||||
|
||||
return db.recipes.get_all(session, limit=limit, start=start, order_by="date_updated", override_schema=RecipeSummary)
|
||||
if user:
|
||||
return db.recipes.get_all(
|
||||
session, limit=limit, start=start, order_by="date_updated", override_schema=RecipeSummary
|
||||
)
|
||||
|
||||
else:
|
||||
return db.recipes.get_all_not_private(
|
||||
session, limit=limit, start=start, order_by="date_updated", override_schema=RecipeSummary
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/recipes/summary/untagged", response_model=list[RecipeSummary])
|
||||
@router.get(
|
||||
"/api/recipes/summary/untagged", response_model=list[RecipeSummary], dependencies=[Depends(get_current_user)]
|
||||
)
|
||||
async def get_untagged_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
||||
return db.recipes.count_untagged(session, count=count, override_schema=RecipeSummary)
|
||||
|
||||
|
||||
@router.get("/api/recipes/summary/uncategorized", response_model=list[RecipeSummary])
|
||||
@router.get(
|
||||
"/api/recipes/summary/uncategorized", response_model=list[RecipeSummary], dependencies=[Depends(get_current_user)]
|
||||
)
|
||||
async def get_uncategorized_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
||||
return db.recipes.count_uncategorized(session, count=count, override_schema=RecipeSummary)
|
||||
|
||||
|
||||
@router.post("/api/recipes/category")
|
||||
@router.post("/api/recipes/category", deprecated=True, dependencies=[Depends(get_current_user)])
|
||||
def filter_by_category(categories: list, session: Session = Depends(generate_session)):
|
||||
""" pass a list of categories and get a list of recipes associated with those categories """
|
||||
# ! This should be refactored into a single database call, but I couldn't figure it out
|
||||
@@ -49,7 +60,7 @@ def filter_by_category(categories: list, session: Session = Depends(generate_ses
|
||||
return in_category
|
||||
|
||||
|
||||
@router.post("/api/recipes/tag")
|
||||
@router.post("/api/recipes/tag", deprecated=True, dependencies=[Depends(get_current_user)])
|
||||
async def filter_by_tags(tags: list, session: Session = Depends(generate_session)):
|
||||
""" pass a list of tags and get a list of recipes associated with those tags"""
|
||||
# ! This should be refactored into a single database call, but I couldn't figure it out
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.routes.deps import get_current_user, is_logged_in
|
||||
from mealie.schema.category import CategoryIn, RecipeCategoryResponse
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
@@ -21,15 +21,22 @@ def get_empty_categories(session: Session = Depends(generate_session)):
|
||||
|
||||
|
||||
@router.get("/{category}", response_model=RecipeCategoryResponse)
|
||||
def get_all_recipes_by_category(category: str, session: Session = Depends(generate_session)):
|
||||
""" Returns a list of recipes associated with the provided category. """
|
||||
return db.categories.get(session, category)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_recipe_category(
|
||||
category: CategoryIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
def get_all_recipes_by_category(
|
||||
category: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
||||
):
|
||||
""" Returns a list of recipes associated with the provided category. """
|
||||
|
||||
category_obj = db.categories.get(session, category)
|
||||
category_obj = RecipeCategoryResponse.from_orm(category_obj)
|
||||
|
||||
if not is_user:
|
||||
category_obj.recipes = [x for x in category_obj.recipes if x.settings.public]
|
||||
|
||||
return category_obj
|
||||
|
||||
|
||||
@router.post("", dependencies=[Depends(get_current_user)])
|
||||
async def create_recipe_category(category: CategoryIn, session: Session = Depends(generate_session)):
|
||||
""" Creates a Category in the database """
|
||||
|
||||
try:
|
||||
@@ -38,13 +45,8 @@ async def create_recipe_category(
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@router.put("/{category}", response_model=RecipeCategoryResponse)
|
||||
async def update_recipe_category(
|
||||
category: str,
|
||||
new_category: CategoryIn,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
@router.put("/{category}", response_model=RecipeCategoryResponse, dependencies=[Depends(get_current_user)])
|
||||
async def update_recipe_category(category: str, new_category: CategoryIn, session: Session = Depends(generate_session)):
|
||||
""" Updates an existing Tag in the database """
|
||||
|
||||
try:
|
||||
@@ -53,13 +55,13 @@ async def update_recipe_category(
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@router.delete("/{category}")
|
||||
async def delete_recipe_category(
|
||||
category: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
):
|
||||
"""Removes a recipe category from the database. Deleting a
|
||||
@router.delete("/{category}", dependencies=[Depends(get_current_user)])
|
||||
async def delete_recipe_category(category: str, session: Session = Depends(generate_session)):
|
||||
"""
|
||||
Removes a recipe category from the database. Deleting a
|
||||
category does not impact a recipe. The category will be removed
|
||||
from any recipes that contain it"""
|
||||
from any recipes that contain it
|
||||
"""
|
||||
|
||||
try:
|
||||
db.categories.delete(session, category)
|
||||
|
||||
@@ -6,7 +6,7 @@ from mealie.core.config import settings
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.routes.deps import get_current_user, is_logged_in
|
||||
from mealie.schema.recipe import Recipe, RecipeAsset, RecipeURLIn
|
||||
from mealie.schema.user import UserInDB
|
||||
from mealie.services.events import create_recipe_event
|
||||
@@ -71,18 +71,24 @@ def parse_recipe_url(
|
||||
|
||||
|
||||
@router.get("/{recipe_slug}", response_model=Recipe)
|
||||
def get_recipe(recipe_slug: str, session: Session = Depends(generate_session)):
|
||||
def get_recipe(recipe_slug: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)):
|
||||
""" Takes in a recipe slug, returns all data for a recipe """
|
||||
|
||||
return db.recipes.get(session, recipe_slug)
|
||||
recipe: Recipe = db.recipes.get(session, recipe_slug)
|
||||
|
||||
if recipe.settings.public or is_user:
|
||||
|
||||
return recipe
|
||||
|
||||
else:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, {"details": "unauthorized"})
|
||||
|
||||
|
||||
@router.put("/{recipe_slug}")
|
||||
@router.put("/{recipe_slug}", dependencies=[Depends(get_current_user)])
|
||||
def update_recipe(
|
||||
recipe_slug: str,
|
||||
data: Recipe,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
|
||||
@@ -93,12 +99,11 @@ def update_recipe(
|
||||
return recipe
|
||||
|
||||
|
||||
@router.patch("/{recipe_slug}")
|
||||
@router.patch("/{recipe_slug}", dependencies=[Depends(get_current_user)])
|
||||
def patch_recipe(
|
||||
recipe_slug: str,
|
||||
data: Recipe,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Updates a recipe by existing slug and data. """
|
||||
|
||||
@@ -148,18 +153,17 @@ def update_recipe_image(
|
||||
return {"image": new_version}
|
||||
|
||||
|
||||
@router.post("/{recipe_slug}/image")
|
||||
@router.post("/{recipe_slug}/image", dependencies=[Depends(get_current_user)])
|
||||
def scrape_image_url(
|
||||
recipe_slug: str,
|
||||
url: RecipeURLIn,
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Removes an existing image and replaces it with the incoming file. """
|
||||
|
||||
scrape_image(url.url, recipe_slug)
|
||||
|
||||
|
||||
@router.post("/{recipe_slug}/assets", response_model=RecipeAsset)
|
||||
@router.post("/{recipe_slug}/assets", response_model=RecipeAsset, dependencies=[Depends(get_current_user)])
|
||||
def upload_recipe_asset(
|
||||
recipe_slug: str,
|
||||
name: str = Form(...),
|
||||
@@ -167,7 +171,6 @@ def upload_recipe_asset(
|
||||
extension: str = Form(...),
|
||||
file: UploadFile = File(...),
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Upload a file to store as a recipe asset """
|
||||
file_name = slugify(name) + "." + extension
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes.deps import get_current_user
|
||||
from mealie.routes.deps import get_current_user, is_logged_in
|
||||
from mealie.schema.category import RecipeTagResponse, TagIn
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
@@ -23,33 +23,35 @@ def get_empty_tags(session: Session = Depends(generate_session)):
|
||||
|
||||
|
||||
@router.get("/{tag}", response_model=RecipeTagResponse)
|
||||
def get_all_recipes_by_tag(tag: str, session: Session = Depends(generate_session)):
|
||||
""" Returns a list of recipes associated with the provided tag. """
|
||||
return db.tags.get(session, tag)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_recipe_tag(
|
||||
tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
def get_all_recipes_by_tag(
|
||||
tag: str, session: Session = Depends(generate_session), is_user: bool = Depends(is_logged_in)
|
||||
):
|
||||
""" Returns a list of recipes associated with the provided tag. """
|
||||
tag_obj = db.tags.get(session, tag)
|
||||
tag_obj = RecipeTagResponse.from_orm(tag_obj)
|
||||
|
||||
if not is_user:
|
||||
tag_obj.recipes = [x for x in tag_obj.recipes if x.settings.public]
|
||||
|
||||
return tag_obj
|
||||
|
||||
|
||||
@router.post("", dependencies=[Depends(get_current_user)])
|
||||
async def create_recipe_tag(tag: TagIn, session: Session = Depends(generate_session)):
|
||||
""" Creates a Tag in the database """
|
||||
|
||||
return db.tags.create(session, tag.dict())
|
||||
|
||||
|
||||
@router.put("/{tag}", response_model=RecipeTagResponse)
|
||||
async def update_recipe_tag(
|
||||
tag: str, new_tag: TagIn, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
):
|
||||
@router.put("/{tag}", response_model=RecipeTagResponse, dependencies=[Depends(get_current_user)])
|
||||
async def update_recipe_tag(tag: str, new_tag: TagIn, session: Session = Depends(generate_session)):
|
||||
""" Updates an existing Tag in the database """
|
||||
|
||||
return db.tags.update(session, tag, new_tag.dict())
|
||||
|
||||
|
||||
@router.delete("/{tag}")
|
||||
async def delete_recipe_tag(
|
||||
tag: str, session: Session = Depends(generate_session), current_user=Depends(get_current_user)
|
||||
):
|
||||
@router.delete("/{tag}", dependencies=[Depends(get_current_user)])
|
||||
async def delete_recipe_tag(tag: str, session: Session = Depends(generate_session)):
|
||||
"""Removes a recipe tag from the database. Deleting a
|
||||
tag does not impact a recipe. The tag will be removed
|
||||
from any recipes that contain it"""
|
||||
|
||||
@@ -17,11 +17,10 @@ def get_main_settings(session: Session = Depends(generate_session)):
|
||||
return db.settings.get(session, 1)
|
||||
|
||||
|
||||
@router.put("")
|
||||
@router.put("", dependencies=[Depends(get_current_user)])
|
||||
def update_settings(
|
||||
data: SiteSettings,
|
||||
session: Session = Depends(generate_session),
|
||||
current_user=Depends(get_current_user),
|
||||
):
|
||||
""" Returns Site Settings """
|
||||
db.settings.update(session, 1, data.dict())
|
||||
|
||||
Reference in New Issue
Block a user