Reputation: 328
I am creating a captcha just for an exercise. The creation of the captcha images seem fine. But every time I try validating the captcha challenge entered by the user, the validation is done against the next captcha. I am stuck at how to go with this. Function for creating captcha images- captcha.py
import random
import Image
import ImageFont
import ImageDraw
import ImageFilter
import JpegImagePlugin
import PngImagePlugin
def gen_captcha(text, fnt, fnt_sz, file_name, fmt='JPEG'):
fgcolor = random.randint(0,0xff0000)
bgcolor = fgcolor ^ 0xffffff
font = ImageFont.truetype(fnt,fnt_sz)
dim = font.getsize(text)
im = Image.new('RGB', (dim[0]+5,dim[1]+5), bgcolor)
d = ImageDraw.Draw(im)
x, y = im.size
r = random.randint
for num in range(100):
d.rectangle((r(0,x),r(0,y),r(0,x),r(0,y)),fill=r(0,0xffff00))
d.text((3,3), text, font=font, fill=fgcolor)
im = im.filter(ImageFilter.EDGE_ENHANCE_MORE)
im.save(file_name)
signup function from views.py
@app.route('/signup', methods = ['GET', 'POST'])
def signup():
if g.user is not None and g.user.is_authenticated():
return redirect(url_for('index'))
words = open('app/corncob_caps.txt').readlines()
captcha_word = words[random.randint(1,len(words))]
captcha_filename = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10)) + '.jpg'
captcha.gen_captcha(captcha_word.strip(), 'app/os.ttf', 25, 'app/static/' + captcha_filename + '')
form = SignUpForm(captcha_word)
if form.validate_on_submit() == False:
return render_template('signup.html', form = form, filename = captcha_filename)
else:
user = User(form.email.data, form.password.data)
db.session.add(user)
db.session.commit()
flash('You have successfully signed up.')
flash('You may login now.')
return redirect(url_for('login'))
return render_template('signup.html', form = form, filename = captcha_filename)
I am passing the captcha_word to my form class. The form class is:
class SignUpForm(Form):
email = EmailField('Email Address', validators = [email()])
password = PasswordField('Password', validators = [Required('Please enter a valid password between 8 and 30 characters.'), Length(min = 8, max = 30)])
captcha = TextField('Captcha', validators = [Required('You must enter the challenge captcha.')])
submit = SubmitField('Create Account')
captcha_word = ''
def __init__(self, word, *args, **kwargs):
Form.__init__(self, *args, **kwargs)
self.get_word(word)
def get_word(self, word):
self.captcha_word = word
def validate(self):
if not Form.validate(self):
return False
elif self.captcha_word != self.captcha.data.upper():
print self.captcha_word
print self.captcha.data.upper()
self.captcha.errors.append("Wrong captcha!")
return False
user = self.get_user()
if user:
self.email.errors.append("That email is already taken.")
return False
else:
return True
def get_user(self):
return User.query.filter_by(email = self.email.data.lower()).first()
I inserted the two print statements inside to see why the comparison was coming wrong. The first print showed the next captcha whereas the print self.captcha.data.upper()
displayed the user entered data.
I am not sure, but it seems the signup route is being called twice. But I don't know how to fix this. Any ideas?
Upvotes: 2
Views: 10748
Reputation: 9440
If you need to use a captcha, you can use the feature that's already built into Flask-WTF and save yourself reinventing the wheel.
If you do want to reinvent the wheel, then the main problem you're having is that you're recreating the captcha when the user submits the form, you have no way to remember and refer to the old value.
So this is how it's working at the moment:
So you're missing persistence, the captcha you generate the first time round doesn't get held anywhere, so when the user submits you've got no way to refer back to it. So you need to store that captcha word somewhere. You could simply just store that captcha word in the user's session and use that to validate against when you need to, or perhaps sign it with itsdangerous and store it in the form itself as a hidden field?
This just takes your code and adjusts it a little to store the value in the session-- not tested, and can definitely been improved, but should work:
@app.route('/signup', methods = ['GET', 'POST'])
def signup():
if g.user is not None and g.user.is_authenticated():
return redirect(url_for('index'))
if request.method == 'post':
captcha_word = session["captcha"]
else:
words = open('app/corncob_caps.txt').readlines()
captcha_word = words[random.randint(1,len(words))]
session["captcha"] = captcha_word
captcha_filename = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10)) + '.jpg'
captcha.gen_captcha(captcha_word.strip(), 'app/os.ttf', 25, 'app/static/' + captcha_filename + '')
form = SignUpForm(captcha_word)
if form.validate_on_submit() == False:
return render_template('signup.html', form = form, filename = captcha_filename)
else:
user = User(form.email.data, form.password.data)
db.session.add(user)
db.session.commit()
flash('You have successfully signed up.')
flash('You may login now.')
return redirect(url_for('login'))
return render_template('signup.html', form = form, filename = captcha_filename)
Upvotes: 4