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 PasswordUsername and/or password incorrectCannot 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