diff --git a/docs/configuration.md b/docs/configuration.md index b38f2b985..45a4501ce 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1302,6 +1302,35 @@ started by the container. setting set. This setting is always ignored if the corresponding frontend setting has been set. +## Email sending + +Setting an SMTP server for the backend will allow you to reset your +password. All of these options come from their similarly-named [Django settings](https://docs.djangoproject.com/en/4.2/ref/settings/#email-host) + +#### [`PAPERLESS_EMAIL_HOST=`](#PAPERLESS_EMAIL_HOST) {#PAPERLESS_EMAIL_HOST} + +: Defaults to 'localhost'. + +#### [`PAPERLESS_EMAIL_PORT=`](#PAPERLESS_EMAIL_PORT) {#PAPERLESS_EMAIL_PORT} + +: Defaults to port 25. + +#### [`PAPERLESS_EMAIL_HOST_USER=`](#PAPERLESS_EMAIL_HOST_USER) {#PAPERLESS_EMAIL_HOST_USER} + +: Defaults to ''. + +#### [`PAPERLESS_EMAIL_HOST_PASSWORD=`](#PAPERLESS_EMAIL_HOST_PASSWORD) {#PAPERLESS_EMAIL_HOST_PASSWORD} + +: Defaults to ''. + +#### [`PAPERLESS_EMAIL_USE_TLS=`](#PAPERLESS_EMAIL_USE_TLS) {#PAPERLESS_EMAIL_USE_TLS} + +: Defaults to false. + +#### [`PAPERLESS_EMAIL_USE_SSL=`](#PAPERLESS_EMAIL_USE_SSL) {#PAPERLESS_EMAIL_USE_SSL} + +: Defaults to false. + ## SSO with OpenIDConnect To configure OpenIDConnect you need a provider. The redirect url is `${PAPERLESS_URL}/accounts/complete/oidc/`. diff --git a/docs/usage.md b/docs/usage.md index 340a1f2c3..aad4976f8 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -261,6 +261,11 @@ These can be found under Settings > Users & Groups, assuming the user has access as a member of a group those permissions will be inherited and this is reflected in the UI. Explicit permissions can be granted to limit access to certain parts of the UI (and corresponding API endpoints). +### Password reset + +In order to enable the password reset feature you will need to setup an SMTP backend, see +[`PAPERLESS_EMAIL_HOST`](/configuration#PAPERLESS_EMAIL_HOST) + ## Consumption templates Consumption templates were introduced in v2.0 and allow for finer control over what metadata (tags, doc diff --git a/src/documents/context_processors.py b/src/documents/context_processors.py new file mode 100644 index 000000000..90c856aeb --- /dev/null +++ b/src/documents/context_processors.py @@ -0,0 +1,8 @@ +from django.conf import settings as django_settings + + +def settings(request): + return { + "EMAIL_ENABLED": django_settings.EMAIL_HOST != "localhost" + or django_settings.EMAIL_HOST_USER != "", + } diff --git a/src/documents/static/signin.css b/src/documents/static/signin.css index 7115adae2..02f460c7c 100644 --- a/src/documents/static/signin.css +++ b/src/documents/static/signin.css @@ -2,32 +2,43 @@ body { --bs-body-bg: #f5f5f5; --bs-link-color-rgb: 23, 84, 31; /* #17541f */ --bs-link-hover-color-rgb: 15, 56, 20; + --pngx-primary: #17541f; + --pngx-primary-hover: #0f3614; + --pngx-primary-active: #0c2c10; } .form-control { --bs-body-bg: #fff; } -.btn { - --bs-btn-bg: #17541f; - --bs-btn-border-color: #17541f; - --bs-btn-hover-bg: #0f3614; +.btn.btn-primary { + --bs-btn-bg: var(--pngx-primary); + --bs-btn-border-color: var(--pngx-primary); + --bs-btn-hover-bg: var(--pngx-primary-hover); --bs-btn-hover-border-color: #0c2c10; - --bs-btn-active-bg: #0c2c10; + --bs-btn-active-bg: var(--pngx-primary-active); --bs-btn-active-border-color: #09220d; } +.btn-link { + --bs-btn-color: var(--pngx-primary); + --bs-btn-hover-color: var(--pngx-primary-hover); + --bs-btn-active-color: var(--pngx-primary-active); +} + .form-signin { max-width: 330px; } -#inputUsername { +#inputUsername, +#inputPassword1 { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } -#inputPassword { +#inputPassword, +#inputPassword2 { border-top-left-radius: 0; border-top-right-radius: 0; } diff --git a/src/documents/templates/registration/login.html b/src/documents/templates/registration/login.html index b7e7e41e0..73e80fcf8 100644 --- a/src/documents/templates/registration/login.html +++ b/src/documents/templates/registration/login.html @@ -51,26 +51,30 @@ - {% endif %} - {% if not settings.SOCIAL_AUTH_DISABLE_NORMAL_AUTH %} - {% translate "Username" as i18n_username %} - {% translate "Password" as i18n_password %} -
- - -
-
- - -
-
- -
- {% endif %} - {% if settings.SOCIAL_AUTH_OIDC_ENABLE %} - {{ settings.SOCIAL_AUTH_OIDC_NAME }} - {% endif %} - + {% endif %} + {% if not SOCIAL_AUTH_DISABLE_NORMAL_AUTH %} + {% translate "Username" as i18n_username %} + {% translate "Password" as i18n_password %} +
+ + +
+
+ + +
+
+ +
+ {% if EMAIL_ENABLED %} + + {% endif %} + {% endif %} + {% if SOCIAL_AUTH_OIDC_ENABLE %} + {{ SOCIAL_AUTH_OIDC_NAME }} + {% endif %} diff --git a/src/documents/templates/registration/password_reset_complete.html b/src/documents/templates/registration/password_reset_complete.html new file mode 100644 index 000000000..d9b0a3b72 --- /dev/null +++ b/src/documents/templates/registration/password_reset_complete.html @@ -0,0 +1,45 @@ + + +{% load static %} +{% load i18n %} + + + + + + + + + + {% translate "Paperless-ngx reset password complete" %} + + + + + + +
+ +

