diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4d8b27e370d6..492df5395abe 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -26,7 +26,7 @@ ENV PYTHONUNBUFFERED=1 \ # prepend poetry and venv to path ENV PATH="$POETRY_HOME/bin:$PATH" -RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - +RUN curl -sSL https://install.python-poetry.org | python3 - # RUN poetry config virtualenvs.create false RUN apt-get update \ diff --git a/docker/api.Dockerfile b/docker/api.Dockerfile index 098eeff29d47..66bf9697c6d7 100644 --- a/docker/api.Dockerfile +++ b/docker/api.Dockerfile @@ -71,6 +71,7 @@ ENV GIT_COMMIT_HASH=$COMMIT RUN apt-get update \ && apt-get install --no-install-recommends -y \ gosu \ + iproute2 \ tesseract-ocr-all \ libldap-2.4-2 \ && apt-get autoremove \ diff --git a/docker/api.entry.sh b/docker/api.entry.sh index f4a5a70f6fbe..cbbd5cb9f53d 100644 --- a/docker/api.entry.sh +++ b/docker/api.entry.sh @@ -50,10 +50,10 @@ init GUNICORN_PORT=${API_PORT:-9000} # Start API - -if [ "$WEB_GUNICORN" == 'true' ]; then +hostip=`/sbin/ip route|awk '/default/ { print $3 }'` +if [ "$WEB_GUNICORN" = 'true' ]; then echo "Starting Gunicorn" - gunicorn mealie.app:app -b 0.0.0.0:$GUNICORN_PORT -k uvicorn.workers.UvicornWorker -c /app/gunicorn_conf.py --preload + gunicorn mealie.app:app -b 0.0.0.0:$GUNICORN_PORT --forwarded-allow-ips=$hostip -k uvicorn.workers.UvicornWorker -c /app/gunicorn_conf.py --preload else - uvicorn mealie.app:app --host 0.0.0.0 --port $GUNICORN_PORT + uvicorn mealie.app:app --host 0.0.0.0 --forwarded-allow-ips=$hostip --port $GUNICORN_PORT fi diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 606451a8e5b7..ce59fc9fba80 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -64,11 +64,10 @@ services: # ===================================== # Web Concurrency - WEB_GUNICORN: "true" + WEB_GUNICORN: "false" WORKERS_PER_CORE: 0.5 MAX_WORKERS: 1 WEB_CONCURRENCY: 1 - # ===================================== # Email Configuration # SMTP_HOST= @@ -79,13 +78,13 @@ services: # SMTP_USER= # SMTP_PASSWORD= - # postgres: - # container_name: postgres - # image: postgres - # restart: always - # environment: - # POSTGRES_PASSWORD: mealie - # POSTGRES_USER: mealie + # postgres: + # container_name: postgres + # image: postgres + # restart: always + # environment: + # POSTGRES_PASSWORD: mealie + # POSTGRES_USER: mealie volumes: mealie-data: diff --git a/docker/omni.Dockerfile b/docker/omni.Dockerfile index 1997bb88ab21..7505d1594b16 100644 --- a/docker/omni.Dockerfile +++ b/docker/omni.Dockerfile @@ -94,6 +94,7 @@ ENV GIT_COMMIT_HASH=$COMMIT RUN apt-get update \ && apt-get install --no-install-recommends -y \ gosu \ + iproute2 \ tesseract-ocr-all \ curl \ gnupg \ diff --git a/docker/omni.docker-compose.yml b/docker/omni.docker-compose.yml index 90ffba194d34..6f57bc4f7005 100644 --- a/docker/omni.docker-compose.yml +++ b/docker/omni.docker-compose.yml @@ -26,11 +26,10 @@ services: # ===================================== # Web Concurrency - WEB_GUNICORN: true + WEB_GUNICORN: "false" WORKERS_PER_CORE: 0.5 MAX_WORKERS: 1 WEB_CONCURRENCY: 1 - # ===================================== # Email Configuration # SMTP_HOST= diff --git a/docker/omni.entry.sh b/docker/omni.entry.sh index 730cdbc9fd07..e53084d7bf88 100644 --- a/docker/omni.entry.sh +++ b/docker/omni.entry.sh @@ -46,12 +46,12 @@ init GUNICORN_PORT=${API_PORT:-9000} # Start API - -if [ "$WEB_GUNICORN" == 'true' ]; then +hostip=`/sbin/ip route|awk '/default/ { print $3 }'` +if [ "$WEB_GUNICORN" = 'true' ]; then echo "Starting Gunicorn" - gunicorn mealie.app:app -b 0.0.0.0:$GUNICORN_PORT -k uvicorn.workers.UvicornWorker -c /app/gunicorn_conf.py --preload & + gunicorn mealie.app:app -b 0.0.0.0:$GUNICORN_PORT --forwarded-allow-ips=$hostip -k uvicorn.workers.UvicornWorker -c /app/gunicorn_conf.py --preload & else - uvicorn mealie.app:app --host 0.0.0.0 --port $GUNICORN_PORT & + uvicorn mealie.app:app --host 0.0.0.0 --forwarded-allow-ips=$hostip --port $GUNICORN_PORT & fi # ------------------------------ diff --git a/docs/docs/documentation/getting-started/faq.md b/docs/docs/documentation/getting-started/faq.md index 591e6362bfe1..787944982e43 100644 --- a/docs/docs/documentation/getting-started/faq.md +++ b/docs/docs/documentation/getting-started/faq.md @@ -132,6 +132,15 @@ stateDiagram-v2 p3 --> n1: No ``` +## Can I use fail2ban with mealie? +Yes, mealie is configured to properly forward external IP addresses into the `mealie.log` logfile. Note that, due to restrictions in docker, IP address forwarding only works on linux. + +Your fail2ban usage should look like the following: +``` +Use datepattern : %d-%b-%y %H:%M:%S : Day-MON-Year2 24hour:Minute:Second +Use failregex line : ^ERROR:\s+Incorrect username or password from +``` + ## Why An API? An API allows integration into applications like [Home Assistant](https://www.home-assistant.io/) that can act as notification engines to provide custom notifications based of Meal Plan data to remind you to defrost the chicken, marinade the steak, or start the CrockPot. Additionally, you can access nearly any backend service via the API giving you total control to extend the application. To explore the API spin up your server and navigate to http://yourserver.com/docs for interactive API documentation. diff --git a/frontend/nuxt.config.js b/frontend/nuxt.config.js index 588311864970..98148abeee04 100644 --- a/frontend/nuxt.config.js +++ b/frontend/nuxt.config.js @@ -295,10 +295,12 @@ export default { }, changeOrigin: true, target: process.env.API_URL || "http://localhost:9000", + xfwd: true, }, "/api": { changeOrigin: true, target: process.env.API_URL || "http://localhost:9000", + xfwd: true, }, }, diff --git a/mealie/routes/auth/auth.py b/mealie/routes/auth/auth.py index 682aee622c6b..3a84cc7c7514 100644 --- a/mealie/routes/auth/auth.py +++ b/mealie/routes/auth/auth.py @@ -52,15 +52,21 @@ class MealieAuthToken(BaseModel): def get_token(request: Request, data: CustomOAuth2Form = Depends(), session: Session = Depends(generate_session)): email = data.username password = data.password + if "x-forwarded-for" in request.headers: + ip = request.headers["x-forwarded-for"] + if "," in ip: # if there are multiple IPs, the first one is canonically the true client + ip = str(ip.split(",")[0]) + else: + ip = request.client.host try: user = authenticate_user(session, email, password) # type: ignore except UserLockedOut as e: - logger.error(f"User is locked out from {request.client.host}") + logger.error(f"User is locked out from {ip}") raise HTTPException(status_code=status.HTTP_423_LOCKED, detail="User is locked out") from e if not user: - logger.error(f"Incorrect username or password from {request.client.host}") + logger.error(f"Incorrect username or password from {ip}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, )