how to send OTP in django via sms or email - django

I need some help sending and verifying OTP in Django. am trying to add 2FA to my authentication module and I came across a package called pyotp that helps generate and verify OTP. The good thing is that am able to use this package to generate OTP but my problem is how to verify this OTP if expired or incorrect when I prompt the user to supply the otp sent to his/her phone or mail. the below code is what I have implemented from the doc but I don't know why the verification part ain't working.
I could actually verify manually but that wont tell me if OTP has expired or not and i don't also know how to expire OTP after a particular time
TO GENERATE OTP
import pyotp
base32secret3232 = pyotp.random_base32()
otp = pyotp.TOTP(base32secret3232)
time_otp = otp.now()
user.otp = time_otp
user.save()
TO VERIFY OTP
if totp.verify(otp):
user.is_verified = True
user.save()

after much more research, I was able to fix it.
the code below will generate the otp
1. import pyotp
2. base32secret3232 = pyotp.random_base32()
3. otp = pyotp.TOTP(base32secret3232, interval=60, digits=5)
4. time_otp = otp.now()
5. user.otp = time_otp
6. user.otp_secret = base32secret3232
7. user.save()
8. send the otp to the user's email or phone
line 1 import the package after pip install
line 2 generates a secrete key,
line 3 specifies the expiry time(sec) and the number of digits the otp should have
line 4 generate an OTP from the secret key using the current time
Lines 5 and 6 save the generated secret key and otp to the database, although you might choose not to save the otp
the code below verifies the otp
1 otp = request.GET.get('otp')
2 user_id = request.GET.get('id')
3 try:
4 user = User.objects.get(otp = otp, id = user_id)
5 if pyotp.TOTP(user.otp_secrete, interval=60, digits=5).verify(otp):
6 user.is_verified = True
7 user.save()
8 return Response({'email':'Successully activated'},
status=status.HTTP_200_OK)
9 else:
return Response({'email':'Activations OTP expired or Invalid OTP'}, status=status.HTTP_400_BAD_REQUEST)
except:
return Response({'error': ' something went wrong'}, status=status.HTTP_400_BAD_REQUEST)
lines 1 and 2 get the otp from the user and also user_id
line 4 queries the database with the above details, if the user is available
line 5 verifies the supplied otp using the saved secret key.
NOTE: make sure that interval=60, digits=5 used in generating the otp is the same as the one used in verifying the otp else it won't work.
thanks and i hope it helps someone out there

Related

Email conflict while login by different socials

I have python-django backend, that allows u to sign in through fb, apple, email, google. My email field is unique, so I can't have more than one user with single email.
When user sign in with socials I take his email and create new user.
Problem is, if u have two socials with single email, u can't use both of them to sign in. It works like:
We have Facebook and appleId with same email
Sign in with apple -> I create user with appleId, name, email -> user press logout -> user press sign in with Facebook -> I can't create new user because I have that email in db already.
So the question is, what should I do and where I can find examples of it.
Details: I have custom Django User and I have to take email in any case. I can't use Django-social.
I think on the last step I should give user profile, that was made in second step, but I don't know how to google this problem and how its done common practice
when someone logs in through FB or Google and its email not present in the local account, It creates a new social account. If the email present in local accounts matches with google or Facebook accounts while logging in through this, it only authenticates it (no need to put local account password). I also saved few things into the User during #receiver(user_signed_up).
This code solved the conflict between same google and Facebook using the same email id
I did not use verification, you can use it if you want
class MyAppSocialAccountAdapter(DefaultSocialAccountAdapter):
# login(request, user) Before we did this
#transaction.atomic
def pre_social_login(self, request, sociallogin):
# social account already exists, so no need to do anything Auto login will happen
if sociallogin.is_existing:
return
# some social logins don't have an email address, e.g. facebook accounts
# with mobile numbers only, but allauth takes care of this case so just
# ignore it
if 'email' not in sociallogin.account.extra_data:
return
# find the first verified email that we get from this sociallogin
# verified_email = None
# for email in sociallogin.email_addresses:
# if email.verified:
# verified_email = email
# break
try:
user = User.objects.get(email=sociallogin.email_addresses[0])
# This user now can be authenticated without password through google or facebook
sociallogin.connect(request, user)
raise ImmediateHttpResponse(redirect('logout_process')) # send it back to login
except Exception as e:
print(e)
# if social account does not exist, it creates one by default

Django send_mail method : Include session userid in mail message

