Email confirmation in Flask fails - python

The below code successfully generates a token and sends a link to the user's inbox for confirmation. But when the user clicks on it, Flask is not recognizing the token it just created. Here is the error message:
"Got exception from ts.loads: 404 Not Found: The requested URL was not
found on the server. If you entered the URL manually please check
your spelling and try again."
The bottom line is that this is what should execute if I could make the confirmation procedure work properly:
return redirect(url_for('tutorials'))
But, as you can piece together by noting the error message that is coming out of #app.errorhandler(404), something is going wrong. I'm really stuck. These tests are being done way before the max_age of 86400 seconds is reached. Any help would be much appreciated!!!
from itsdangerous import URLSafeTimedSerializer
ts = URLSafeTimedSerializer(SECRET_KEY, salt='email-confirm-key')
#app.route('/signup', methods=['GET', 'POST'])
def signup():
#skipping unrelated lines of code
token = ts.dumps(form.email.data, salt='email-confirm-key')
subject = 'subject goes here'
msg = Message(subject=subject, sender='name#email.com', recipients=form.email.data.split())
link = url_for('confirm_email', token=token, _external=True)
msg.html = render_template("email_confirmationemail.html", link=link, name=request.form['first_name'])
with app.app_context():
mail.send(msg)
return redirect(url_for('checkyouremail'))
#app.route('/confirmemail/<token>')
def confirm_email(token):
try:
email = ts.loads(token, salt='email-confirm-key', max_age=86400)
#skipping unrelated lines of code
return redirect(url_for('tutorials'))
#app.errorhandler(404)
def not_found(e):
print('Got exception from ts.loads: {}'.format(e))
return render_template('404.html')

In models.py, my __init__ method for the User class has this line:
self.email = email.lower()
When users create a profile on a phone, their email address often starts with an uppercase letter.
So I just needed to change
token = ts.dumps(form.email.data, salt='email-confirm-key')
to
token = ts.dumps(form.email.data.lower(), salt='email-confirm-key')
so that the email held in the token matched with the email in the database when the user clicked on the confirmation link I sent them. (In short, adding .lower() as shown above in my call do dumps solved my problem).

Related

Unable to load correct page in Bottle Python

i'm having a hard time understanding why i can't load the "/ticket" page properly, but i can load the "/" fine. I should have the /ticket page running and able to type the information under username and email but i'm missing something.
I'm running Python 3.10.4
"localhost:8080/"
"localhost:8080/ticket"
import os
import bottle
import base64
import traceback
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from binascii import hexlify
from html import escape
from bottle import route, request, get, post, response
page = '''
<html>
<head><title>Welcome</title></head>
<body>
<p><h2>Welcome, %s!</h2></p>
<p><h3>There are %s tickets left</h3></p>
<p>%s</p>
</body>
</html>
'''
key = os.urandom(32)
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
def get_ticket(username, email, amount):
encryptor = cipher.encryptor()
padder = padding.PKCS7(128).padder()
message = padder.update("%s&%s&%d" % (username, email, amount)) + padder.finalize()
ct = encryptor.update(message) + encryptor.finalize()
return base64.b64encode(ct)
#bottle.route('/', method=('GET', 'POST'))
def default():
decryptor = cipher.decryptor()
unpadder = padding.PKCS7(128).unpadder()
try:
ticket = bottle.request.get_cookie('ticket')
if ticket:
message = decryptor.update(base64.b64decode(ticket)) + decryptor.finalize()
username, email, amount = (unpadder.update(message) + unpadder.finalize()).split('&')
return page % (escape(username), escape(amount),
'Already done' if amount == '1' else
'Go ahead')
else:
return '<html><body><p><a href=/ticket>Fill in the forms</a></p></body><html>\n'
except Exception as e:
print(traceback.format_exc())
raise bottle.HTTPError(status=401, body=e.message)
#bottle.post('/ticket', ['GET', 'POST'])
def ticket():
username = bottle.request.forms.get('username')
email = bottle.request.forms.get('email')
if username and email:
ticket = get_ticket(username, email, 1)
bottle.response.set_cookie('ticket', ticket)
return '<html><body><p>Thank you!</p></body></html>\n'
raise bottle.HTTPError(status=400, body='Please fill in the form')
bottle.run(host='localhost', port=8080)
The reason this code hits an error is that you never actually serve a form for the user to fill in (and thus your code correctly errors). I don't really understand your flow at all, but I think it's supposed to look something like this:
user navigates to /
if user doesn't have a ticket, user is sent to a login page
user logs in and gets a ticket
user goes back to / and requests it, this time with a ticket (authentication cookie) in the get request.
Mind you I don't understand what these tickets are, as they appear to be a hashed form of whatever the user submits and the constant 1, but I guess there's more logic coming along later.
Currently there are quite a few problems with the setup:
/ accepts post requests, but never does anything with them. It also doesn't make sense for it to accept post requests, since POST is to send data to a server.
your /ticket endpoint would do what you want if you sent it a form, but you never serve a form for the user to fill in.
your /ticket endpoint needs to return one thing if you make a GET request to it, another completely different thing if you make a POST request. This is possible, but it's a bad way to build these endpoints in general.
after getting the cookie the browser is simply going to throw it away. If you want to do cookie-based authentication, you need to store the cookie and handle it (normally you use this with JS in the webbrowser, and take care to add it to all requests).
If we add a login endpoint:
#bottle.route("/login")
def login():
return """<html><body>
<form action="./ticket" method="POST">
Username: <input type="text" name="username"><br>
Email: <input type="text" name="email"><br>
<input type="submit">
</form>
</body></html>"""
And edit the previous href to point us to /login, we now get a form. Filling that form in and pressing 'submit' correctly sends the data to the /ticket endpoint, calling the code to get the ticket. Unfortunately that fails for unrelated reasons (your hashing needs bytes, not strings) but that's a different question.
I suggest you have a think about the overall flow of this application---what are these tickets for, and where is the user supposed to end up? Regardless you will need to serve some kind of login form at some point. You probably want this to be a static file, by the way, and served as such---see the bottle docs.

