Skip to content

Commit

Permalink
Implement registration (#94)
Browse files Browse the repository at this point in the history
* feat: connect registration to store

* feat: add registration function to store

* feat: add registration path

* feat: add registration module

* feat: add registration email template

* Update index.tsx

* Update SignIn.tsx

* Update authSlice.ts

* Update custom_auth_view.py

* Create test_views.py

* Update urls.py

* Rename django_project/core/templates/email.html to django_project/core/templates/account/email_confirmation.html

* Update urls.py

* Update test_views.py

* Update urls.py

* Update custom_auth_view.py

* Add files via upload

* Update project.py

* Update .template.env

* Update SignIn.tsx

* Update authSlice.ts

* Update SignIn.tsx

* Update test_views.py

* Update test_views.py

* Update custom_auth_view.py

* Update contrib.py

* Update SignIn.tsx

* Update authSlice.ts

* Update test_views.py

* Update authSlice.ts

* Update authSlice.ts

* Update SignIn.tsx

---------

Co-authored-by: Dimas Ciputra <[email protected]>
  • Loading branch information
tinashechiraya and dimasciput authored Nov 20, 2024
1 parent e1f171c commit 773ef97
Show file tree
Hide file tree
Showing 9 changed files with 736 additions and 26 deletions.
198 changes: 198 additions & 0 deletions django_project/core/custom_auth_view.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from django.core.mail import send_mail
from django.contrib.sites.shortcuts import get_current_site
from django.utils.http import (
urlsafe_base64_encode,
urlsafe_base64_decode
)
from django.template.loader import render_to_string
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth import get_user_model
from django.http import JsonResponse
from django.shortcuts import redirect
from rest_framework.permissions import AllowAny
from django.core.exceptions import MultipleObjectsReturned
from django.core.exceptions import ValidationError
from django.core.validators import EmailValidator
from django.contrib.auth.password_validation import validate_password
from rest_framework.throttling import AnonRateThrottle




class CheckTokenView(APIView):
Expand All @@ -17,3 +37,181 @@ def get(self, request):
"last_name": user.last_name
}
}, status=status.HTTP_200_OK)


class CustomRegistrationView(APIView):
permission_classes = [AllowAny]
throttle_classes = [AnonRateThrottle]

def post(self, request, *args, **kwargs):
email = request.data.get('email')
password1 = request.data.get('password1')
password2 = request.data.get('password2')

error_messages = []

try:
EmailValidator()(email)
except ValidationError:
error_messages.append('Invalid email format.')

if get_user_model().objects.filter(email=email).exists():
error_messages.append('Email is already registered.')

if password1 != password2:
error_messages.append('Passwords do not match.')

try:
validate_password(password1)
except ValidationError as e:
error_messages.extend(e.messages)

if error_messages:
return Response(
{'errors': error_messages},
status=status.HTTP_400_BAD_REQUEST
)

user = get_user_model().objects.create_user(
email=email,
password=password1,
username=email
)
user.is_active = False
user.save()

token = default_token_generator.make_token(user)
uid = urlsafe_base64_encode(str(user.pk).encode())

activation_link = f"{
get_current_site(request).domain}/auth/activate/{uid}/{token}/"

# Send email with activation link
subject = "Activate Your Account"
message = render_to_string('account/email_confirmation.html', {
'user': user,
'activation_url': activation_link,
'django_backend_url': settings.DJANGO_BACKEND_URL,
})
send_mail(subject, message, settings.NO_REPLY_EMAIL, [email])

return Response(
{'message': 'Verification email sent.'},
status=status.HTTP_201_CREATED
)



class AccountActivationView(APIView):
permission_classes = [AllowAny]

def get(self, request, uidb64, token, *args, **kwargs):
try:
uid = urlsafe_base64_decode(uidb64).decode()
user = get_user_model().objects.get(pk=uid)
except (
TypeError, ValueError,
OverflowError, get_user_model().DoesNotExist
):
return JsonResponse(
{'error': 'Invalid activation link'},
status=status.HTTP_400_BAD_REQUEST
)