I was able to use send_mail method and it works without any problem.
What I am trying to achieve is to include session's username in mail message.
My views.py allow a certain authenticated user to create numbers. On successful addition of numbers, an email is triggered to the administrators, which at the moment does not include user's userid. So the administrators have no way of knowing which user created the numbers.
My attempt to get userid displayed in mail body below. I also tried another variant -
#send mail
subject= 'Numbers created by {request.user}'
message = 'This user {request.user} has created numbers. '
from_email= settings.EMAIL_HOST_USER
to_list = [settings.EMAIL_ADMIN]
Thanks #MohitC, I did miss f string format and plus, I was incorrectly using ```request.user method. username = request.user
subject= f" Numbers created by {username}"

How to Create a Unique One Time Reset Password Link in Django?

I have Django website which users can register and login with their phone number.
Recently I decide to add a recover password with phone number part to my site,I read Authentication Views in Django which users can reset their password by sending them an email but first of all it use email to reset password and second it use django built-in views, but I want a function in my view which generate a unique one time reset password link then I send this link to them with my sms api so they can reset their password using this link.
So how can I generate reset password link in a secure way?
the only idea that I've got is to implement a model which store a random string with OnetoOne relation and use it as a reset password link.
Yeah your solution is good idea, I think
Better implementation of your idea is:
Add following in your settings:
JWT_SECRET = SECRET_KEY # use settings secret key for JWT secret
JWT_ALGORITHM = 'HS256'
JWT_EXP_DELTA_SECONDS = 86400 # token expiring time in seconds let's assign one day
and here are the functions to encode and decode the reset token:
from your_django_project import settings
from datetime import datetime, timedelta
import jwt
def encoded_reset_token(user_id):
payload = {
'user_id': user_id,
'exp': datetime.utcnow() + timedelta(seconds=settings.JWT_EXP_DELTA_SECONDS)
}
encoded_data = jwt.encode(payload, settings.JWT_SECRET, settings.JWT_ALGORITHM)
return encoded_data.decode('utf-8')
def decode_reset_token(reset_token):
try:
decoded_data = jwt.decode(reset_token, settings.JWT_SECRET,
algorithms=[settings.JWT_ALGORITHM])
except (jwt.DecodeError, jwt.ExpiredSignatureError):
return None # means expired token
return decoded_data['user_id']
so there is no need to use extra table to store your reset tokens

django tests response.request.user.is_authenticated() returning True after logout

I am trying to write some tests for the authentication part of my application and I encountered a problem with checking if the user is logged in or not. Here's the code:
client = Client()
# user signup form
response = client.post(signup_url, data={
'email': "lorem#ipsum.pl",
'password1': 'hunter2',
'password2': 'hunter2',
}, follow=True)
# checking if the user is logged in
with self.assertRaises(KeyError):
client.session['_auth_user_id']
self.assertEquals(len(mail.outbox), 1)
url = find_verification_url(mail.outbox[0].body)
response = client.get(url, follow=True)
self.assertEqual(200, response.status_code)
user = User.objects.get(email="lorem#ipsum.pl")
self.assertEqual(client.session['_auth_user_id'], user.pk)
# how to logout a user?
force_logout()
self.assertFalse(response.request.user.is_authenticated())
The user fills the form and clicks submit, then receives an email with a verification url. After he clicks the verification url in the email he's supposed to get directed to the site and authenticated. My questions is, what is a good way to find out if the user is authenticated or not? What is a preferred way to log out a user in this situation? I want to check that if the user is logged out and clicks the link the verification link second time it doesn't work. I tried some things like:
client.logout()
But unfortunately it seems to work once every two times even when I remove this line.
Thanks for any help!
Ok so the problem was that the authentication system was using a timestamp function to know if a url was expired or not. When run in a test the verification url was not expired when it should be. The login request after the logout was too fast and the system was thinking that the verification url was still valid and the user got authenticated. And that's why user.is_authenticated() was returning True all the time.

Some user attributes not showing up in django admin

In my custom authentication backend I extract the username, email, first and last name from an LDAP response and try to stick them into a newly generated User object if the user doesn't yet exist:
user = User(username=username, email=result[0][1].get('mail')[0], first_name=result[0][1].get('givenName')[0], last_name=result[0][1].get('sn')[0])
user.save()
And another variant I tried:
user = User.objects.create_user(username, result[0][1].get('mail')[0])
user.first_name = result[0][1].get('givenName')[0]
user.last_name = result[0][1].get('sn')[0]
user.save()
While the username and email show up in the admin after the user's initial successful authentication attempt I can't get the first and last name to display. Logging the values from the LDAP response shows that these exist.
Any idea what's going wrong here?
Ok, it was indeed my own stupidity: should not only have restarted the frontend webserver but also uWSGI! I could add to my defense that these are my baby steps with uWSGI...