diff --git a/backend/api/v1/v1_users/serializers.py b/backend/api/v1/v1_users/serializers.py index c845ab9a2..cc5281d55 100644 --- a/backend/api/v1/v1_users/serializers.py +++ b/backend/api/v1/v1_users/serializers.py @@ -21,6 +21,17 @@ class LoginSerializer(serializers.Serializer): password = CustomCharField() +class ForgotPasswordSerializer(serializers.Serializer): + email = CustomEmailField() + + def validate_email(self, email): + try: + user = SystemUser.objects.get(email=email) + except SystemUser.DoesNotExist: + raise ValidationError('Invalid email, user not found') + return user + + class VerifyInviteSerializer(serializers.Serializer): invite = CustomCharField() diff --git a/backend/api/v1/v1_users/tests/tests.py b/backend/api/v1/v1_users/tests/tests.py index 8acc3049e..91cd9f8cc 100644 --- a/backend/api/v1/v1_users/tests/tests.py +++ b/backend/api/v1/v1_users/tests/tests.py @@ -82,3 +82,16 @@ def test_login(self): user, content_type='application/json') self.assertEqual(user.status_code, 400) + + # test forgor password to valid email + user = {"email": "admin@rtmis.com"} + user = self.client.post('/api/v1/user/forgot-password', + user, + content_type='application/json') + self.assertEqual(user.status_code, 200) + # test forgor password to invalid email + user = {"email": "notuser@domain.com"} + user = self.client.post('/api/v1/user/forgot-password', + user, + content_type='application/json') + self.assertEqual(user.status_code, 400) diff --git a/backend/api/v1/v1_users/tests/tests_user_invitation.py b/backend/api/v1/v1_users/tests/tests_user_invitation.py index 0ae303576..f821bdd38 100644 --- a/backend/api/v1/v1_users/tests/tests_user_invitation.py +++ b/backend/api/v1/v1_users/tests/tests_user_invitation.py @@ -245,6 +245,11 @@ def test_get_email_template(self): '/api/v1/email_template?type=user_approval', content_type='application/json') self.assertEqual(response.status_code, 200) + # test get user_forgot_password template + response = self.client.get( + '/api/v1/email_template?type=user_forgot_password', + content_type='application/json') + self.assertEqual(response.status_code, 200) # test get data_approval template response = self.client.get( '/api/v1/email_template?type=data_approval', diff --git a/backend/api/v1/v1_users/urls.py b/backend/api/v1/v1_users/urls.py index e129ece01..d744befc2 100644 --- a/backend/api/v1/v1_users/urls.py +++ b/backend/api/v1/v1_users/urls.py @@ -2,7 +2,8 @@ from api.v1.v1_users.views import login, verify_invite, \ set_user_password, list_administration, add_user, list_users, \ - get_profile, get_user_roles, list_levels, UserEditDeleteView + get_profile, get_user_roles, list_levels, UserEditDeleteView, \ + forgot_password urlpatterns = [ re_path(r'^(?P(v1))/levels', list_levels), @@ -14,10 +15,10 @@ re_path(r'^(?P(v1))/users', list_users), re_path(r'^(?P(v1))/user/(?P[0-9]+)', UserEditDeleteView.as_view()), + re_path(r'^(?P(v1))/user/forgot-password', forgot_password), re_path(r'^(?P(v1))/user/set-password', set_user_password), re_path(r'^(?P(v1))/user/roles', get_user_roles), re_path(r'^(?P(v1))/user', add_user), re_path(r'^(?P(v1))/invitation/(?P.*)$', verify_invite), - ] diff --git a/backend/api/v1/v1_users/views.py b/backend/api/v1/v1_users/views.py index 991d3c11b..07d03e8ee 100644 --- a/backend/api/v1/v1_users/views.py +++ b/backend/api/v1/v1_users/views.py @@ -1,4 +1,5 @@ # Create your views here. +import os import datetime from math import ceil from pathlib import Path @@ -30,13 +31,16 @@ from api.v1.v1_users.serializers import LoginSerializer, UserSerializer, \ VerifyInviteSerializer, SetUserPasswordSerializer, \ ListAdministrationSerializer, AddEditUserSerializer, ListUserSerializer, \ - ListUserRequestSerializer, ListLevelSerializer, UserDetailSerializer + ListUserRequestSerializer, ListLevelSerializer, UserDetailSerializer, \ + ForgotPasswordSerializer from rtmis.settings import REST_FRAMEWORK from utils.custom_permissions import IsSuperAdmin, IsAdmin from utils.custom_serializer_fields import validate_serializers_message from utils.email_helper import send_email from utils.email_helper import ListEmailTypeRequestSerializer, EmailTypes +webdomain = os.environ["WEBDOMAIN"] + @extend_schema(description='Use to check System health', tags=['Dev']) @api_view(['GET']) @@ -61,8 +65,7 @@ def get_config_file(request, version): required=False, enum=EmailTypes.FieldStr.keys(), type=OpenApiTypes.STR, - location=OpenApiParameter.QUERY), - ], + location=OpenApiParameter.QUERY)], summary='To show email template by type') @api_view(['GET']) def email_template(request, version): @@ -181,8 +184,7 @@ def set_user_password(request, version): OpenApiParameter(name='filter', required=False, type=OpenApiTypes.NUMBER, - location=OpenApiParameter.QUERY), - ], + location=OpenApiParameter.QUERY)], summary='Get list of administration') @api_view(['GET']) def list_administration(request, version, administration_id): @@ -263,8 +265,7 @@ def add_user(request, version): required=False, default=True, type=OpenApiTypes.BOOL, - location=OpenApiParameter.QUERY), - ]) + location=OpenApiParameter.QUERY)]) @api_view(['GET']) @permission_classes([IsAuthenticated, IsSuperAdmin | IsAdmin]) def list_users(request, version): @@ -388,7 +389,7 @@ def delete(self, request, user_id, version): }, tags=['User'], description='Role Choice are SuperAdmin:1,Admin:2,Approver:3,' - 'User:4', + 'User:4,ReadOnly:5', summary='To update user') def put(self, request, user_id, version): instance = get_object_or_404(SystemUser, pk=user_id) @@ -402,3 +403,28 @@ def put(self, request, user_id, version): serializer.save() return Response({'message': 'User updated successfully'}, status=status.HTTP_200_OK) + + +@extend_schema(request=ForgotPasswordSerializer, + responses={ + (200, 'application/json'): + inline_serializer( + "Response", + fields={"message": serializers.CharField()}) + }, + tags=['User'], + summary='To send reset password instructions') +@api_view(['POST']) +def forgot_password(request, version): + serializer = ForgotPasswordSerializer(data=request.data) + if not serializer.is_valid(): + return Response( + {'message': validate_serializers_message(serializer.errors)}, + status=status.HTTP_400_BAD_REQUEST) + user: SystemUser = serializer.validated_data.get('email') + url = f"{webdomain}/login/{signing.dumps(user.pk)}" + data = {'button_url': url, 'send_to': [user.email]} + send_email(type=EmailTypes.user_forgot_password, context=data) + return Response( + {'message': 'Reset password instructions sent to your email'}, + status=status.HTTP_200_OK) diff --git a/backend/rtmis/templates/email/main.html b/backend/rtmis/templates/email/main.html index 2cb98f44b..7216ea5fe 100644 --- a/backend/rtmis/templates/email/main.html +++ b/backend/rtmis/templates/email/main.html @@ -188,6 +188,18 @@ color: #fff; } + .btn.btn-link { + border-radius: 5px; + background: transparent; + color: #000000; + border: 2px solid #000000; + min-width: 50%; + } + + .btn.block { + width: 100%; + } + h1, h2, h3, @@ -458,7 +470,7 @@ -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; background: #ffffff; - padding: 1em; + padding: 0 1em; mso-table-lspace: 0pt !important; mso-table-rspace: 0pt !important; " @@ -594,6 +606,30 @@ {% endif %} + {% if button and button_url and button_text %} + + + + + + + + {% endif %} {% if explore_button %}