if default_token_generator.check_token(user, token):
user.is_active = True
user.save()
redirect_url = (
f"{settings.DJANGO_BACKEND_URL}/#/?"
"registration_complete=true"
)


return redirect(redirect_url)

return JsonResponse(
{'error': 'Invalid activation link'},
status=status.HTTP_400_BAD_REQUEST
)


class ForgotPasswordView(APIView):
permission_classes = [AllowAny]

def post(self, request, *args, **kwargs):
email = request.data.get('email')

try:
users = get_user_model().objects.filter(email=email)
if not users.exists():
return Response(
{'error': 'Email not found'},
status=status.HTTP_400_BAD_REQUEST
)
elif users.count() > 1:
return Response(
{'error': 'Multiple users with this email address'},
status=status.HTTP_400_BAD_REQUEST
)
user = users.first()

except MultipleObjectsReturned:
return Response(
{'error': 'Multiple users with this email address'},
status=status.HTTP_400_BAD_REQUEST
)

token = default_token_generator.make_token(user)
uid = urlsafe_base64_encode(str(user.pk).encode())

reset_password_link = (
f"{get_current_site(request).domain}/auth/password-reset/"
f"{uid}/{token}/"
)

# Send the password reset email
subject = "Password Reset"
message = render_to_string('account/password_reset_email.html', {
'user': user,
'reset_password_url': reset_password_link,
'django_backend_url': settings.DJANGO_BACKEND_URL,
})
send_mail(subject, message, settings.NO_REPLY_EMAIL, [email])

return Response(
{'message': 'Password reset link sent to your email.'},
status=status.HTTP_200_OK
)



class ResetPasswordConfirmView(APIView):
permission_classes = [AllowAny]

def post(self, request, uidb64, token, *args, **kwargs):
try:
uid = urlsafe_base64_decode(uidb64).decode()
user = get_user_model().objects.get(pk=uid)
except (
TypeError, ValueError,
OverflowError,
get_user_model().DoesNotExist
):
return Response(
{'error': 'Invalid reset link'},
status=status.HTTP_400_BAD_REQUEST
)

if default_token_generator.check_token(user, token):
new_password = request.data.get('new_password')
user.set_password(new_password)
user.save()
return Response(
{'message': 'Password has been successfully reset.'},
status=status.HTTP_200_OK
)

return Response(
{'error': 'Invalid reset link'},
status=status.HTTP_400_BAD_REQUEST
)
25 changes: 25 additions & 0 deletions django_project/core/settings/contrib.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
'allauth.socialaccount',
'rest_framework.authtoken',
'dj_rest_auth',
'dj_rest_auth.registration',

'invitations',
)
Expand Down Expand Up @@ -48,7 +49,31 @@
'DEFAULT_VERSIONING_CLASS': (
'rest_framework.versioning.NamespaceVersioning'
),
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '30/minute', # This limits to 30 requests per minute for anonymous users.
},
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 8,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
]


AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # default
Expand Down
60 changes: 60 additions & 0 deletions django_project/core/templates/account/email_confirmation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Registration and Verify Email Notification</title>
</head>
<body style="font-family: Helvetica, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="900" style="background-color: #2f5c4a; padding: 10px;">
<tr>
<!-- Left Column: Logo -->
<td align="left" width="50%" style="padding: 10px;">
<img src="https://arw.sta.do.kartoza.com/static/images/main_logo.svg" alt="Logo" width="200" style="display: block;">
</td>
<!-- Right Column: Title or Navigation Links -->
<td align="right" width="50%" style="padding: 10px; color: #ffffff; font-family: Helvetica,sans-serif;">
<h2 style="margin: 0; font-size: 24px;">Africa Rangeland Watch</h2>
<!-- Optional: Add links or other content here -->
</td>
</tr>
</table>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="900" style="background-color: #ffffff; padding: 20px; border: 10px solid #dddddd;">
<tr>
<td style="padding: 20px;">
<h2 style="font-size: 20px; color: #333333;">Welcome to the Africa Rangeland Watch Platform!</h2>