Sending emails from my django website, via sendgrid

I want to send account confirmation emails to all the users who sign up on my website, so it has to be an HTML email with the activation link. This is how i am trying to do it:
import os
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
def my_view(request):
current_site = get_current_site(request)
mail_subject = 'Welcome To IIITU Alumni Network'
to_email = email
ctx = {
'user': user,
'domain': current_site.domain,
'uid':urlsafe_base64_encode(force_bytes(user.pk)),
'token':account_activation_token.make_token(user),
}
message = Mail(
to_emails = to_email,
from_email = '<my email address>',
subject = mail_subject,
#I made this html page specifically for sending confirmation email
#I am not sure if this is the correct way to do it
html_content = render_to_string('network/email.html', ctx)
)
try:
#I have this key as an environment variable
sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
#Now this is where the problem is
response = sg.send(message)
print(response.status_code)
print(response.body)
print(response.headers)
return HttpResponse("A confirmation Email has been sent to your Institute email address. Please Confirm Your email to complete your registration")
except Exception as e:
print(e.message)
return HttpResponse("The Server seems too busy!! Sorry for the inconvenience. Please try again later, or contact administrator if this problem persists.")
When this particular view is rendered, it throws the following error message:
'UnauthorizedError' object has no attribute 'message'
Since this doesn't send the emails via an SMTP server, according to my understanding i don't need to add anything to my settings.py file(i did add 'sendgrid' to my installed apps after i installed it using pip)Can someone please point out what i did wrong? I have been stuck on this for a while now...
Thank you for your time
I think this line is causing an error.
...
except Exception as e:
print(e.message)
...
Whatever exception is occuring, it doens't have message property.
Two solutions.
print(e)
remove try except block and try to figure out which exception is occurring and then catch that specifically.

How to do a "typing_on" post with pymessenger to FB messenger?

I'm trying to set my chatbot to display a typing bubble before sending the user the response so that it's more human like. I followed the documentation but am still getting instant responses from the bot with no typing bubble. As per the documentation I am using this sender action:
{"recipient":{"id":recipient_id}, "sender_action":"typing_on"}
And to put it in context, here is how I'm using it in the flask app:
#app.route('/', methods=['GET', 'POST'])
def receive_message():
global tag, latest_message
if request.method == 'GET':
# Before allowing people to message your bot Facebook has implemented a verify token
# that confirms all requests that your bot receives came from Facebook.
token_sent = request.args.get("hub.verify_token")
return verify_fb_token(token_sent)
# If the request was not GET, it must be POSTand we can just proceed with sending a message
# back to user
else:
# get whatever message a user sent the bot
output = request.get_json()
for event in output['entry']:
messaging = event['messaging']
for message in messaging:
if message.get('message'):
# Facebook Messenger ID for user so we know where to send response back to
recipient_id = message['sender']['id']
if message['message'].get('text'):
response_sent_text = send(message['message'].get('text'))
send_message(recipient_id, response_sent_text)
latest_message = response_sent_text
return "Message Processed"
def send_message(recipient_id, response):
# sends user the text message provided via input response parameter
typing_payload = {"recipient":{"id":recipient_id}, "sender_action":"typing_on"}
bot.send_raw(typing_payload)
print(bot.send_raw(typing_payload))
bot.send_text_message(recipient_id, response)
return "success"
Any ideas would be much appreciated!
You should pass a string representation of the payload (not a dictionary).
try doing something like this:
import json
...
def send_message(recipient_id, response):
# sends user the text message provided via input response parameter
typing_payload = json.dumps({"recipient":{"id":recipient_id}, "sender_action":"typing_on"})
bot.send_raw(typing_payload)
print(bot.send_raw(typing_payload))
bot.send_text_message(recipient_id, response)
return "success"
Also it's best practice to send these indicators upon receiving message (to show the typing indicator while processing), as opposed to directly before sending message.

