Skip to content

Upgrade Flask app to Python 3.12: Authlib, Flask 3, SQLAlchemy 2, factory pattern, security hardening#98

Draft
Copilot wants to merge 3 commits intomainfrom
copilot/upgrade-flask-to-python-3-12
Draft

Upgrade Flask app to Python 3.12: Authlib, Flask 3, SQLAlchemy 2, factory pattern, security hardening#98
Copilot wants to merge 3 commits intomainfrom
copilot/upgrade-flask-to-python-3-12

Conversation

Copy link
Contributor

Copilot AI commented Mar 8, 2026

Modernises the entire application stack from Python 3.8/Flask 1.x to Python 3.12/Flask 3.x, replacing all deprecated libraries and patterns that were blocking the runtime upgrade.

Dependency Overhaul (requirements.txt, runtime.txt)

  • Flask 1.1.2 → 3.0.3, Werkzeug 0.16.1 → 3.0.3, SQLAlchemy 1.4 → 2.0.30, Flask-SQLAlchemy 2.5 → 3.1.1
  • Flask-OAuthlib (unmaintained) replaced with Authlib 1.6.7
  • firebase-admin 3.2.1 → 6.5.0, gunicorn 20 → 22.0.0 (request-smuggling CVEs patched), Flask-Login 0.4 → 0.6.3, Flask-MQTT 1.1.1 → 1.3.0
  • runtime.txt: python-3.8.9python-3.12.2

OAuth (my_oauth.py, routes.py)

Full rewrite from OAuth2Provider decorator pattern to Authlib's AuthorizationServer + ResourceProtector:

# Before (Flask-OAuthlib)
oauth = OAuth2Provider()
@oauth.clientgetter
def load_client(client_id): ...
@oauth.token_handler
def access_token(): return {'version': '0.1.0'}

# After (Authlib)
authorization = AuthorizationServer()
require_oauth = ResourceProtector()
def init_oauth(app):
    authorization.init_app(app, query_client=_query_client, save_token=_save_token)
    authorization.register_grant(_AuthorizationCodeGrant)
    require_oauth.register_token_validator(_BearerTokenValidator())

Existing DB schema (Client, Grant, Token tables) is preserved; Authlib interface methods are added to models instead.

Application Factory (app.py)

  • Global app = Flask(...) replaced with create_app(config_class=None) factory
  • @app.before_first_request removed; db.create_all() runs in with app.app_context() during factory init
  • _init_full_features() returns bool; fallback routes auto-register on failure (keeps test/dev parity)
  • All print() replaced with logging

Security Hardening (auth.py)

Removed explicit method='sha256' from generate_password_hash() — Werkzeug 3.x defaults to scrypt.

Python 3.12 Deprecation Fixes

  • datetime.utcnow()datetime.now(timezone.utc) everywhere
  • ReportState.py: removed .decode("utf-8") on jwt.encode() return value — google-auth ≥ 2.x returns str
  • models.py: removed username column from User; email is now the sole identifier

Intent Routing (action_devices.py)

Replaced nested if/elif chain with a dispatch map:

_INTENT_DISPATCH = {
    "action.devices.SYNC": lambda req: onSync(),
    "action.devices.QUERY": onQuery,
    "action.devices.EXECUTE": _handle_execute,
    "action.devices.DISCONNECT": lambda req: {},
}

Validation

  • Local checks passed (make test)
  • Runtime check done where relevant (make health)
  • CI checks are green

AI-Assisted Review (if applicable)

  • I used AI assistance for parts of this change
  • I manually reviewed all AI-generated code
  • I verified no secrets/credentials were added

Risk & Rollback

  • Risk level: High — full dependency stack replaced, OAuth provider swapped
  • Rollback plan: revert to previous commit; the DB schema is additive-only (no columns dropped from existing tables except user.username) so no migration rollback is needed
Original prompt

Objective

Upgrade the legacy Flask application from Python 3.8 to Python 3.12 by refactoring the codebase to use the Flask Application Factory pattern, modernizing deprecated libraries, and hardening the security posture.

Current Issues

The application currently relies on deprecated libraries and patterns:

  • Flask 1.1.2 (released 2019) — deprecated, incompatible with Python 3.12
  • Werkzeug 0.16.1 — lacks Python 3.12 support; collections.MutableMapping was removed in Python 3.12
  • Flask-OAuthlib 0.9.6 — unmaintained, uses deprecated patterns
  • datetime.utcnow() — deprecated in Python 3.12; must use timezone-aware datetimes
  • @app.before_first_request — removed in Flask 2.3
  • Firebase Admin 3.2.1 — too old to support modern Python
  • Weak password hashing — explicitly uses SHA256 instead of modern defaults
  • Brittle intent routing — deeply nested if/elif blocks in action_devices.py

Execution Checklist

