Django password reset with email using REST APIs - python

I'm developing a website using DjangoRest and Flutter and I want to add password reset using email.
I know that django.contrib.auth has views that help with password reset (PasswordResetView, PasswordResetDoneView, etc). But as long as I see, they return HTML files as a response when you call them in Postman.
Is there any way to use the same easy-to-use views but instead of getting HTML files, get an HTTP response so it can be called by the Flutter app?

This can be handled in basically Two Steps:
1. One is to send anything like OTP or Reset Link view email
2. The second is to verify whether the OTP/link either is valid or not with a new password.
This can be achieved via simple function-based API views:
I can demonstrate the simplest form using OTP for basic understanding, as u said you are using flutter at frontend and that will be easier to manage otp instead of link
Step 1: Add a top Field into User Model.
Let's say we have field otp in the user model. Later we use it for verification purposes.
class CustomerUser(models.Model):
#...
otp = models.CharField(
max_length=6, null=True, blank=True)
# Method to Put a Random OTP in the CustomerUser table.
def save(self, *args, **kwargs):
number_list = [x for x in range(10)] # Use of list comprehension
code_items_for_otp = []
for i in range(6):
num = random.choice(number_list)
code_items_for_otp.append(num)
code_string = "".join(str(item)
for item in code_items_for_otp) # list comprehension again
# A six digit random number from the list will be saved in top field
self.otp = code_string
super().save(*args, **kwargs)
Step:2: Function to send Email with OTP on User Request
#api_view(['POST'])
def reset_request(request):
data = request.data
email = data['email']
user = CustomUser.objects.get(email=email)
if CustomUser.objects.filter(email=email).exists():
# send email with otp
send_mail(
'Subject here',
f'Here is the message with {user.otp}.',
'from#example.com',
[user.email],
fail_silently=False,
)
message = {
'detail': 'Success Message'}
return Response(message, status=status.HTTP_200_OK)
else:
message = {
'detail': 'Some Error Message'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
Last Step: Verify OTP And reset Password
#api_view(['PUT'])
def reset_password(request):
"""reset_password with email, OTP and new password"""
data = request.data
user = CustomUser.objects.get(email=data['email'])
if user.is_active:
# Check if otp is valid
if data['otp'] == user.opt:
if new_password != '':
# Change Password
user.set_password(data['password'])
user.save() # Here user otp will also be changed on save automatically
return Response('any response or you can add useful information with response as well. ')
else:
message = {
'detail': 'Password cant be empty'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
else:
message = {
'detail': 'OTP did not matched'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
else:
message = {
'detail': 'Something went wrong'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
So you can replicate it with your custom approach as well and can refactor it easily.
I have used Simple API views in these examples you can check the detailed information in DRF DOCS Requests and Response Section as well
So You don't have to use HTML at all, just can work with Response, HttpResponse whatever you prefer.

Related

Django - request.user returns AnonymousUser with custom user

I am having some problems on the authentication part for my Django app, using a CustomUser.
The logic is the following: I need to send credentials (email/password) to an external API, from which I retrieve the access token which will be used on the later requests. During the process, I also create (or update, if it's already there) a CustomUser inside my local db, with the same credentials as in the external database. Then I try to authenticate the user in my app with the same credentials.
Below the relevant parts of code:
models.py:
class CustomUser(AbstractUser):
email = models.EmailField("Email", max_length=255, unique=True, null=True)
custom_user_id = models.IntegerField("Custom user id", unique=True, null=True)
name = models.CharField("Name", max_length=255, null=True)
initials = models.CharField("Initials", max_length=255, null=True)
views.py
from django.contrib.auth import login as auth_login
#api_view(['POST'])
#never_cache
def user_login(request):
''' User login '''
if request.method == 'POST':
url = "THE EXTERNAL API"
payload = {
'email':request.data['email'],
'password':request.data['password']
}
headers = {
'Origin': 'REDACTED',
'Content-Type': 'text/plain'
}
email = request.data['email']
username = email
password = request.data['password']
payload = '{"email":"' + email + ',"password":"' + password + '}'
r = requests.post(url, data = payload, headers=headers)
if r.status_code == 200:
data = r.json()
# Get user // create one if it doesn't exist yet
user, created = CustomUser.objects.update_or_create(
custom_user_id = data['data']['uid'],
defaults = {
'username': username,
'initials': data['data']['initials'],
'email': data['data']['email'],
'name': data['data']['name']
})
#trying to login user
auth_login(request, user)
request.session['access_token'] = data['data']['access_token']
First all, this code works perfectly fine when I run the app in local - in fact I can see that request.user correctly stores the logged user inside any other views, and the #login_required decorator works as expected.
The problems arise when I run the same app on my deployed version (aws ec2 with nginx). In this case, 'request.user' contains the correct user only inside this login view. But if I print request.user in any other view, it actually returns AnonymousUser, as if the auth_login function has actually failed.
As a result, the #login_required decorathor no longer works properly (because request.user does not contain the authenticated user). Strangely, its counterpart for class-based view (LoginRequiredMixin) works perfectly fine instead (not sure if they look at different things?), however I have a mix of function-based and class-based views in my app and I can't convert everything into class-based view (also I'd like to solve the issue at the root).
Another strange thing is, despite the fact that request.user contains AnonymousUser, request.session contains the right credentials of the user (for example I'm able to retrieve the ID of the logged user with request.session['_auth_user_id']
If the authentication has failed, then why request.session correctly contains the information about the logged user?
I also tried the following, based on the answer on a similar question on SO:
from django.contrib.auth import authenticate
user = authenticate(username=username, password=password)
However it didn't solve anything.

How to send a reset password email in Django on User creation?

I want to be able to let an admin create user accounts and then, instead of setting up a password for the user, the user would automatically receive a reset password email.
The view for the user creation, which also includes a Member model, is the following:
def newmember(request):
if request.method == 'POST':
nu_form = NewUser(request.POST)
nm_form = NewMember(request.POST)
if nu_form.is_valid() and nm_form.is_valid():
nusave = nu_form.save()
nmsave = nm_form.save(commit = False)
nmsave.user = nusave
nmsave.save()
return redirect(members)
else:
print(nu_form.errors)
print(nm_form.errors)
else:
nu_form = NewUser()
nm_form = NewMember()
context = {
'nu_form': nu_form,
'nm_form': nm_form}
return render(request, 'web/newmember.html', context)
How can I make so that upon creation of a new user, Django automatically sends an email to that new user requestion a password reset?
In order to send an email on user creation you need to define a method which shoot an email like below :-
Create a text file name such as 'email_content.txt'
Please reset password for your profile {{username}}
Click Here
Update the newmember method and add below code into it :-
template = get_template('email_content.txt')
context = {"usename": nmsave.user.username}
content = template.render(context)
email = EmailMessage(
"Congratulation, please reset your account password", content, 'App Name' <sender_email>
)
email.content_subtype = "html"
email.send()
add above code in try catch block
In your models.py:
def save(self, *args, **kwargs):
send_mail('subject', 'message', 'your email', 'user email')
return super().save(*args, **kwargs)

Get data from a APIview in django rest framework

In my app I am trying to get the User data using a get request and it works in Postman but the problem occurs when I try to send the data from FrontEnd as we cannot send Body in get Request
URL:
path('user/meta/', UserDetails.as_view()),
Views.py
class UserDetails(APIView):
"""Get basic details of user"""
def get(self, request):
username = request.data["username"]
if User.objects.filter(username = username).exists():
user = User.objects.get(username = username)
print(user)
return Response({
'verified': user.verified,
})
else:
return Response("User doesn't exists", status=status.HTTP_400_BAD_REQUEST)
How should I modify it such that I can get the data from get request?
So, your requesting URL will become /user-info/?username=john
and then, use request.GET
username = request.GET.get("username","default_value")
The good practice is to use query params in such case,
so your url will look like,
/user-info/?username=john
and you modify your code to,
username= self.request.query_params.get('username')
I know this is an old topic and the question is answered, but just to underline that the queried data, you obtained from the user in the API should go through the Serializers (https://www.django-rest-framework.org/tutorial/1-serialization/) to at least somehow protect against unwanted values. This will also avoid you directly querying the database models.

Django testing in creating a user with email address which is from session input, assertRedirects is not working

The user creation is using an email address as USERNAME_FIELD and it is extracted from session and save in the form save(). It seems it is not going further down to the redirection. How can I test the redirection in this case?
tests.py:
class RegistraionViewTest(TestCase):
valid_data = {
'email': 'good#day.com',
'password1': 'test1234',
}
kwargs = {
'email': 'good#day.com'
}
def test_registration(self):
response = self.client.post(reverse('registration'), data=self.valid_data, follow=True)
self.assertTrue(response.context['form'].is_valid())
# mocking the session input
response.context['form'].save(email=self.kwargs['email'])
self.assertTrue(account.check_password(self.valid_data['password1']))
# working so far, but it seems there is no redirect url in response
self.assertRedirects(response, reverse('next_url'))
In views.py:
if request.method == 'POST':
form = RegistraionForm(request.POST)
if form.is_valid():
email = request.session.get('email')
try:
account = form.save(email=email)
return HttpResponseRedirect('next_url'))
In forms.py:
def save(self, **kwargs):
user = super(RegistrationForm, self).save(commit=False)
user.email = kwargs.pop('email')
user.save()
return user
It seems there is no url in the response in tests.py. What went wrong here?
Your response may be a 500, not a 302, which would mean there is no Location header.
The call for request.session.get('email') will likely throw a KeyError, as your test does not appear to set the session['email'] field, and there is no default.
Note that when using a session in a test case, you need to assign it to a variable in the beginning, as in the example below (from Django Testing Tool docs):
def test_registration(self):
session = self.client.session
session['email'] = self.kwargs['email']
session.save()
# and now make your call to self.client.post
response = self.client.post(...)
self.assertEqual(response.status_code,302)
# .. and the rest

Django rest framework check if user exist with password

The code I'm showing you below its what works for me right now. its not the most secure but does the job but i want to do it using POST method. any ideas how to change it?
I have a serializer.py class
class userLoginSerializer(serializers.ModelSerializer):
class Meta:
model = users
fields = ('nick', 'pass_field')
#api_view(['GET'])
def user_login(request,nick,pass_field):
but when i sent the 2 values nick and passfield it says that the nick already exist and returns 404 because it passes it to serializers.errors. I just need to pass the code using POST and validating if it exist and return a success JSON. The code below works but its not the best implementation.
if request.method == 'GET':
try:
users.objects.get(nick=nick,pass_field=pass_field)
json = {}
json['message'] = 'success'
return Response(json, status=status.HTTP_201_CREATED)
except users.DoesNotExist:
json = {}
json['message'] = 'error'
return Response(json, status=status.HTTP_400_BAD_REQUEST)
The models is users or User? Why don't you use the Django User model?
The class User has already a check_password method and store it with a hash algoritm: https://docs.djangoproject.com/en/dev/ref/contrib/auth/#methods
Never store a password in plain text, it's very insecure.
Using Django User model (or a class that inherits from it) you can simply check if it's valid this way:
try:
user = User.objects.get(username=nick)
if user.check_password(pass_field):
#TODO: Valid password, insert your code here
else:
#TODO: Password not valid, handle it here
pass
except User.DoesNotExist:
#TODO: Your error handler goes here
pass
Another thing you can do is inherits from ApiView and implement your code in post method: http://www.django-rest-framework.org/api-guide/views
I implemented a method for Sign In with JWT and what it does is:
Fetches the email and password that is send with the request and
converts it into a string variable
I check if the email already
exists in the custom user model i made.
If the user already
exists, i convert the object model to dictionary so that i can get
its particular password.
In that i match the password
corresponding to user model and the password that is send with the
post request.
if the email exists in the user model and the password corresponding to that user model matches the password that is sent with the post request i use the pyJWT to make the JWT with my custom data and return the response.
In all other cases the email and password don't match and i return "No Match"
Suppose the request is {"email":"xyz#gmail.com", "password":"12345" }
#api_view(['POST'])
def signin(request):
email = list(request.data.values())[0] #gets email value from post request {"email":"xyz#gmail.com", "password":"123"} -> this xyz#gmail.com
password = list(request.data.values())[1] #gets password value from post request {"email":"xyz#gmail.com", "password":"123"} -> this 123
usr = User.objects.filter(email=email).exists() #checks if email exists
if usr:
dictionary = User.objects.filter(email=email).values()[0] #converts object to dictionary for accessing data like dictionary["password"] dictionary["first_name"] etc
if usr and dictionary["password"] == password: #check if email and its corresponing password stored matches the password that is sent
branch = dictionary["branch"]
id = dictionary["id"]
encoded_jwt = jwt.encode({'email': email,}, 'secret', algorithm='HS256')
return Response({'token':encoded_jwt,'email':email,'branch':branch,'id':id})
else:
return Response({'No Match'})
return Response({'No Match'})

Categories

Resources