diff --git a/docs/installation.md b/docs/installation.md index b27f4e2d..df14a752 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -40,6 +40,16 @@ On MAC: https://docs.docker.com/docker-for-mac/#memory On Windows: https://docs.docker.com/docker-for-windows/#resources +### (Optional) SMTP Setup + +For password resets and other emailing services email environment variables are required to be set up. + +Personal email accounts can be set up by users to do this, or you can contact someone in CogStack for a cogstack no email credentials. + +The environment variables required are listed in [Environment Variables.](#(optional)-environment-variables) + +Environment Variables are located in envs/env or envs/env-prod, when those are set webapp/frontend/.env must change "VITE_APP_EMAIL" to 1. + ### (Optional) Environment Variables Environment variables are used to configure the app: @@ -48,6 +58,10 @@ Environment variables are used to configure the app: |MEDCAT_CONFIG_FILE|MedCAT config file as described [here](https://github.com/CogStack/MedCAT/blob/master/medcat/config.py)| |BEHIND_RP| If you're running MedCATtrainer, use 1, otherwise this defaults to 0 i.e. False| |MCTRAINER_PORT|The port to run the trainer app on| +|EMAIL_USER|Email address which will be used to send users emails regarding password resets| +|EMAIL_PASS|The password or authentication key which will be used with the email address| +|EMAIL_HOST|The hostname of the SMTP server which will be used to send email (default: mail.cogstack.org)| +|EMAIL_PORT|The port that the SMTP server is listening to, common numbers are 25, 465, 587 (default: 465)| Set these and re-run the docker-compose file. diff --git a/envs/env b/envs/env index 1b3aa62f..404f8884 100644 --- a/envs/env +++ b/envs/env @@ -33,3 +33,9 @@ RESUBMIT_ALL_ON_STARTUP=1 # Front end env vars LOAD_NUM_DOC_PAGES=10 + +# SMTP email settings - when settings are configured go to webapp/frontend/.env and set VITE_APP_EMAIL to 1 +EMAIL_USER=example@cogstack.org +EMAIL_PASS="to be changed" +EMAIL_HOST=mail.cogstack.org +EMAIL_PORT=465 \ No newline at end of file diff --git a/envs/env-prod b/envs/env-prod index 0026437c..19d6460b 100644 --- a/envs/env-prod +++ b/envs/env-prod @@ -35,3 +35,8 @@ RESUBMIT_ALL_ON_STARTUP=1 # Front end env vars LOAD_NUM_DOC_PAGES=10 +# SMTP EMAIL SETTINGS +EMAIL_USER=example@cogstack.org +EMAIL_PASS="to be changed" +EMAIL_HOST=mail.cogstack.org +EMAIL_PORT=465 \ No newline at end of file diff --git a/webapp/api/api/views.py b/webapp/api/api/views.py index bd08fee3..3dc71083 100644 --- a/webapp/api/api/views.py +++ b/webapp/api/api/views.py @@ -9,11 +9,13 @@ from django.shortcuts import render from django.utils import timezone from django_filters import rest_framework as drf +from django.contrib.auth.views import PasswordResetView from medcat.cdb import CDB from medcat.utils.helpers import tkns_from_doc from rest_framework import viewsets from rest_framework.decorators import api_view from rest_framework.response import Response +from smtplib import SMTPException from core.settings import MEDIA_ROOT from .admin import download_projects_with_text, download_projects_without_text, \ @@ -213,6 +215,17 @@ class OPCSCodeViewSet(viewsets.ModelViewSet): filterset_class = OPCSCodeFilter filterset_fields = ['code', 'id'] +class ResetPasswordView(PasswordResetView): + email_template_name = 'password_reset_email.html' + subject_template_name = 'password_reset_subject.txt' + def post(self, request, *args, **kwargs): + try: + return super().post(request, *args, **kwargs) + except SMTPException: + return HttpResponseServerError('''SMTP settings are not configured correctly.
+ Please visit https://medcattrainer.readthedocs.io for more information to resolve this.
+ You can also ask a question at: https://discourse.cogstack.org/c/medcat/5''') + @api_view(http_method_names=['GET']) def get_anno_tool_conf(_): diff --git a/webapp/api/core/settings.py b/webapp/api/core/settings.py index 00b938a8..ce3de7a1 100644 --- a/webapp/api/core/settings.py +++ b/webapp/api/core/settings.py @@ -75,6 +75,7 @@ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(BASE_DIR, "..", "frontend", "dist"), + os.path.join(BASE_DIR, "..", "templates", "registration") ], 'APP_DIRS': True, 'OPTIONS': { @@ -192,3 +193,13 @@ SOLR_PORT = os.environ.get('CONCEPT_SEARCH_SERVICE_PORT', '8983') SILENCED_SYSTEM_CHECKS = ['admin.E130'] + +# For testing only +# EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = os.environ.get('EMAIL_HOST') +EMAIL_PORT = os.environ.get('EMAIL_PORT') +EMAIL_HOST_USER = os.environ.get('EMAIL_USER') +EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_PASS') +EMAIL_USE_TLS = True \ No newline at end of file diff --git a/webapp/api/core/urls.py b/webapp/api/core/urls.py index a458e408..3746f48e 100644 --- a/webapp/api/core/urls.py +++ b/webapp/api/core/urls.py @@ -14,12 +14,12 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin +from django.contrib.auth import views as pw_views from django.urls import path, include, re_path from rest_framework.authtoken import views as auth_views from rest_framework import routers import api.views - router = routers.DefaultRouter() router.register(r'users', api.views.UserViewSet) router.register(r'entities', api.views.EntityViewSet) @@ -70,5 +70,9 @@ path('api/concept-path/', api.views.cdb_concept_path), path('api/generate-concept-filter-json/', api.views.generate_concept_filter_flat_json), path('api/generate-concept-filter/', api.views.generate_concept_filter), + path('reset_password/', api.views.ResetPasswordView.as_view(), name ='reset_password'), + path('reset_password_sent/', pw_views.PasswordResetDoneView.as_view(), name ='password_reset_done'), + path('reset//', pw_views.PasswordResetConfirmView.as_view(), name ='password_reset_confirm'), + path('reset_password_complete/', pw_views.PasswordResetCompleteView.as_view(), name ='password_reset_complete'), re_path('^.*$', api.views.index, name='index'), # Match everything else to home ] diff --git a/webapp/frontend/.env b/webapp/frontend/.env new file mode 100644 index 00000000..6ed2c18c --- /dev/null +++ b/webapp/frontend/.env @@ -0,0 +1 @@ +VITE_APP_EMAIL=0 # Set to 1 when email settings are correctly configured \ No newline at end of file diff --git a/webapp/frontend/src/components/common/Login.vue b/webapp/frontend/src/components/common/Login.vue index 2b048f2a..c9486bec 100644 --- a/webapp/frontend/src/components/common/Login.vue +++ b/webapp/frontend/src/components/common/Login.vue @@ -7,6 +7,7 @@ + Forgotten Password Username and/or password incorrect Cannot determine admin status of username @@ -40,7 +41,8 @@ export default { uname: '', password: '', failed: false, - failedAdminStatusCheck: false + failedAdminStatusCheck: false, + reset_pw: import.meta.env.VITE_APP_EMAIL === '1', } }, methods: { @@ -102,4 +104,6 @@ export default { width: 400px; } } + + diff --git a/webapp/frontend/vite.config.js b/webapp/frontend/vite.config.js index 24385af2..e4aef709 100644 --- a/webapp/frontend/vite.config.js +++ b/webapp/frontend/vite.config.js @@ -1,9 +1,14 @@ import { createVuePlugin as vue } from "vite-plugin-vue2"; -import { defineConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' // https://vitejs.dev/config/ const path = require("path"); -export default defineConfig({ +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), '') + return { + define: { + __APP_ENV__: JSON.stringify(env.APP_ENV), + }, plugins: [vue()], resolve: { alias: { @@ -39,4 +44,5 @@ export default defineConfig({ } } } +} }) diff --git a/webapp/templates/registration/password_reset_email.html b/webapp/templates/registration/password_reset_email.html new file mode 100644 index 00000000..dda38a8a --- /dev/null +++ b/webapp/templates/registration/password_reset_email.html @@ -0,0 +1,17 @@ +{% autoescape off %} +You’re receiving this email because you requested a password reset for your user account +with MedCATTrainer at {{ site_name }}. + +Please go to the following page and choose a new password: + +{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} + +Your username, in case you've forgotten: {{ user.username }}. + +If clicking the link above doesn't work, please copy and paste the URL in a new browser +window instead. + +Sincerely, + +The Cogstack Team +{% endautoescape %} \ No newline at end of file diff --git a/webapp/templates/registration/password_reset_subject.txt b/webapp/templates/registration/password_reset_subject.txt new file mode 100644 index 00000000..7147d40b --- /dev/null +++ b/webapp/templates/registration/password_reset_subject.txt @@ -0,0 +1 @@ +MedCATtrainer: Password Reset \ No newline at end of file