0

I'm working on forget password page in which the user first have to answer the question for enabling the textfields for creating new password.

Here, I have two forms, One for security question and second for password and confirm password.

Following is my forms.py

from django import forms
from .models import SecurityQuestions

class PasswordForm(forms.Form):
    password = forms.CharField(disabled=True, widget=forms.PasswordInput(attrs={'placeholder':'New Password'}))
    password_confirm = forms.CharField(disabled=True, widget=forms.PasswordInput(attrs={'placeholder':'Re-enter Password'}))

    def clean(self, *args,**kwargs):
        password = self.cleaned_data.get('password')
        password_confirm = self.cleaned_data.get('password_confirm')

        if password and password_confirm:
            if password != password_confirm:
                raise forms.ValidationError('Password Mismatch')
        return super(PasswordForm, self).clean(*args, **kwargs)

class PasswordVerificationForm(forms.Form):
    question = forms.ModelChoiceField(queryset=SecurityQuestions.objects.all(), empty_label=None, widget=forms.Select(attrs={'class':'form-control','id': 'sectxt'}))
    answer = forms.CharField(label='answer', widget=forms.TextInput(attrs={'placeholder':'Answer','id': 'anstxt'}))

Following is my views.py

from django.shortcuts import render, redirect
from .forms import PasswordForm, PasswordVerificationForm
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.hashers import make_password
from .models import SecurityQuestions
from django.contrib import messages

@login_required
@csrf_exempt
def password_reset(request):
    form = PasswordForm(request.POST or None)
    form1 = PasswordVerificationForm(request.POST or None)
    if request.method == 'POST':
        if request.POST.get("verify", False):
            question = request.POST.get('question')
            answer = request.POST.get('answer')
            print("question",question)
            print("answer",answer)
            check = SecurityQuestions.objects.get(id=question) #id=1
            print(check.answer)
            if check.answer == answer:
                messages.success(request, 'Enter Your New Password', 'alert-success')
                form.fields['password'].disabled = False
                form.fields['password_confirm'].disabled = False
            else:
                redirect('/')
                messages.error(request, 'Incorrect Answer', 'alert-danger')
        if request.POST.get("create", False):
            if form.is_valid():
                print("For Changing Password...")
                password = form.cleaned_data.get('password')
                request.user.password = make_password(password)
                request.user.save()
                return redirect('/')
    else:
        form = PasswordForm()
        form1 = PasswordVerificationForm()
    return render(request,"forget_password.html", {"form": form, "form1":form1})

Following is my forget_password.html

<div class="container">
        <div class="main">
            <div class="row justify-content-center">
                <div class="col-md-4">
                        <div class="login-form">
                            <div class="row">

                                <div class="col-md-12">
                                    <div class="login-title-holder">
                                        <h4>Forgot Password</h4>
                                    </div>
                                </div>
                                <form method="post">
                                <div class="form-group col-md-12">
                                <div class="input-group">
                                  {{ form1.question | add_class:'form-control' }}
                                  <span class="input-group-append">
                                        <div class="input-group-text input-group-icon"><i class="fa fa-question" aria-hidden="true"></i></div>
                                    </span>
                                </div>
                            </div>
                            <div class="form-group col-md-12">
                                    <div class="input-group">
                                        {{ form1.answer | add_class:'form-control' }}
                                        <span class="input-group-append">
                                            <div class="input-group-text input-group-icon  "><i class="fa fa-comment" aria-hidden="true"></i></div>
                                        </span>
                                    </div>
                                </div>

                            <div class="col-md-12">
                                 {% if messages %}
                                    {% for message in messages %}
                                        <div {% if message.tags %} class="alert {{ message.tags }} text-center"{% endif %}>
                                            <a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
                                            {{ message }}
                                        </div>
                                    {% endfor %}
                                {% endif %}
                            <input type="submit" name = "verify" formmethod="post" style="visibility: hidden;">
                        </div>
                        </form>
                        <form method="post">
                                <div class="form-group col-md-12">
                                    <div class="input-group">
                                        {{ form.password | add_class:'form-control' }}
                                        <span class="input-group-append">
                                            <div class="input-group-text input-group-icon"><i class="fa fa-key" aria-hidden="true"></i></div>
                                        </span>

                                    </div>
                                </div>
                                <div class="form-group col-md-12">
                                    <div class="input-group">
                                        {{ form.password_confirm | add_class:'form-control' }}
                                        <span class="input-group-append">
                                            <div class="input-group-text input-group-icon"><i class="fa fa-key" aria-hidden="true"></i></div>
                                        </span>

                                    </div>
                                </div>


                                <div class="col-md-12">
                                    <div class="button-holder">
                                        <a href="index.html" class="login-btn">Cancel</a>
                                        <button class="login-btn" type="submit" formmethod="post" name="create">Create</button>
                                    </div>
                                </div>
                            </form>
                            </div>
                            </div>
                </div>
            </div>
        </div>
    </div>

If I enter the security answer first, based on the condition, if true, it enables the textfields for password and password_confirm. But it's not creating the new password. However, if I change the disabled = False in PasswordForm then it creating the new password successfully. I want to know why it's not executing the code after the first form executes successfully.

Thanks!

sodmzs
  • 164
  • 1
  • 11
  • I don't see the "create" and "verify" fields. – Melvyn May 27 '20 at 12:59
  • Hi, I have added my forget_password.html code. Here you can find what you need. However, create and verify are the names of the input type="submit" to understand what form to execute when. Thanks. – sodmzs May 27 '20 at 14:17
  • You have a several nesting problems in your template. You can't [nest forms](https://stackoverflow.com/q/379610/1600649) and I think you want to end the row at least after the first form. Solve these first and then see where you're at. – Melvyn May 27 '20 at 15:29
  • Thankyou for letting me know about the nest forms, I have replaced form tag with div tag and now its no more nest form problem. But still my main problem is not resolved. – sodmzs May 27 '20 at 19:05

1 Answers1

0

You really should chain this into 2 urls, rather then trying 2 forms in one page. You can only submit one form and this is the problem you're facing. Once you have submitted the security question, you instantiate the form again with fields disabled:

form = PasswordForm(request.POST or None)

And now they do not get enabled, because the submit button called 'verify' from form1 is no longer present, so the code in that branch is not executed.

Let's say url is /password_reset/ - a rough outline (untested):

@login_required
@csrf_exempt
def security_question(request):
    form = PasswordVerificationForm(request.POST)
    if request.method == 'POST':
        if form.is_valid():
             token = generate_strong_token()  # Implement: generate a strong token, url safe
             request.session["password_reset_token"] = token
             return redirect(f'/password_reset/{token}/')
    else:
        return render(...)

@login_required
@csrf_exempt
def change_password(request, **kwargs):
    form = PasswordForm(request.POST)
    token = request.session.get('password_reset_token')
    if token == kwargs['token']:
        if request.method == 'POST' and form.is_valid():
            del request.session['password_reset_token']

            # handle password change and redirect to wherever
        else:
            return render(...)
    else:
        raise SecurityError('Invalid token')

Your urls would be something like:

urlpatterns = [
    re_path('password_reset/(?P<token>[0-9A-F]{32})/', change_password)
    path('password_reset/', security_question)
]
Melvyn
  • 8,808
  • 1
  • 22
  • 36