diff --git a/.gitignore b/.gitignore index 93d96aca7..88de70f24 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,4 @@ node_modules/ mealie/data/debug/last_recipe.json *.sqlite app_data/db/test.db +scratch.py diff --git a/mealie/app.py b/mealie/app.py index e242c380c..11c2f386c 100644 --- a/mealie/app.py +++ b/mealie/app.py @@ -2,7 +2,9 @@ import uvicorn from fastapi import FastAPI # import utils.startup as startup -from app_config import APP_VERSION, PORT, SECRET, docs_url, redoc_url +from core.config import APP_VERSION, PORT, SECRET, docs_url, redoc_url +from db.db_setup import sql_exists +from db.init_db import init_db from routes import ( backup_routes, debug_routes, @@ -18,7 +20,6 @@ from routes.recipe import ( tag_routes, ) from routes.users import users -from services.settings_services import default_settings_init from utils.logger import logger app = FastAPI( @@ -30,16 +31,14 @@ app = FastAPI( ) +def data_base_first_run(): + init_db() + + def start_scheduler(): import services.scheduler.scheduled_jobs -def init_settings(): - default_settings_init() - import services.theme_services - import services.users.users - - def api_routers(): # Authentication app.include_router(users.router) @@ -60,9 +59,11 @@ def api_routers(): app.include_router(debug_routes.router) +if not sql_exists: + data_base_first_run() + api_routers() start_scheduler() -init_settings() if __name__ == "__main__": logger.info("-----SYSTEM STARTUP-----") diff --git a/mealie/app_config.py b/mealie/core/config.py similarity index 97% rename from mealie/app_config.py rename to mealie/core/config.py index 4bc9f2466..4003f8cb6 100644 --- a/mealie/app_config.py +++ b/mealie/core/config.py @@ -33,7 +33,7 @@ else: redoc_url = None # Helpful Globals -DATA_DIR = CWD.parent.joinpath("app_data") +DATA_DIR = CWD.parent.parent.joinpath("app_data") if PRODUCTION: DATA_DIR = Path("/app/data") diff --git a/mealie/core/security.py b/mealie/core/security.py new file mode 100644 index 000000000..f6cc9a805 --- /dev/null +++ b/mealie/core/security.py @@ -0,0 +1,29 @@ +from passlib.context import CryptContext + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +def verify_password(plain_password: str, hashed_password: str) -> bool: + """Compares a plain string to a hashed password + + Args: + plain_password (str): raw password string + hashed_password (str): hashed password from the database + + Returns: + bool: Returns True if a match return False + """ + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password: str) -> str: + """Takes in a raw password and hashes it. Used prior to saving + a new password to the database. + + Args: + password (str): Password String + + Returns: + str: Hashed Password + """ + return pwd_context.hash(password) diff --git a/mealie/db/db_setup.py b/mealie/db/db_setup.py index aa315a012..aa1854090 100644 --- a/mealie/db/db_setup.py +++ b/mealie/db/db_setup.py @@ -1,4 +1,4 @@ -from app_config import SQLITE_FILE, USE_SQL +from core.config import SQLITE_FILE, USE_SQL from sqlalchemy.orm.session import Session from db.sql.db_session import sql_global_init diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py new file mode 100644 index 000000000..5d48cb66d --- /dev/null +++ b/mealie/db/init_db.py @@ -0,0 +1,64 @@ +from core.security import get_password_hash +from fastapi.logger import logger +from models.settings_models import SiteSettings, Webhooks +from sqlalchemy.orm import Session +from sqlalchemy.orm.session import Session +from utils.logger import logger + +from db.database import db +from db.db_setup import create_session + + +def init_db(db: Session = None) -> None: + if not db: + db = create_session() + + default_settings_init(db) + default_theme_init(db) + default_user_init(db) + + db.close() + + +def default_theme_init(session: Session): + default_theme = { + "name": "default", + "colors": { + "primary": "#E58325", + "accent": "#00457A", + "secondary": "#973542", + "success": "#5AB1BB", + "info": "#4990BA", + "warning": "#FF4081", + "error": "#EF5350", + }, + } + + try: + db.themes.create(session, default_theme) + logger.info("Generating default theme...") + except: + logger.info("Default Theme Exists.. skipping generation") + + +def default_settings_init(session: Session): + try: + webhooks = Webhooks() + default_entry = SiteSettings(name="main", webhooks=webhooks) + document = db.settings.create(session, default_entry.dict()) + logger.info(f"Created Site Settings: \n {document}") + except: + pass + + +def default_user_init(session: Session): + default_user = { + "full_name": "Change Me", + "email": "changeme@email.com", + "password": get_password_hash("MyPassword"), + "family": "public", + "is_superuser": True, + } + + logger.info("Generating Default User") + db.users.create(session, default_user) diff --git a/mealie/routes/backup_routes.py b/mealie/routes/backup_routes.py index df64c38f7..1496ac3d7 100644 --- a/mealie/routes/backup_routes.py +++ b/mealie/routes/backup_routes.py @@ -1,7 +1,7 @@ import operator import shutil -from app_config import BACKUP_DIR, TEMPLATE_DIR +from core.config import BACKUP_DIR, TEMPLATE_DIR from db.db_setup import generate_session from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from models.backup_models import BackupJob, ImportJob, Imports, LocalBackup diff --git a/mealie/routes/debug_routes.py b/mealie/routes/debug_routes.py index 9b7af5037..9fb156178 100644 --- a/mealie/routes/debug_routes.py +++ b/mealie/routes/debug_routes.py @@ -1,6 +1,6 @@ import json -from app_config import APP_VERSION, DEBUG_DIR +from core.config import APP_VERSION, DEBUG_DIR from fastapi import APIRouter from utils.logger import LOGGER_FILE diff --git a/mealie/routes/deps.py b/mealie/routes/deps.py index eda883299..09b580af4 100644 --- a/mealie/routes/deps.py +++ b/mealie/routes/deps.py @@ -1,4 +1,4 @@ -from app_config import SECRET +from core.config import SECRET from db.database import db from db.db_setup import create_session from fastapi_login import LoginManager diff --git a/mealie/routes/migration_routes.py b/mealie/routes/migration_routes.py index 19e2c7a5d..90a200e20 100644 --- a/mealie/routes/migration_routes.py +++ b/mealie/routes/migration_routes.py @@ -2,7 +2,7 @@ import operator import shutil from typing import List -from app_config import MIGRATION_DIR +from core.config import MIGRATION_DIR from db.db_setup import generate_session from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from models.migration_models import MigrationFile, Migrations diff --git a/mealie/routes/setting_routes.py b/mealie/routes/setting_routes.py index a0f6b01cd..908117e0d 100644 --- a/mealie/routes/setting_routes.py +++ b/mealie/routes/setting_routes.py @@ -2,7 +2,6 @@ from db.database import db from db.db_setup import generate_session from fastapi import APIRouter, Depends from models.settings_models import SiteSettings -from services.settings_services import default_settings_init from sqlalchemy.orm.session import Session from utils.post_webhooks import post_webhooks from utils.snackbar import SnackResponse @@ -17,8 +16,7 @@ def get_main_settings(session: Session = Depends(generate_session)): try: data = db.settings.get(session, "main") except: - default_settings_init(session) - data = db.settings.get(session, "main") + return return data diff --git a/mealie/routes/users/auth.py b/mealie/routes/users/auth.py index a849d9e23..2d6c06ae1 100644 --- a/mealie/routes/users/auth.py +++ b/mealie/routes/users/auth.py @@ -1,3 +1,4 @@ +from core.security import verify_password from db.db_setup import generate_session from fastapi import APIRouter, Depends from fastapi.security import OAuth2PasswordRequestForm @@ -17,9 +18,10 @@ def token( password = data.password user = query_user(email, session) + print(user) if not user: raise InvalidCredentialsException # you can also use your own HTTPException - elif password != user["password"]: + elif not verify_password(password, user["password"]): raise InvalidCredentialsException access_token = manager.create_access_token(data=dict(sub=email)) diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py index 35341d357..909a6626c 100644 --- a/mealie/routes/users/crud.py +++ b/mealie/routes/users/crud.py @@ -1,14 +1,15 @@ +from core.security import get_password_hash from db.database import db from db.db_setup import generate_session from fastapi import APIRouter, Depends from models.user_models import CreateUser, UserResponse -from routes.deps import manager, query_user +from routes.deps import manager from sqlalchemy.orm.session import Session router = APIRouter(prefix="/api/users", tags=["Users"]) -@router.post("/", response_model=UserResponse) +@router.post("", response_model=UserResponse, status_code=201) async def create_user( new_user: CreateUser, current_user=Depends(manager), @@ -16,16 +17,21 @@ async def create_user( ): """ Returns a list of all user in the Database """ + new_user.password = get_password_hash(new_user.password) + data = db.users.create(session, new_user.dict()) - print(data) return data -@router.get("/", response_model=list[UserResponse]) +@router.get("", response_model=list[UserResponse]) async def get_all_users( current_user=Depends(manager), session: Session = Depends(generate_session) ): - return db.users.get_all(session) + + if current_user.get("is_superuser"): + return db.users.get_all(session) + else: + return {"details": "user not authorized"} @router.get("/{id}", response_model=UserResponse) @@ -43,6 +49,7 @@ async def update_user( session: Session = Depends(generate_session), ): current_user_id = current_user.get("id") + new_data.password = get_password_hash(new_data.password) is_superuser = current_user.get("is_superuser") if current_user_id == id or is_superuser: return db.users.update(session, id, new_data.dict()) diff --git a/mealie/services/backups/exports.py b/mealie/services/backups/exports.py index 8c53e3d70..499af98ef 100644 --- a/mealie/services/backups/exports.py +++ b/mealie/services/backups/exports.py @@ -3,7 +3,7 @@ import shutil from datetime import datetime from pathlib import Path -from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR +from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR, TEMPLATE_DIR from db.database import db from db.db_setup import create_session from jinja2 import Template diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py index 267cba715..4f91e37e5 100644 --- a/mealie/services/backups/imports.py +++ b/mealie/services/backups/imports.py @@ -4,7 +4,7 @@ import zipfile from pathlib import Path from typing import List -from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR +from core.config import BACKUP_DIR, IMG_DIR, TEMP_DIR from db.database import db from models.import_models import RecipeImport, SettingsImport, ThemeImport from models.theme_models import SiteTheme diff --git a/mealie/services/image_services.py b/mealie/services/image_services.py index 4e6d542e4..8dadfdc46 100644 --- a/mealie/services/image_services.py +++ b/mealie/services/image_services.py @@ -2,7 +2,7 @@ import shutil from pathlib import Path import requests -from app_config import IMG_DIR +from core.config import IMG_DIR from utils.logger import logger diff --git a/mealie/services/migrations/chowdown.py b/mealie/services/migrations/chowdown.py index a26215ab9..85bad0554 100644 --- a/mealie/services/migrations/chowdown.py +++ b/mealie/services/migrations/chowdown.py @@ -2,7 +2,7 @@ import shutil from pathlib import Path import yaml -from app_config import IMG_DIR, TEMP_DIR +from core.config import IMG_DIR, TEMP_DIR from services.recipe_services import Recipe from sqlalchemy.orm.session import Session from utils.unzip import unpack_zip diff --git a/mealie/services/migrations/nextcloud.py b/mealie/services/migrations/nextcloud.py index 155d1d120..fe8547a52 100644 --- a/mealie/services/migrations/nextcloud.py +++ b/mealie/services/migrations/nextcloud.py @@ -4,10 +4,10 @@ import shutil import zipfile from pathlib import Path -from app_config import IMG_DIR, MIGRATION_DIR, TEMP_DIR +from core.config import IMG_DIR, MIGRATION_DIR, TEMP_DIR from services.recipe_services import Recipe from services.scraper.cleaner import Cleaner -from app_config import IMG_DIR, TEMP_DIR +from core.config import IMG_DIR, TEMP_DIR def process_selection(selection: Path) -> Path: @@ -81,7 +81,7 @@ def migrate(session, selection: str): successful_imports.append(recipe.name) except: logging.error(f"Failed Nextcloud Import: {dir.name}") - logging.exception('') + logging.exception("") failed_imports.append(dir.name) cleanup() diff --git a/mealie/services/scraper/open_graph.py b/mealie/services/scraper/open_graph.py index 49d1072ef..3b359bcc2 100644 --- a/mealie/services/scraper/open_graph.py +++ b/mealie/services/scraper/open_graph.py @@ -1,7 +1,7 @@ from typing import Tuple import extruct -from app_config import DEBUG_DIR +from core.config import DEBUG_DIR from slugify import slugify from w3lib.html import get_base_url diff --git a/mealie/services/scraper/scraper.py b/mealie/services/scraper/scraper.py index a13ed79e2..b15eba5b5 100644 --- a/mealie/services/scraper/scraper.py +++ b/mealie/services/scraper/scraper.py @@ -3,10 +3,10 @@ from typing import List import requests import scrape_schema_recipe -from app_config import DEBUG_DIR +from core.config import DEBUG_DIR from services.image_services import scrape_image from services.recipe_services import Recipe -from services.scraper import open_graph +from services.scraper import open_graph from services.scraper.cleaner import Cleaner from utils.logger import logger diff --git a/mealie/services/settings_services.py b/mealie/services/settings_services.py deleted file mode 100644 index 62e32bb3d..000000000 --- a/mealie/services/settings_services.py +++ /dev/null @@ -1,17 +0,0 @@ -from db.database import db -from db.db_setup import create_session -from models.settings_models import SiteSettings, Webhooks -from sqlalchemy.orm.session import Session -from utils.logger import logger - - -def default_settings_init(session: Session = None): - if session == None: - session = create_session() - try: - webhooks = Webhooks() - default_entry = SiteSettings(name="main", webhooks=webhooks) - document = db.settings.create(session, default_entry.dict()) - logger.info(f"Created Site Settings: \n {document}") - except: - pass diff --git a/mealie/services/theme_services.py b/mealie/services/theme_services.py deleted file mode 100644 index 12cd9c889..000000000 --- a/mealie/services/theme_services.py +++ /dev/null @@ -1,28 +0,0 @@ -from db.database import db -from db.db_setup import create_session, sql_exists -from utils.logger import logger - - -def default_theme_init(): - default_theme = { - "name": "default", - "colors": { - "primary": "#E58325", - "accent": "#00457A", - "secondary": "#973542", - "success": "#5AB1BB", - "info": "#4990BA", - "warning": "#FF4081", - "error": "#EF5350", - }, - } - session = create_session() - try: - db.themes.create(session, default_theme) - logger.info("Generating default theme...") - except: - logger.info("Default Theme Exists.. skipping generation") - - -if not sql_exists: - default_theme_init() diff --git a/mealie/services/users/users.py b/mealie/services/users/users.py deleted file mode 100644 index 62c28c23b..000000000 --- a/mealie/services/users/users.py +++ /dev/null @@ -1,25 +0,0 @@ -from db.database import db -from db.db_setup import create_session, sql_exists -from fastapi.logger import logger - - -def init_super_user(): - session = create_session() - - default_user = { - "full_name": "Change Me", - "email": "changeme@email.com", - "password": "MyPassword", - "family": "public", - "is_superuser": True, - } - - logger.info("Generating Default User") - - db.users.create(session, default_user) - - session.close() - - -if not sql_exists: - init_super_user() diff --git a/mealie/tests/conftest.py b/mealie/tests/conftest.py index 4a3c1c763..e73807528 100644 --- a/mealie/tests/conftest.py +++ b/mealie/tests/conftest.py @@ -1,27 +1,27 @@ -from pathlib import Path +import json +import requests from app import app -from app_config import SQLITE_DIR +from core.config import SQLITE_DIR from db.db_setup import generate_session, sql_global_init +from db.init_db import init_db from fastapi.testclient import TestClient from pytest import fixture -from services.settings_services import default_settings_init -from services.theme_services import default_theme_init from tests.test_config import TEST_DATA SQLITE_FILE = SQLITE_DIR.joinpath("test.db") SQLITE_FILE.unlink(missing_ok=True) +TOKEN_URL = "/api/auth/token" TestSessionLocal = sql_global_init(SQLITE_FILE, check_thread=False) +init_db(TestSessionLocal()) def override_get_db(): try: db = TestSessionLocal() - default_theme_init() - default_settings_init() yield db finally: db.close() @@ -31,11 +31,22 @@ def override_get_db(): def api_client(): app.dependency_overrides[generate_session] = override_get_db + yield TestClient(app) - SQLITE_FILE.unlink() + # SQLITE_FILE.unlink() @fixture(scope="session") def test_image(): return TEST_DATA.joinpath("test_image.jpg") + + +@fixture(scope="session") +def token(api_client: requests): + form_data = {"username": "changeme@email.com", "password": "MyPassword"} + response = api_client.post(TOKEN_URL, form_data) + + token = json.loads(response.text).get("access_token") + + return {"Authorization": f"Bearer {token}"} diff --git a/mealie/tests/test_migrations/test_nextcloud.py b/mealie/tests/test_migrations/test_nextcloud.py index 52a251411..b6d363358 100644 --- a/mealie/tests/test_migrations/test_nextcloud.py +++ b/mealie/tests/test_migrations/test_nextcloud.py @@ -1,10 +1,14 @@ from pathlib import Path -from app_config import TEMP_DIR +from core.config import TEMP_DIR import pytest -from app_config import TEMP_DIR +from core.config import TEMP_DIR from services.image_services import IMG_DIR -from services.migrations.nextcloud import (cleanup, import_recipes, prep, - process_selection) +from services.migrations.nextcloud import ( + cleanup, + import_recipes, + prep, + process_selection, +) from services.recipe_services import Recipe from tests.test_config import TEST_NEXTCLOUD_DIR diff --git a/mealie/tests/test_routes/test_migration_routes.py b/mealie/tests/test_routes/test_migration_routes.py index 2bb35ec93..4efa280b7 100644 --- a/mealie/tests/test_routes/test_migration_routes.py +++ b/mealie/tests/test_routes/test_migration_routes.py @@ -2,7 +2,7 @@ import json import shutil import pytest -from app_config import MIGRATION_DIR +from core.config import MIGRATION_DIR from tests.test_config import TEST_CHOWDOWN_DIR, TEST_NEXTCLOUD_DIR from tests.utils.routes import MIGRATIONS_PREFIX, RECIPES_PREFIX diff --git a/mealie/tests/test_routes/test_settings_routes.py b/mealie/tests/test_routes/test_settings_routes.py index 56ccae369..fe4bbdf65 100644 --- a/mealie/tests/test_routes/test_settings_routes.py +++ b/mealie/tests/test_routes/test_settings_routes.py @@ -34,8 +34,6 @@ def default_theme(api_client): }, } - api_client.post(THEMES_CREATE, json=default_theme) - return default_theme diff --git a/mealie/tests/test_routes/test_user_routes.py b/mealie/tests/test_routes/test_user_routes.py new file mode 100644 index 000000000..2d6284718 --- /dev/null +++ b/mealie/tests/test_routes/test_user_routes.py @@ -0,0 +1,94 @@ +import json + +import requests +from pytest import fixture + +BASE = "/api/users" +TOKEN_URL = "/api/auth/token" + + + + + +@fixture(scope="session") +def default_user(): + return { + "id": 1, + "full_name": "Change Me", + "email": "changeme@email.com", + "family": "public", + } + + +@fixture(scope="session") +def new_user(): + return { + "id": 2, + "full_name": "My New User", + "email": "newuser@email.com", + "family": "public", + } + + +def test_superuser_login(api_client: requests): + form_data = {"username": "changeme@email.com", "password": "MyPassword"} + response = api_client.post(TOKEN_URL, form_data) + + assert response.status_code == 200 + token = json.loads(response.text).get("access_token") + + return {"Authorization": f"Bearer {token}"} + + +def test_init_superuser(api_client: requests, token, default_user): + response = api_client.get(f"{BASE}/1", headers=token) + assert response.status_code == 200 + + assert json.loads(response.text) == default_user + + +def test_create_user(api_client: requests, token, new_user): + create_data = { + "full_name": "My New User", + "email": "newuser@email.com", + "password": "MyStrongPassword", + "family": "public", + } + + response = api_client.post(f"{BASE}", json=create_data, headers=token) + + assert response.status_code == 201 + assert json.loads(response.text) == new_user + assert True + + +def test_get_all_users(api_client: requests, token, new_user, default_user): + response = api_client.get(f"{BASE}", headers=token) + + assert response.status_code == 200 + + assert json.loads(response.text) == [default_user, new_user] + + +def test_update_user(api_client: requests, token): + update_data = { + "full_name": "Updated Name", + "email": "updated@email.com", + "password": "MyStrongPassword", + "family": "public", + } + response = api_client.put(f"{BASE}/1", headers=token, json=update_data) + + assert response.status_code == 200 + assert json.loads(response.text) == { + "id": 1, + "full_name": "Updated Name", + "email": "updated@email.com", + "family": "public", + } + + +def test_delete_user(api_client: requests, token): + response = api_client.delete(f"{BASE}/2", headers=token) + + assert response.status_code == 200 diff --git a/mealie/tests/test_services/test_migrations/test_nextcloud.py b/mealie/tests/test_services/test_migrations/test_nextcloud.py index 52a251411..b6d363358 100644 --- a/mealie/tests/test_services/test_migrations/test_nextcloud.py +++ b/mealie/tests/test_services/test_migrations/test_nextcloud.py @@ -1,10 +1,14 @@ from pathlib import Path -from app_config import TEMP_DIR +from core.config import TEMP_DIR import pytest -from app_config import TEMP_DIR +from core.config import TEMP_DIR from services.image_services import IMG_DIR -from services.migrations.nextcloud import (cleanup, import_recipes, prep, - process_selection) +from services.migrations.nextcloud import ( + cleanup, + import_recipes, + prep, + process_selection, +) from services.recipe_services import Recipe from tests.test_config import TEST_NEXTCLOUD_DIR diff --git a/mealie/utils/api_docs.py b/mealie/utils/api_docs.py index 356789789..d0abc39aa 100644 --- a/mealie/utils/api_docs.py +++ b/mealie/utils/api_docs.py @@ -1,6 +1,6 @@ import json -from app_config import DATA_DIR +from core.config import DATA_DIR """Script to export the ReDoc documentation page into a standalone HTML file.""" @@ -37,6 +37,3 @@ HTML_PATH = DATA_DIR.parent.joinpath("docs/docs/html/api.html") def generate_api_docs(app): with open(HTML_PATH, "w") as fd: print(HTML_TEMPLATE % json.dumps(app.openapi()), file=fd) - - - diff --git a/mealie/utils/logger.py b/mealie/utils/logger.py index 03fa87933..d8901a5c5 100644 --- a/mealie/utils/logger.py +++ b/mealie/utils/logger.py @@ -1,7 +1,7 @@ import logging from pathlib import Path -from app_config import DATA_DIR +from core.config import DATA_DIR LOGGER_LEVEL = "INFO" CWD = Path(__file__).parent diff --git a/mealie/utils/startup.py b/mealie/utils/startup.py deleted file mode 100644 index b97811693..000000000 --- a/mealie/utils/startup.py +++ /dev/null @@ -1,13 +0,0 @@ -from pathlib import Path - -from services.settings_services import default_theme_init - -CWD = Path(__file__).parent - - -def post_start(): - default_theme_init() - - -if __name__ == "__main__": - pass diff --git a/mealie/utils/unzip.py b/mealie/utils/unzip.py index b8e53767b..930da7365 100644 --- a/mealie/utils/unzip.py +++ b/mealie/utils/unzip.py @@ -2,7 +2,7 @@ import tempfile import zipfile from pathlib import Path -from app_config import TEMP_DIR +from core.config import TEMP_DIR def unpack_zip(selection: Path) -> tempfile.TemporaryDirectory: diff --git a/poetry.lock b/poetry.lock index 7040e92bb..4fa3efa24 100644 --- a/poetry.lock +++ b/poetry.lock @@ -83,6 +83,22 @@ docs = ["furo", "sphinx", "zope.interface"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +[[package]] +name = "bcrypt" +version = "3.2.0" +description = "Modern password hashing for your software and your servers" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.1" +six = ">=1.4.1" + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + [[package]] name = "beautifulsoup4" version = "4.9.3" @@ -128,6 +144,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "cffi" +version = "1.14.5" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + [[package]] name = "chardet" version = "4.0.0" @@ -457,6 +484,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pydantic" version = "1.7.3" @@ -872,7 +907,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "957b8a18406390a7bf3d02b0ccbf0af89e52d0a7d89e25adb81650f942d5108c" +content-hash = "d952f188238a85c926a047e9741d24830fa613f3e378725045654f0277a6061e" [metadata.files] aiofiles = [ @@ -903,6 +938,15 @@ attrs = [ {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] +bcrypt = [ + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, + {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, + {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, + {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, +] beautifulsoup4 = [ {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, @@ -915,6 +959,45 @@ certifi = [ {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] +cffi = [ + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, +] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, @@ -1194,6 +1277,10 @@ py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] pydantic = [ {file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"}, {file = "pydantic-1.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009"}, diff --git a/pyproject.toml b/pyproject.toml index 5c8655042..a83a07a31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ extruct = "^0.12.0" scrape-schema-recipe = "^0.1.3" python-multipart = "^0.0.5" fastapi-login = "^1.5.3" +bcrypt = "^3.2.0" [tool.poetry.dev-dependencies] pylint = "^2.6.0"