PasswordResetConfirmView를 상속하며 문제를 해결해 나아가는 과정 기록
원인은 맨 밑으로 또한 결과를 보시려면
url
기존의 코드 소개
views.py
from django.contrib.auth.views import LoginView, LogoutView, PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView
from django.contrib.auth.tokens import default_token_generator
from django.utils.decorators import method_decorator
from django.utils.http import is_safe_url, urlsafe_base64_decode
from django.contrib.auth import (
REDIRECT_FIELD_NAME, get_user_model, login as auth_login,
logout as auth_logout, update_session_auth_hash,
)
from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.cache import never_cache
from django.core.exceptions import ValidationError
# 해당 소스는 class 밖에 들어갑니다.
INTERNAL_RESET_URL_TOKEN = 'set-password'
INTERNAL_RESET_SESSION_TOKEN = '_password_reset_token'
class UserPasswordResetConfirmView(PasswordResetConfirmView):
template_name = 'password_reset_confirm.html'
token_generator = default_token_generator
success_url = reverse_lazy('password_reset_complete')
@method_decorator(sensitive_post_parameters())
@method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
assert 'uidb64' in kwargs and 'token' in kwargs
self.validlink = False
self.user = self.get_user(kwargs['uidb64'])
if self.user is not None:
token = kwargs['token']
if token == INTERNAL_RESET_URL_TOKEN:
session_token = self.request.session.get(INTERNAL_RESET_SESSION_TOKEN)
if self.token_generator.check_token(self.user, session_token):
# If the token is valid, display the password reset form.
self.validlink = True
return super().dispatch(*args, **kwargs)
else:
if self.token_generator.check_token(self.user, token):
# Store the token in the session and redirect to the
# password reset form at a URL without the token. That
# avoids the possibility of leaking the token in the
# HTTP Referer header.
self.request.session[INTERNAL_RESET_SESSION_TOKEN] = token
redirect_url = self.request.path.replace(token, INTERNAL_RESET_URL_TOKEN)
return HttpResponseRedirect(redirect_url)
# Display the "Password reset unsuccessful" page.
return self.render_to_response(self.get_context_data())
def get_user(self, uidb64):
try:
# urlsafe_base64_decode() decodes to bytestring
uid = urlsafe_base64_decode(uidb64).decode()
user = User._default_manager.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist, ValidationError):
user = None
return user
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.user
return kwargs
def form_valid(self, form):
user = form.save()
del self.request.session[INTERNAL_RESET_SESSION_TOKEN]
if self.post_reset_login:
auth_login(self.request, user, self.post_reset_login_backend)
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.validlink:
context['validlink'] = True
else:
context.update({
'form': None,
'title': _('Password reset unsuccessful'),
'validlink': False,
})
return context
password_reset_confirm.html
<h1>비번 바꾸는 창</h1>
<form method="post" action="{% url 'password_reset' %}">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">회원가입</button>
</form>
PasswordResetView를 그대로 가져왔지만 비밀번호를 바꾸며 문제가 생겼다.
https://github.com/django/django/pull/8062
https://code.djangoproject.com/ticket/27840
https://code.djangoproject.com/ticket/30952
다른 글들을 보니 나와 같은 사람이 많았다.
글을 읽어보니 로그인한 사용자의 세션이 물려있어서 그렇다는데..
del self.request.session[INTERNAL_RESET_SESSION_TOKEN]
해당 부분을 pop으로도 바꿔보고
위로 올려서 먼저 del이 실행되게도 해보았다.
2020-08-25 23:16
print 시도
너무 모르겠다.
솔직히 너무 모르겠어서 함수마다 모두 print를 뽑아 어디서 에러가 나오는지 확인해 보았다.
form_valid까지 찍히고 이하부터 에러가 찍혔다
Internal Server Error: /user/password_reset_confirm/MjQ/set-password/
uidb64에 문제가 있을 수도 있다. urls.py의 문제일 수도?
2020-08-25 23:25
개발자 도구를 이용한 parameter 확인 시도
django에서 제공하는 auth_views.PasswordResetConfirmView.as_view()
새로 만든 views.UserPasswordResetConfirmView.as_view()
기존의 url과 새로 만든 url의 값을 비교합니다.
차이점 x
흠..
2020-08-25 23:50
Django 2.2+, urlsafe_base64_encode문자열 반환에서, 디코딩할 필요가 없습니다.라는 글을 발견
uid에서. decode 변환 부분을 삭제
실패
문제를 찾으면서의 키워드
- Internal Server Error: /user/password_reset_confirm/MjQ/set-password/
- django uid
2020-08-26 22:30
문득 생각났다.
django 백 단 문제가 아니라면?
path('password_reset_confirm/<uidb64>/<token>/', views.UserPasswordResetConfirmView.as_view(), name="password_reset_confirm"),
urls.py에 보면 uidb64와 함께 토큰 값을 같이 넘겨주고 있다.
하지만 html을 봐보면?
그냥 submit를 누르면 form이 실행이 되게 되어있다.
그래서 넣어 보았다.
{% extends 'signBase.html' %}
{% load static %}
{% block signcontent %}
<body class="text-center">
<form class="form-signin" method="POST" action="{% url 'password_reset_confirm' uidb64=uid token=token %}">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">비밀번호 초기화</button>
</form>
</body>
{% endblock %}
form url에 uid와 토큰을 넣어주었다.
물론 실패하였지만 뭔가 느낌이 있다. 해결할 거 같은 느낌이
2020-08-26 22:43
혹시 몰라 auth에 있는 url과 비교해봤다
달랐던 점을 수정해 보았다
실패
2020-08-26 23:20
https://github.com/awesto/django-shop/issues/752
좀 비슷한 거 같다. 특히 마지막 말
We found a temporary workaround for this issue: |
메모..
2020-08-27 21:43
UserPasswordResetConfirmView 함수 수정
수정된 부분은 views.py에서 모두 지우고 form 만 넘기는 부분이었다.
왜냐하면 print로 로그를 찍었을 때 form_valid 다음에서 막혔기 때문이다..
html도 다시 submit를 누르면 form이 post로 실행되게 바꾸었었다..
<body class="text-center">
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">비밀번호 초기화</button>
</form>
</body>
2020-08-27 21:43
실행이 되었다.
아무래도 클래스를 상속받으면서 같은 소스가 중복되어 호출되어 값이 꼬인 거 같다.
아무 생각 없이 소스를 상속받아야 한다 라는 생각이 머릿속에 자리 잡았던 것 같다.
소스를 상속받을 때 해당 소스가 무슨 기능을 하는지 어떤 순서로 실행이 되는지
또한 나의 소스와 어떠한 연결점을 가지고 실행이 되는지 생각할 수 있는 좋은 기회였다.