Coverage for app_modules/initializer.py: 100%
46 statements
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-20 00:55 +0200
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-20 00:55 +0200
1"""
2initializer.py
4Purpose:
5 Build and configure the Flask app, applying configuration, registering
6 teardown hooks, assets, and all blueprints. This keeps `app.py` minimal so it
7 only imports the created app and optionally runs a dev server.
9Exports:
10 - create_app() -> Flask
11"""
13import os
14from flask import Flask
15from .paths import ROOT_DIR, STATIC_PATH
16from .config import apply_config
17from .db import register_teardown
18from .assets import register_assets
19from .views import bp as views_bp
20from .cameras_api import bp as cameras_api_bp
21from .gallery import bp as gallery_bp
22from .map_routes import bp as map_bp
23from .admin_routes import bp as admin_bp
24from .media_routes import bp as media_bp
25from .security_enhancements import security_headers_middleware
28def create_app() -> Flask:
29 app = Flask(
30 __name__,
31 template_folder=os.path.join(ROOT_DIR, 'templates'),
32 static_folder=STATIC_PATH,
33 static_url_path='/static'
34 )
36 apply_config(app)
37 register_teardown(app)
38 register_assets(app)
40 # Enhanced security headers using security_enhancements module
41 @app.after_request
42 def add_security_headers(resp):
43 return security_headers_middleware(resp)
45 # Mount blueprints (no prefixes to preserve existing URLs)
46 app.register_blueprint(views_bp)
47 app.register_blueprint(cameras_api_bp)
48 app.register_blueprint(gallery_bp)
49 app.register_blueprint(map_bp)
50 app.register_blueprint(admin_bp)
51 app.register_blueprint(media_bp)
53 # Minimal CSRF token support for JSON and forms
54 # Token is stored in session and mirrored in a meta tag on HTML pages
55 import secrets
56 from flask import session, request, abort, g
58 @app.before_request
59 def ensure_csrf_token():
60 # Create token if not present
61 if 'csrf_token' not in session:
62 session['csrf_token'] = secrets.token_urlsafe(32)
63 # Per-request CSP nonce
64 g.csp_nonce = secrets.token_urlsafe(16)
66 @app.context_processor
67 def inject_csrf_token():
68 return { 'CSRF_TOKEN': session.get('csrf_token', ''), 'CSP_NONCE': getattr(g, 'csp_nonce', '') }
70 # Enforce CSRF on state-changing requests
71 @app.before_request
72 def csrf_protect():
73 if request.method in ('POST', 'PUT', 'PATCH', 'DELETE'):
74 # Allow login to work before token is set, but encourage adding token later
75 if request.endpoint in {'views.login_root', 'views.login_login', 'views.login_post_alias'}:
76 return
77 token = request.headers.get('X-CSRF-Token') or request.form.get('csrf_token') or request.args.get('csrf_token')
78 if not token or token != session.get('csrf_token'):
79 abort(403)
81 return app