<p style="font-size: 16px; color: #333333;">
To finalize your registration and make sure your account is fully set up, please take a moment to verify your email address. Just click the button below:
</p>
<table width="100%" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 20px 0;">
<a href="{{ activate_url }}" style="background-color: #FFA500; color: #ffffff; padding: 15px 50px; text-decoration: none; font-size: 16px; border-radius: 5px;">Verify Email</a>
</td>
</tr>
</table>
<p style="font-size: 16px; color: #333333;">
If the button doesn’t work, you can also verify by copying and pasting the link below into your browser:
</p>
<p style="font-size: 16px; color: #333333; font-weight: regular;">
<a href="{{ activate_url }}" style="color: #FFA500;">{{ activate_url }}</a>
</p>

<p style="font-size: 16px; color: #333333;">
Thank you for joining us, and we’re excited to have you as part of our community! If you have any questions, feel free to reach out to our support team at <a href="mailto:[email protected]" style="color: #FFA500;">[email protected]</a>.
</p>
<p style="font-size: 16px; color: #333333;">
Best,<br>
The Africa Rangeland Watch Team
</p>
</td>
</tr>
<tr>
<td style="padding: 10px; text-align: center; font-size: 14px; color: #999999;">
<p>Find the platform here <a href="https://africarangelandwatch.com" style="color: #FFA500;">Africa Rangeland Watch</a>. Not interested in emails from us? <a href="[Unsubscribe Link]" style="color: #FFA500;">Unsubscribe here</a>.</p>
</td>
</tr>
</table>
</body>
</html>
58 changes: 58 additions & 0 deletions django_project/core/templates/account/password_reset_email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset Password Request</title>
</head>
<body style="font-family: Helvetica, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="900" style="background-color: #2f5c4a; padding: 10px;">
<tr>
<!-- Left Column: Logo -->
<td align="left" width="50%" style="padding: 10px;">
<img src="{{ django_backend_url }}/static/images/main_logo.svg" alt="Logo" width="200" style="display: block;">
</td>
<!-- Right Column: Title or Navigation Links -->
<td align="right" width="50%" style="padding: 10px; color: #ffffff; font-family: Helvetica,sans-serif;">
<h2 style="margin: 0; font-size: 24px;">Africa Rangeland Watch</h2>
<!-- Optional: Add links or other content here -->
</td>
</tr>
</table>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="900" style="background-color: #ffffff; padding: 20px; border: 10px solid #dddddd;">
<tr>
<td style="padding: 20px;">
<h2 style="font-size: 20px; color: #333333;">Reset Password Request</a></h2>

<p style="font-size: 16px; color: #333333;">
Dear User,
</p>
<p style="font-size: 16px; color: #333333;">
We received a request to reset the password for your Africa Rangeland Watch account. If you made this request, simply click the link below to reset your password:
</p>
<table width="100%" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 20px 0;">
<a href="{{ reset_password_url }}" style="background-color: #FFA500; color: #ffffff; padding: 15px 50px; text-decoration: none; font-size: 16px; border-radius: 5px;">Reset Password</a>
</td>
</tr>
</table>
<p style="font-size: 16px; color: #333333;">
If you didn’t request a password reset, please ignore this email, and your password will remain the same.
</p>
<p style="font-size: 16px; color: #333333;">
If you need any further assistance, feel free to contact us at <a href="mailto:[Support Email]" style="color: #FFA500;">[Support Email]</a>.
</p>
<p style="font-size: 16px; color: #333333;">
Thank you,
<br>The Africa Rangeland Watch Team
</p>
</td>
</tr>
<td style="padding: 10px; text-align: center; font-size: 14px; color: #999999;">
<p>Find the platform here <a href="{{ django_backend_url }}" style="color: #FFA500;">Africa Rangeland Watch</a>. Not interested in emails from us? <a href="[Unsubscribe Link]" style="color: #FFA500;">Unsubscribe here</a>.</p>
</td>
</tr>
</table>
</body>
</html>
Loading

0 comments on commit 773ef97

Please sign in to comment.