tornado app failing to set cookie after FB login with OAuth

I have written a tornado app which uses FB authentication. First it gets permission from the user & gets an access token. And then it makes another call to the FB with the access token, get the email id and save it in a cookie.
Following is the handler:
class FAuthLoginHandler(tornado.web.RequestHandler, tornado.auth.FacebookGraphMixin):
#tornado.web.asynchronous
def get(self):
user_id = self.get_secure_cookie('trakr')
if self.get_argument('code', None):
self.get_authenticated_user(
redirect_uri=settings.redirect_url,
client_id=self.settings['facebook_api_key'],
client_secret=self.settings['facebook_secret'],
code=self.get_argument('code'),
callback=self.async_callback(self._on_facebook_login))
return
elif self.get_secure_cookie('access_token'):
self.redirect('/')
return
self.authorize_redirect(
redirect_uri=settings.redirect_url,
client_id=self.settings['facebook_api_key'],
extra_params={'scope': 'email'}
)
def _on_facebook_login(self, user):
if not user:
self.clear_all_cookies()
raise tornado.web.HTTPError(500, 'Facebook authentication failed')
# print "http//graph.facebook.com/%s/picture" % str(user['id'])
# print "http//graph.facebook.com/%s" % str(user['id'])
self.set_secure_cookie('fb_id', str(user['id']))
self.set_secure_cookie('access_token', str(user['access_token']))
self.facebook_request("/me", access_token=user["access_token"], callback=self.async_callback(self._save_user_profile))
self.redirect('/')
def _save_user_profile(self, user):
if not user:
raise tornado.web.HTTPError(500, "Facebook authentication failed.")
# print user
print user.get('email')
self.set_secure_cookie('trakr', user.get('email'))
print self.get_secure_cookie('trakr')
Inside _save_user_profile, the first print statement prints the email id the user logged in. It successfully prints my email. Then I try to save that in the cookie, but when I print that I am getting response as None
I thought I messed with using classes & may be I was using wrong self. I printed id of self in several places, seems it's same. And I also printed dir(self) and it does have set_secure_cookie function. So why it is failing to set the cookie?
You're redirecting the user away before you have a chance to set the cookie. Remove the redirect call in _on_facebook_login and move it to the end of _save_user_profile.
Also, you can't read a cookie back immediately after you set it - setting cookies will take effect on the next request; the get_cookie APIs return the cookies that were present when the request came in.

Python Flask Mailchimp signup

I'm trying to use PostMonkey & Flask to HTTP GET an email address (from a from on my website) and then subscribe this to the specified list.
It works and sends the email requesting the user to confirm the subscription, but either server error 500's or when debug mode is on it comes up with
TypeError: signup() takes no arguments (2 given)
Here's my code:
#app.route("/signup", methods=['GET'])
def signup():
try:
email = request.args.get('email')
pm.listSubscribe(id="cdc2ba625c", email_address=email)
except MailChimpException, e:
print e.code
print e.error
return redirect("/")
return signup
I'm not sure what's causing it and it's been bugging me for a while!
If anyone is interested the problem was related to my 'Return' statement, turns out flask doesn't like returning nothing.
#app.route('/signup', methods=['POST'])
def signup():
try:
email = request.form['email']
#email = request.args.get('email')
pm.listSubscribe(id="cdc2ba625c", email_address=email, double_optin=False)
except MailChimpException, e:
print e.code
print e.error
return redirect("/")
return render_template('index.html')
Thanks to all those that commented back

Categories