{% translate "Password reset complete." %}

+ {% url 'login' as login_url %} +

{% blocktranslate %}Your new password has been set. You can now log in{% endblocktranslate %}.

+
+ + diff --git a/src/documents/templates/registration/password_reset_confirm.html b/src/documents/templates/registration/password_reset_confirm.html new file mode 100644 index 000000000..8f24212a7 --- /dev/null +++ b/src/documents/templates/registration/password_reset_confirm.html @@ -0,0 +1,70 @@ + + +{% load static %} +{% load i18n %} + + + + + + + + + + {% translate "Paperless-ngx reset password confirmation" %} + + + + + + + + + diff --git a/src/documents/templates/registration/password_reset_done.html b/src/documents/templates/registration/password_reset_done.html new file mode 100644 index 000000000..b798ee324 --- /dev/null +++ b/src/documents/templates/registration/password_reset_done.html @@ -0,0 +1,44 @@ + + +{% load static %} +{% load i18n %} + + + + + + + + + + {% translate "Paperless-ngx reset password sent" %} + + + + + + +
+ +

{% translate "Check your inbox." %}

+

{% translate "We've emailed you instructions for setting your password. You should receive the email shortly!" %}

+
+ + diff --git a/src/documents/templates/registration/password_reset_form.html b/src/documents/templates/registration/password_reset_form.html new file mode 100644 index 000000000..9bdc22943 --- /dev/null +++ b/src/documents/templates/registration/password_reset_form.html @@ -0,0 +1,58 @@ + + +{% load static %} +{% load i18n %} + + + + + + + + + + {% translate "Paperless-ngx reset password request" %} + + + + + + + + + diff --git a/src/locale/en_US/LC_MESSAGES/django.po b/src/locale/en_US/LC_MESSAGES/django.po index a4662aaf7..c1358185a 100644 --- a/src/locale/en_US/LC_MESSAGES/django.po +++ b/src/locale/en_US/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-28 23:21-0700\n" +"POT-Creation-Date: 2023-09-28 10:56-0700\n" "PO-Revision-Date: 2022-02-17 04:17\n" "Last-Translator: \n" "Language-Team: English\n" @@ -710,147 +710,222 @@ msgstr "" msgid "Share link has expired." msgstr "" -#: documents/templates/registration/login.html:56 +#: documents/templates/registration/login.html:55 msgid "Username" msgstr "" -#: documents/templates/registration/login.html:57 +#: documents/templates/registration/login.html:56 msgid "Password" msgstr "" -#: documents/templates/registration/login.html:67 +#: documents/templates/registration/login.html:66 msgid "Sign in" msgstr "" +#: documents/templates/registration/login.html:70 +msgid "Forgot your password?" +msgstr "" + +#: documents/templates/registration/password_reset_complete.html:14 +msgid "Paperless-ngx reset password complete" +msgstr "" + +#: documents/templates/registration/password_reset_complete.html:40 +msgid "Password reset complete." +msgstr "" + +#: documents/templates/registration/password_reset_complete.html:42 +#, python-format +msgid "" +"Your new password has been set. You can now log " +"in" +msgstr "" + +#: documents/templates/registration/password_reset_confirm.html:14 +msgid "Paperless-ngx reset password confirmation" +msgstr "" + +#: documents/templates/registration/password_reset_confirm.html:42 +msgid "Set a new password." +msgstr "" + +#: documents/templates/registration/password_reset_confirm.html:46 +msgid "Passwords did not match or too weak. Try again." +msgstr "" + +#: documents/templates/registration/password_reset_confirm.html:49 +msgid "New Password" +msgstr "" + +#: documents/templates/registration/password_reset_confirm.html:50 +msgid "Confirm Password" +msgstr "" + +#: documents/templates/registration/password_reset_confirm.html:61 +msgid "Change my password" +msgstr "" + +#: documents/templates/registration/password_reset_confirm.html:65 +msgid "request a new password reset" +msgstr "" + +#: documents/templates/registration/password_reset_done.html:14 +msgid "Paperless-ngx reset password sent" +msgstr "" + +#: documents/templates/registration/password_reset_done.html:40 +msgid "Check your inbox." +msgstr "" + +#: documents/templates/registration/password_reset_done.html:41 +msgid "" +"We've emailed you instructions for setting your password. You should receive " +"the email shortly!" +msgstr "" + +#: documents/templates/registration/password_reset_form.html:14 +msgid "Paperless-ngx reset password request" +msgstr "" + +#: documents/templates/registration/password_reset_form.html:41 +msgid "" +"Enter your email address below, and we'll email instructions for setting a " +"new one." +msgstr "" + +#: documents/templates/registration/password_reset_form.html:44 +msgid "An error occurred. Please try again." +msgstr "" + +#: documents/templates/registration/password_reset_form.html:47 +msgid "Email" +msgstr "" + +#: documents/templates/registration/password_reset_form.html:54 +msgid "Send me instructions!" +msgstr "" + #: paperless/apps.py:10 msgid "Paperless" msgstr "" -#: paperless/models.py:17 -msgid "SSO group" -msgstr "" - -#: paperless/models.py:18 -msgid "SSO groups" -msgstr "" - -#: paperless/settings.py:605 +#: paperless/settings.py:574 msgid "English (US)" msgstr "" -#: paperless/settings.py:606 +#: paperless/settings.py:575 msgid "Arabic" msgstr "" -#: paperless/settings.py:607 +#: paperless/settings.py:576 msgid "Afrikaans" msgstr "" -#: paperless/settings.py:608 +#: paperless/settings.py:577 msgid "Belarusian" msgstr "" -#: paperless/settings.py:609 +#: paperless/settings.py:578 msgid "Catalan" msgstr "" -#: paperless/settings.py:610 +#: paperless/settings.py:579 msgid "Czech" msgstr "" -#: paperless/settings.py:611 +#: paperless/settings.py:580 msgid "Danish" msgstr "" -#: paperless/settings.py:612 +#: paperless/settings.py:581 msgid "German" msgstr "" -#: paperless/settings.py:613 +#: paperless/settings.py:582 msgid "Greek" msgstr "" -#: paperless/settings.py:614 +#: paperless/settings.py:583 msgid "English (GB)" msgstr "" -#: paperless/settings.py:615 +#: paperless/settings.py:584 msgid "Spanish" msgstr "" -#: paperless/settings.py:616 +#: paperless/settings.py:585 msgid "Finnish" msgstr "" -#: paperless/settings.py:617 +#: paperless/settings.py:586 msgid "French" msgstr "" -#: paperless/settings.py:618 +#: paperless/settings.py:587 msgid "Italian" msgstr "" -#: paperless/settings.py:619 +#: paperless/settings.py:588 msgid "Luxembourgish" msgstr "" -#: paperless/settings.py:620 +#: paperless/settings.py:589 msgid "Norwegian" msgstr "" -#: paperless/settings.py:621 +#: paperless/settings.py:590 msgid "Dutch" msgstr "" -#: paperless/settings.py:622 +#: paperless/settings.py:591 msgid "Polish" msgstr "" -#: paperless/settings.py:623 +#: paperless/settings.py:592 msgid "Portuguese (Brazil)" msgstr "" -#: paperless/settings.py:624 +#: paperless/settings.py:593 msgid "Portuguese" msgstr "" -#: paperless/settings.py:625 +#: paperless/settings.py:594 msgid "Romanian" msgstr "" -#: paperless/settings.py:626 +#: paperless/settings.py:595 msgid "Russian" msgstr "" -#: paperless/settings.py:627 +#: paperless/settings.py:596 msgid "Slovak" msgstr "" -#: paperless/settings.py:628 +#: paperless/settings.py:597 msgid "Slovenian" msgstr "" -#: paperless/settings.py:629 +#: paperless/settings.py:598 msgid "Serbian" msgstr "" -#: paperless/settings.py:630 +#: paperless/settings.py:599 msgid "Swedish" msgstr "" -#: paperless/settings.py:631 +#: paperless/settings.py:600 msgid "Turkish" msgstr "" -#: paperless/settings.py:632 +#: paperless/settings.py:601 msgid "Ukrainian" msgstr "" -#: paperless/settings.py:633 +#: paperless/settings.py:602 msgid "Chinese Simplified" msgstr "" -#: paperless/urls.py:187 +#: paperless/urls.py:184 msgid "Paperless-ngx administration" msgstr "" diff --git a/src/paperless/settings.py b/src/paperless/settings.py index dfe602e43..1f5daef50 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -358,7 +358,7 @@ TEMPLATES = [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", - "paperless.context_processors.settings", + "documents.context_processors.settings", "social_django.context_processors.backends", ], }, @@ -1034,3 +1034,15 @@ def _get_nltk_language_setting(ocr_lang: str) -> Optional[str]: NLTK_ENABLED: Final[bool] = __get_boolean("PAPERLESS_ENABLE_NLTK", "yes") NLTK_LANGUAGE: Optional[str] = _get_nltk_language_setting(OCR_LANGUAGE) + +############################################################################### +# Email (SMTP) Backend # +############################################################################### + +EMAIL_HOST: Final[str] = os.getenv("PAPERLESS_EMAIL_HOST", "localhost") +EMAIL_PORT: Final[int] = int(os.getenv("PAPERLESS_EMAIL_PORT", 25)) +EMAIL_HOST_USER: Final[str] = os.getenv("PAPERLESS_EMAIL_HOST_USER", "") +EMAIL_HOST_PASSWORD: Final[str] = os.getenv("PAPERLESS_EMAIL_HOST_PASSWORD", "") +EMAIL_USE_TLS: Final[bool] = __get_boolean("PAPERLESS_EMAIL_USE_TLS") +EMAIL_USE_SSL: Final[bool] = __get_boolean("PAPERLESS_EMAIL_USE_SSL") +EMAIL_SUBJECT_PREFIX: Final[str] = "[Paperless-ngx] "