1. requirements.txt & runtime.txt (Dependency Overhaul)

  • Update all dependencies to Python 3.12 compatible versions:
    • Flask >= 3.0.0
    • Werkzeug >= 3.0.0
    • Flask-SQLAlchemy >= 3.1.0 and SQLAlchemy >= 2.0.0
    • Replace Flask-OAuthlib with Authlib (modern OAuth2 library)
    • firebase-admin >= 6.4.0
    • google-auth >= 2.22.0
    • gunicorn >= 21.2.0
  • Update runtime.txt to python-3.12.2

2. app.py (Application Factory & Lifecycle)

  • Refactor global app initialization into create_app(config_class) factory function
  • Remove deprecated @app.before_first_request decorator
  • Move db.create_all() logic into app.app_context() block during initialization
  • Integrate standard Python logging to replace print() statements

3. my_oauth.py (OAuth & Python 3.12 Deprecations)

  • Completely refactor to use Authlib instead of Flask-OAuthlib
  • Remove outdated decorators (@oauth.clientgetter, @oauth.grantgetter, etc.)
  • Replace all datetime.utcnow() with timezone-aware datetime.now(timezone.utc)
  • Implement modern OAuth2 provider pattern

4. auth.py (Security Hardening)

  • Remove explicitly weak method='sha256' parameter from generate_password_hash()
  • Rely on Werkzeug's modern default algorithm (scrypt or pbkdf2:sha256)

5. ReportState.py (JWT & State Management)

  • Refactor generate_jwt() function to use google.auth.jwt natively
  • Remove manual payload construction and signing

6. action_devices.py (Intent Routing)

  • Replace deeply nested if/elif blocks with dictionary-based dispatch map
  • Implement strategy pattern for Google Home intent routing (SYNC, QUERY, EXECUTE, DISCONNECT)

7. models.py (Schema Cleanup)

  • Remove unused username column from User model
  • Ensure email is the primary identifier
  • Verify all SQLAlchemy 2.0 syntax compliance

Constraints

  • Do not break existing database schemas beyond specified cleanup
  • Ensure all SQLAlchemy queries conform to SQLAlchemy 2.0 syntax
  • Provide complete, updated code blocks for each modified file
  • No partial snippets

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…rn, security hardening

Co-authored-by: DaTiC0 <13198638+DaTiC0@users.noreply.github.com>
response = requests.post(url, json=data)

logger.debug('Requests Code: %s Response Code: %s', requests.codes["ok"], response.status_code)
logger.debug('Response: %s', response.text)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.

Copilot Autofix

AI 10 days ago

In general, to fix clear‑text logging of sensitive information you should avoid logging any value that may contain secrets (API keys, tokens, passwords, or responses that could include them). Instead, log only non‑sensitive metadata (e.g., HTTP status code, high‑level result). If detailed body content is ever needed for debugging, it should be gated behind explicit development / debug flags and must be carefully sanitized before logging.

For this specific case, the best minimal fix is to stop logging response.text from the request_sync call, since that response is tainted by the API key used in the request. We already log the response status code on the previous line, which is usually sufficient in production. If keeping some indication of the response is desirable, we can log a short, generic message (e.g., “Response body omitted for security reasons”) instead of the raw text. This preserves existing functionality (the HTTP call and return value are unchanged) while eliminating the sensitive sink.

Concretely:

  • In action_devices.py, inside request_sync, replace logger.debug('Response: %s', response.text) with a non‑sensitive debug line that does not include the body, e.g. logger.debug('Response body omitted from logs for security.') or simply remove that line.
  • No changes are needed in app.py or routes.py beyond how they call request_sync, because the leak occurs when the response body is logged, not when the key is passed into the function.

No new imports or helper methods are required.


Suggested changeset 1
action_devices.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/action_devices.py b/action_devices.py
--- a/action_devices.py
+++ b/action_devices.py
@@ -313,7 +313,8 @@
         response = requests.post(url, json=data)
 
         logger.debug('Requests Code: %s  Response Code: %s', requests.codes["ok"], response.status_code)
-        logger.debug('Response: %s', response.text)
+        # Do not log response.text directly to avoid leaking sensitive information.
+        logger.debug('Response body omitted from logs for security.')
 
         return response.status_code == requests.codes['ok']
     except Exception as e:
EOF
@@ -313,7 +313,8 @@
response = requests.post(url, json=data)

logger.debug('Requests Code: %s Response Code: %s', requests.codes["ok"], response.status_code)
logger.debug('Response: %s', response.text)
# Do not log response.text directly to avoid leaking sensitive information.
logger.debug('Response body omitted from logs for security.')

return response.status_code == requests.codes['ok']
except Exception as e:
Copilot is powered by AI and may make mistakes. Always verify output.
Co-authored-by: DaTiC0 <13198638+DaTiC0@users.noreply.github.com>
Copilot AI changed the title [WIP] Upgrade legacy Flask application to Python 3.12 Upgrade Flask app to Python 3.12: Authlib, Flask 3, SQLAlchemy 2, factory pattern, security hardening Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants