2022-04-11 23:44:17 -04:00
from flask import Flask , render_template , request , make_response , redirect
from freecaptcha import captcha
import uuid
2023-06-12 09:56:17 -04:00
import os
2023-06-29 17:01:21 -04:00
from cryptography . fernet import Fernet
import jwt
import base64
# Generate a new AES key
key = Fernet . generate_key ( )
JWT_SECRET_KEY = ' obviously this needs to be an env variable secret thingie. '
# Create an instance of the Fernet cipher with the key
cipher = Fernet ( key )
# Encrypt a message
message = b ' This is a secret message. '
ciphertext = cipher . encrypt ( message )
# Decrypt the ciphertext
2022-04-11 23:44:17 -04:00
app = Flask ( __name__ , static_url_path = ' ' , static_folder = ' static ' , )
2023-06-29 17:01:21 -04:00
@app.route ( " /captcha " , methods = [ ' GET ' , ' POST ' ] )
def captcha_handler ( ) :
if request . method == " POST " :
captcha_attempt = len ( list ( request . form ) )
token = request . cookies . get ( ' Authorization ' ) . split ( ' Bearer ' ) [ - 1 ]
try :
# TODO: set JWT to expire very soon.
payload = jwt . decode ( token , JWT_SECRET_KEY , algorithms = [ ' HS256 ' ] )
b64_and_encrypted_correct_answer = payload [ ' encrypted_correct_answer ' ]
encrypted_correct_answer_bytes = base64 . b64decode ( b64_and_encrypted_correct_answer )
correct_answer = cipher . decrypt ( encrypted_correct_answer_bytes ) . decode ( ' utf-8 ' ) . split ( ' ||| ' ) [ 0 ]
return f '''
The correct answer was { correct_answer }
You flipped it { captcha_attempt }
'''
except jwt . ExpiredSignatureError :
return ' Token expired. Please log in again. '
except jwt . InvalidTokenError :
return ' Invalid token. Authentication required. '
## Get their guess from POST body
## Get JWT cookie
## Decrypt payload
## Check if guess (from post body) == correct answer (from decrypted JWT payload)
## If so: give them a new JWT for winning the CAPTCHA, and forward them to the URL they originally wanted to access precaptcha (just like oauth does)
## If not: Redirect them to the GET version of this same URL, with warning enabled to tell them they failed
if request . method == " GET " :
image_path = captcha . random_image ( )
answer , options = captcha . captchafy ( image_path )
print ( ' the correct answer is: ' , answer )
# remember to store the salt since we'll need it when we compare the hashes
salt = uuid . uuid4 ( )
plaintext_bytes = bytes ( str ( answer ) + ' ||| ' + str ( salt ) , ' utf-8 ' )
encrypted_bytes = cipher . encrypt ( plaintext_bytes )
2023-06-12 09:56:17 -04:00
2023-06-29 17:01:21 -04:00
ciphertext = base64 . b64encode ( encrypted_bytes ) . decode ( ' utf-8 ' )
token = jwt . encode ( {
' encrypted_correct_answer ' : ciphertext ,
' salt ' : str ( salt )
} , JWT_SECRET_KEY , algorithm = ' HS256 ' )
# Set the Authorization header cookie with the JWT
resp = make_response ( captcha . generate_captcha_html ( list ( options ) ) )
resp . set_cookie ( ' Authorization ' , f ' Bearer { token } ' )
return resp
# Set JWT with encrypted answer as HTTP cookie
# issue: they could compare our encrypted answer with how we encrypt all numbers 1-6, since they will have seem them before.
# We could solve this with a salt, but then we have to store salts. We could include the salt in plaintext, but then they could build a rainbow table of our guess + salts (since there are only 6 possible guesses)
# the solution is to use salts big enough that we don't have to fear them repeating / being turned into a rainbow table.
# We will use UUID's as the salts.
#
# Anyway, we pass the data to our Jinja template and render it.
# Flask should take care of unsupported methods for us.
## Handle cookie
## Get random image
## Generate
2023-06-12 09:56:17 -04:00
return captcha . generate_captcha_html ( os . listdir ( ' static/images/ ' ) )
2023-06-29 17:01:21 -04:00
@app.route ( " /captcha_old " , methods = [ ' GET ' , ' POST ' ] )
2022-04-11 23:44:17 -04:00
def login ( ) :
# This means they just submitted a CAPTCHA
# We need to see if they got it right
incorrect_captcha = False
if request . method == ' POST ' :
2023-06-29 17:01:21 -04:00
captcha_guess = len ( list ( request . form ) )
print ( request . form . get ( ' captcha ' ) )
# What if they POST with the cookie below absent? Uh oh...
2022-04-11 23:44:17 -04:00
captcha_cookie = request . cookies . get ( ' freecaptcha_cookie ' )
2023-06-29 17:01:21 -04:00
2022-04-11 23:44:17 -04:00
real_answer = captcha_solutions . get ( captcha_cookie , None )
if real_answer is not None :
2023-06-29 17:01:21 -04:00
if captcha_guess == int ( real_answer ) :
2022-04-11 23:44:17 -04:00
captcha_solved . append ( captcha_cookie )
return redirect ( " / " , code = 302 )
else :
incorrect_captcha = True
2023-06-29 17:01:21 -04:00
2022-04-11 23:44:17 -04:00
# Select an image
image_path = captcha . random_image ( )
# Generate list of rotated versions of image
# and save which one is correct
2023-06-29 17:01:21 -04:00
# change answer to be the number of turns needed?
2022-04-11 23:44:17 -04:00
answer , options = captcha . captchafy ( image_path )
2023-06-29 17:01:21 -04:00
print ( answer )
2022-04-11 23:44:17 -04:00
# Provide the CAPTCHA options to the web page using the CAPTCHA
resp = make_response ( render_template ( " index.html " , captcha_options = options , incorrect_captcha = incorrect_captcha ) )
2023-06-29 17:01:21 -04:00
resp = make_response ( captcha . generate_captcha_html ( list ( options ) ) )
2022-04-11 23:44:17 -04:00
# Track this user with a cookie and store the correct answer
2023-06-29 17:01:21 -04:00
# by linking the cookie with the answer, we can check their answer later
2022-04-11 23:44:17 -04:00
freecaptcha_cookie = str ( uuid . uuid4 ( ) )
resp . set_cookie ( ' freecaptcha_cookie ' , freecaptcha_cookie )
captcha_solutions [ freecaptcha_cookie ] = answer
return resp