Django: неверный токен для сброса пароля после создания учетной записи
в приложении пользователь с ролью администратора через конечную точку DRF может создавать новые учетные записи пользователей.
Необходимо автоматически отправлять ссылку на сброс пароля на электронные адреса вновь созданных пользователей.
Я определил url:
path('v1/account/register/',
AccountCreationView.as_view(),
name='custom_account_creation'),
представление, которое в первую очередь проверяет, что роль пользователя позволяет создавать новых пользователей:
class AccountCreationView(RegisterView):
"""
Accounts Creation
"""
serializer_class = RegisterWithMailSendSerializer
def get_response_data(self, user):
# print('get_response_data', user)
self.user = user
def create(self, request, *args, **kwargs):
role_section = 'UsersAdmins'
#
rights_check = role_rights_check(
request.user,
role_section,
"R",
)
if rights_check[0] == False:
return Response({"error": rights_check[1]},
status=status.HTTP_401_UNAUTHORIZED)
response = super().create(request, *args, **kwargs)
и пользовательский сериализатор для этого представления, где после проверки данных, сохранить, а затем создать ссылку сброса пароля и отправить по электронной почте только что созданному пользователю:
class RegisterWithMailSendSerializer(RegisterSerializer):
def save(self, request, **kwargs):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
user = adapter.save_user(request, user, self, commit=False)
if "password1" in self.cleaned_data:
try:
adapter.clean_password(self.cleaned_data['password1'],
user=user)
except DjangoValidationError as exc:
raise serializers.ValidationError(
detail=serializers.as_serializer_error(exc))
user.save()
self.custom_signup(request, user)
setup_user_email(request, user, [])
pg = PasswordResetTokenGenerator()
pg_token = pg.make_token(user)
print('>>> pg_token', pg_token)
frontend_site = settings.FRONTEND_APP_BASE_URL
token_generator = kwargs.get('token_generator',
default_token_generator)
temp_key = token_generator.make_token(user)
path = reverse(
'password_reset_confirm',
args=[user_pk_to_url_str(user), temp_key],
)
full_url = frontend_site + path
context = {
'current_site': frontend_site,
'user': user,
'password_reset_url': full_url,
'request': request,
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
email = self.get_cleaned_data()['email']
get_adapter(request).send_mail('password_reset_key', email, context)
return user
в settings.py CSRF_COOKIE_SECURE не установлен и имеет значение по умолчанию False.
все вроде бы работает, пользователь создан и ссылка с uid и токеном отправляется на относительный email НО токен кажется недействительным, когда пользователь пытается сбросить свой пароль...
Печатный 'pg_token' - это тот же самый, который заложен в отправленном URL.
Для полноты картины вот пользовательский сериализатор, используемый для сброса пароля:
в settings.py
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER':
'api.serializers.serializers_auth.CustomPasswordResetSerializer',
'TOKEN_SERIALIZER': 'api.serializers.serializers_auth.TokenSerializer',
}
serializers_auth.py
class CustomAllAuthPasswordResetForm(AllAuthPasswordResetForm):
def save(self, request, **kwargs):
frontend_site = settings.FRONTEND_APP_BASE_URL
email = self.cleaned_data['email']
token_generator = kwargs.get('token_generator',
default_token_generator)
for user in self.users:
temp_key = token_generator.make_token(user)
path = reverse(
'password_reset_confirm',
args=[user_pk_to_url_str(user), temp_key],
)
full_url = frontend_site + path
context = {
'current_site': frontend_site,
'user': user,
'password_reset_url': full_url,
'request': request,
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
get_adapter(request).send_mail('password_reset_key', email,
context)
return self.cleaned_data['email']
class CustomPasswordResetSerializer(PasswordResetSerializer):
@property
def password_reset_form_class(self):
return CustomAllAuthPasswordResetForm
Я перепробовал все, включая те же вызовы для создания и сброса через Postman, думая, что по какой-то причине токен был аннулирован автоматическим входом в веб-интерфейс DRF после создания пользователя, но я не понимаю, почему токен не действителен.
Если я попробую вручную POST адрес электронной почты на /api/v1/auth/password/reset/ и затем использую предоставленный uid/token на /api/v1/auth/password/reset/confirm/ сброс пароля работает как ожидалось.
Опыт и советы будут очень признательны.
вы можете легко реализовать полную аутентификацию пользователей с помощью Django Djoser Проверьте документацию: https://djoser.readthedocs.io/en/latest/getting_started.html
Доступные конечные точки
/users/
/users/me/
/users/confirm/
/users/resend_activation/
/users/set_password/
/users/reset_password/
/users/reset_password_confirm/
/users/set_username/
/users/reset_username/
/users/reset_username_confirm/
/token/login/ (Аутентификация на основе токенов)
/token/logout/ (Аутентификация на основе токенов)
/jwt/create/ (JSON Web Token Authentication)
/jwt/refresh/ (JSON Web Token Authentication)
/jwt/verify/ (JSON Web Token Authentication)
Решается вызовом конечной точки сброса пароля с параметром email сразу после создания пользователя, без какой-либо пользовательской логики или переопределений:
from rest_framework.test import APIClient
if settings.SEND_EMAIL_PWD_CHANGE_TO_NEW_USERS == True:
client = APIClient()
client.post('/api/v1/auth/password/reset/', {'email': user.email}, format='json')
Теперь письмо со ссылкой на сброс содержит действительный токен для сброса пароля.