Compare commits

..

12 Commits

Author SHA1 Message Date
Hayden
8154ee548a Chore/bump deps (#853)
* add LDAP dep

* bump extruct and scraper

* allow for disabling auto backup

* v0.5.4 changelog
2021-12-01 21:07:17 -09:00
Hayden
591bf9d98f Update v1-task.yaml 2021-11-24 11:46:43 -09:00
Hayden
f202b1f922 Update v1-task.yaml 2021-11-24 11:45:59 -09:00
Hayden
17b2e37e4e Update v1-task.yaml 2021-11-24 11:40:53 -09:00
Hayden
0ec8087ac6 Rename v1-task to v1-task.yaml 2021-11-24 11:33:59 -09:00
Hayden
e580d6f904 Create v1-tasks template 2021-11-24 11:33:26 -09:00
dvdkon
56d9cafb68 Add LDAP authentication support (v2, onto dev) (#803)
* Add LDAP authentication support

* Add test for LDAP authentication
2021-11-24 08:59:03 -09:00
Hayden
32c864c703 New Crowdin updates (#818)
* New translations en-US.json (French, Canada)

* New translations en-US.json (French, Canada)

* New translations en-US.json (French, Canada)

* New translations en-US.json (French)

* New translations en-US.json (French, Canada)

* New translations en-US.json (French, Canada)

* New translations en-US.json (French)
2021-11-23 20:38:25 -09:00
Bryce Willey
37280a3da0 Improve the SWAG Community Guide (#793)
* Freshen up the SWAG documentation

Added some extra pointers for setting up HTTPS with Mealie and SWAG, and indented the `docker-compose.yml` as it should be.

* Replaced <code> html blocks with backticks

* Better formatting and comments in example config files

* Made DuckDNS consistent with other places on the page
2021-11-23 20:34:23 -09:00
cadamswaite
7f850fba98 Use firefox UA when making requests (#780) 2021-11-23 20:34:10 -09:00
J.P. Krauss
b40f201430 Allow arrow keys to function when SearchDialog is not open (#777) 2021-11-07 10:11:07 -09:00
sephrat
a0d796551c Add support for new languages (#781)
* Add support for Slovak language

* Add support for new languages

Catalan, Danish, Norwegian and Russian
2021-11-07 10:03:37 -09:00
20 changed files with 1361 additions and 505 deletions

38
.github/ISSUE_TEMPLATE/v1-task.yaml vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: v1.0.0b Task
description: "CONTRIBUTORS ONLY: Submit a Task that needs to be completed"
title: "[v1.0.0b] [Task] - TASK DESCRIPTION"
labels:
- task
- v1
body:
- type: markdown
attributes:
value: |
Thanks for your interest in Mealie! 🚀
This is a place for Mealie contributors to find tasks that need to get done around the repository. Tasks are different than issues as they are generally related to providing a new feature or improve an existing feature. They are _generally_ not related to an issue.
**DO NOT** create a task unless
- You are a contributors who has prior approval via discord/discussions
- You have otherwise been given approval to post the tasks
Otherwise, your post will be closed/deleted.
**Interested in Taking This?**
If you're interested in completing this tasks and it hasn't already been taken, comment below and to let others know you're working on it. As you work through the task, I ask that you submit a draft pull request as soon as possible, and tag this issue so we can all collaborate as best as possible.
- type: textarea
id: problem
attributes:
label: What is the problem this task addresses?
placeholder: A clear and concise description of what the problem this task will address.
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed/Possible Solution(s)?
placeholder: Provide as much context around the idea as possible with potential files and roadblocks that may come up
validations:
required: true

View File

@@ -57,6 +57,7 @@ jobs:
#---------------------------------------------- #----------------------------------------------
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get install libsasl2-dev libldap2-dev libssl-dev
poetry install poetry install
poetry add "psycopg2-binary==2.8.6" poetry add "psycopg2-binary==2.8.6"
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'

View File

@@ -44,6 +44,8 @@ RUN apt-get update \
build-essential \ build-essential \
libpq-dev \ libpq-dev \
libwebp-dev \ libwebp-dev \
# LDAP Dependencies
libsasl2-dev libldap2-dev libssl-dev \
gnupg gnupg2 gnupg1 \ gnupg gnupg2 gnupg1 \
debian-keyring \ debian-keyring \
debian-archive-keyring \ debian-archive-keyring \

View File

@@ -0,0 +1,28 @@
# v0.5.4 - Bug Fixes
**App Version: v0.5.4**
**Database Version: v0.5.0**
## Breaking Changes
!!! error "Breaking Changes"
None
## What's Changed
* Add support for new languages by @sephrat in https://github.com/hay-kot/mealie/pull/781
* Allow arrow keys to function when SearchDialog is not open by @asymworks in https://github.com/hay-kot/mealie/pull/777
* Use firefox user agent when making requests by @cadamswaite in https://github.com/hay-kot/mealie/pull/780
* Improve the SWAG Community Guide by @BryceStevenWilley in https://github.com/hay-kot/mealie/pull/793
* New Crowdin updates by @hay-kot in https://github.com/hay-kot/mealie/pull/818
* Add LDAP authentication support (v2, onto dev) by @dvdkon in https://github.com/hay-kot/mealie/pull/803
* Auto backup is now disabled by default. Enable it by setting the AUTO_BACKUP_ENABLED env variable to true.
## New Contributors
* @asymworks made their first contribution in https://github.com/hay-kot/mealie/pull/777
* @dvdkon made their first contribution in https://github.com/hay-kot/mealie/pull/803
**Full Changelog**: https://github.com/hay-kot/mealie/compare/v0.5.3...v0.5.4

View File

@@ -4,63 +4,71 @@
This guide was submitted by a community member. Find something wrong? Submit a PR to get it fixed! This guide was submitted by a community member. Find something wrong? Submit a PR to get it fixed!
To make the setup of a Reverse Proxy much easier, Linuxserver.io developed [SWAG](https://github.com/linuxserver/docker-swag) To make the setup of a Reverse Proxy much easier, Linuxserver.io developed [SWAG](https://github.com/linuxserver/docker-swag)
SWAG - Secure Web Application Gateway (formerly known as letsencrypt, no relation to Let's Encrypt™) sets up an Nginx web server and reverse proxy with PHP support and a built-in certbot client that automates free SSL server certificate generation and renewal processes (Let's Encrypt and ZeroSSL). It also contains fail2ban for intrusion prevention. SWAG - Secure Web Application Gateway (formerly known as letsencrypt, no relation to Let's Encrypt™) sets up an Nginx web server and reverse proxy with PHP support and a built-in certbot client that automates free TLS server certificate generation and renewal processes (Let's Encrypt and ZeroSSL). It also contains fail2ban for intrusion prevention.
## Step 1: Get a domain ## Step 1: Get a domain
The first step is to grab a dynamic DNS if you don't have your own subdomain already. You can get this from for example [DuckDNS](https://www.duckdns.org). The first step is to grab a dynamic DNS if you don't have your own subdomain already. You can get this from for example [DuckDNS](https://www.duckdns.org).
If you already own a domain, you'll need to create an `A` record that points to the machine that SWAG is running on. See
the [SWAG documentation](https://docs.linuxserver.io/general/swag#create-container-via-http-validation) for more details.
## Step 2: Set-up SWAG ## Step 2: Set-up SWAG
Then you will need to set up SWAG, the variables of the docker-compose are explained on the Github page of [SWAG](https://github.com/linuxserver/docker-swag). Then you will need to set up SWAG, the variables of the docker-compose are explained on the Github page of [SWAG](https://github.com/linuxserver/docker-swag).
This is an example of how to set it up using duckdns and docker-compose. This is an example of how to set it up using DuckDNS and docker-compose.
!!! example "docker-compose.yml" !!! example "docker-compose.yml"
```yaml ```yaml
version: "2.1" version: "2.1"
services: services:
swag: swag:
image: ghcr.io/linuxserver/swag image: ghcr.io/linuxserver/swag
container_name: swag container_name: swag
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
environment: environment:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
- TZ=Europe/Brussels # valid TZs at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
- URL=<mydomain.duckdns> - TZ=Europe/Brussels
- SUBDOMAINS=wildcard - URL=<mydomain.duckdns>
- VALIDATION=duckdns - SUBDOMAINS=wildcard
- CERTPROVIDER= #optional - VALIDATION=duckdns
- DNSPLUGIN= #optional - CERTPROVIDER= #optional
- DUCKDNSTOKEN=<duckdnstoken> - DNSPLUGIN= #optional
- EMAIL=<e-mail> #optional - DUCKDNSTOKEN=<duckdnstoken>
- ONLY_SUBDOMAINS=false #optional - EMAIL=<e-mail> #optional
- EXTRA_DOMAINS=<extradomains> #optional - ONLY_SUBDOMAINS=false #optional
- STAGING=false #optional - EXTRA_DOMAINS=<extradomains> #optional
volumes: - STAGING=false #optional
- /etc/config/swag:/config volumes:
ports: - /etc/config/swag:/config
- 443:443 ports:
restart: unless-stopped - 443:443
# required if VALIDATION=http above, if you aren't using DuckDNS
- 80:80
restart: unless-stopped
``` ```
Don't forget to change the <code>mydomain.duckns</code> into your personal domain and the <code>duckdnstoken</code> into your token and remove the brackets. Don't forget to change the `mydomain.duckns` into your personal domain and the `duckdnstoken` into your token and remove the brackets.
You can also include the contents of the [mealie docker-compose](mealie/documentation/getting-started/install/#docker-compose-with-sqlite) in the SWAG
docker-compose, without the `ports` section under mealie. This allows SWAG and mealie to communicate on the same docker network, without
making mealie visible to other applications on your machine.
## Step 3: Change the config files ## Step 3: Change the config files
Navigate to the config folder of SWAG and head to <code>proxy-confs</code>. If you used the example above, you should navigate to: <code>/etc/config/swag/nginx/proxy-confs/</code>. Navigate to the config folder of SWAG and head to `proxy-confs`. If you used the example above, you should navigate to: `/etc/config/swag/nginx/proxy-confs/`.
There are a lot of preconfigured files to use for different apps such as radarr, sonarr, overseerr, ... There are a lot of preconfigured files to use for different apps such as radarr, sonarr, overseerr, ...
To use the bundled configuration file, simply rename <code>mealie.subdomain.conf.sample</code> in the proxy-confs folder to <code>mealie.subdomain.conf</code>. To use the bundled configuration file, simply rename `mealie.subdomain.conf.sample` in the proxy-confs folder to `mealie.subdomain.conf`.
Alternatively, you can create a new file <code>mealie.subdomain.conf</code> in proxy-confs with the following configuration: Alternatively, you can create a new file `mealie.subdomain.conf` in proxy-confs with the following configuration:
!!! example "mealie.subdomain.conf" !!! example "mealie.subdomain.conf"
```yaml ```nginx
server { server {
listen 443 ssl http2; listen 443 ssl http2;
listen [::]:443 ssl http2; listen [::]:443 ssl http2;
@@ -78,15 +86,14 @@ Alternatively, you can create a new file <code>mealie.subdomain.conf</code> in p
set $upstream_proto http; set $upstream_proto http;
proxy_pass $upstream_proto://$upstream_app:$upstream_port; proxy_pass $upstream_proto://$upstream_app:$upstream_port;
} }
}
}
``` ```
## Step 4: Port-forward port 443 ## Step 4: Port-forward port 443
Since SWAG allows you to set up a secure connection, you will need to open port 443 on your router for encrypted traffic. This is way more secure than port 80 for http. Since SWAG allows you to set up a secure connection, you will need to open port 443 on your router for encrypted traffic. This is way more secure than port 80 for http. For more information about using TLS on port 443, see [SWAG's documentation](https://docs.linuxserver.io/general/swag#cert-provider-lets-encrypt-vs-zerossl) on cert providers and port forwarding.
## Step 5: Restart SWAG ## Step 5: Restart SWAG
When you change anything in the config of Nginx, you will need to restart the container using <code>docker restart swag</code>. When you change anything in the config of Nginx, you will need to restart the container using `docker restart swag`.
If everything went well, you can now access mealie on the subdomain you configured: mealie.mydomain.duckdns.org If everything went well, you can now access mealie on the subdomain you configured: `mealie.mydomain.duckdns.org`

View File

@@ -128,12 +128,17 @@ services:
| POSTGRES_PORT | 5432 | Postgres database port | | POSTGRES_PORT | 5432 | Postgres database port |
| POSTGRES_DB | mealie | Postgres database name | | POSTGRES_DB | mealie | Postgres database name |
| TOKEN_TIME | 2 | The time in hours that a login/auth token is valid | | TOKEN_TIME | 2 | The time in hours that a login/auth token is valid |
| LDAP_AUTH_ENABLED | False | Authenticate via an external LDAP server in addidion to built-in Mealie auth |
| LDAP_SERVER_URL | None | LDAP server URL (e.g. ldap://ldap.example.com) |
| LDAP_BIND_TEMPLATE | None | Templated DN for users, `{}` will be replaced with the username (e.g. `cn={},dc=example,dc=com`) |
| LDAP_ADMIN_FILTER | None | Optional LDAP filter, which tells Mealie the LDAP user is an admin (e.g. `(memberOf=cn=admins,dc=example,dc=com)`) |
| RECIPE_PUBLIC | True | Default Recipe Settings - Make Recipe Public | | RECIPE_PUBLIC | True | Default Recipe Settings - Make Recipe Public |
| RECIPE_SHOW_NUTRITION | True | Default Recipe Settings - Show Recipe Nutrition | | RECIPE_SHOW_NUTRITION | True | Default Recipe Settings - Show Recipe Nutrition |
| RECIPE_SHOW_ASSETS | True | Default Recipe Settings - Show Recipe Assets | | RECIPE_SHOW_ASSETS | True | Default Recipe Settings - Show Recipe Assets |
| RECIPE_LANDSCAPE_VIEW | True | Default Recipe Settings - Set Landscape View | | RECIPE_LANDSCAPE_VIEW | True | Default Recipe Settings - Set Landscape View |
| RECIPE_DISABLE_COMMENTS | False | Default Recipe Settings - Disable Comments | | RECIPE_DISABLE_COMMENTS | False | Default Recipe Settings - Disable Comments |
| RECIPE_DISABLE_AMOUNT | False | Default Recipe Settings - Disable Amount | | RECIPE_DISABLE_AMOUNT | False | Default Recipe Settings - Disable Amount |
| AUTO_BACKUP_ENABLED | False | Disable/Enable Mealie's Auto Backup Function |
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** | | API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
| API_DOCS | True | Turns on/off access to the API documentation locally. | | API_DOCS | True | Turns on/off access to the API documentation locally. |
| TZ | UTC | Must be set to get correct date/time on the server | | TZ | UTC | Must be set to get correct date/time on the server |

View File

@@ -136,6 +136,7 @@ export default {
this.$emit(SELECTED_EVENT, recipe); this.$emit(SELECTED_EVENT, recipe);
}, },
onUpDown(e) { onUpDown(e) {
if (this.dialog) {
if (e.keyCode === 38) { if (e.keyCode === 38) {
e.preventDefault(); e.preventDefault();
this.selectedIndex--; this.selectedIndex--;
@@ -146,6 +147,7 @@ export default {
return; return;
} }
this.selectRecipe(); this.selectRecipe();
}
}, },
resetSelected() { resetSelected() {
this.searchString = ""; this.searchString = "";

View File

@@ -0,0 +1,21 @@
{
"short": {
"month": "short",
"day": "numeric",
"weekday": "long"
},
"medium": {
"month": "long",
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric"
}
}

View File

@@ -0,0 +1,489 @@
{
"about": {
"about": "À propos",
"about-mealie": "À propos de Mealie",
"api-docs": "Documentation de l'API",
"api-port": "Port de l'API",
"application-mode": "Mode de l'application",
"database-type": "Type de base de données",
"database-url": "URL de la base de données",
"default-group": "Groupe par défaut",
"demo": "Oui",
"demo-status": "Mode démo",
"development": "Développement",
"docs": "Documentation",
"download-log": "Télécharger les logs",
"download-recipe-json": "Dernier JSON récupéré",
"github": "GitHub",
"log-lines": "Lignes de log",
"not-demo": "Non",
"portfolio": "Portfolio",
"production": "Production",
"support": "Soutenir",
"version": "Version"
},
"asset": {
"assets": "Ressources",
"code": "Code",
"file": "Fichier",
"image": "Image",
"new-asset": "Nouvelle ressource",
"pdf": "PDF",
"recipe": "Recette",
"show-assets": "Afficher les ressources"
},
"category": {
"category-created": "Catégorie créée",
"category-creation-failed": "La création de la catégorie a échoué",
"category-deleted": "Catégorie supprimée",
"category-deletion-failed": "La suppression de la catégorie a échoué",
"category-filter": "Filtre par catégories",
"category-update-failed": "La mise à jour de la catégorie a échoué",
"category-updated": "Catégorie mise à jour",
"uncategorized-count": "{count} non catégorisée|{count} non catégorisées"
},
"events": {
"apprise-url": "URL apprise",
"database": "Base de données",
"delete-event": "Supprimer lévènement",
"new-notification-form-description": "Mealie utilise la bibliothèque apprise pour générer des notifications. Elle propose de nombreux services à utiliser pour les notifications. Consultez leur wiki pour un guide complet sur la façon de créer l'URL de votre service. Si disponible, sélectionner le type de votre notification peut inclure des fonctionnalités supplémentaires.",
"new-version": "Nouvelle version disponible !",
"notification": "Notification",
"refresh": "Rafraîchir",
"scheduled": "Planifié",
"something-went-wrong": "Une erreur s'est produite !",
"subscribed-events": "Évènements suivis",
"test-message-sent": "Message de test envoyé"
},
"general": {
"cancel": "Annuler",
"clear": "Effacer",
"close": "Fermer",
"confirm": "Confirmer",
"confirm-delete-generic": "Voulez-vous vraiment supprimer ceci ?",
"copied": "Copié",
"create": "Créer",
"created": "Créé",
"custom": "Personnalisé",
"dashboard": "Tableau de bord",
"delete": "Supprimer",
"disabled": "Désactivé",
"download": "Télécharger",
"edit": "Modifier",
"enabled": "Activé",
"exception": "Exception",
"failed-count": "Échec : {count}",
"failure-uploading-file": "Échec de l'envoi du fichier",
"favorites": "Favoris",
"field-required": "Champ obligatoire",
"file-folder-not-found": "Fichier/dossier introuvable",
"file-uploaded": "Fichier envoyé",
"filter": "Filtrer",
"friday": "Vendredi",
"general": "Général",
"get": "Envoyer",
"home": "Page d'accueil",
"image": "Image",
"image-upload-failed": "Le téléchargement de l'image a échoué",
"import": "Importer",
"json": "JSON",
"keyword": "Mot-clé",
"link-copied": "Lien copié",
"loading-recipes": "Chargement des recettes",
"monday": "Lundi",
"name": "Nom",
"new": "Nouveau",
"no": "Non",
"no-recipe-found": "Aucune recette trouvée",
"ok": "OK",
"options": "Options :",
"print": "Imprimer",
"random": "Aléatoire",
"rating": "Note",
"recent": "Récent",
"recipe": "Recette",
"recipes": "Recettes",
"rename-object": "Renommer {0}",
"reset": "Réinitialiser",
"saturday": "Samedi",
"save": "Sauvegarder",
"settings": "Paramètres",
"share": "Partager",
"shuffle": "Aléatoire",
"sort": "Trier",
"sort-alphabetically": "Alphabétique",
"status": "Statut",
"submit": "Importer",
"success-count": "Succès : {count}",
"sunday": "Dimanche",
"templates": "Modèles :",
"test": "Tester",
"themes": "Thèmes",
"thursday": "Jeudi",
"token": "Jeton",
"tuesday": "Mardi",
"type": "Type",
"update": "Mettre à jour",
"updated": "Mis à jour",
"upload": "Importer",
"url": "URL",
"view": "Afficher",
"wednesday": "Mercredi",
"yes": "Oui"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Êtes-vous certain de vouloir supprimer <b>{groupName}<b/> ?",
"cannot-delete-default-group": "Vous ne pouvez pas supprimer le groupe par défaut",
"cannot-delete-group-with-users": "Impossible de supprimer un groupe avec des utilisateurs",
"confirm-group-deletion": "Confirmer la suppression du groupe",
"create-group": "Créer un groupe",
"error-updating-group": "Erreur lors de la mise à jour du groupe",
"group": "Groupe",
"group-deleted": "Groupe supprimé",
"group-deletion-failed": "Échec de la suppression du groupe",
"group-id-with-value": "ID groupe : {groupID}",
"group-name": "Nom du groupe",
"group-not-found": "Groupe non trouvé",
"group-with-value": "Groupe : {groupID}",
"groups": "Groupes",
"manage-groups": "Gérer les groupes",
"user-group": "Groupe d'utilisateurs",
"user-group-created": "Groupe d'utilisateurs créé",
"user-group-creation-failed": "La création du groupe d'utilisateur a échoué"
},
"meal-plan": {
"create-a-new-meal-plan": "Créer un nouveau menu",
"dinner-this-week": "Menu de la semaine",
"dinner-today": "Menu du jour",
"dinner-tonight": "AU MENU CE SOIR",
"edit-meal-plan": "Modifier le menu",
"end-date": "Date de fin",
"group": "Regrouper (Bêta)",
"main": "Plat principal",
"meal-planner": "Menus",
"meal-plans": "Menus",
"mealplan-categories": "CATÉGORIES DES MENUS",
"mealplan-created": "Menu créé",
"mealplan-creation-failed": "La création du menu a échoué",
"mealplan-deleted": "Menu supprimé",
"mealplan-deletion-failed": "La suppression du menu a échoué",
"mealplan-settings": "Paramètres des menus",
"mealplan-update-failed": "La mise à jour du menu a échoué",
"mealplan-updated": "Menu mis à jour",
"no-meal-plan-defined-yet": "Aucun menu planifié",
"no-meal-planned-for-today": "Aucun repas prévu pour aujourd'hui",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Seules les recettes appartenant à ces catégories seront utilisées dans les menus",
"planner": "Planificateur",
"quick-week": "Semaine rapide",
"side": "Accompagnement",
"sides": "Accompagnements",
"start-date": "Date de début"
},
"migration": {
"chowdown": {
"description": "Importer des recettes depuis Chowdown",
"title": "Chowdown"
},
"migration-data-removed": "Données de migration supprimées",
"nextcloud": {
"description": "Importer des recettes depuis un livre de recettes Nextcloud existant",
"title": "Nextcloud Cookbook"
},
"no-migration-data-available": "Aucune donnée d'importation n'est disponible",
"recipe-migration": "Migrer les recettes"
},
"new-recipe": {
"bulk-add": "Ajouter en masse",
"error-details": "Seuls les sites web contenant ld+json ou des microdonnées peuvent être importés par Mealie. La plupart des grands sites web de recettes sont compatibles avec cette structure de données. Si votre site ne peut pas être importé mais qu'il y a des données json dans le journal, veuillez soumettre un problème GitHub avec l'URL et les données.",
"error-title": "On dirait qu'on n'a pas pu trouver quoi que ce soit",
"from-url": "Depuis une adresse web",
"github-issues": "Anomalies GitHub",
"google-ld-json-info": "Infos Json-Ld Google",
"must-be-a-valid-url": "Doit être une URL valide",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Copiez votre recette ici. Chaque ligne sera traitée comme un objet de la liste",
"recipe-markup-specification": "Spécification du marquage des recettes",
"recipe-url": "Adresse de la recette",
"upload-a-recipe": "Télécharger une recette",
"upload-individual-zip-file": "Chargez un fichier .zip exporté depuis une autre instance Mealie.",
"url-form-hint": "Copiez et collez un lien depuis votre site de recettes favori",
"view-scraped-data": "Voir les données récupérées"
},
"page": {
"404-page-not-found": "404 Page introuvable",
"all-recipes": "Toutes les recettes",
"new-page-created": "Nouvelle page créée",
"page": "Page",
"page-creation-failed": "La création de la page a échoué",
"page-deleted": "Page supprimée",
"page-deletion-failed": "La suppression de la page a échoué",
"page-update-failed": "La mise à jour de la page a échoué",
"page-updated": "Page mise à jour",
"pages-update-failed": "La mise à jour des pages a échoué",
"pages-updated": "Pages mises à jour"
},
"recipe": {
"add-key": "Ajouter une clé",
"add-to-favorites": "Ajouter aux favoris",
"api-extras": "Extras API",
"calories": "Calories",
"calories-suffix": "calories",
"carbohydrate-content": "Glucides",
"categories": "Catégories",
"comment-action": "Commenter",
"comments": "Commentaires",
"delete-confirmation": "Êtes-vous sûr(e) de vouloir supprimer cette recette ?",
"delete-recipe": "Supprimer la recette",
"description": "Description",
"disable-amount": "Désactiver les quantités d'ingrédients",
"disable-comments": "Désactiver les commentaires",
"fat-content": "Matières grasses",
"fiber-content": "Fibres",
"grams": "grammes",
"ingredient": "Ingrédient",
"ingredients": "Ingrédients",
"insert-section": "Insérer une section",
"instructions": "Instructions",
"key-name-required": "Un nom de clé est requis",
"landscape-view-coming-soon": "Vue paysage (bientôt disponible)",
"milligrams": "milligrammes",
"new-key-name": "Nouveau nom de clé",
"no-white-space-allowed": "Aucun espace blanc autorisé",
"note": "Note",
"nutrition": "Valeurs nutritionnelles",
"object-key": "Clé d'objet",
"object-value": "Valeur d'objet",
"original-url": "Recette originale",
"perform-time": "Temps de cuisson",
"prep-time": "Temps de préparation",
"protein-content": "Protéines",
"public-recipe": "Recette publique",
"recipe-created": "Recette créée",
"recipe-creation-failed": "La création de la recette a échoué",
"recipe-deleted": "Recette supprimée",
"recipe-image": "Image de la recette",
"recipe-image-updated": "L'image de la recette a été mise à jour",
"recipe-name": "Nom de la recette",
"recipe-settings": "Paramètres de la recette",
"recipe-update-failed": "La mise à jour de la recette a échoué",
"recipe-updated": "Recette mise à jour",
"remove-from-favorites": "Supprimer des favoris",
"remove-section": "Supprimer une section",
"save-recipe-before-use": "Enregistrez la recette avant utilisation",
"section-title": "Titre de la section",
"servings": "Portions",
"share-recipe-message": "Je voulais partager ma recette de {0} avec vous.",
"show-nutrition-values": "Afficher les valeurs nutritionnelles",
"sodium-content": "Sodium",
"step-index": "Étape {step}",
"sugar-content": "Sucres",
"title": "Titre",
"total-time": "Temps total",
"unable-to-delete-recipe": "Impossible de supprimer la recette"
},
"reicpe": {
"no-recipe": "Pas de recette"
},
"search": {
"advanced-search": "Recherche avancée",
"and": "et",
"exclude": "Exclure",
"include": "Inclure",
"max-results": "Nombre de résultats maximum",
"or": "Ou",
"results": "Résultats",
"search": "Rechercher",
"search-mealie": "Rechercher dans Mealie (appuyez sur /)",
"search-placeholder": "Rechercher...",
"tag-filter": "Filtre par mots-clés"
},
"settings": {
"add-a-new-theme": "Ajouter un nouveau thème",
"admin-settings": "Paramètres d'administration",
"backup": {
"backup-created-at-response-export_path": "Sauvegarde créée dans {path}",
"backup-deleted": "Sauvegarde supprimée",
"backup-tag": "Tag de la sauvegarde",
"create-heading": "Créer une sauvegarde",
"delete-backup": "Supprimer la sauvegarde",
"error-creating-backup-see-log-file": "Erreur de création de la sauvegarde. Voir les logs",
"full-backup": "Sauvegarde complète",
"import-summary": "Résumé de l'importation",
"partial-backup": "Sauvegarde partielle",
"unable-to-delete-backup": "Impossible de supprimer la sauvegarde."
},
"backup-and-exports": "Sauvegardes",
"change-password": "Modifier le mot de passe",
"current": "Version :",
"custom-pages": "Pages personnalisées",
"edit-page": "Modifier la page",
"events": "Évènements",
"first-day-of-week": "Premier jour de la semaine",
"group-settings-updated": "Paramètres du groupe mis à jour",
"homepage": {
"all-categories": "Toutes les catégories",
"card-per-section": "Tuiles par section",
"home-page": "Page d'accueil",
"home-page-sections": "Sections de la page d'accueil",
"show-recent": "Afficher les récentes"
},
"language": "Langue",
"latest": "Dernière",
"local-api": "API locale",
"locale-settings": "Paramètres régionaux",
"migrations": "Migrations",
"new-page": "Nouvelle page",
"notify": "Notifier",
"organize": "Organiser",
"page-name": "Nom de la page",
"pages": "Pages",
"profile": "Profil",
"remove-existing-entries-matching-imported-entries": "Supprimer les entrées existantes correspondant aux entrées importées",
"set-new-time": "Indiquer une nouvelle heure",
"settings-update-failed": "La mise à jour des paramètres a échoué",
"settings-updated": "Paramètres mis à jour",
"site-settings": "Paramètres du site",
"theme": {
"accent": "Accentué",
"dark": "Sombre",
"default-to-system": "Identique au système",
"error": "Erreur",
"error-creating-theme-see-log-file": "Erreur lors de la création du thème. Consultez les logs.",
"error-deleting-theme": "Erreur lors de la suppression du thème",
"error-updating-theme": "Erreur lors de la mise à jour du thème",
"info": "Information",
"light": "Clair",
"primary": "Primaire",
"secondary": "Secondaire",
"success": "Succès",
"switch-to-dark-mode": "Basculer en mode sombre",
"switch-to-light-mode": "Basculer en mode clair",
"theme-deleted": "Thème supprimé",
"theme-name": "Nom du thème",
"theme-name-is-required": "Un nom de thème est requis.",
"theme-saved": "Thème enregistré",
"theme-updated": "Thème mis à jour",
"warning": "Avertissement"
},
"token": {
"active-tokens": "JETONS ACTIFS",
"api-token": "Jeton de l'API",
"api-tokens": "Jetons de l'API",
"copy-this-token-for-use-with-an-external-application-this-token-will-not-be-viewable-again": "Copiez ce jeton pour l'utiliser avec une application externe. Ce jeton ne sera plus consultable.",
"create-an-api-token": "Créer un jeton API",
"token-name": "Nom du jeton"
},
"toolbox": {
"assign-all": "Assigner tout",
"bulk-assign": "Assigner en masse",
"new-name": "Nouveau nom",
"no-unused-items": "Aucun élément inutilisé",
"recipes-affected": "Aucune recette affectée|Une recette affectée|{count} recettes affectées",
"remove-unused": "Supprimer orphelins",
"title-case-all": "Majuscules partout",
"toolbox": "Boîte à outils",
"unorganized": "Non organisé(s)"
},
"webhooks": {
"test-webhooks": "Tester les webhooks",
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "Les liens dans cette liste recevront les webhooks contenant les recettes pour le menu du jour. Actuellement, les webhooks se lancent à",
"webhook-url": "Lien du webhook",
"webhooks-caps": "WEBHOOKS"
}
},
"shopping-list": {
"all-lists": "Toutes les listes",
"create-shopping-list": "Créer une liste d'épicerie",
"from-recipe": "À partir d'une recette",
"list-name": "Nom de la liste",
"new-list": "Nouvelle liste",
"quantity": "Quantité : {0}",
"shopping-list": "Liste d'épicerie",
"shopping-lists": "Listes d'épicerie"
},
"sidebar": {
"all-recipes": "Les recettes",
"categories": "Catégories",
"dashboard": "Dashboard",
"home-page": "Accueil",
"manage-users": "Utilisateurs",
"migrations": "Migrations",
"profile": "Profil",
"search": "Rechercher",
"site-settings": "Paramètres",
"tags": "Mots-clés",
"toolbox": "Boîte à outils"
},
"signup": {
"error-signing-up": "Erreur lors de l'inscription",
"sign-up": "S'inscrire",
"sign-up-link-created": "Lien d'inscription créé",
"sign-up-link-creation-failed": "La création du lien d'inscription a échoué",
"sign-up-links": "Liens d'inscription",
"sign-up-token-deleted": "Jeton d'inscription supprimé",
"sign-up-token-deletion-failed": "La suppression du jeton d'inscription a échoué",
"welcome-to-mealie": "Bienvenue sur Mealie ! Pour devenir un utilisateur de cette instance, vous devez avoir un lien d'invitation valide. Si vous n'avez pas reçu d'invitation, vous ne pouvez pas vous inscrire. Pour recevoir un lien, contactez l'administrateur du site."
},
"tag": {
"tag-created": "Mot-clé créé",
"tag-creation-failed": "La création du mot-clé a échoué",
"tag-deleted": "Mot-clé supprimé",
"tag-deletion-failed": "La suppression du mot-clé a échoué",
"tag-update-failed": "La mise à jour du mot-clé a échoué",
"tag-updated": "Mot-clé mis à jour",
"tags": "Mots-clés",
"untagged-count": "{count} sans mot-clés"
},
"user": {
"admin": "Administrateur",
"are-you-sure-you-want-to-delete-the-link": "Voulez-vous réellement supprimer le lien <b>{link}<b/> ?",
"are-you-sure-you-want-to-delete-the-user": "Voulez-vous réellement supprimer l'utilisateur <b>{activeName} ID : {activeId}<b/> ?",
"confirm-link-deletion": "Confirmer la suppression du lien",
"confirm-password": "Confirmer mot de passe",
"confirm-user-deletion": "Confirmer la suppression",
"could-not-validate-credentials": "La vérification de vos identifiants a échoué",
"create-link": "Créer un lien",
"create-user": "Créer utilisateur",
"current-password": "Mot de passe actuel",
"e-mail-must-be-valid": "Le courriel doit être valide",
"edit-user": "Modifier l'utilisateur",
"email": "Courriel",
"error-cannot-delete-super-user": "Erreur ! Impossible de supprimer le super utilisateur",
"existing-password-does-not-match": "Le mot de passe actuel ne correspond pas",
"full-name": "Nom",
"link-id": "ID du lien",
"link-name": "Nom du lien",
"login": "Connexion",
"logout": "Déconnexion",
"manage-users": "Gérer les utilisateurs",
"new-password": "Nouveau mot de passe",
"new-user": "Nouvel utilisateur",
"password": "Mot de passe",
"password-has-been-reset-to-the-default-password": "Le mot de passe a été réinitialisé à la valeur par défaut",
"password-must-match": "Les mots de passe doivent correspondre",
"password-reset-failed": "Échec de la réinitialisation du mot de passe",
"password-updated": "Mot de passe mis à jour",
"reset-password": "Réinitialiser le mot de passe",
"sign-in": "Se connecter",
"total-mealplans": "Nombre de menus",
"total-users": "Nombre d'utilisateurs",
"upload-photo": "Importer une photo",
"use-8-characters-or-more-for-your-password": "Utilisez au moins 8 caractères pour votre mot de passe",
"user": "Utilisateur",
"user-created": "Utilisateur créé",
"user-creation-failed": "La création de l'utilisateur a échoué",
"user-deleted": "Utilisateur supprimé",
"user-id": "ID utilisateur",
"user-id-with-value": "ID utilisateur : {id}",
"user-password": "Mot de passe de l'utilisateur",
"user-successfully-logged-in": "Connexion réussie",
"user-update-failed": "La mise à jour de l'utilisateur a échoué",
"user-updated": "Utilisateur mis à jour",
"username": "Nom d'utilisateur",
"users": "Utilisateurs",
"users-header": "UTILISATEURS",
"webhook-time": "Heure du Webhook",
"webhooks-enabled": "Webhooks activés",
"you-are-not-allowed-to-create-a-user": "Vous n'avez pas le droit de créer un utilisateur",
"you-are-not-allowed-to-delete-this-user": "Vous n'avez pas le droit de supprimer cet utilisateur"
}
}

View File

@@ -47,11 +47,11 @@
"database": "Base de données", "database": "Base de données",
"delete-event": "Supprimer lévènement", "delete-event": "Supprimer lévènement",
"new-notification-form-description": "Mealie utilise la bibliothèque Apprise pour générer des notifications. Elle propose de nombreux services à utiliser pour les notifications. Consultez leur wiki pour un guide complet sur la façon de créer l'URL de votre service. Si disponible, sélectionner le type de votre notification peut inclure des fonctionnalités supplémentaires.", "new-notification-form-description": "Mealie utilise la bibliothèque Apprise pour générer des notifications. Elle propose de nombreux services à utiliser pour les notifications. Consultez leur wiki pour un guide complet sur la façon de créer l'URL de votre service. Si disponible, sélectionner le type de votre notification peut inclure des fonctionnalités supplémentaires.",
"new-version": "Nouvelle version disponible!", "new-version": "Nouvelle version disponible !",
"notification": "Notification", "notification": "Notification",
"refresh": "Rafraîchir", "refresh": "Rafraîchir",
"scheduled": "Planifié", "scheduled": "Planifié",
"something-went-wrong": "Une erreur s'est produite!", "something-went-wrong": "Une erreur s'est produite !",
"subscribed-events": "Évènements suivis", "subscribed-events": "Évènements suivis",
"test-message-sent": "Message de test envoyé" "test-message-sent": "Message de test envoyé"
}, },
@@ -60,7 +60,7 @@
"clear": "Effacer", "clear": "Effacer",
"close": "Fermer", "close": "Fermer",
"confirm": "Confirmer", "confirm": "Confirmer",
"confirm-delete-generic": "Voulez-vous vraiment supprimer ceci?", "confirm-delete-generic": "Voulez-vous vraiment supprimer ceci ?",
"copied": "Copié", "copied": "Copié",
"create": "Créer", "create": "Créer",
"created": "Créé", "created": "Créé",
@@ -316,7 +316,7 @@
"current": "Version :", "current": "Version :",
"custom-pages": "Pages personnalisées", "custom-pages": "Pages personnalisées",
"edit-page": "Modifier la page", "edit-page": "Modifier la page",
"events": "Evènements", "events": "Évènements",
"first-day-of-week": "Premier jour de la semaine", "first-day-of-week": "Premier jour de la semaine",
"group-settings-updated": "Paramètres du groupe mis à jour", "group-settings-updated": "Paramètres du groupe mis à jour",
"homepage": { "homepage": {

View File

@@ -3,6 +3,7 @@ import Vuetify from "vuetify/lib";
Vue.use(Vuetify); Vue.use(Vuetify);
import ca from "vuetify/es5/locale/ca";
import de from "vuetify/es5/locale/de"; import de from "vuetify/es5/locale/de";
import en from "vuetify/es5/locale/en"; import en from "vuetify/es5/locale/en";
import es from "vuetify/es5/locale/es"; import es from "vuetify/es5/locale/es";
@@ -10,7 +11,10 @@ import fr from "vuetify/es5/locale/fr";
import hu from "vuetify/es5/locale/hu"; import hu from "vuetify/es5/locale/hu";
import it from "vuetify/es5/locale/it"; import it from "vuetify/es5/locale/it";
import nl from "vuetify/es5/locale/nl"; import nl from "vuetify/es5/locale/nl";
import no from "vuetify/es5/locale/no";
import pl from "vuetify/es5/locale/pl"; import pl from "vuetify/es5/locale/pl";
import ru from "vuetify/es5/locale/ru";
import sk from "vuetify/es5/locale/sk";
import sv from "vuetify/es5/locale/sv"; import sv from "vuetify/es5/locale/sv";
import zhHans from "vuetify/es5/locale/zh-Hans"; import zhHans from "vuetify/es5/locale/zh-Hans";
import zhHant from "vuetify/es5/locale/zh-Hant"; import zhHant from "vuetify/es5/locale/zh-Hant";
@@ -43,6 +47,8 @@ const vuetify = new Vuetify({
}, },
lang: { lang: {
locales: { locales: {
"ca-ES": ca,
"da-DK": en, // language not supported by Vuetify
"de-DE": de, "de-DE": de,
"en-US": en, "en-US": en,
"en-GB": en, "en-GB": en,
@@ -51,7 +57,10 @@ const vuetify = new Vuetify({
"hu-HU": hu, "hu-HU": hu,
"it-IT": it, "it-IT": it,
"nl-NL": nl, "nl-NL": nl,
"no-NO": no,
"pl-PL": pl, "pl-PL": pl,
"ru-RU": ru,
"sk-SK": sk,
"sv-SE": sv, "sv-SE": sv,
"zh-CN": zhHans, "zh-CN": zhHans,
"zh-TW": zhHant, "zh-TW": zhHant,

View File

@@ -9,6 +9,14 @@ const state = {
name: "British English", name: "British English",
value: "en-GB", value: "en-GB",
}, },
{
name: "Català (Catalan)",
value: "ca-ES",
},
{
name: "Dansk (Danish)",
value: "da-DK",
},
{ {
name: "Deutsch (German)", name: "Deutsch (German)",
value: "de-DE", value: "de-DE",
@@ -29,6 +37,10 @@ const state = {
name: "Italiano (Italian)", name: "Italiano (Italian)",
value: "it-IT", value: "it-IT",
}, },
{
name: "Norsk (Norwegian)",
value: "no-NO",
},
{ {
name: "Nederlands (Dutch)", name: "Nederlands (Dutch)",
value: "nl-NL", value: "nl-NL",
@@ -37,6 +49,14 @@ const state = {
name: "Polski (Polish)", name: "Polski (Polish)",
value: "pl-PL", value: "pl-PL",
}, },
{
name: "Pусский (Russian)",
value: "ru-RU",
},
{
name: "Slovenčina (Slovak)",
value: "sk-SK",
},
{ {
name: "Svenska (Swedish)", name: "Svenska (Swedish)",
value: "sv-SE", value: "sv-SE",

View File

@@ -151,6 +151,11 @@ class AppSettings(BaseSettings):
DEFAULT_EMAIL: str = "changeme@email.com" DEFAULT_EMAIL: str = "changeme@email.com"
DEFAULT_PASSWORD: str = "MyPassword" DEFAULT_PASSWORD: str = "MyPassword"
LDAP_AUTH_ENABLED: bool = False
LDAP_SERVER_URL: str = None
LDAP_BIND_TEMPLATE: str = None
LDAP_ADMIN_FILTER: str = None
SCHEDULER_DATABASE = f"sqlite:///{app_dirs.DATA_DIR.joinpath('scheduler.db')}" SCHEDULER_DATABASE = f"sqlite:///{app_dirs.DATA_DIR.joinpath('scheduler.db')}"
TOKEN_TIME: int = 2 # Time in Hours TOKEN_TIME: int = 2 # Time in Hours
@@ -167,6 +172,8 @@ class AppSettings(BaseSettings):
RECIPE_DISABLE_COMMENTS: bool = False RECIPE_DISABLE_COMMENTS: bool = False
RECIPE_DISABLE_AMOUNT: bool = False RECIPE_DISABLE_AMOUNT: bool = False
AUTO_BACKUP_ENABLED: bool = False
class Config: class Config:
env_file = BASE_DIR.joinpath(".env") env_file = BASE_DIR.joinpath(".env")
env_file_encoding = "utf-8" env_file_encoding = "utf-8"

View File

@@ -11,6 +11,44 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
ALGORITHM = "HS256" ALGORITHM = "HS256"
def user_from_ldap(session, username: str, password: str) -> UserInDB:
"""Given a username and password, tries to authenticate by BINDing to an
LDAP server
If the BIND succeeds, it will either create a new user of that username on
the server or return an existing one.
Returns False on failure.
"""
import ldap
conn = ldap.initialize(settings.LDAP_SERVER_URL)
user_dn = settings.LDAP_BIND_TEMPLATE.format(username)
try:
conn.simple_bind_s(user_dn, password)
except (ldap.INVALID_CREDENTIALS, ldap.NO_SUCH_OBJECT):
return False
user = db.users.get(session, username, "username", any_case=True)
if not user:
user = db.users.create(
session,
{
"username": username,
"password": "LDAP",
# Fill the next two values with something unique and vaguely
# relevant
"full_name": username,
"email": username,
},
)
if settings.LDAP_ADMIN_FILTER:
user.admin = len(conn.search_s(user_dn, ldap.SCOPE_BASE, settings.LDAP_ADMIN_FILTER, [])) > 0
db.users.update(session, user.id, user)
return user
def create_access_token(data: dict(), expires_delta: timedelta = None) -> str: def create_access_token(data: dict(), expires_delta: timedelta = None) -> str:
to_encode = data.copy() to_encode = data.copy()
expires_delta = expires_delta or timedelta(hours=settings.TOKEN_TIME) expires_delta = expires_delta or timedelta(hours=settings.TOKEN_TIME)
@@ -31,6 +69,8 @@ def authenticate_user(session, email: str, password: str) -> UserInDB:
if not user: if not user:
user = db.users.get(session, email, "username", any_case=True) user = db.users.get(session, email, "username", any_case=True)
if settings.LDAP_AUTH_ENABLED and (not user or user.password == "LDAP"):
return user_from_ldap(session, email, password)
if not user: if not user:
return False return False

View File

@@ -6,7 +6,7 @@ from typing import Union
from jinja2 import Template from jinja2 import Template
from mealie.core import root_logger from mealie.core import root_logger
from mealie.core.config import app_dirs from mealie.core.config import app_dirs, settings
from mealie.db.database import db from mealie.db.database import db
from mealie.db.db_setup import create_session from mealie.db.db_setup import create_session
from mealie.services.events import create_backup_event from mealie.services.events import create_backup_event
@@ -153,6 +153,9 @@ def backup_all(
def auto_backup_job(): def auto_backup_job():
if not settings.AUTO_BACKUP_ENABLED:
return
for backup in app_dirs.BACKUP_DIR.glob("Auto*.zip"): for backup in app_dirs.BACKUP_DIR.glob("Auto*.zip"):
backup.unlink() backup.unlink()

View File

@@ -43,6 +43,7 @@ def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path:
def scrape_image(image_url: str, slug: str) -> Path: def scrape_image(image_url: str, slug: str) -> Path:
logger.info(f"Image URL: {image_url}") logger.info(f"Image URL: {image_url}")
_FIREFOX_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"
if isinstance(image_url, str): # Handles String Types if isinstance(image_url, str): # Handles String Types
pass pass
@@ -54,7 +55,7 @@ def scrape_image(image_url: str, slug: str) -> Path:
all_image_requests = [] all_image_requests = []
for url in image_url: for url in image_url:
try: try:
r = requests.get(url, stream=True, headers={"User-Agent": ""}) r = requests.get(url, stream=True, headers={"User-Agent": _FIREFOX_UA})
except Exception: except Exception:
logger.exception("Image {url} could not be requested") logger.exception("Image {url} could not be requested")
continue continue
@@ -72,7 +73,7 @@ def scrape_image(image_url: str, slug: str) -> Path:
filename = Recipe(slug=slug).image_dir.joinpath(filename) filename = Recipe(slug=slug).image_dir.joinpath(filename)
try: try:
r = requests.get(image_url, stream=True) r = requests.get(image_url, stream=True, headers={"User-Agent": _FIREFOX_UA})
except Exception: except Exception:
logger.exception("Fatal Image Request Exception") logger.exception("Fatal Image Request Exception")
return None return None

1011
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@ python-dotenv = "^0.15.0"
python-slugify = "^4.0.1" python-slugify = "^4.0.1"
requests = "^2.25.1" requests = "^2.25.1"
PyYAML = "^5.3.1" PyYAML = "^5.3.1"
extruct = "^0.12.0" extruct = "^0.13.0"
python-multipart = "^0.0.5" python-multipart = "^0.0.5"
fastapi-camelcase = "^1.0.2" fastapi-camelcase = "^1.0.2"
bcrypt = "^3.2.0" bcrypt = "^3.2.0"
@@ -32,8 +32,9 @@ lxml = "4.6.2"
Pillow = "^8.2.0" Pillow = "^8.2.0"
pathvalidate = "^2.4.1" pathvalidate = "^2.4.1"
apprise = "0.9.3" apprise = "0.9.3"
recipe-scrapers = "^13.2.7" recipe-scrapers = "^13.7.0"
psycopg2-binary = {version = "^2.9.1", optional = true} psycopg2-binary = {version = "^2.9.1", optional = true}
python-ldap = "^3.3.0"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
@@ -67,3 +68,4 @@ skip_empty = true
[tool.poetry.extras] [tool.poetry.extras]
pgsql = ["psycopg2-binary"] pgsql = ["psycopg2-binary"]
ldap = ["python-ldap"]

View File

@@ -34,3 +34,9 @@ RECIPE_SHOW_ASSETS=False
RECIPE_LANDSCAPE_VIEW=False RECIPE_LANDSCAPE_VIEW=False
RECIPE_DISABLE_COMMENTS=False RECIPE_DISABLE_COMMENTS=False
RECIPE_DISABLE_AMOUNT=False RECIPE_DISABLE_AMOUNT=False
# Configuration for authentication via an external LDAP server
LDAP_AUTH_ENABLED=False
LDAP_SERVER_URL=None
LDAP_BIND_TEMPLATE=None
LDAP_ADMIN_FILTER=None

View File

@@ -2,6 +2,8 @@ from pathlib import Path
from mealie.core import security from mealie.core import security
from mealie.routes.deps import validate_file_token from mealie.routes.deps import validate_file_token
from mealie.core.config import settings
from mealie.db.db_setup import create_session
def test_create_file_token(): def test_create_file_token():
@@ -9,3 +11,37 @@ def test_create_file_token():
file_token = security.create_file_token(file_path) file_token = security.create_file_token(file_path)
assert file_path == validate_file_token(file_token) assert file_path == validate_file_token(file_token)
def test_ldap_authentication_mocked(monkeypatch):
import ldap
user = "testinguser"
password = "testingpass"
bind_template = "cn={},dc=example,dc=com"
admin_filter = "(memberOf=cn=admins,dc=example,dc=com)"
monkeypatch.setattr(settings, "LDAP_AUTH_ENABLED", True)
monkeypatch.setattr(settings, "LDAP_SERVER_URL", "") # Not needed due to mocking
monkeypatch.setattr(settings, "LDAP_BIND_TEMPLATE", bind_template)
monkeypatch.setattr(settings, "LDAP_ADMIN_FILTER", admin_filter)
class LdapConnMock:
def simple_bind_s(self, dn, bind_pw):
assert dn == bind_template.format(user)
return bind_pw == password
def search_s(self, dn, scope, filter, attrlist):
assert attrlist == []
assert filter == admin_filter
assert dn == bind_template.format(user)
assert scope == ldap.SCOPE_BASE
return [()]
def ldap_initialize_mock(url):
assert url == ""
return LdapConnMock()
monkeypatch.setattr(ldap, "initialize", ldap_initialize_mock)
result = security.authenticate_user(create_session(), user, password)
assert result is not False
assert result.username == user