From 04d4b1b3e7a64038274558e31848ece4358a5a45 Mon Sep 17 00:00:00 2001 From: Cole Stowell Date: Fri, 19 Dec 2025 12:28:25 -0600 Subject: [PATCH 01/13] bump: clean up, bump deps --- config.env.py | 12 +- docker-compose.yaml | 6 +- requirements.dev | 22 -- requirements.in | 19 ++ requirements.txt | 330 ++++++++++++++++++++-------- selfservice/__init__.py | 10 +- selfservice/blueprints/recovery.py | 4 +- selfservice/models.py | 6 +- selfservice/templates/recovery.html | 2 +- 9 files changed, 276 insertions(+), 135 deletions(-) delete mode 100644 requirements.dev create mode 100644 requirements.in diff --git a/config.env.py b/config.env.py index e510b81..cfb8823 100644 --- a/config.env.py +++ b/config.env.py @@ -35,12 +35,12 @@ ) SQLALCHEMY_TRACK_MODIFICATIONS = False -RECAPTCHA_ENABLED = True -RECAPTCHA_SITE_KEY = os.environ.get("RECAPTCHA_SITE_KEY", "") -RECAPTCHA_SECRET_KEY = os.environ.get("RECAPTCHA_SECRET_KEY", "") -RECAPTCHA_THEME = "light" -RECAPTCHA_TYPE = "image" -RECAPTCHA_SIZE = "normal" +XCAPTCHA_ENABLED = True +XCAPTCHA_SITE_KEY = os.environ.get("XCAPTCHA_SITE_KEY", "") +XCAPTCHA_SECRET_KEY = os.environ.get("XCAPTCHA_SECRET_KEY", "") +XCAPTCHA_THEME = "light" +XCAPTCHA_TYPE = "image" +XCAPTCHA_SIZE = "normal" TWILIO_SID = os.environ.get("TWILIO_SID", "") TWILIO_TOKEN = os.environ.get("TWILIO_TOKEN", "") diff --git a/docker-compose.yaml b/docker-compose.yaml index e088131..fbc8ff6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ version: '2' services: postgres: - image: postgres:9.6 + image: docker.io/postgres:9.6 container_name: selfservice-postgres restart: always volumes: @@ -12,7 +12,7 @@ services: ports: - 127.0.0.1:5433:5432 phppgadmin: - image: bitnami/phppgadmin:latest + image: docker.io/dockage/phppgadmin:latest container_name: selfservice-pgadmin links: - postgres @@ -22,4 +22,4 @@ services: restart: always ports: - 127.0.0.1:8081:8080 - - 127.0.0.1:8444:8443 \ No newline at end of file + - 127.0.0.1:8444:8443 diff --git a/requirements.dev b/requirements.dev deleted file mode 100644 index 8394dae..0000000 --- a/requirements.dev +++ /dev/null @@ -1,22 +0,0 @@ -beautifulsoup4 -black -bs4 -csh-ldap>=2.2.0 -dill -dnspython<2.0.0 -Flask -Flask-Limiter -Flask-Migrate -Flask-pyoidc -Flask-QRcode -Flask-ReCaptcha -Flask-SQLAlchemy -psycopg2 -pylint -python-freeipa -python-keycloak -qrcode -requests -sentry-sdk[flask]~=0.13.1 -srvlookup -twillio \ No newline at end of file diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..22a14fc --- /dev/null +++ b/requirements.in @@ -0,0 +1,19 @@ +Flask~=3.1.2 +Flask-pyoidc~=3.14.3 +Flask-Migrate~=4.1.0 +Flask-SQLAlchemy~=3.1.1 +Flask-Limiter~=4.1.1 +Flask-QRcode~=3.2.0 +Flask-xCaptcha~=0.5.5 +srvlookup~=3.0.0 +csh-ldap @ git+https://github.com/costowell/csh_ldap@cole-dev +python-freeipa~=1.0.10 +python-keycloak~=5.8.1 +sentry-sdk[Flask] +psycopg2-binary~=2.9.11 +twilio~=9.9.0 +pyotp~=2.9.0 +dill~=0.4.0 +beautifulsoup4~=4.14.3 +passlib~=1.7.4 +xkcdpass~=1.20.0 diff --git a/requirements.txt b/requirements.txt index ee9be7c..ebe8767 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,93 +1,237 @@ -alabaster==0.7.10 -alembic==0.9.9 -appdirs==1.4.3 -asn1crypto==0.24.0 -aspy.yaml==1.1.1 -astroid==2.4.2 -attrs==18.1.0 -Beaker==1.9.1 -beautifulsoup4==4.9.1 -black==20.8b1 -blinker==1.4 -bs4==0.0.1 -cached-property==1.4.2 -certifi==2018.4.16 -cffi==1.13.2 -cfgv==1.0.0 -chardet==3.0.4 -click==7.1.2 -cryptography==3.2 -csh-ldap==2.3.1 -dataclasses==0.7 -defusedxml==0.6.0 -dill==0.3.2 -distlib==0.3.1 -dnspython==1.16.0 -ecdsa==0.13.3 -filelock==3.0.12 -Flask==1.1.2 -Flask-Limiter==1.4 -Flask-Migrate==2.5.3 -Flask-pyoidc==3.5.1 -Flask-QRcode==3.0.0 -Flask-ReCaptcha==0.4.2 -Flask-SQLAlchemy==2.4.4 -future==0.16.0 -gunicorn==19.8.1 -httmock==1.2.5 -identify==1.0.18 -idna==2.6 -importlib-metadata==1.7.0 -importlib-resources==3.3.0 -isort==4.3.4 -itsdangerous==0.24 -Jinja2==2.10.1 -lazy-object-proxy==1.4.3 -limits==1.3 -Mako==1.0.7 -MarkupSafe==1.0 -mccabe==0.6.1 -mypy-extensions==0.4.3 -nodeenv==1.3.0 -oic==1.1.2 -passlib==1.7.1 -pathspec==0.8.0 -Pillow==6.2.2 -pre-commit==1.10.1 -psycopg2==2.8.5 -pyasn1==0.4.2 -pyasn1-modules==0.2.1 -pycparser==2.18 -pycrypto==2.6.1 -pycryptodomex==3.6.1 -pyjwkest==1.4.0 -PyJWT==1.7.1 -pylint==2.6.0 -pyOpenSSL==18.0.0 -pyotp==2.2.6 -python-dateutil==2.7.3 -python-editor==1.0.3 -python-freeipa==1.0.6 -python-jose==1.4.0 -python-keycloak==0.22.0 -python-ldap~=3.0.0 -pytz==2020.1 -PyYAML==5.4.1 -qrcode==6.1 -regex==2020.7.14 -requests==2.24.0 -sentry-sdk==0.13.5 -six==1.12.0 -soupsieve==2.0.1 -SQLAlchemy==1.3.0 -srvlookup==2.0.0 -toml==0.10.1 -twilio==6.45.1 -typed-ast==1.4.1 -typing-extensions==3.7.4.3 -urllib3==1.24.2 -virtualenv==16.0.0 -Werkzeug==0.15.3 -wrapt==1.11.2 -xkcdpass==1.16.5 -zipp==3.1.0 +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in +aiofiles==25.1.0 + # via python-keycloak +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.2 + # via + # aiohttp-retry + # twilio +aiohttp-retry==2.9.1 + # via twilio +aiosignal==1.4.0 + # via aiohttp +alembic==1.17.2 + # via flask-migrate +annotated-types==0.7.0 + # via pydantic +anyio==4.12.0 + # via httpx +async-property==0.2.2 + # via python-keycloak +attrs==25.4.0 + # via aiohttp +beautifulsoup4==4.14.3 + # via -r requirements.in +blinker==1.9.0 + # via + # flask + # sentry-sdk +certifi==2025.11.12 + # via + # httpcore + # httpx + # requests + # sentry-sdk +cffi==2.0.0 + # via cryptography +charset-normalizer==3.4.4 + # via requests +click==8.3.1 + # via flask +cryptography==46.0.3 + # via + # jwcrypto + # oic +csh-ldap @ git+https://github.com/costowell/csh_ldap@dbc3193482615b4cd52c5d1fb260662ec8157ace + # via -r requirements.in +defusedxml==0.7.1 + # via oic +deprecated==1.3.1 + # via limits +deprecation==2.1.0 + # via python-keycloak +dill==0.4.0 + # via -r requirements.in +dnspython==2.8.0 + # via srvlookup +flask==3.1.2 + # via + # -r requirements.in + # flask-limiter + # flask-migrate + # flask-pyoidc + # flask-qrcode + # flask-sqlalchemy + # flask-xcaptcha + # sentry-sdk +flask-limiter==4.1.1 + # via -r requirements.in +flask-migrate==4.1.0 + # via -r requirements.in +flask-pyoidc==3.14.3 + # via -r requirements.in +flask-qrcode==3.2.0 + # via -r requirements.in +flask-sqlalchemy==3.1.1 + # via + # -r requirements.in + # flask-migrate +flask-xcaptcha==0.5.5 + # via -r requirements.in +frozenlist==1.8.0 + # via + # aiohttp + # aiosignal +future==1.0.0 + # via pyjwkest +greenlet==3.3.0 + # via sqlalchemy +h11==0.16.0 + # via httpcore +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via python-keycloak +idna==3.11 + # via + # anyio + # httpx + # requests + # yarl +importlib-resources==6.5.2 + # via flask-pyoidc +itsdangerous==2.2.0 + # via flask +jinja2==3.1.6 + # via flask +jwcrypto==1.5.6 + # via python-keycloak +limits==5.6.0 + # via flask-limiter +mako==1.3.10 + # via + # alembic + # oic +markupsafe==3.0.3 + # via + # flask + # flask-xcaptcha + # jinja2 + # mako + # sentry-sdk + # werkzeug +multidict==6.7.0 + # via + # aiohttp + # yarl +oic==1.6.1 + # via flask-pyoidc +ordered-set==4.1.0 + # via flask-limiter +packaging==25.0 + # via + # deprecation + # limits +passlib==1.7.4 + # via -r requirements.in +pillow==12.0.0 + # via flask-qrcode +propcache==0.4.1 + # via + # aiohttp + # yarl +psycopg2-binary==2.9.11 + # via -r requirements.in +pyasn1==0.6.1 + # via + # pyasn1-modules + # python-ldap +pyasn1-modules==0.4.2 + # via python-ldap +pycparser==2.23 + # via cffi +pycryptodomex==3.23.0 + # via + # oic + # pyjwkest +pydantic==2.12.5 + # via pydantic-settings +pydantic-core==2.41.5 + # via pydantic +pydantic-settings==2.12.0 + # via oic +pyjwkest==1.4.4 + # via oic +pyjwt==2.10.1 + # via twilio +pyotp==2.9.0 + # via -r requirements.in +python-dotenv==1.2.1 + # via pydantic-settings +python-freeipa==1.0.10 + # via -r requirements.in +python-keycloak==5.8.1 + # via -r requirements.in +python-ldap==3.4.5 + # via csh-ldap +qrcode==8.2 + # via flask-qrcode +requests==2.32.5 + # via + # flask-pyoidc + # flask-xcaptcha + # oic + # pyjwkest + # python-freeipa + # python-keycloak + # requests-toolbelt + # twilio +requests-toolbelt==1.0.0 + # via python-keycloak +sentry-sdk==2.48.0 + # via -r requirements.in +six==1.17.0 + # via pyjwkest +soupsieve==2.8.1 + # via beautifulsoup4 +sqlalchemy==2.0.45 + # via + # alembic + # flask-sqlalchemy +srvlookup==3.0.0 + # via + # -r requirements.in + # csh-ldap +twilio==9.9.0 + # via -r requirements.in +typing-extensions==4.15.0 + # via + # aiosignal + # alembic + # anyio + # beautifulsoup4 + # flask-limiter + # jwcrypto + # limits + # pydantic + # pydantic-core + # sqlalchemy + # typing-inspection +typing-inspection==0.4.2 + # via + # pydantic + # pydantic-settings +urllib3==2.6.2 + # via + # requests + # sentry-sdk +werkzeug==3.1.4 + # via flask +wrapt==2.0.1 + # via deprecated +xkcdpass==1.20.0 + # via -r requirements.in +yarl==1.22.0 + # via aiohttp diff --git a/selfservice/__init__.py b/selfservice/__init__.py index f8e2889..ee47b63 100644 --- a/selfservice/__init__.py +++ b/selfservice/__init__.py @@ -13,7 +13,7 @@ from flask import Flask, render_template, request, redirect, url_for, flash from flask_pyoidc.flask_pyoidc import OIDCAuthentication from flask_pyoidc.provider_configuration import ProviderConfiguration, ClientMetadata -from flask_recaptcha import ReCaptcha +from flask_xcaptcha import XCaptcha from flask_sqlalchemy import SQLAlchemy from python_freeipa import Client from flask_limiter import Limiter @@ -52,9 +52,9 @@ migrate = Migrate(app, db) -# Create recaptcha object -recaptcha = ReCaptcha() -recaptcha.init_app(app) +# Create xcaptcha object +if app.config["XCAPTCHA_ENABLED"]: + xcaptcha = XCaptcha(app=app) # OIDC Initialization OIDC_PROVIDER = "csh" @@ -77,7 +77,7 @@ # Configure rate limiting if not app.config["DEBUG"]: limiter = Limiter( - app, key_func=get_remote_address, default_limits=["50 per day", "10 per hour"] + get_remote_address, app=app, default_limits=["50 per day", "10 per hour"] ) # Initialize QR Code Generator diff --git a/selfservice/blueprints/recovery.py b/selfservice/blueprints/recovery.py index 1efbb1b..addc6ba 100644 --- a/selfservice/blueprints/recovery.py +++ b/selfservice/blueprints/recovery.py @@ -18,7 +18,7 @@ from selfservice.utilities.ldap import verif_methods, get_members from selfservice.models import RecoverySession, PhoneVerification, ResetToken -from selfservice import db, auth, recaptcha, ldap, version, OIDC_PROVIDER +from selfservice import db, auth, xcaptcha, ldap, version, OIDC_PROVIDER LOG = logging.getLogger(__name__) @@ -35,7 +35,7 @@ def create_session(): if request.method == "GET": return render_template("recovery.html", version=version) - if recaptcha.verify(): + if xcaptcha.verify(): # If we can't find an account, flash error. try: diff --git a/selfservice/models.py b/selfservice/models.py index 7e73bc5..e7f9916 100644 --- a/selfservice/models.py +++ b/selfservice/models.py @@ -9,7 +9,7 @@ ForeignKey, DateTime, Boolean, - Binary, + LargeBinary, func, ) from selfservice import db @@ -62,8 +62,8 @@ class OTPSession(db.Model): __tablename__ = "otp_session" secret = Column(String(100), primary_key=True) - form = Column(Binary) - session = Column(Binary) + form = Column(LargeBinary) + session = Column(LargeBinary) class AppSpecificPassword(db.Model): diff --git a/selfservice/templates/recovery.html b/selfservice/templates/recovery.html index 6d186b1..743eb2f 100644 --- a/selfservice/templates/recovery.html +++ b/selfservice/templates/recovery.html @@ -17,7 +17,7 @@

Account Recovery

- {{recaptcha}} + {{xcaptcha}}