Skip to content
Snippets Groups Projects
Commit eaf055c9 authored by Chris Li's avatar Chris Li
Browse files

Add profile and email verification.

parent 81d844bb
No related branches found
No related tags found
1 merge request!1User Authentication Feature Added
from django.shortcuts import render
from rest_framework import generics
from rest_framework.response import Response
from knox.models import AuthToken
......@@ -33,3 +34,30 @@ class LoginAPI(generics.GenericAPIView):
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token[1],
})
def verify_user_and_activate(request, token):
#print(AuthToken.objects.filter(pk=token).first())
print(AuthToken.objects.all())
try:
auth = AuthToken.objects.filter(digest=token).first()
auth.user.is_active = True
auth.user.save()
return render(
request,
template_name='email/verification_success.html',
context={
'msg': 'Your Email is verified successfully and account has been activated.',
'status': 'Verification Successful!',
}
)
except:
return render(
request,
template_name='email/verification_fail.html',
context={
'msg': 'There is something wrong with this link, unable to verify the user...',
'minor_msg': 'There is something wrong with this link...',
'status': 'Verification Failed!',
}
)
from rest_framework import status
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from core.models.profile import Profile
from core.serializers.profile import ProfileSerializer
class ProfileViewSet(generics.RetrieveUpdateAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = ProfileSerializer
def retrieve(self, request, *args, **kwargs):
instance = request.user.profile
serializer = self.get_serializer(instance)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = request.user.profile
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
......@@ -6,11 +6,9 @@ from django.db import models
from django_countries.fields import CountryField
from django_extensions.db.models import TimeStampedModel
from .utils import UUIDModel
class Profile(TimeStampedModel, UUIDModel):
user = models.OneToOneField(User, on_delete=models.CASCADE)
class Profile(TimeStampedModel):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
bio = models.TextField(blank=True)
birthday = models.DateField(verbose_name="Birthday", null=True)
country = CountryField(null=True, verbose_name="Country")
......
......@@ -10,4 +10,4 @@ class LoginSerializer(serializers.Serializer):
user = authenticate(**data)
if user and user.is_active:
return user
raise serializers.ValidationError('Incorrect Credentials Passed.')
raise serializers.ValidationError('Account is not activated.')
......@@ -4,6 +4,7 @@ from core.models.profile import Profile
class ProfileSerializer(serializers.ModelSerializer):
country = serializers.CharField()
class Meta:
model = Profile
fields = ('id', 'user', 'bio', 'birthday', 'country', 'city', 'affiliation', 'photo')
fields = ('user', 'bio', 'birthday', 'country', 'city', 'affiliation', 'photo')
from django.contrib.auth.models import User
from rest_framework import serializers
# Register Serializer
from core.models.profile import Profile
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
......
from django.contrib.auth.models import User
from django.core.mail import EmailMultiAlternatives
from django.core.mail import EmailMessage
from django.db.models.signals import post_save
from django.dispatch import receiver
from django_rest_passwordreset.signals import reset_password_token_created
from django.template.loader import render_to_string
from django.urls import reverse
from knox.models import AuthToken
from core.models.profile import Profile
# @receiver(post_save, sender=User)
# def create_related_profile(sender, instance, created, *args, **kwargs):
# # Notice that we're checking for `created` here. We only want to do this
# # the first time the `User` instance is created. If the save that caused
# # this signal to be run was an update action, we know the user already
# # has a profile.
# if instance and created:
# print(instance,created)
# instance.profile = Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def create_related_profile(sender, instance, created, *args, **kwargs):
if instance and created:
# create profile for new user
instance.profile = Profile.objects.create(user=instance)
# reset user's status to inactive
instance.is_active = False
@receiver(post_save, sender=User)
def send_activation_email(sender, instance, created, *args, **kwargs):
# send an e-mail to the user
if instance and created:
context = {
'current_user': instance,
'username': instance.username,
'email': instance.email,
'domain': 'localhost:8000',
'token': AuthToken.objects.create(instance)[0].digest
}
# render email text
email_html_message = render_to_string('email/user_verification.html', context)
msg = EmailMessage(
# title:
"User Verification for {title}".format(title="Some website title"),
# message:
email_html_message,
# from:
"noreply@somehost.local",
# to:
[instance.email]
)
msg.send()
@receiver(reset_password_token_created)
def password_reset_token_created(sender, instance, reset_password_token, *args, **kwargs):
# send an e-mail to the user
......@@ -34,18 +60,15 @@ def password_reset_token_created(sender, instance, reset_password_token, *args,
# render email text
email_html_message = render_to_string('email/user_reset_password.html', context)
email_plaintext_message = render_to_string('email/user_reset_password.txt', context)
# email_plaintext_message = "{}?token={}".format(reverse('password_reset:reset-password-request'), reset_password_token.key)
msg = EmailMultiAlternatives(
msg = EmailMessage(
# title:
"Password Reset for {title}".format(title="Some website title"),
# message:
email_plaintext_message,
email_html_message,
# from:
"noreply@somehost.local",
# to:
[reset_password_token.user.email]
)
msg.attach_alternative(email_html_message, "text/html")
msg.send()
from django.urls import path, include
from knox import views as knox_views
from rest_framework import routers
from core.api.auth import RegisterAPI, LoginAPI
from core.api.auth import RegisterAPI, LoginAPI, verify_user_and_activate
from core.api.password import ChangePasswordView
from core.api.profile import ProfileViewSet
router = routers.DefaultRouter()
urlpatterns = [
urlpatterns = router.urls
urlpatterns += [
# auth
path('api/auth/register', RegisterAPI.as_view(), name='register'),
path('api/auth/activate/<token>', verify_user_and_activate, name='activate'),
path('api/auth/login', LoginAPI.as_view(), name='login'),
path('api/auth/logout', knox_views.LogoutView.as_view(), name='logout'),
# passwd
path('api/change-password', ChangePasswordView.as_view(), name='change-password'),
path('api/password_reset', include('django_rest_passwordreset.urls', namespace='password_reset')),
path('api/password_reset/', include('django_rest_passwordreset.urls', namespace='password_reset')),
# profile
path('api/profile', ProfileViewSet.as_view(), name='profile')
]
......@@ -9,6 +9,7 @@ https://docs.djangoproject.com/en/4.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
from datetime import timedelta
import os
from pathlib import Path
......@@ -52,6 +53,11 @@ REST_FRAMEWORK = {
],
}
REST_KNOX = {
'TOKEN_TTL': timedelta(days=3),
'AUTO_REFRESH': True,
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
......
......@@ -2,11 +2,11 @@
To initiate the password reset process for you {{ username }} for your BuyforFree Account,
click the link below:
http://josla.com/confirm{{reset_password_url}}
{{reset_password_url}}
If clicking the link above doesn't work, please copy and paste the URL in a new browser
window instead.
Sincerely,
Josla
ECE651_DEMO
{% endautoescape %}
\ No newline at end of file
{% autoescape off %}
To initiate the password reset process for you {{ username }} for your Account,
click the link below:
http://{{ domain }}{% url 'activate' token=token %}
If clicking the link above doesn't work, please copy and paste the URL in a new browser
window instead.
Sincerely,
ECE651_DEMO
{% endautoescape %}
\ No newline at end of file
{% extends "email_index.html" %}
{%block content%}
<div class="container" style="height: auto; margin-bottom: 0px; padding-bottom: 0px; border: 0px solid red;">
<div class="row" style="height: auto; border: 0px solid green;">
<div class="col" style="width: auto; border: 0px solid red;">
<div class="container" style="border: 0px solid; margin-top: 200px; padding-bottom: 10px;">
<h1 style="font-family: 'Mulish', sans-serif; text-align: center; color: white; font-size: calc(25px + 4vw);">
{{status}}
</h1>
</div>
<hr style="background-color: white; width: 150px;">
<div class="container" style="border: 0px solid;">
<h1 style="font-family: 'Mulish', sans-serif; text-align: center; color: white; font-size: calc(15px + 1.5vw);">
{{msg}}
</h1>
</div>
<div class="mt-5">
<h1 style="font-family: 'Mulish', sans-serif; text-align: center; color: white; font-size: calc(15px + .5vw);">
{{ minor_msg }}
</h1>
</div>
</div>
</div>
</div>
{%endblock%}
\ No newline at end of file
{% extends "email_index.html" %}
{%block content%}
<div class="container" style="height: auto; margin-bottom: 0px; padding-bottom: 0px; border: 0px solid red;">
<div class="row" style="height: auto; border: 0px solid green;">
<div class="col" style="width: auto; border: 0px solid red;">
<div class="container" style="border: 0px solid; margin-top: 200px; padding-bottom: 10px;">
<h1 style="font-family: 'Mulish', sans-serif; text-align: center; color: white; font-size: calc(25px + 4vw);">
{{status}}
</h1>
</div>
<hr style="background-color: white; width: 150px;">
<div class="container" style="border: 0px solid;">
<h1 style="font-family: 'Mulish', sans-serif; text-align: center; color: white; font-size: calc(15px + .8vw);">
{{msg}}
</h1>
</div>
</div>
</div>
</div>
{%endblock%}
\ No newline at end of file
{%load static%}
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link href="https://fonts.googleapis.com/css2?family=Mulish:wght@200&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<title>{{status}}</title>
</head>
<style>
body {
background-image: linear-gradient(to right, rgb(218, 28, 123) , rgb(0, 110, 253));
/* background-image: linear-gradient(rgb(117, 224, 67), rgb(15, 231, 247)); */
}
</style>
<body>
{% if messages%}
{% for message in messages %}
<div class="alert alert-{{message.tags}}">
{{message}}
{% endfor %}
</div>
{% endif %}
{%block content%}{%endblock%}
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
</body>
</html>
\ No newline at end of file
Josla password reset
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment