# flask-ishuman
> simple flask captcha validation
# usage
a good example of usage can be found in [/tests/main.py](/tests/main.py), although heres a basic example
```py
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, passes `image_args` to captcha.image.ImageCaptcha and same with `audio_args`, just for audio ( [underlying captcha library](https://pypi.org/project/captcha/), although [i forked it](https://pypi.org/project/more-captcha/) )
- `cimage` attr is an instance of `captcha.image.ImageCaptcha`
- `caudio` attr is an instance of `captcha.audio.AudioCaptcha`
- `rand` is a cryptographically secure randomness source, or `secrets.SystemRandom()`
- `skey` is the unique captcha key in the session
- `app` is the flask app ( can be `None` if `init_app()` was not called )
- `pepper` is the pepper of captchas ( also can b `None` if `init_app()` was not called )
- `init_app(app: flask.Flask) -> Self` -- initialize flask app, set up variables, configuration, generate keys
- `random(length: int | None) -> str` -- returns a random code of `length` length, uses a random number in `CAPTCHA_RANGE` length by default
- `digest(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 code
- `get_digest() -> (bytes, bytes, float) | None` -- returns the current captcha digest if available, returns `(digest, salt, timestamp)`, returns `None` if unavailable or expired
- `verify(code: str | None, expire: bool = True) -> bool` -- returns if a code is a valid hash, if `code` is `None` will always return `False`, which helps to work with flask apis like `flask.request.from.get`, will also call `expire()` if `expire=True` ( default ) is passed
- `new(code: str | None, length: str | None, set_c: bool = True)` -- returns a new `CaptchaGenerator`, passes code as the code and uses `random(length)` by default, `set_code()` is called if `set_c` is `True`, which is the default
- `expire() -> Self` -- expire the current captcha
- `expired_dt(ts: float) -> bool` -- check if the current captcha is expired according to its `ts` ( timestamp )
- `auto_expire(ts: float) -> bool` -- runs `expire()` if `expired_dt()` is `True`, returns the result of `expired_dt()`
- `CaptchaGenerator` -- generate captchas
- `__init__(code: str, cimage: captcha.image.ImageCaptcha, caudio: captcha.audio.AudioCaptcha) -> None` -- constructor, takes in the captcha code and captcha helpers
- `code` is the captcha code
- `cimage` is an instance of `captcha.image.ImageCaptcha`
- `caudio` is an instance of `captcha.audio.AudioCaptcha`
- `rawpng() -> bytes` -- returns raw png data used in `png()`
- `rawwav() -> bytes` -- returns raw wav data used in `wav()`
- `png() -> str` -- returns base64 encoded png of the image captcha
- `wav() -> str` -- returns base64 encoded wav of the audio captcha
- `image(alt: str = "Image CAPTCHA") -> str` -- returns html to embed for the captcha, `alt` attr is set as `alt`, note tht `alt` is not escaped
- `audio(alt: str = "Audio CAPTCHA", controls: bool = True) -> str` -- returns the audio captcha embedding html, `alt` attr is not set, but embded in the `audio` element as `alt`, and `controls` is added too if `controls` is set to `True`, note tht `alt` 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
# configuration
- `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 unset
- `CAPTCHA_SALT_LEN` -- the salt length to use for salting of hashes, by default `32`
- `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 is `None` which means the lifetime is infinite
- `CAPTCHA_PEPPER_SIZE` -- the size of the pepper value, by default `2048`
- `CAPTCHA_PEPPER_FILE` -- the pepper file to use, which is like a constant salt not stored in the session, by default `captcha_pepper`
these should be a part of `app.config`, although optional -- will use default values if unspecified
## best configuration practices
- 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
## styling and selection
- image captchas get the `image-captcha` id ( `<img id=... />` )
- audio captchas get the `audio-captcha` id ( `<audio id=...><source /><audio>` )
## logging
all logging of flask-ishuman is done through `logging.DEBUG`
Raw data
{
"_id": null,
"home_page": "https://ari-web.xyz/gh/flask-ishuman",
"name": "flask-ishuman",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "flask,captcha,recaptcha,audio captcha,image captcha,human validation",
"author": "Ari Archer",
"author_email": "ari.web.xyz@gmail.com",
"download_url": "",
"platform": null,
"description": "# flask-ishuman\n\n> simple flask captcha validation\n\n# usage\n\na good example of usage can be found in [/tests/main.py](/tests/main.py), although heres a basic example\n\n```py\nimport flask\nimport flask_ishuman\n\napp = flask.Flask(__name__)\nh = flask_ishuman.IsHuman()\n\n@app.get(\"/\")\ndef index():\n c = h.new()\n return ... # now render it, like c.image() maybe or c.rawpng() or something\n\n@app.post(\"/\")\ndef validate():\n 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\n\n # if code is None then itll return false regardless\n\n if h.verify(code):\n pass # captcha valid\n else:\n pass # captcha invalid\n\napp.config[\"SECRET_KEY\"] = h.rand.randbytes(2048)\n\n# firefox throws warnings if these are not set\napp.config[\"SESSION_COOKIE_SAMESITE\"] = \"None\"\napp.config[\"SESSION_COOKIE_SECURE\"] = True\n\nh.init_app(app)\napp.run(\"127.0.0.1\", 8080)\n```\n\nheres the functions and classes we have :\n\n- `IsHuman` -- captcha wrapper\n - `__init__(image_args: dict, audio_args: dict) -> None` -- constructor, passes `image_args` to captcha.image.ImageCaptcha and same with `audio_args`, just for audio ( [underlying captcha library](https://pypi.org/project/captcha/), although [i forked it](https://pypi.org/project/more-captcha/) )\n - `cimage` attr is an instance of `captcha.image.ImageCaptcha`\n - `caudio` attr is an instance of `captcha.audio.AudioCaptcha`\n - `rand` is a cryptographically secure randomness source, or `secrets.SystemRandom()`\n - `skey` is the unique captcha key in the session\n - `app` is the flask app ( can be `None` if `init_app()` was not called )\n - `pepper` is the pepper of captchas ( also can b `None` if `init_app()` was not called )\n - `init_app(app: flask.Flask) -> Self` -- initialize flask app, set up variables, configuration, generate keys\n - `random(length: int | None) -> str` -- returns a random code of `length` length, uses a random number in `CAPTCHA_RANGE` length by default\n - `digest(code: str, salt: bytes | None) -> (bytes, bytes, float)` -- returns a salted and peppered sha3-512 digest of a code, returns `(digest, salt, timestamp)`\n - `set_code(code: str) -> Self` -- sets the captcha to a code\n - `get_digest() -> (bytes, bytes, float) | None` -- returns the current captcha digest if available, returns `(digest, salt, timestamp)`, returns `None` if unavailable or expired\n - `verify(code: str | None, expire: bool = True) -> bool` -- returns if a code is a valid hash, if `code` is `None` will always return `False`, which helps to work with flask apis like `flask.request.from.get`, will also call `expire()` if `expire=True` ( default ) is passed\n - `new(code: str | None, length: str | None, set_c: bool = True)` -- returns a new `CaptchaGenerator`, passes code as the code and uses `random(length)` by default, `set_code()` is called if `set_c` is `True`, which is the default\n - `expire() -> Self` -- expire the current captcha\n - `expired_dt(ts: float) -> bool` -- check if the current captcha is expired according to its `ts` ( timestamp )\n - `auto_expire(ts: float) -> bool` -- runs `expire()` if `expired_dt()` is `True`, returns the result of `expired_dt()`\n- `CaptchaGenerator` -- generate captchas\n - `__init__(code: str, cimage: captcha.image.ImageCaptcha, caudio: captcha.audio.AudioCaptcha) -> None` -- constructor, takes in the captcha code and captcha helpers\n - `code` is the captcha code\n - `cimage` is an instance of `captcha.image.ImageCaptcha`\n - `caudio` is an instance of `captcha.audio.AudioCaptcha`\n - `rawpng() -> bytes` -- returns raw png data used in `png()`\n - `rawwav() -> bytes` -- returns raw wav data used in `wav()`\n - `png() -> str` -- returns base64 encoded png of the image captcha\n - `wav() -> str` -- returns base64 encoded wav of the audio captcha\n - `image(alt: str = \"Image CAPTCHA\") -> str` -- returns html to embed for the captcha, `alt` attr is set as `alt`, note tht `alt` is not escaped\n - `audio(alt: str = \"Audio CAPTCHA\", controls: bool = True) -> str` -- returns the audio captcha embedding html, `alt` attr is not set, but embded in the `audio` element as `alt`, and `controls` is added too if `controls` is set to `True`, note tht `alt` is not escaped\n\nwhat u have to do is basically :\n\n- create `IsHuman`\n- call `init_app` on it\n- call `new` on it\n- use functions provided in` CaptchaGenerator` to display captcha\n - 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\n\n# configuration\n\n- `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 unset\n- `CAPTCHA_SALT_LEN` -- the salt length to use for salting of hashes, by default `32`\n- `CAPTCHA_CHARSET` -- the charset to use in captchas, by default all ascii letters, digits and characters `@#%?`\n- `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)` )\n- `CAPTCHA_EXPIRY` -- a float, which defines the lifetime of a single captcha in seconds, by default it is `None` which means the lifetime is infinite\n- `CAPTCHA_PEPPER_SIZE` -- the size of the pepper value, by default `2048`\n- `CAPTCHA_PEPPER_FILE` -- the pepper file to use, which is like a constant salt not stored in the session, by default `captcha_pepper`\n\nthese should be a part of `app.config`, although optional -- will use default values if unspecified\n\n## best configuration practices\n\n- large, cryptographically secure, random secret key\n- a salt length that is anywhere from 16 to 64 bytes, dont go overboard though as that will increase the size of the session\n- charset of readable characters when messed with in a captcha sense\n- a sensible range, so it isnt too large like 100 characters or too small like 1 characters\n- 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\n- a big pepper size, maybe like from 512 to 4096 bytes\n\n## styling and selection\n\n- image captchas get the `image-captcha` id ( `<img id=... />` )\n- audio captchas get the `audio-captcha` id ( `<audio id=...><source /><audio>` )\n\n## logging\n\nall logging of flask-ishuman is done through `logging.DEBUG`\n",
"bugtrack_url": null,
"license": "GPLv3+",
"summary": "simple flask captcha validation",
"version": "2.1.0",
"project_urls": {
"Homepage": "https://ari-web.xyz/gh/flask-ishuman"
},
"split_keywords": [
"flask",
"captcha",
"recaptcha",
"audio captcha",
"image captcha",
"human validation"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "fa51548325a133de0b4c71a0513caa4aef9078a076a36d294ec6415154680291",
"md5": "15b265154a88c1ef1cdd761515944aa4",
"sha256": "8bc877fd8ddd8961c839d470ad88f37f958143355cf5e35b4ea491a65c8edb6f"
},
"downloads": -1,
"filename": "flask_ishuman-2.1.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "15b265154a88c1ef1cdd761515944aa4",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 19310,
"upload_time": "2023-11-04T15:36:44",
"upload_time_iso_8601": "2023-11-04T15:36:44.460252Z",
"url": "https://files.pythonhosted.org/packages/fa/51/548325a133de0b4c71a0513caa4aef9078a076a36d294ec6415154680291/flask_ishuman-2.1.0-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-11-04 15:36:44",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "flask-ishuman"
}