Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions admin/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.urls import reverse
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail
from django.shortcuts import redirect
from django.core.paginator import Paginator
from django.core.exceptions import ValidationError
Expand Down Expand Up @@ -47,7 +46,8 @@
AddSystemTagForm
)
from admin.base.views import GuidView
from website.settings import DOMAIN, OSF_SUPPORT_EMAIL
from api.users.services import send_password_reset_email
from website.settings import DOMAIN
from django.urls import reverse_lazy


Expand Down Expand Up @@ -523,17 +523,9 @@ class ResetPasswordView(UserMixin, View):
def post(self, request, *args, **kwargs):
email = self.request.POST['emails']
user = get_user(email)
url = furl(DOMAIN)

user.verification_key_v2 = generate_verification_key(verification_type='password_admin')
user.save()
url.add(path=f'resetpassword/{user._id}/{user.verification_key_v2["token"]}')
send_mail(
subject='Reset OSF Password',
message=f'Follow this link to reset your password: {url.url}\n Note: this link will expire in 12 hours',
from_email=OSF_SUPPORT_EMAIL,
recipient_list=[email]
)
send_password_reset_email(user, email, institutional=False, verification_type='password_admin')

update_admin_log(
user_id=self.request.user.id,
object_id=user.pk,
Expand Down
27 changes: 27 additions & 0 deletions api/users/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from furl import furl
from django.utils import timezone

from framework.auth.core import generate_verification_key
from website import settings, mails


def send_password_reset_email(user, email, verification_type='password', institutional=False, **mail_kwargs):
"""Generate a password reset token, save it to the user and send the password reset email.
"""
# new verification key (v2)
user.verification_key_v2 = generate_verification_key(verification_type=verification_type)
user.email_last_sent = timezone.now()
user.save()

reset_link = furl(settings.DOMAIN).add(path=f'resetpassword/{user._id}/{user.verification_key_v2["token"]}').url
mail_template = mails.FORGOT_PASSWORD if not institutional else mails.FORGOT_PASSWORD_INSTITUTION

mails.send_mail(
to_addr=email,
mail=mail_template,
reset_link=reset_link,
can_change_preferences=False,
**mail_kwargs,
)

return reset_link
41 changes: 17 additions & 24 deletions api/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from api.registrations.serializers import RegistrationSerializer
from api.resources import annotations as resource_annotations

from api.users.services import send_password_reset_email
from api.users.permissions import (
CurrentUser, ReadOnlyOrCurrentUser,
ReadOnlyOrCurrentUserRelationship,
Expand Down Expand Up @@ -864,38 +865,30 @@ class ResetPassword(JSONAPIBaseView, generics.ListCreateAPIView):
throttle_classes = (NonCookieAuthThrottle, BurstRateThrottle, RootAnonThrottle, SendEmailThrottle)

def get(self, request, *args, **kwargs):
institutional = bool(request.query_params.get('institutional', None))
email = request.query_params.get('email', None)
if not email:
raise ValidationError('Request must include email in query params.')

institutional = bool(request.query_params.get('institutional', None))
mail_template = mails.FORGOT_PASSWORD if not institutional else mails.FORGOT_PASSWORD_INSTITUTION

status_message = language.RESET_PASSWORD_SUCCESS_STATUS_MESSAGE.format(email=email)
kind = 'success'
# check if the user exists
user_obj = get_user(email=email)

if user_obj:
if user_obj and user_obj.is_active:
# rate limit forgot_password_post
if not throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE):
status_message = 'You have recently requested to change your password. Please wait a few minutes ' \
'before trying again.'
kind = 'error'
return Response({'message': status_message, 'kind': kind}, status=status.HTTP_429_TOO_MANY_REQUESTS)
elif user_obj.is_active:
# new random verification key (v2)
user_obj.verification_key_v2 = generate_verification_key(verification_type='password')
user_obj.email_last_sent = timezone.now()
user_obj.save()
reset_link = f'{settings.RESET_PASSWORD_URL}{user_obj._id}/{user_obj.verification_key_v2['token']}/'
mails.send_mail(
to_addr=email,
mail=mail_template,
reset_link=reset_link,
can_change_preferences=False,
)
return Response(status=status.HTTP_200_OK, data={'message': status_message, 'kind': kind, 'institutional': institutional})
status_message = 'You have recently requested to change your password. ' \
'Please wait a few minutes before trying again.'
return Response({'message': status_message, 'kind': 'error'}, status=status.HTTP_429_TOO_MANY_REQUESTS)

send_password_reset_email(user_obj, email, institutional=institutional)

return Response(
status=status.HTTP_200_OK,
data={
'message': language.RESET_PASSWORD_SUCCESS_STATUS_MESSAGE.format(email=email),
'kind': 'success',
'institutional': institutional,
},
)

def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
Expand Down
2 changes: 1 addition & 1 deletion api_tests/users/views/test_user_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def test_get(self, app, url, user_one):
mock_send_mail.assert_called_with(
to_addr=user_one.username,
mail=mails.FORGOT_PASSWORD,
reset_link=f'{settings.RESET_PASSWORD_URL}{user_one._id}/{user_one.verification_key_v2['token']}/',
reset_link=f'{settings.DOMAIN}resetpassword/{user_one._id}/{user_one.verification_key_v2['token']}',
can_change_preferences=False,
)

Expand Down
Loading