Fakultas Ilmu Komputer UI

Commit 648487be authored by Arief Pratama's avatar Arief Pratama
Browse files

[#9] Auth: Register Admin/Contrib Email Verification

parent 5e569261
......@@ -24,6 +24,8 @@
<!-- Custom styles for this page -->
<link href="{% static 'vendor/datatables/dataTables.bootstrap4.min.css' %}" rel="stylesheet">
{% block stylesheets %}{% endblock %}
<script type="text/javascript" src="{% static 'vendor/jquery/jquery-3.2.1.min.js' %}"></script>
</head>
<body id="page-top" style="font-family: 'Poppins', sans-serif;">
......
......@@ -3,18 +3,18 @@
{% block title %}
<title>Sunting Profil | Digipus</title>
{% endblock %}
{% endblock %}
{% block content %}
<div class="container">
<div class="col-20">
<h1 class="mt-2">
{% if user.is_admin %}
Sunting Profil Admin
Sunting Profil Admin
{% elif user.is_contributor %}
Sunting Profil Kontributor
Sunting Profil Kontributor
{% else %}
Sunting Profil User
Sunting Profil User
{% endif %}
</h1>
<hr class="mt-0 mb-4">
......@@ -24,6 +24,26 @@
<div class="col-md-6">
<div class="fieldWrapper">
{{ field.label_tag }} {{ field }}
{% if field.name == 'email' %}
<div class="my-3">
{% if user.is_email_verified %}
<span class="text-success">
Email Status: Verified
</span>
{% else %}
<span class="text-danger">
Email Status: Unverified
<button class="btn btn-success" id='verify-button'
style="background-color: #615CFD; border-color: #615CFD;">
Verify
</button>
</span>
{% endif %}
</div>
{% endif %}
{% if field.errors %}
<span class="text-danger">
{{ field.errors }}
......@@ -33,15 +53,41 @@
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
</div>
{% endfor %}
<div class="form-margin"></div>
<div class="col-md-6">
<div class="fieldWrapper">
<button type="submit" class="btn btn-success btn-edit" style="background-color: #615CFD; border-color: #615CFD;">Simpan</button>
<button type="submit" class="btn btn-success btn-edit"
style="background-color: #615CFD; border-color: #615CFD;">Simpan</button>
</div>
</div>
</form>
</div>
</div>
<script type="text/javascript">
$(document).ready(function () {
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
$('#verify-button').click(function () {
$.ajax({
url: '/registrasi/send-verify-email',
headers: {
'X-CSRFToken': csrftoken
},
method: 'POST',
data: {
email: $('#id_email').val()
},
success: function (data, status) {
alert(data)
},
})
return false;
});
});
</script>
{% endblock %}
\ No newline at end of file
......@@ -81,6 +81,7 @@ class User(AbstractUser):
default_profile_picture = models.BooleanField(blank=True, default=False)
profile_picture = models.ImageField(default="default-image.jpg")
is_subscribing_to_material_comments = models.BooleanField(blank=False, default=True)
is_email_verified = models.BooleanField(default=False)
objects = CustomUserManager()
......
from authentication.models import User
from django.db import models
import uuid
# Create your models here.
class EmailVerification(models.Model):
token = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_on = models.DateTimeField(auto_now_add=True)
expire_on = models.DateTimeField(null=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
import datetime
from django.contrib.auth.hashers import make_password
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from .models import EmailVerification
from django.utils import timezone
from django.conf import settings
from django.core.mail import send_mail
from django.urls import reverse
class RegistrationService:
......@@ -69,3 +75,27 @@ class RegistrationService:
create_result["form"] = form
return create_result
@staticmethod
def create_email_verification(request, user):
verify = EmailVerification()
verify.created_on = timezone.now()
verify.expire_on = verify.created_on + datetime.timedelta(hours=24)
verify.user = user
verify.save()
path = reverse('register:verify-email', args=[verify.token])
url = request.build_absolute_uri(path)
email_content = f"""
Dear {user.name},\n\n
Mohon verifikasi email Anda dengan klik pada link berikut: {url}
"""
send_mail(
subject = 'DIGIPUS: Verifikasi Alamat Email',
message = email_content,
from_email = getattr(settings, 'EMAIL_HOST_USER'),
recipient_list = [user.email],
fail_silently = False)
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verifikasi Email</title>
<link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" />
<!--===============================================================================================-->
<link rel="stylesheet" type="text/css" href="{% static 'vendor/bootstrap/css/bootstrap.min.css' %}">
<!--===============================================================================================-->
<link rel="stylesheet" type="text/css" href="{% static 'fonts/font-awesome-4.7.0/css/font-awesome.min.css' %}">
<!--===============================================================================================-->
<!--===============================================================================================-->
<link rel="stylesheet" type="text/css" href="{% static 'vendor/animate/animate.css' %}">
<!--===============================================================================================-->
<link rel="stylesheet" type="text/css" href="{% static 'vendor/css-hamburgers/hamburgers.min.css' %}">
<!--===============================================================================================-->
<link rel="stylesheet" type="text/css" href="{% static 'vendor/animsition/css/animsition.min.css' %}">
<!--===============================================================================================-->
<link rel="stylesheet" type="text/css" href="{% static 'vendor/select2/select2.min.css' %}">
<!--===============================================================================================-->
<link rel="stylesheet" type="text/css" href="{% static 'vendor/daterangepicker/daterangepicker.css' %}">
<!--===============================================================================================-->
<link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/util.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
<!--===============================================================================================-->
<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital@1&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
</head>
<body>
<main>
{% if message %}
<div>
{{ message }}
</div>
<div>
Kembali ke <a href="/">home</a>
</div>
{% endif %}
</main>
<!--===============================================================================================-->
<script src="../static/vendor/jquery/jquery-3.2.1.min.js"></script>
<!--===============================================================================================-->
<script src="../static/vendor/animsition/js/animsition.min.js"></script>
<!--===============================================================================================-->
<script src="../static/vendor/bootstrap/js/popper.js"></script>
<script
src="../static/../static/../static/../static/../static/../static/vendor/bootstrap/js/bootstrap.min.js"></script>
<!--===============================================================================================-->
<script src="../static/../static/../static/../static/../static/vendor/select2/select2.min.js"></script>
<!--===============================================================================================-->
<script src="../static/../static/../static/../static/vendor/daterangepicker/moment.min.js"></script>
<script src="../static/../static/../static/vendor/daterangepicker/daterangepicker.js"></script>
<!--===============================================================================================-->
<script src="../static/../static/vendor/countdowntime/countdowntime.js"></script>
<!--===============================================================================================-->
<script src="../static/js/login.js"></script>
<script src="../static/js/navbar.js"></script>
</body>
</html>
\ No newline at end of file
import datetime
from django.core import mail
from django.utils import timezone
from register.models import EmailVerification
from django.test import Client, TestCase
from django.urls import resolve
......@@ -883,3 +888,47 @@ class RegisterPublicTest(TestCase):
self.assertEqual(User.objects.all().count(), 0)
self.assertIn(b"Password must have at least 8 characters", response.content)
class VerifyEmailTest(TestCase):
def setUp(self):
self.credential = {
'email':'example@test.com',
'password':'P@ssw0rd'
}
self.user = User.objects.create_contributor(**self.credential)
self.user.save()
self.verif_valid = EmailVerification(
expire_on=timezone.now() + datetime.timedelta(hours=24),
user = self.user)
self.verif_valid.save()
self.verif_expire = EmailVerification(
created_on=timezone.now() + datetime.timedelta(hours=-24),
expire_on=timezone.now() + datetime.timedelta(hours=-1),
user = self.user)
self.verif_expire.save()
self.client = Client()
def test_email_notif_anonymous(self):
response = self.client.post('/registrasi/send-verify-email', {'email': self.user.email})
self.assertEqual(response.status_code, 403)
def test_email_notif_authorized(self):
login = self.client.login(**self.credential)
self.client.post('/registrasi/send-verify-email', {'email': self.user.email})
self.assertEqual(len(mail.outbox), 1)
def test_verify_email_before_24h(self):
response = self.client.get(f'/registrasi/verify-email/{self.verif_valid.token}')
self.assertContains(response, f'Email Anda {self.verif_valid.user.email} berhasil diverifikasi')
def test_verify_email_after_24h(self):
response = self.client.get(f'/registrasi/verify-email/{self.verif_expire.token}')
self.assertContains(response, 'Link verifikasi telah expire')
def test_verify_email_wrong_token(self):
response = self.client.get(f'/registrasi/verify-email/not-a-valid-token')
self.assertContains(response, 'Email gagal diverifikasi')
......@@ -7,5 +7,7 @@ app_name = "register"
urlpatterns = [
path("", views.index.as_view()),
path("umum/", views.RegistrasiUmum.as_view()),
path("admin/", views.RegistrasiAdmin.as_view())
path("admin/", views.RegistrasiAdmin.as_view()),
path("verify-email/<str:token>", views.verify_email, name='verify-email'),
path("send-verify-email", views.send_verify_email),
]
from django.http import response
from authentication.models import User
from django.utils import timezone
from django.shortcuts import render
from register.models import EmailVerification
from django.contrib.auth import login
from django.db import models
from django.http import HttpResponseRedirect
from django.views.generic import TemplateView
from django.core.exceptions import PermissionDenied
from register.forms import UserForm
# Create your views here.
from register.services import RegistrationService
......@@ -98,3 +105,36 @@ class RegistrasiUmum(TemplateView):
context = self.get_context_data(**kwargs)
context["form"] = UserForm
return self.render_to_response(context=context)
def send_verify_email(request):
if not request.user.is_authenticated:
raise PermissionDenied(request)
if request.method == 'POST':
RegistrationService.create_email_verification(request, request.user)
return response.HttpResponse('Email verifikasi telah dikirim')
def verify_email(request, token):
template_name = 'email_verify.html'
try:
verif = EmailVerification.objects.get(token=token)
if verif.expire_on > timezone.now():
user = User.objects.get(email=verif.user.email)
user.is_email_verified = True
user.save()
return render(request, template_name, {
'message': f'Email Anda {verif.user.email} berhasil diverifikasi'
})
else:
return render(request, template_name, {
'message': f'Link verifikasi telah expire'
})
except:
return render(request, template_name, {
'message': f'Email gagal diverifikasi'
})
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment