Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: CI

on:
pull_request:
push:
branches: [master]
schedule:
- cron: "17 13 * * 1" # Mondays: re-audit master for newly published CVEs
workflow_dispatch: # "Run workflow" button on the Actions tab

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false

- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version-file: .python-version
cache: pip
cache-dependency-path: |
requirements.txt
requirements-dev.txt

- name: Install app dependencies (hash-checked, same as Heroku)
run: python -m pip install --require-hashes -r requirements.txt

- name: Check requirements.txt was compiled from requirements.in
run: |
if ! out=$(python -m pip install --dry-run -r requirements.in 2>&1) \
|| grep -q "Would install" <<< "$out"; then
echo "$out" | grep -E "Would install|ERROR"
echo "::error file=requirements.txt::requirements.txt is out of date with requirements.in. Run: pip-compile --generate-hashes requirements.in"
exit 1
fi
echo "requirements.txt satisfies requirements.in"

- name: Install test dependencies (hash-checked)
run: python -m pip install --require-hashes -r requirements-dev.txt

- name: Run tests
run: python -m pytest

audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false

- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version-file: .python-version

- name: Install pip-audit
run: python -m pip install pip-audit==2.10.1

- name: Audit pinned dependencies for known CVEs
run: pip-audit -r requirements.txt -r requirements-dev.txt --disable-pip
5 changes: 0 additions & 5 deletions .github/workflows/github-actions.yml

This file was deleted.

9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@
.venv
venv
__pycache__
.local/
.local/
*.pem
*.log
.pytest_cache/
Lib/
Include/
Scripts/
pyvenv.cfg
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
12 changes: 4 additions & 8 deletions app_setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from flask import Flask
import config
from flask_recaptcha import ReCaptcha # type: ignore
from recaptcha_helper import Recaptcha


app = Flask(__name__)

recaptcha = Recaptcha()
isProd = config.IS_PROD

# Set secret key for Flask App.
Expand All @@ -14,13 +16,7 @@
app.config['RECAPTCHA_SITE_KEY'] = config.RECAPTCHA_SITE_KEY
app.config['RECAPTCHA_SECRET_KEY'] = config.RECAPTCHA_SECRET_KEY
# initialize reCAPTCHA
recaptcha = ReCaptcha(app)
else:
recaptcha = "Disabled for DEV"


# email service account.
taskapp_email = config.SECRET_KEY
recaptcha.init_app(app)

# specifies database to use.
db = config.MONGO_CLIENT["TaskAppLoginDB"]
6 changes: 6 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[pytest]
testpaths = tests
norecursedirs = .venv venv Lib Include Scripts static templates task-lists .git
addopts = -ra
filterwarnings =
ignore::SyntaxWarning
38 changes: 38 additions & 0 deletions recaptcha_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import requests
from flask import request
from markupsafe import Markup

class Recaptcha:
VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"

def __init__(self):
self.enabled = False
self.site_key = self.secret_key = None

def init_app(self, app):
self.site_key = app.config.get("RECAPTCHA_SITE_KEY")
self.secret_key = app.config.get("RECAPTCHA_SECRET_KEY")
self.enabled = bool(self.site_key and self.secret_key)
app.jinja_env.globals["recaptcha"] = self.get_code()

def get_code(self):
if not self.enabled:
return ""
return Markup(
'<script src="https://www.google.com/recaptcha/api.js" async defer></script>'
f'<div class="g-recaptcha" data-sitekey="{self.site_key}"></div>'
)

def verify(self):
if not self.enabled:
return True # dev mode
token = request.form.get("g-recaptcha-response", "")
if not token:
return False
try:
r = requests.post(self.VERIFY_URL,
data={"secret": self.secret_key, "response": token},
timeout=10)
return bool(r.json().get("success"))
except (requests.RequestException, ValueError):
return False
3 changes: 3 additions & 0 deletions requirements-dev.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-c requirements.txt
pytest
mongomock
44 changes: 44 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --generate-hashes --no-index requirements-dev.in
#
colorama==0.4.6 \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
# via pytest
iniconfig==2.3.0 \
--hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \
--hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
# via pytest
mongomock==4.3.0 \
--hash=sha256:32667b79066fabc12d4f17f16a8fd7361b5f4435208b3ba32c226e52212a8c30 \
--hash=sha256:5ef86bd12fc8806c6e7af32f21266c61b6c4ba96096f85129852d1c4fec1327e
# via -r requirements-dev.in
packaging==26.3 \
--hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \
--hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
# via
# mongomock
# pytest
pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via pytest
pygments==2.21.0 \
--hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \
--hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c
# via pytest
pytest==9.1.1 \
--hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \
--hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c
# via -r requirements-dev.in
pytz==2026.4 \
--hash=sha256:464303645bafafd72418898368b2429458f709cf1eb6a15372fbcc396b64da63 \
--hash=sha256:9d514388fbc89ca0833203464272ac485b8828568ab73532f5020f17e892a0ff
# via mongomock
sentinels==1.1.1 \
--hash=sha256:3c2f64f754187c19e0a1a029b148b74cf58dd12ec27b4e19c0e5d6e22b5a9a86 \
--hash=sha256:835d3b28f3b47f5284afa4bf2db6e00f2dc5f80f9923d4b7e7aeeeccf6146a11
# via mongomock
10 changes: 10 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# requirements.in
Flask>=3.1.3
Flask-WTF
gunicorn>=22.0
pymongo>=4.6.3
PyJWT>=2.13
bcrypt
requests>=2.33
sendgrid
itsdangerous
Loading
Loading