Linked Questions

Popular Questions

So, I'm trying to make a blog website. I'm with the authentication, and I'm struggling a bit with the security part. I'm trying to make email verification security a thing in my auth security. I've done all the basic stuff like user registration, login & logout functions, view profile, and edit profile. I have done the email verification part in the user registration, but I am somehow struggling a lot when trying to add email verification after users change their email.

What I want my code to do is when a user edits his/her email, the code has to save the user's old email address temporarily but change the email to the new one. I also want the code to send an email to the old one saying that the email has been edited and a link should be forwarded that can change back the email to the old one(which will only be usable for the next 24 hours) automatically, and an email to the new one asking for verification if he/she were the one to request to change the email.

Oh, and I just wanted to mention that I'm trying to do this without using any models. I know I can do this with allauth, but I'm looking for a way to do this without it.

views.py

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import authenticate, login, logout, update_session_auth_hash
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, PasswordChangeForm
from django.contrib.auth.views import PasswordChangeView
from .forms import RegisterUserForm, EditProfileForm, EditProfileFormNormal, PasswordChangingForm
from django.views import generic
from django.urls import reverse_lazy
from django.contrib import messages
from django.http import HttpResponse
from mysite import settings
from django.core.mail import send_mail, EmailMessage
from django.contrib.auth.models import User
from .tokens import generate_token
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes, force_str
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.sites.shortcuts import get_current_site
from django.contrib.sessions.backends.db import SessionStore
from django.views.generic.edit import FormView
from django.core.exceptions import ObjectDoesNotExist


class UserEditView(LoginRequiredMixin, UserPassesTestMixin, generic.UpdateView):
    model = User
    form_class = EditProfileForm
    template_name = 'authstuff/edit_profile.html'
    success_url = reverse_lazy('profile')

    def test_func(self):
        user = self.get_object()
        return self.request.user.is_authenticated and self.request.user.pk == user.pk

    def form_valid(self, form):
        user = form.save(commit=False)
        new_email = form.cleaned_data['email']
        if user.email != new_email:
            session = SessionStore()
            session['pending_email'] = new_email
            session.save()

            current_site = get_current_site(self.request)
            email_subject = "Confirm Your New Email - My Blog Site"
            email_message = render_to_string('authstuff/email_edit_confirmation.html', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                'token': generate_token.make_token(user),
            })
            email = EmailMessage(
                email_subject,
                email_message,
                settings.EMAIL_HOST_USER,
                [user.email],  # Send the email to the old email address
            )
            email.send()

            messages.success(self.request, "Email Change Request: Successful. Please check your email for confirmation instructions.")

        return super().form_valid(form)

    def get_success_url(self):
        uidb64 = urlsafe_base64_encode(force_bytes(self.request.user.pk))
        token = generate_token.make_token(self.request.user)
        return f'/account/confirm_email/{uidb64}/{token}/'

    def get_object(self, queryset=None):
        return get_object_or_404(User, pk=self.kwargs['pk'])

def initiate_email_change(request):
    # Retrieve the user object
    user = request.user

    # Get the new email from the form data or any other source
    new_email = request.POST.get('new_email')

    # Generate a unique token for the email change request
    token = default_token_generator.make_token(user)

    # Store the new email and token in the user's session
    request.session['email_change_data'] = {
        'new_email': new_email,
        'token': token
    }

    # Generate the confirmation URL with uidb64 and token
    uidb64 = urlsafe_base64_encode(force_bytes(user.pk))
    confirmation_url = request.build_absolute_uri(
        f'/confirm_email/{uidb64}/{token}/'
    )

    # Send the confirmation URL to the user via email or display it in the UI
    # You can use Django's email sending mechanism or any other method

    return HttpResponse('Email change request initiated successfully')


def confirm_email(request, uidb64, token):
    try:
        uid = force_str(urlsafe_base64_decode(uidb64))
        user = get_object_or_404(User, pk=uid)
    except (TypeError, ValueError, OverflowError, User.DoesNotExist):
        user = None

    if user is not None and generate_token.check_token(user, token):
        # Retrieve the email change data from the user's session
        email_change_data = request.session.get('email_change_data')
        if email_change_data:
            new_email = email_change_data['new_email']

            # Update the user's email address
            user.email = new_email
            user.save()

            # Clear the email change data from the session
            del request.session['email_change_data']

            return render(request, 'authstuff/email_change_success.html')

    return HttpResponse('Invalid confirmation link')

urls.py

from django.urls import path
from . import views
from .views import UserEditView, from django.utils.http import urlsafe_base64_encode

urlpatterns = [
    path('edit_user_profile/<int:pk>/', UserEditView.as_view(), name="edit-profile"),
]

tokens.py

from django.contrib.auth.tokens import PasswordResetTokenGenerator

from six import text_type

class TokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self,user,timestamp):
        return (
        text_type(user.pk) + text_type(timestamp) 
        # text_type(user.profile.signup_confirmation)
        )

generate_token = TokenGenerator()

email_edit_confirmation.html

{% autoescape off %}  

Hi {{ user.username }},  

We noticed a change in your account's email address, was that you? If so, please click on the link below. If not, It will be better for you to change your password

Confirmation Link:

http://{{ domain }}{% url 'confirm-email' uidb64=uid token=token %}

{% endautoescape %}

I know the code is a bit wrong because my question was different before StefanoTrv (in the comments) suggested changing my logic, but I don't know how to imply his suggestion.

Related Questions