This repository has been migrated to the self-hosted ari-web Forgejo instance: https://git.ari.lt/ari/flask-ishuman
simple flask captcha validation
a good example of usage can be found in /tests/main.py, although heres a basic example
import flask
import flask_ishuman
app = flask.Flask(__name__)
h = flask_ishuman.IsHuman()
@app.get("/")
def index():
c = h.new()
return ... # now render it, like c.image() maybe or c.rawpng() or something
@app.post("/")
def validate():
code = flask.request.form.get("code") # this if u have a <form> that has name=code in it, but ur free to get the `code` in any way u want
# if code is None then itll return false regardless
if h.verify(code):
pass # captcha valid
else:
pass # captcha invalid
app.config["SECRET_KEY"] = h.rand.randbytes(2048)
# firefox throws warnings if these are not set
app.config["SESSION_COOKIE_SAMESITE"] = "None"
app.config["SESSION_COOKIE_SECURE"] = True
h.init_app(app)
app.run("127.0.0.1", 8080)
heres the functions and classes we have :
IsHuman
-- captcha wrapper__init__(image_args: dict, audio_args: dict) -> None
-- constructor, passesimage_args
to captcha.image.ImageCaptcha and same withaudio_args
, just for audio ( underlying captcha library, although i forked it )cimage
attr is an instance ofcaptcha.image.ImageCaptcha
caudio
attr is an instance ofcaptcha.audio.AudioCaptcha
rand
is a cryptographically secure randomness source, orsecrets.SystemRandom()
skey
is the unique captcha key in the sessionapp
is the flask app ( can beNone
ifinit_app()
was not called )pepper
is the pepper of captchas ( also can bNone
ifinit_app()
was not called )
init_app(app: flask.Flask) -> Self
-- initialize flask app, set up variables, configuration, generate keysrandom(length: int | None) -> str
-- returns a random code oflength
length, uses a random number inCAPTCHA_RANGE
length by defaultdigest(code: str, salt: bytes | None) -> (bytes, bytes, float)
-- returns a salted and peppered sha3-512 digest of a code, returns(digest, salt, timestamp)
set_code(code: str) -> Self
-- sets the captcha to a codeget_digest() -> (bytes, bytes, float) | None
-- returns the current captcha digest if available, returns(digest, salt, timestamp)
, returnsNone
if unavailable or expiredverify(code: str | None, expire: bool = True) -> bool
-- returns if a code is a valid hash, ifcode
isNone
will always returnFalse
, which helps to work with flask apis likeflask.request.from.get
, will also callexpire()
ifexpire=True
( default ) is passednew(code: str | None, length: str | None, set_c: bool = True)
-- returns a newCaptchaGenerator
, passes code as the code and usesrandom(length)
by default,set_code()
is called ifset_c
isTrue
, which is the defaultexpire() -> Self
-- expire the current captchaexpired_dt(ts: float) -> bool
-- check if the current captcha is expired according to itsts
( timestamp )auto_expire(ts: float) -> bool
-- runsexpire()
ifexpired_dt()
isTrue
, returns the result ofexpired_dt()
CaptchaGenerator
-- generate captchas__init__(code: str, cimage: captcha.image.ImageCaptcha, caudio: captcha.audio.AudioCaptcha) -> None
-- constructor, takes in the captcha code and captcha helperscode
is the captcha codecimage
is an instance ofcaptcha.image.ImageCaptcha
caudio
is an instance ofcaptcha.audio.AudioCaptcha
rawpng() -> bytes
-- returns raw png data used inpng()
rawwav() -> bytes
-- returns raw wav data used inwav()
png() -> str
-- returns base64 encoded png of the image captchawav() -> str
-- returns base64 encoded wav of the audio captchaimage(alt: str = "Image CAPTCHA") -> str
-- returns html to embed for the captcha,alt
attr is set asalt
, note thtalt
is not escapedaudio(alt: str = "Audio CAPTCHA", controls: bool = True) -> str
-- returns the audio captcha embedding html,alt
attr is not set, but embded in theaudio
element asalt
, andcontrols
is added too ifcontrols
is set toTrue
, note thtalt
is not escaped
what u have to do is basically :
- create
IsHuman
- call
init_app
on it - call
new
on it - use functions provided in
CaptchaGenerator
to display captcha- for example embed it in html using
.png()
or have a route like/captcha.png
to return the actual png although do whatever u want
- for example embed it in html using
SECRET_KEY
-- this is default in flask, set this to a secure random value, this is used for session storage and protection, will throw a warning if unsetCAPTCHA_SALT_LEN
-- the salt length to use for salting of hashes, by default32
CAPTCHA_CHARSET
-- the charset to use in captchas, by default all ascii letters, digits and characters@#%?
CAPTCHA_RANGE
-- a 2 value tuple storing(from, to)
arguments, used to generation of random captcha lengths, by default from 4 to 8 ((4, 8)
)CAPTCHA_EXPIRY
-- a float, which defines the lifetime of a single captcha in seconds, by default it isNone
which means the lifetime is infiniteCAPTCHA_PEPPER_SIZE
-- the size of the pepper value, by default2048
CAPTCHA_PEPPER_FILE
-- the pepper file to use, which is like a constant salt not stored in the session, by defaultcaptcha_pepper
these should be a part of app.config
, although optional -- will use default values if unspecified
- large, cryptographically secure, random secret key
- a salt length that is anywhere from 16 to 64 bytes, dont go overboard though as that will increase the size of the session
- charset of readable characters when messed with in a captcha sense
- a sensible range, so it isnt too large like 100 characters or too small like 1 characters
- a short expiry time, but not so sort that users cant figure it out in time, maybe like 5 to 10 mins, keep in mind audio captchas if ur using them, audio captchas tend to take longer
- a big pepper size, maybe like from 512 to 4096 bytes
- image captchas get the
image-captcha
id (<img id=... />
) - audio captchas get the
audio-captcha
id (<audio id=...><source /><audio>
)
all logging of flask-ishuman is done through logging.DEBUG